Skip to content

Commit 2fa45e0

Browse files
authored
Test that the locked version of dependencies of sdk packages equal the lower bound (#183395)
Part of https://flutter.dev/go/unpin-flutter-sdk-dependencies Preparing for #158050 This test will trivially succeed as long as packages are pinned. But when we start unpinning it will be desirable to enforce that we always run tests against the lower bounds of our dependencies.
1 parent ee6bbd9 commit 2fa45e0

1 file changed

Lines changed: 87 additions & 5 deletions

File tree

packages/flutter_tools/test/integration.shard/only_allowed_dependencies_test.dart renamed to packages/flutter_tools/test/integration.shard/dependencies_test.dart

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44

55
import 'dart:convert';
66
import 'dart:io';
7+
8+
import 'package:pub_semver/pub_semver.dart';
79
import 'package:test/test.dart';
10+
import 'package:yaml/yaml.dart';
11+
12+
/// These are the sdk packages living inside the Flutter SDK that apps can
13+
/// depend on.
14+
const List<String> sdkPackages = <String>['flutter', 'flutter_test', 'flutter_localizations'];
815

916
/// List of allowed external packages that Flutter framework packages can depend on.
1017
/// Subject to review and approval when adding new packages.
@@ -61,11 +68,9 @@ Future<void> main() async {
6168
);
6269
final packageGraph = jsonDecode(packageGraphFile.readAsStringSync()) as Map<String, Object?>;
6370

64-
// These are the sdk packages that Flutter apps can depend on.
65-
final roots = <String>['flutter', 'flutter_test', 'flutter_localizations'];
6671
final allowedPackages = <String>{
6772
// Allow depending on other Flutter framework sdk packages.
68-
...roots,
73+
...sdkPackages,
6974
// Allow depending on allowed external packages.
7075
...allowedExternalPackages,
7176
};
@@ -75,9 +80,9 @@ Future<void> main() async {
7580
(package! as Map<String, Object?>)['name']! as String: package as Map<String, Object?>,
7681
};
7782

78-
// Do a transitive parse of the package graph rooted in `roots` to find any
83+
// Do a transitive parse of the package graph rooted in `sdkPackages` to find any
7984
// disallowed dependencies.
80-
final toVisit = <String?>[...roots];
85+
final toVisit = <String?>[...sdkPackages];
8186
final visited = <String>{};
8287
final stack = <String>[];
8388
while (toVisit.isNotEmpty) {
@@ -118,4 +123,81 @@ See: packages/flutter_tools/test/integration.shard/only_allowed_dependencies_tes
118123
''');
119124
}
120125
});
126+
127+
test('All dependencies are locked to their lower bound', () {
128+
// This test will ensure that the lower bound of the version constraints from
129+
// the sdk packages, are all equal to the version of the package in the lockfile.
130+
//
131+
// This is to ensure that we test against the lower bounds of our dependencies.
132+
// So we don't accidentally rely on features added after the lower bound version.
133+
final String flutterRoot = Platform.environment['FLUTTER_ROOT']!;
134+
final flutterRootDirectory = Directory(flutterRoot);
135+
// These are the sdk packages that Flutter apps can depend on.
136+
final lockfile = File.fromUri(flutterRootDirectory.uri.resolve('pubspec.lock'));
137+
final lockfileJson = loadYaml(lockfile.readAsStringSync()) as YamlMap;
138+
final lockedDependencies = lockfileJson['packages']! as YamlMap;
139+
140+
for (final String sdkPackage in sdkPackages) {
141+
final pubspecFile = File.fromUri(
142+
flutterRootDirectory.uri.resolve('packages/$sdkPackage/pubspec.yaml'),
143+
);
144+
final pubspec = loadYaml(pubspecFile.readAsStringSync()) as YamlMap;
145+
final dependencies = pubspec['dependencies']! as YamlMap;
146+
for (final MapEntry<Object?, Object?> dependency in dependencies.entries) {
147+
final dependencyName = dependency.key! as String;
148+
final Object? descriptor = dependency.value;
149+
final VersionConstraint constraint;
150+
if (descriptor is String) {
151+
constraint = VersionConstraint.parse(descriptor);
152+
} else if (descriptor is YamlMap) {
153+
if (descriptor['sdk'] == 'flutter') {
154+
// These are allowed.
155+
continue;
156+
}
157+
if (descriptor['hosted'] == null) {
158+
fail('''
159+
Dependency "$dependencyName" in "${pubspecFile.path}" is not a hosted or sdkpackage.
160+
''');
161+
}
162+
final versionString = descriptor['version'] as String?;
163+
if (versionString == null) {
164+
fail('''
165+
Dependency "$dependencyName" in "${pubspecFile.path}" has no version constraint.
166+
''');
167+
}
168+
constraint = VersionConstraint.parse(versionString);
169+
} else {
170+
fail('''
171+
Dependency "$dependencyName" in "${pubspecFile.path}" is not a hosted or sdk package.
172+
''');
173+
}
174+
if (constraint is! VersionRange) {
175+
fail('''
176+
Dependency "$dependencyName" in "${pubspecFile.path}" is not a version range.
177+
''');
178+
}
179+
if (constraint.min == null) {
180+
fail('''
181+
Dependency "$dependencyName" in "${pubspecFile.path}" has no lower bound.
182+
''');
183+
}
184+
if (constraint.min != constraint.max) {
185+
fail('''
186+
Dependency "$dependencyName" in "${pubspecFile.path}" is not locked at its lower bound.
187+
''');
188+
}
189+
190+
final currentVersion = Version.parse(
191+
(lockedDependencies[dependencyName]! as YamlMap)['version']! as String,
192+
);
193+
194+
if (currentVersion != constraint.min) {
195+
fail('''
196+
Dependency "$dependencyName" is not locked at its lower bound in "${pubspecFile.path}".
197+
Either upgrade the constraint to $currentVersion in pubspec.yaml or downgrade the version to ${constraint.min} in pubspec.lock.
198+
''');
199+
}
200+
}
201+
}
202+
});
121203
}

0 commit comments

Comments
 (0)