From d26afc46ec39a0318d11892ed2144dcc2ef83f88 Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Tue, 21 Mar 2023 13:53:29 +0000 Subject: [PATCH 1/4] Api entrypoint for ensuring resolution is up-to-date --- lib/pub.dart | 19 + lib/src/command/deps.dart | 3 +- lib/src/command/list_package_dirs.dart | 79 --- lib/src/entrypoint.dart | 143 ++--- lib/src/executable.dart | 26 +- test/embedding/embedding_test.dart | 18 +- test/must_pub_get_test.dart | 572 ------------------ ...--verbose and on unexpected exceptions.txt | 8 +- tool/test-bin/pub_command_runner.dart | 21 +- 9 files changed, 139 insertions(+), 750 deletions(-) delete mode 100644 lib/src/command/list_package_dirs.dart delete mode 100644 test/must_pub_get_test.dart diff --git a/lib/pub.dart b/lib/pub.dart index fc79917bd..b1239f761 100644 --- a/lib/pub.dart +++ b/lib/pub.dart @@ -3,7 +3,9 @@ // BSD-style license that can be found in the LICENSE file. import 'package:args/command_runner.dart'; +import 'src/entrypoint.dart'; import 'src/pub_embeddable_command.dart'; +import 'src/system_cache.dart'; export 'src/executable.dart' show getExecutableForCommand, @@ -25,3 +27,20 @@ Command pubCommand({ required bool Function() isVerbose, }) => PubEmbeddableCommand(analytics, isVerbose); + +/// Makes sure that [dir]/pubspec.yaml is resolved such that pubspec.lock and +/// .dart_tool/package_config.json are up-to-date and all packages are +/// downloaded to the cache. +/// +/// Will attempt +Future ensurePubspecResolved( + String dir, { + PubAnalytics? analytics, + bool isOffline = false, + bool checkForSdkUpdate = false, +}) async { + await Entrypoint(dir, SystemCache(isOffline: isOffline)).ensureUpToDate( + analytics: analytics, + checkForSdkUpdate: checkForSdkUpdate, + ); +} diff --git a/lib/src/command/deps.dart b/lib/src/command/deps.dart index b34e33e96..0fe86c25a 100644 --- a/lib/src/command/deps.dart +++ b/lib/src/command/deps.dart @@ -72,7 +72,7 @@ class DepsCommand extends PubCommand { @override Future runProtected() async { // Explicitly Run this in the directorycase we don't access `entrypoint.packageGraph`. - entrypoint.assertUpToDate(); + await entrypoint.ensureUpToDate(); final buffer = StringBuffer(); if (argResults['json']) { @@ -89,6 +89,7 @@ class DepsCommand extends PubCommand { if (argResults.wasParsed('style')) { usageException('Cannot combine --json and --style.'); } + await entrypoint.ensureUpToDate(); final visited = []; final toVisit = [entrypoint.root.name]; final packagesJson = []; diff --git a/lib/src/command/list_package_dirs.dart b/lib/src/command/list_package_dirs.dart deleted file mode 100644 index b572a9e12..000000000 --- a/lib/src/command/list_package_dirs.dart +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:path/path.dart' as p; - -import '../command.dart'; -import '../command_runner.dart'; -import '../io.dart'; -import '../log.dart' as log; -import '../package_name.dart'; -import '../utils.dart'; - -/// Handles the `list-package-dirs` pub command. -class ListPackageDirsCommand extends PubCommand { - @override - String get name => 'list-package-dirs'; - @override - String get description => 'Print local paths to dependencies.'; - @override - String get argumentsDescription => ''; - @override - bool get takesArguments => false; - @override - bool get hidden => true; - - ListPackageDirsCommand() { - argParser.addOption( - 'format', - help: 'How output should be displayed.', - allowed: ['json'], - ); - argParser.addOption( - 'directory', - abbr: 'C', - help: 'Run this in the directory .', - valueHelp: 'dir', - ); - } - - @override - Future runProtected() async { - log.json.enabled = true; - entrypoint.assertUpToDate(); - if (!fileExists(entrypoint.lockFilePath)) { - dataError( - 'Package "myapp" has no lockfile. Please run "$topLevelProgram pub get" first.', - ); - } - - var output = {}; - - // Include the local paths to all locked packages. - var packages = mapMap( - entrypoint.lockFile.packages, - value: (String name, PackageId package) { - var packageDir = cache.getDirectory(package); - // Normalize paths and make them absolute for backwards compatibility - // with the protocol used by the analyzer. - return p.normalize(p.absolute(p.join(packageDir, 'lib'))); - }, - ); - - // Include the self link. - packages[entrypoint.root.name] = - p.normalize(p.absolute(entrypoint.root.path('lib'))); - - output['packages'] = packages; - - // Include the file(s) which when modified will affect the results. For pub, - // that's just the pubspec and lockfile. - output['input_files'] = [ - p.normalize(p.absolute(entrypoint.lockFilePath)), - p.normalize(p.absolute(entrypoint.pubspecPath)) - ]; - - log.json.message(output); - } -} diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart index 9a01f9b3c..f92ff2665 100644 --- a/lib/src/entrypoint.dart +++ b/lib/src/entrypoint.dart @@ -169,7 +169,7 @@ class Entrypoint { PackageGraph get packageGraph => _packageGraph ??= _createPackageGraph(); PackageGraph _createPackageGraph() { - assertUpToDate(); + ensureUpToDate(); // TODO, really? var packages = { for (var packageEntry in packageConfig.nonInjectedPackages) packageEntry.name: Package.load( @@ -603,41 +603,47 @@ To update `$lockFilePath` run `$topLevelProgram pub get`$suffix without } } - /// Throws a [DataError] if the `.dart_tool/package_config.json` file doesn't - /// exist or if it's out-of-date relative to the lockfile or the pubspec. - /// - /// A `.packages` file is not required. But if it exists it is checked for - /// consistency with the pubspec.lock. + /// Does a fast-pass check to see if the resolution is up-to-date ([_isUpToDate]). + /// If not, run a resolution with `pub get` semantics. /// /// If [checkForSdkUpdate] is `true`, the resolution is considered outdated if /// the package_config.json was created by a different sdk. See - /// [_checkPackageConfigSameDartSdk]. - /// TODO(sigurdm): we should consider if we can instead in all places update - /// the resolution automatically. - void assertUpToDate({bool checkForSdkUpdate = false}) { - if (isCached) return; + /// [_isPackageConfigGeneratedBySameDartSdk]. + Future ensureUpToDate({ + bool checkForSdkUpdate = false, + PubAnalytics? analytics, + }) async { + if (!_isUpToDate(checkForSdkUpdate: checkForSdkUpdate)) { + await acquireDependencies(SolveType.get, analytics: analytics); + } else { + log.fine('Package Config up to date.'); + } + } + + /// Whether `.dart_tool/package_config.json` file exists and if it's + /// up-to-date relative to the lockfile and the pubspec. + /// + /// A `.packages` file is not required. But if it exists it is checked for + /// consistency with the pubspec.lock. + bool _isUpToDate({bool checkForSdkUpdate = false}) { + if (isCached) return true; final pubspecStat = tryStatFile(pubspecPath); if (pubspecStat == null) { - throw FileException( + log.fine( 'Could not find a file named "pubspec.yaml" in ' '"${canonicalize(rootDir)}".', - pubspecPath, ); + return false; } final lockFileStat = tryStatFile(lockFilePath); if (lockFileStat == null) { - dataError( - 'No $lockFilePath file found, please run "$topLevelProgram pub get" first.', - ); + log.fine('No $lockFilePath file found.'); + return false; } final packageConfigStat = tryStatFile(packageConfigPath); if (packageConfigStat == null) { - dataError( - 'No $packageConfigPath file found, please run "$topLevelProgram pub get".\n' - '\n' - 'Starting with Dart 2.7, the package_config.json file configures ' - 'resolution of package import URIs; run "$topLevelProgram pub get" to generate it.', - ); + log.fine('No $packageConfigPath file found".\n'); + return false; } // Manually parse the lockfile because a full YAML parse is relatively slow @@ -665,14 +671,17 @@ To update `$lockFilePath` run `$topLevelProgram pub get`$suffix without // * Prevent missing packages when `pubspec.lock` is checked into git. // * Mitigate missing transitive dependencies when the `pubspec.yaml` in // a path dependency is changed. - _assertLockFileUpToDate(); + if (!_isLockFileUpToDate()) { + return false; + } if (_arePackagesAvailable()) { touchedLockFile = true; touch(lockFilePath); } else { var filePath = pubspecChanged ? pubspecPath : pubspecOverridesPath; - dataError('The $filePath file has changed since the $lockFilePath ' + log.fine('The $filePath file has changed since the $lockFilePath ' 'file was generated, please run "$topLevelProgram pub get" again.'); + return false; } } @@ -683,7 +692,7 @@ To update `$lockFilePath` run `$topLevelProgram pub get`$suffix without // correct configuration on the local machine. This aims to: // * Mitigate issues when copying a folder from one machine to another. // * Force `pub get` if a path dependency has changed language version. - _checkPackageConfigUpToDate(); + if (!_isPackageConfigUpToDate()) return false; touch(packageConfigPath); } else { if (touchedLockFile) { @@ -694,7 +703,6 @@ To update `$lockFilePath` run `$topLevelProgram pub get`$suffix without for (final match in _sdkConstraint.allMatches(lockFileText)) { final identifier = match[1] == 'sdk' ? 'dart' : match[1]!.trim(); final sdk = sdks[identifier]!; - // Don't complain if there's an SDK constraint for an unavailable SDK. For // example, the Flutter SDK being unavailable just means that we aren't // running from within the `flutter` executable, and we want users to be @@ -703,8 +711,9 @@ To update `$lockFilePath` run `$topLevelProgram pub get`$suffix without final parsedConstraint = VersionConstraint.parse(match[2]!); if (!parsedConstraint.allows(sdk.version!)) { - dataError('${sdk.name} ${sdk.version} is incompatible with your ' - "dependencies' SDK constraints. Please run \"$topLevelProgram pub get\" again."); + log.fine('${sdk.name} ${sdk.version} is incompatible with your ' + "dependencies' SDK constraints."); + return false; } } // We want to do ensure a pub get gets run when updating a minor version of @@ -712,20 +721,22 @@ To update `$lockFilePath` run `$topLevelProgram pub get`$suffix without // // Putting this check last because it leads to less specific messages than // the 'incompatible sdk' check above. - if (checkForSdkUpdate) _checkPackageConfigSameDartSdk(); + if (checkForSdkUpdate && !_isPackageConfigGeneratedBySameDartSdk()) { + return false; + } + return true; } - /// Determines whether or not the lockfile is out of date with respect to the + /// Whether the lockfile is out of date with respect to the /// pubspec. /// /// If any mutable pubspec contains dependencies that are not in the lockfile - /// or that don't match what's in there, this will throw a [DataError] - /// describing the issue. - void _assertLockFileUpToDate() { + /// or that don't match what's in there, this will return `false`. + bool _isLockFileUpToDate() { if (!root.immediateDependencies.values.every(_isDependencyUpToDate)) { - dataError( - 'The $pubspecPath file has changed since the $lockFilePath file ' - 'was generated, please run "$topLevelProgram pub get" again.'); + log.fine('The $pubspecPath file has changed since the $lockFilePath file ' + 'was generated.'); + return false; } var overrides = MapKeySet(root.dependencyOverrides); @@ -749,10 +760,11 @@ To update `$lockFilePath` run `$topLevelProgram pub get`$suffix without final relativePubspecPath = p.join(cache.getDirectory(id, relativeFrom: '.'), 'pubspec.yaml'); - dataError('$relativePubspecPath has ' - 'changed since the $lockFilePath file was generated, please run ' - '"$topLevelProgram pub get" again.'); + log.fine('$relativePubspecPath has ' + 'changed since the $lockFilePath file was generated.'); + return false; } + return true; } /// Returns whether the locked version of [dep] matches the dependency. @@ -838,35 +850,28 @@ To update `$lockFilePath` run `$topLevelProgram pub get`$suffix without }); } - /// Checks whether or not the `.dart_tool/package_config.json` file is + /// Whether or not the `.dart_tool/package_config.json` file is /// out of date with respect to the lockfile. - /// - /// This will throw a [DataError] if the [lockfile] contains dependencies that - /// are not in the `.dart_tool/package_config.json` or that don't match - /// what's in there. - /// - /// Throws [DataException], if `.dart_tool/package_config.json` is not - /// up-to-date for some other reason. - void _checkPackageConfigUpToDate() { - void outOfDate() { - dataError('The $lockFilePath file has changed since the ' - '$packageConfigPath file ' - 'was generated, please run "$topLevelProgram pub get" again.'); - } - + bool _isPackageConfigUpToDate() { final packagePathsMapping = {}; final packagesToCheck = packageConfig.nonInjectedPackages; for (final pkg in packagesToCheck) { // Pub always makes a packageUri of lib/ if (pkg.packageUri == null || pkg.packageUri.toString() != 'lib/') { - badPackageConfig(); + log.fine( + 'The "$packageConfigPath" file is not recognized by this pub version.', + ); + return false; } packagePathsMapping[pkg.name] = root.path('.dart_tool', p.fromUri(pkg.rootUri)); } if (!_isPackagePathsMappingUpToDateWithLockfile(packagePathsMapping)) { - outOfDate(); + log.fine('The $lockFilePath file has changed since the ' + '$packageConfigPath file ' + 'was generated, please run "$topLevelProgram pub get" again.'); + return false; } // Check if language version specified in the `package_config.json` is @@ -899,30 +904,30 @@ To update `$lockFilePath` run `$topLevelProgram pub get`$suffix without cache.getDirectory(id, relativeFrom: '.'), 'pubspec.yaml', ); - dataError('$relativePubspecPath has ' - 'changed since the $lockFilePath file was generated, please run ' - '"$topLevelProgram pub get" again.'); + log.fine('$relativePubspecPath has ' + 'changed since the $lockFilePath file was generated.'); + return false; } } on FileException { - dataError('Failed to read pubspec.yaml for "${pkg.name}", perhaps the ' - 'entry is missing, please run "$topLevelProgram pub get".'); + log.fine('Failed to read pubspec.yaml for "${pkg.name}", perhaps the ' + 'entry is missing.'); + return false; } } + return true; } - /// Checks whether or not the `.dart_tool/package_config.json` file is was - /// generated by a different sdk down changes in minor versions. - /// - /// Throws [DataException], if `.dart_tool/package_config.json` the version - /// changed sufficiently. - void _checkPackageConfigSameDartSdk() { + /// Whether or not the `.dart_tool/package_config.json` file is was + /// generated by a different sdk down to changes in minor versions. + bool _isPackageConfigGeneratedBySameDartSdk() { final generatorVersion = packageConfig.generatorVersion; if (generatorVersion == null || generatorVersion.major != sdk.version.major || generatorVersion.minor != sdk.version.minor) { - dataError('The sdk was updated since last package resolution. Please run ' - '"$topLevelProgram pub get" again.'); + log.fine('The sdk was updated since last package resolution.'); + return false; } + return true; } /// We require an SDK constraint lower-bound as of Dart 2.12.0 diff --git a/lib/src/executable.dart b/lib/src/executable.dart index accde6e42..578966549 100644 --- a/lib/src/executable.dart +++ b/lib/src/executable.dart @@ -18,7 +18,6 @@ import 'isolate.dart' as isolate; import 'log.dart' as log; import 'log.dart'; import 'pub_embeddable_command.dart'; -import 'solver/type.dart'; import 'system_cache.dart'; import 'utils.dart'; @@ -91,7 +90,7 @@ Future runExecutable( if (useSnapshot) { // Since we don't access the package graph, this doesn't happen // automatically. - entrypoint.assertUpToDate(); + await entrypoint.ensureUpToDate(); if (!fileExists(snapshotPath) || entrypoint.packageGraph.isPackageMutable(package)) { @@ -315,23 +314,12 @@ Future getExecutableForCommand( } final entrypoint = Entrypoint(root, SystemCache(rootDir: pubCacheDir)); try { - // TODO(sigurdm): it would be nicer with a 'isUpToDate' function. - entrypoint.assertUpToDate(checkForSdkUpdate: true); - } on DataException catch (e) { - log.fine('Resolution not up to date: ${e.message}. Redoing.'); - try { - await errorsOnlyUnlessTerminal( - () => entrypoint.acquireDependencies( - SolveType.get, - analytics: analytics, - ), - ); - } on ApplicationException catch (e) { - throw CommandResolutionFailedException._( - e.toString(), - CommandResolutionIssue.pubGetFailed, - ); - } + await entrypoint.ensureUpToDate(checkForSdkUpdate: true); + } on ApplicationException catch (e) { + throw CommandResolutionFailedException._( + e.toString(), + CommandResolutionIssue.pubGetFailed, + ); } late final String command; diff --git a/test/embedding/embedding_test.dart b/test/embedding/embedding_test.dart index 8a7d59b78..4b8e7acbc 100644 --- a/test/embedding/embedding_test.dart +++ b/test/embedding/embedding_test.dart @@ -15,6 +15,7 @@ import 'package:test_process/test_process.dart'; import '../descriptor.dart' as d; import '../golden_file.dart'; import '../test_pub.dart'; +import 'ensure_pubspec_resolved.dart'; const _commandRunner = 'tool/test-bin/pub_command_runner.dart'; @@ -27,16 +28,21 @@ Future runEmbeddingToBuffer( List args, StringBuffer buffer, { String? workingDirectory, - Map? environment, + Map? environment, dynamic exitCode = 0, }) async { + final combinedEnvironment = getPubTestEnvironment(); + (environment ?? {}).forEach((key, value) { + if (value == null) { + combinedEnvironment.remove(key); + } else { + combinedEnvironment[key] = value; + } + }); final process = await TestProcess.start( Platform.resolvedExecutable, ['--enable-asserts', snapshot, ...args], - environment: { - ...getPubTestEnvironment(), - ...?environment, - }, + environment: combinedEnvironment, workingDirectory: workingDirectory, ); await process.shouldExit(exitCode); @@ -414,6 +420,8 @@ main() { ); } }); + + testEnsurePubspecResolved(); } String _filter(String input) { diff --git a/test/must_pub_get_test.dart b/test/must_pub_get_test.dart deleted file mode 100644 index 4eace86de..000000000 --- a/test/must_pub_get_test.dart +++ /dev/null @@ -1,572 +0,0 @@ -// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:path/path.dart' as p; -import 'package:pub/src/exit_codes.dart' as exit_codes; -import 'package:pub/src/io.dart'; -import 'package:test/test.dart'; - -import 'descriptor.dart' as d; -import 'test_pub.dart'; - -late PackageServer server; - -void main() { - setUp(() async { - server = await servePackages(); - - server.serve('foo', '1.0.0'); - server.serve('foo', '2.0.0'); - - await d.dir(appPath, [ - d.appPubspec(), - d.dir('web', []), - d.dir('bin', [d.file('script.dart', "main() => print('hello!');")]) - ]).create(); - - await pubGet(); - }); - - test( - 'does not require a pub get if a `flutter_gen` package is injected into package_config.json', - () async { - await d.dir('bar', [ - d.pubspec({'name': 'bar'}) - ]).create(); - await d.dir(appPath, [ - d.appPubspec( - dependencies: { - 'bar': {'path': '../bar'} - }, - ) - ]).create(); - - await pubGet(); - - final packageConfig = - p.join(d.sandbox, 'myapp', '.dart_tool', 'package_config.json'); - final contents = json.decode(readTextFile(packageConfig)); - contents['packages'].add({ - 'name': 'flutter_gen', - 'rootUri': '.dart_tool/flutter_gen', - 'languageVersion': '2.8', - }); - writeTextFile(packageConfig, json.encode(contents)); - - await runPub(args: ['run', 'bin/script.dart'], output: endsWith('hello!')); - }); - group('requires the user to run pub get first if', () { - group("there's no lockfile", () { - setUp(() { - deleteEntry(p.join(d.sandbox, 'myapp/pubspec.lock')); - }); - - _requiresPubGet( - 'No pubspec.lock file found, please run "dart pub get" first.', - ); - }); - - group("there's no package_config.json", () { - setUp(() { - deleteEntry(p.join(d.sandbox, 'myapp/.dart_tool/package_config.json')); - }); - - _requiresPubGet( - 'No .dart_tool${p.separator}package_config.json file found, please run "dart pub get".', - ); - }); - - group('the pubspec has a new dependency', () { - setUp(() async { - await d.dir('foo', [d.libPubspec('foo', '1.0.0')]).create(); - - await d.dir(appPath, [ - d.appPubspec( - dependencies: { - 'foo': {'path': '../foo'} - }, - ) - ]).create(); - - // Ensure that the pubspec looks newer than the lockfile. - await _touch('pubspec.yaml'); - }); - - _requiresPubGet('The pubspec.yaml file has changed since the ' - 'pubspec.lock file was generated, please run "dart pub get" again.'); - }); - - group('the lockfile has a dependency from the wrong source', () { - setUp(() async { - await d.dir(appPath, [ - d.appPubspec(dependencies: {'foo': '1.0.0'}) - ]).create(); - - await pubGet(); - - await createLockFile(appPath, dependenciesInSandBox: ['foo']); - - // Ensure that the pubspec looks newer than the lockfile. - await _touch('pubspec.yaml'); - }); - - _requiresPubGet('The pubspec.yaml file has changed since the ' - 'pubspec.lock file was generated, please run "dart pub get" again.'); - }); - - group('the lockfile has a dependency from an unknown source', () { - setUp(() async { - await d.dir(appPath, [ - d.appPubspec(dependencies: {'foo': '1.0.0'}) - ]).create(); - - await pubGet(); - - await d.dir(appPath, [ - d.file( - 'pubspec.lock', - yaml({ - 'packages': { - 'foo': { - 'description': 'foo', - 'version': '1.0.0', - 'source': 'sdk' - } - } - }), - ) - ]).create(); - - // Ensure that the pubspec looks newer than the lockfile. - await _touch('pubspec.yaml'); - }); - - _requiresPubGet('The pubspec.yaml file has changed since the ' - 'pubspec.lock file was generated, please run "dart pub get" again.'); - }); - - group('the lockfile has a dependency with the wrong description', () { - setUp(() async { - await d.dir('bar', [d.libPubspec('foo', '1.0.0')]).create(); - - await d.dir(appPath, [ - d.appPubspec( - dependencies: { - 'foo': {'path': '../bar'} - }, - ) - ]).create(); - - await pubGet(); - - await createLockFile(appPath, dependenciesInSandBox: ['foo']); - - // Ensure that the pubspec looks newer than the lockfile. - await _touch('pubspec.yaml'); - }); - - _requiresPubGet('The pubspec.yaml file has changed since the ' - 'pubspec.lock file was generated, please run "dart pub get" again.'); - }); - - group('the pubspec has an incompatible version of a dependency', () { - setUp(() async { - await d.dir(appPath, [ - d.appPubspec(dependencies: {'foo': '1.0.0'}) - ]).create(); - - await pubGet(); - - await d.dir(appPath, [ - d.appPubspec(dependencies: {'foo': '2.0.0'}) - ]).create(); - - // Ensure that the pubspec looks newer than the lockfile. - await _touch('pubspec.yaml'); - }); - - _requiresPubGet('The pubspec.yaml file has changed since the ' - 'pubspec.lock file was generated, please run "dart pub get" again.'); - }); - - group( - 'the lockfile is pointing to an unavailable package with a newer ' - 'pubspec', () { - setUp(() async { - await d.dir(appPath, [ - d.appPubspec(dependencies: {'foo': '1.0.0'}) - ]).create(); - - await pubGet(); - - deleteEntry(p.join(d.sandbox, cachePath)); - - // Ensure that the pubspec looks newer than the lockfile. - await _touch('pubspec.yaml'); - }); - - _requiresPubGet('The pubspec.yaml file has changed since the ' - 'pubspec.lock file was generated, please run "dart pub get" again.'); - }); - - group('the package_config.json file points to the wrong place', () { - setUp(() async { - await d.dir('bar', [d.libPubspec('foo', '1.0.0')]).create(); - - await d.dir(appPath, [ - d.appPubspec( - dependencies: { - 'foo': {'path': '../bar'} - }, - ) - ]).create(); - - await pubGet(); - - await d.dir(appPath, [ - d.packageConfigFile([ - d.packageConfigEntry( - name: 'foo', - path: '../foo', // this is the wrong path - ), - d.packageConfigEntry( - name: 'myapp', - path: '.', - ), - ]), - ]).create(); - - // Ensure that the pubspec looks newer than the lockfile. - await _touch('pubspec.lock'); - }); - - _requiresPubGet('The pubspec.lock file has changed since the ' - '.dart_tool${p.separator}package_config.json file was generated, ' - 'please run "dart pub get" again.'); - }); - - group("the lock file's SDK constraint doesn't match the current SDK", () { - setUp(() async { - // Avoid using a path dependency because it triggers the full validation - // logic. We want to be sure SDK-validation works without that logic. - server.serve( - 'foo', - '3.0.0', - pubspec: { - 'environment': {'sdk': '>=3.0.0 <3.1.0'} - }, - ); - - await d.dir(appPath, [ - d.pubspec({ - 'name': 'myapp', - 'dependencies': {'foo': '3.0.0'}, - 'environment': { - 'sdk': '>=3.0.0 <3.1.0', - }, - }), - ]).create(); - - await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '3.0.0'}); - }); - - _requiresPubGet("Dart 3.1.2+3 is incompatible with your dependencies' " - 'SDK constraints. Please run "dart pub get" again.'); - }); - - test( - "the lock file's Flutter SDK constraint doesn't match the " - 'current Flutter SDK', () async { - // Avoid using a path dependency because it triggers the full validation - // logic. We want to be sure SDK-validation works without that logic. - server.serve( - 'foo', - '3.0.0', - pubspec: { - 'environment': { - 'flutter': '>=1.0.0 <2.0.0', - 'sdk': defaultSdkConstraint - } - }, - ); - - await d.dir('flutter', [d.file('version', '1.2.3')]).create(); - - await d.dir(appPath, [ - d.appPubspec(dependencies: {'foo': '3.0.0'}) - ]).create(); - - await pubGet(environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')}); - - await d.dir('flutter', [d.file('version', '0.9.0')]).create(); - - // Run pub manually here because otherwise we don't have access to - // d.sandbox. - await runPub( - args: ['run', 'script'], - environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')}, - error: "Flutter 0.9.0 is incompatible with your dependencies' SDK " - 'constraints. Please run "dart pub get" again.', - exitCode: exit_codes.DATA, - ); - }); - - group("a path dependency's dependency doesn't match the lockfile", () { - setUp(() async { - await d.dir('bar', [ - d.libPubspec('bar', '1.0.0', deps: {'foo': '1.0.0'}) - ]).create(); - - await d.dir(appPath, [ - d.appPubspec( - dependencies: { - 'bar': {'path': '../bar'} - }, - ) - ]).create(); - - await pubGet(); - - // Update bar's pubspec without touching the app's. - await d.dir('bar', [ - d.libPubspec('bar', '1.0.0', deps: {'foo': '2.0.0'}) - ]).create(); - }); - - _requiresPubGet('${p.join('..', 'bar', 'pubspec.yaml')} has changed ' - 'since the pubspec.lock file was generated, please run "dart pub get" ' - 'again.'); - }); - - group( - "a path dependency's language version doesn't match the package_config.json", - () { - setUp(() async { - await d.dir('bar', [ - d.libPubspec( - 'bar', - '1.0.0', - deps: {'foo': '1.0.0'}, - // Creates language version requirement 2.99 - sdk: '>= 2.99.0 <=4.0.0', // tests runs with '3.1.2+3' - ), - ]).create(); - - await d.dir(appPath, [ - d.appPubspec( - dependencies: { - 'bar': {'path': '../bar'} - }, - ) - ]).create(); - - await pubGet(); - - // Update bar's pubspec without touching the app's. - await d.dir('bar', [ - d.libPubspec( - 'bar', - '1.0.0', - deps: {'foo': '1.0.0'}, - // Creates language version requirement 2.100 - sdk: '>= 2.100.0 <=4.0.0', // tests runs with '3.1.2+3' - ), - ]).create(); - }); - - _requiresPubGet('${p.join('..', 'bar', 'pubspec.yaml')} has changed ' - 'since the pubspec.lock file was generated, please run "dart pub get" ' - 'again.'); - }); - }); - - group("doesn't require the user to run pub get first if", () { - group( - 'the pubspec is older than the lockfile which is older than the ' - 'package-config, even if the contents are wrong', () { - setUp(() async { - await d.dir(appPath, [ - d.appPubspec(dependencies: {'foo': '1.0.0'}) - ]).create(); - // Ensure we get a new mtime (mtime is only reported with 1s precision) - await _touch('pubspec.yaml'); - - await _touch('pubspec.lock'); - await _touch('.dart_tool/package_config.json'); - }); - - _runsSuccessfully(runDeps: false); - }); - - group("the pubspec is newer than the lockfile, but they're up-to-date", () { - setUp(() async { - await d.dir(appPath, [ - d.appPubspec(dependencies: {'foo': '1.0.0'}) - ]).create(); - - await pubGet(); - - await _touch('pubspec.yaml'); - }); - - _runsSuccessfully(); - }); - - // Regression test for #1416 - group('a path dependency has a dependency on the root package', () { - setUp(() async { - await d.dir('foo', [ - d.libPubspec('foo', '1.0.0', deps: {'myapp': 'any'}) - ]).create(); - - await d.dir(appPath, [ - d.appPubspec( - dependencies: { - 'foo': {'path': '../foo'} - }, - ) - ]).create(); - - await pubGet(); - - await _touch('pubspec.lock'); - }); - - _runsSuccessfully(); - }); - - group("the lockfile is newer than package_config.json, but it's up-to-date", - () { - setUp(() async { - await d.dir(appPath, [ - d.appPubspec(dependencies: {'foo': '1.0.0'}) - ]).create(); - - await pubGet(); - - await _touch('pubspec.lock'); - }); - - _runsSuccessfully(); - }); - - group("an overridden dependency's SDK constraint is unmatched", () { - setUp(() async { - server.serve( - 'bar', - '1.0.0', - pubspec: { - 'environment': {'sdk': '0.0.0-fake'} - }, - ); - - await d.dir(appPath, [ - d.pubspec({ - 'name': 'myapp', - 'dependency_overrides': {'bar': '1.0.0'} - }) - ]).create(); - - await pubGet(); - - await _touch('pubspec.lock'); - }); - - _runsSuccessfully(); - }); - - test('the lock file has a Flutter SDK but Flutter is unavailable', - () async { - // Avoid using a path dependency because it triggers the full validation - // logic. We want to be sure SDK-validation works without that logic. - server.serve( - 'foo', - '3.0.0', - pubspec: { - 'environment': { - 'flutter': '>=1.0.0 <2.0.0', - 'sdk': defaultSdkConstraint - } - }, - ); - - await d.dir('flutter', [d.file('version', '1.2.3')]).create(); - - await d.dir(appPath, [ - d.appPubspec(dependencies: {'foo': '3.0.0'}) - ]).create(); - - await pubGet(environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')}); - - await d.dir('flutter', [d.file('version', '2.4.6')]).create(); - - // Run pub manually here because otherwise we don't have access to - // d.sandbox. - await runPub(args: ['run', 'bin/script.dart']); - }); - }); -} - -/// Runs every command that care about the world being up-to-date, and asserts -/// that it prints [message] as part of its error. -void _requiresPubGet(String message) { - for (var command in ['run', 'deps']) { - test('for pub $command', () { - var args = [command]; - if (command == 'run') args.add('script'); - - return runPub( - args: args, - error: contains(message), - exitCode: exit_codes.DATA, - ); - }); - } -} - -/// Ensures that pub doesn't require "dart pub get" for the current package. -/// -/// If [runDeps] is false, `pub deps` isn't included in the test. This is -/// sometimes not desirable, since it uses slightly stronger checks for pubspec -/// and lockfile consistency. -void _runsSuccessfully({bool runDeps = true}) { - var commands = ['run']; - if (runDeps) commands.add('deps'); - - for (var command in commands) { - test('for pub $command', () async { - var args = [command]; - if (command == 'run') args.add('bin/script.dart'); - - await runPub(args: args); - - // If pub determines that everything is up-to-date, it should set the - // mtimes to indicate that. - var pubspecModified = - File(p.join(d.sandbox, 'myapp/pubspec.yaml')).lastModifiedSync(); - var lockFileModified = - File(p.join(d.sandbox, 'myapp/pubspec.lock')).lastModifiedSync(); - var packageConfigModified = - File(p.join(d.sandbox, 'myapp/.dart_tool/package_config.json')) - .lastModifiedSync(); - - expect(!pubspecModified.isAfter(lockFileModified), isTrue); - expect(!lockFileModified.isAfter(packageConfigModified), isTrue); - }); - } -} - -/// Schedules a non-semantic modification to [path]. -Future _touch(String path) async { - // Delay a bit to make sure the modification times are noticeably different. - // 1s seems to be the finest granularity that dart:io reports. - await Future.delayed(Duration(seconds: 1)); - - path = p.join(d.sandbox, 'myapp', path); - touch(path); -} diff --git a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt index f350065a4..d4ff75870 100644 --- a/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt +++ b/test/testdata/goldens/embedding/embedding_test/logfile is written with --verbose and on unexpected exceptions.txt @@ -298,8 +298,8 @@ $ tool/test-bin/pub_command_runner.dart pub fail [E] package:pub/src/utils.dart $LINE:$COL captureErrors [E] package:pub/src/command.dart $LINE:$COL PubCommand.run [E] package:args/command_runner.dart $LINE:$COL CommandRunner.runCommand -[E] tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.runCommand -[E] tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.run +[E] tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.runCommand +[E] tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.run [E] tool/test-bin/pub_command_runner.dart $LINE:$COL main [E] This is an unexpected error. The full log and other details are collected in: [E] @@ -343,8 +343,8 @@ ERR : tool/test-bin/pub_command_runner.dart $LINE:$COL ThrowingCommand.runProt | package:pub/src/utils.dart $LINE:$COL captureErrors | package:pub/src/command.dart $LINE:$COL PubCommand.run | package:args/command_runner.dart $LINE:$COL CommandRunner.runCommand - | tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.runCommand - | tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.run + | tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.runCommand + | tool/test-bin/pub_command_runner.dart $LINE:$COL Runner.run | tool/test-bin/pub_command_runner.dart $LINE:$COL main ERR : This is an unexpected error. The full log and other details are collected in: | diff --git a/tool/test-bin/pub_command_runner.dart b/tool/test-bin/pub_command_runner.dart index 8e8a8ca3c..9192079f6 100644 --- a/tool/test-bin/pub_command_runner.dart +++ b/tool/test-bin/pub_command_runner.dart @@ -33,6 +33,24 @@ class ThrowingCommand extends PubCommand { } } +// A command for testing the ensurePubspecResolved functionality +class EnsurePubspecResolvedCommand extends PubCommand { + @override + String get name => 'ensure-pubspec-resolved'; + + @override + String get description => 'Resolves pubspec.yaml if needed'; + + @override + bool get hidden => true; + + @override + Future runProtected() async { + await ensurePubspecResolved('.'); + return 0; + } +} + class RunCommand extends Command { @override String get name => 'run'; @@ -70,7 +88,8 @@ class Runner extends CommandRunner { : null; addCommand( pubCommand(analytics: analytics, isVerbose: () => _options['verbose']) - ..addSubcommand(ThrowingCommand()), + ..addSubcommand(ThrowingCommand()) + ..addSubcommand(EnsurePubspecResolvedCommand()), ); addCommand(RunCommand()); argParser.addFlag('verbose'); From 52e33c58dbe7e76af218ef137f07179eec908b28 Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Tue, 21 Mar 2023 13:56:43 +0000 Subject: [PATCH 2/4] Add missing file --- test/embedding/ensure_pubspec_resolved.dart | 568 ++++++++++++++++++++ 1 file changed, 568 insertions(+) create mode 100644 test/embedding/ensure_pubspec_resolved.dart diff --git a/test/embedding/ensure_pubspec_resolved.dart b/test/embedding/ensure_pubspec_resolved.dart new file mode 100644 index 000000000..fa98d9beb --- /dev/null +++ b/test/embedding/ensure_pubspec_resolved.dart @@ -0,0 +1,568 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// TODO(sigurdm): This should ideally be a separate _test.dart file, however to +// share the compiled snapshot for the embedded test-runner this is included by +// embedding_test.dart + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:pub/src/io.dart'; +import 'package:test/test.dart'; + +import '../descriptor.dart' as d; +import '../test_pub.dart'; +import 'embedding_test.dart'; + +late PackageServer server; + +void testEnsurePubspecResolved() { + group('ensurePubspecResolved', () { + setUp(() async { + server = await servePackages(); + + server.serve('foo', '1.0.0'); + server.serve('foo', '2.0.0'); + + await d.dir(appPath, [ + d.appPubspec(), + d.dir('web', []), + d.dir('bin', [d.file('script.dart', "main() => print('hello!');")]) + ]).create(); + + await pubGet(); + }); + + test( + 'does not require a pub get if a `flutter_gen` package is injected into package_config.json', + () async { + await d.dir('bar', [ + d.pubspec({'name': 'bar'}) + ]).create(); + await d.dir(appPath, [ + d.appPubspec( + dependencies: { + 'bar': {'path': '../bar'} + }, + ) + ]).create(); + + await pubGet(); + + final packageConfig = + p.join(d.sandbox, 'myapp', '.dart_tool', 'package_config.json'); + final contents = json.decode(File(packageConfig).readAsStringSync()); + contents['packages'].add({ + 'name': 'flutter_gen', + 'rootUri': '.dart_tool/flutter_gen', + 'languageVersion': '2.8', + }); + writeTextFile(packageConfig, json.encode(contents)); + + await runPub( + args: ['run', 'bin/script.dart'], + output: endsWith('hello!'), + ); + }); + + group('Does an implicit pub get if', () { + test("there's no lockfile", () async { + File(p.join(d.sandbox, 'myapp/pubspec.lock')).deleteSync(); + await _implicitPubGet('No pubspec.lock file found'); + }); + + test("there's no package_config.json", () async { + File(p.join(d.sandbox, 'myapp/.dart_tool/package_config.json')) + .deleteSync(); + + await _implicitPubGet( + 'No .dart_tool${p.separator}package_config.json file found', + ); + }); + + test('the pubspec has a new dependency', () async { + await d.dir('foo', [d.libPubspec('foo', '1.0.0')]).create(); + + await d.dir(appPath, [ + d.appPubspec( + dependencies: { + 'foo': {'path': '../foo'} + }, + ) + ]).create(); + + // Ensure that the pubspec looks newer than the lockfile. + await _touch('pubspec.yaml'); + + await _implicitPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated'); + }); + + test('the lockfile has a dependency from the wrong source', () async { + await d.dir(appPath, [ + d.appPubspec(dependencies: {'foo': '1.0.0'}) + ]).create(); + + await pubGet(); + + await createLockFile(appPath, dependenciesInSandBox: ['foo']); + + // Ensure that the pubspec looks newer than the lockfile. + await _touch('pubspec.yaml'); + + await _implicitPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated'); + }); + + test('the lockfile has a dependency from an unknown source', () async { + await d.dir(appPath, [ + d.appPubspec(dependencies: {'foo': '1.0.0'}) + ]).create(); + + await pubGet(); + + await d.dir(appPath, [ + d.file( + 'pubspec.lock', + yaml({ + 'packages': { + 'foo': { + 'description': 'foo', + 'version': '1.0.0', + 'source': 'sdk' + } + } + }), + ) + ]).create(); + + // Ensure that the pubspec looks newer than the lockfile. + await _touch('pubspec.yaml'); + + await _implicitPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated.'); + }); + + test('the lockfile has a dependency with the wrong description', + () async { + await d.dir('bar', [d.libPubspec('foo', '1.0.0')]).create(); + + await d.dir(appPath, [ + d.appPubspec( + dependencies: { + 'foo': {'path': '../bar'} + }, + ) + ]).create(); + + await pubGet(); + + await createLockFile(appPath, dependenciesInSandBox: ['foo']); + + // Ensure that the pubspec looks newer than the lockfile. + await _touch('pubspec.yaml'); + + await _implicitPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated'); + }); + + test('the pubspec has an incompatible version of a dependency', () async { + await d.dir(appPath, [ + d.appPubspec(dependencies: {'foo': '1.0.0'}) + ]).create(); + + await pubGet(); + + await d.dir(appPath, [ + d.appPubspec(dependencies: {'foo': '2.0.0'}) + ]).create(); + + // Ensure that the pubspec looks newer than the lockfile. + await _touch('pubspec.yaml'); + + await _implicitPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated'); + }); + + test( + 'the lockfile is pointing to an unavailable package with a newer ' + 'pubspec', () async { + await d.dir(appPath, [ + d.appPubspec(dependencies: {'foo': '1.0.0'}) + ]).create(); + + await pubGet(); + + deleteEntry(p.join(d.sandbox, cachePath)); + + // Ensure that the pubspec looks newer than the lockfile. + await _touch('pubspec.yaml'); + + await _implicitPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated'); + }); + + test('the package_config.json file points to the wrong place', () async { + await d.dir('bar', [d.libPubspec('foo', '1.0.0')]).create(); + + await d.dir(appPath, [ + d.appPubspec( + dependencies: { + 'foo': {'path': '../bar'} + }, + ) + ]).create(); + + await pubGet(); + + await d.dir(appPath, [ + d.packageConfigFile([ + d.packageConfigEntry( + name: 'foo', + path: '../foo', // this is the wrong path + ), + d.packageConfigEntry( + name: 'myapp', + path: '.', + ), + ]), + ]).create(); + + // Ensure that the pubspec looks newer than the lockfile. + await _touch('pubspec.lock'); + + await _implicitPubGet('The pubspec.lock file has changed since the ' + '.dart_tool${p.separator}package_config.json file was generated'); + }); + + test("the lock file's SDK constraint doesn't match the current SDK", + () async { + // Avoid using a path dependency because it triggers the full validation + // logic. We want to be sure SDK-validation works without that logic. + server.serve( + 'foo', + '1.0.0', + pubspec: { + 'environment': {'sdk': '>=3.0.0 <3.1.0'} + }, + ); + + await d.dir(appPath, [ + d.pubspec({ + 'name': 'myapp', + 'environment': {'sdk': '^3.0.0'}, + 'dependencies': {'foo': '^1.0.0'}, + }), + ]).create(); + + await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '3.0.0'}); + + server.serve( + 'foo', + '1.0.1', + pubspec: { + 'environment': {'sdk': '^3.0.0'} + }, + ); + + await _implicitPubGet( + "Dart 3.1.2+3 is incompatible with your dependencies' " + 'SDK constraints'); + }); + + test( + "the lock file's Flutter SDK constraint doesn't match the " + 'current Flutter SDK', () async { + // Avoid using a path dependency because it triggers the full validation + // logic. We want to be sure SDK-validation works without that logic. + server.serve( + 'foo', + '3.0.0', + pubspec: { + 'environment': { + 'flutter': '>=1.0.0 <2.0.0', + 'sdk': defaultSdkConstraint + } + }, + ); + + await d.dir('flutter', [d.file('version', '1.2.3')]).create(); + + await d.dir(appPath, [ + d.appPubspec(dependencies: {'foo': '^3.0.0'}) + ]).create(); + + await pubGet( + environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')}, + ); + + await d.dir('flutter', [d.file('version', '0.9.0')]).create(); + + server.serve( + 'foo', + '3.0.1', + pubspec: { + 'environment': { + 'flutter': '>=0.1.0 <2.0.0', + 'sdk': defaultSdkConstraint + } + }, + ); + + await _implicitPubGet( + 'Flutter 0.9.0 is incompatible with your dependencies\' SDK constraints', + environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')}, + ); + }); + + test("a path dependency's dependency doesn't match the lockfile", + () async { + await d.dir('bar', [ + d.libPubspec('bar', '1.0.0', deps: {'foo': '1.0.0'}) + ]).create(); + + await d.dir(appPath, [ + d.appPubspec( + dependencies: { + 'bar': {'path': '../bar'} + }, + ) + ]).create(); + + await pubGet(); + + // Update bar's pubspec without touching the app's. + await d.dir('bar', [ + d.libPubspec('bar', '1.0.0', deps: {'foo': '2.0.0'}) + ]).create(); + + await _implicitPubGet( + '${p.join('..', 'bar', 'pubspec.yaml')} has changed ' + 'since the pubspec.lock file was generated.'); + }); + + test( + "a path dependency's language version doesn't match the package_config.json", + () async { + await d.dir('bar', [ + d.libPubspec( + 'bar', + '1.0.0', + deps: {'foo': '1.0.0'}, + // Creates language version requirement 2.99 + sdk: '>= 2.99.0 <=4.0.0', // tests runs with '3.1.2+3' + ), + ]).create(); + + await d.dir(appPath, [ + d.appPubspec( + dependencies: { + 'bar': {'path': '../bar'} + }, + ) + ]).create(); + + await pubGet(); + + // Update bar's pubspec without touching the app's. + await d.dir('bar', [ + d.libPubspec( + 'bar', + '1.0.0', + deps: {'foo': '1.0.0'}, + // Creates language version requirement 2.100 + sdk: '>= 2.100.0 <=4.0.0', // tests runs with '3.1.2+3' + ), + ]).create(); + + await _implicitPubGet( + '${p.join('..', 'bar', 'pubspec.yaml')} has changed ' + 'since the pubspec.lock file was generated.'); + }); + }); + + group("doesn't require the user to run pub get first if", () { + test( + 'the pubspec is older than the lockfile which is older than the ' + 'package-config, even if the contents are wrong', () async { + await d.dir(appPath, [ + d.appPubspec(dependencies: {'foo': '1.0.0'}) + ]).create(); + // Ensure we get a new mtime (mtime is only reported with 1s precision) + await _touch('pubspec.yaml'); + + await _touch('pubspec.lock'); + await _touch('.dart_tool/package_config.json'); + + await _noImplicitPubGet(); + }); + + test("the pubspec is newer than the lockfile, but they're up-to-date", + () async { + await d.dir(appPath, [ + d.appPubspec(dependencies: {'foo': '1.0.0'}) + ]).create(); + + await pubGet(); + + await _touch('pubspec.yaml'); + + await _noImplicitPubGet(); + }); + + // Regression test for #1416 + test('a path dependency has a dependency on the root package', () async { + await d.dir('foo', [ + d.libPubspec('foo', '1.0.0', deps: {'myapp': 'any'}) + ]).create(); + + await d.dir(appPath, [ + d.appPubspec( + dependencies: { + 'foo': {'path': '../foo'} + }, + ) + ]).create(); + + await pubGet(); + + await _touch('pubspec.lock'); + + await _noImplicitPubGet(); + }); + + test( + "the lockfile is newer than package_config.json, but it's up-to-date", + () async { + await d.dir(appPath, [ + d.appPubspec(dependencies: {'foo': '1.0.0'}) + ]).create(); + + await pubGet(); + + await _touch('pubspec.lock'); + + await _noImplicitPubGet(); + }); + + test("an overridden dependency's SDK constraint is unmatched", () async { + server.serve( + 'bar', + '1.0.0', + pubspec: { + 'environment': {'sdk': '0.0.0-fake'} + }, + ); + + await d.dir(appPath, [ + d.pubspec({ + 'name': 'myapp', + 'dependency_overrides': {'bar': '1.0.0'} + }) + ]).create(); + + await pubGet(); + + await _touch('pubspec.lock'); + + await _noImplicitPubGet(); + }); + + test('the lock file has a Flutter SDK but Flutter is unavailable', + () async { + // Avoid using a path dependency because it triggers the full validation + // logic. We want to be sure SDK-validation works without that logic. + server.serve( + 'foo', + '3.0.0', + pubspec: { + 'environment': { + 'flutter': '>=1.0.0 <2.0.0', + 'sdk': defaultSdkConstraint + } + }, + ); + + await d.dir('flutter', [d.file('version', '1.2.3')]).create(); + + await d.dir(appPath, [ + d.appPubspec(dependencies: {'foo': '3.0.0'}) + ]).create(); + + await pubGet( + environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')}, + ); + + await d.dir('flutter', [d.file('version', '2.4.6')]).create(); + + // Run pub manually here because otherwise we don't have access to + // d.sandbox. + await runPub(args: ['run', 'bin/script.dart']); + }); + }); + }); +} + +/// Runs every command that care about the world being up-to-date, and asserts +/// that it prints [message] as part of its silent output. +Future _implicitPubGet( + String message, { + Map? environment, +}) async { + final buffer = StringBuffer(); + await runEmbeddingToBuffer( + ['pub', 'ensure-pubspec-resolved', '--verbose'], + buffer, + workingDirectory: d.path(appPath), + environment: environment, + ); + final output = buffer.toString(); + expect(output, contains('FINE: $message')); + expect(output, contains('Resolving dependencies')); +} + +/// Ensures that pub doesn't require "dart pub get" for the current package. +/// +/// If [runDeps] is false, `pub deps` isn't included in the test. This is +/// sometimes not desirable, since it uses slightly stronger checks for pubspec +/// and lockfile consistency. +Future _noImplicitPubGet({ + Map? environment, +}) async { + final buffer = StringBuffer(); + await runEmbeddingToBuffer( + ['pub', 'ensure-pubspec-resolved', '--verbose'], + buffer, + workingDirectory: d.path(appPath), + environment: environment, + ); + final output = buffer.toString(); + expect(output, contains('FINE: Package Config up to date.')); + expect(output, isNot(contains('Resolving dependencies'))); + // If pub determines that everything is up-to-date, it should set the + // mtimes to indicate that. + var pubspecModified = + File(p.join(d.sandbox, 'myapp/pubspec.yaml')).lastModifiedSync(); + var lockFileModified = + File(p.join(d.sandbox, 'myapp/pubspec.lock')).lastModifiedSync(); + var packageConfigModified = + File(p.join(d.sandbox, 'myapp/.dart_tool/package_config.json')) + .lastModifiedSync(); + + expect(!pubspecModified.isAfter(lockFileModified), isTrue); + expect(!lockFileModified.isAfter(packageConfigModified), isTrue); +} + +/// Schedules a non-semantic modification to [path]. +Future _touch(String path) async { + // Delay a bit to make sure the modification times are noticeably different. + // 1s seems to be the finest granularity that dart:io reports. + await Future.delayed(Duration(seconds: 1)); + + path = p.join(d.sandbox, 'myapp', path); + touch(path); +} From 707415644021373043a0d7bec953119ad3ed58e6 Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Tue, 21 Mar 2023 14:15:35 +0000 Subject: [PATCH 3/4] Fix windows directory delimiters in test --- test/embedding/ensure_pubspec_resolved.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/embedding/ensure_pubspec_resolved.dart b/test/embedding/ensure_pubspec_resolved.dart index fa98d9beb..16b90a6db 100644 --- a/test/embedding/ensure_pubspec_resolved.dart +++ b/test/embedding/ensure_pubspec_resolved.dart @@ -236,7 +236,7 @@ void testEnsurePubspecResolved() { await _touch('pubspec.lock'); await _implicitPubGet('The pubspec.lock file has changed since the ' - '.dart_tool${p.separator}package_config.json file was generated'); + '.dart_tool/package_config.json file was generated'); }); test("the lock file's SDK constraint doesn't match the current SDK", @@ -340,8 +340,7 @@ void testEnsurePubspecResolved() { d.libPubspec('bar', '1.0.0', deps: {'foo': '2.0.0'}) ]).create(); - await _implicitPubGet( - '${p.join('..', 'bar', 'pubspec.yaml')} has changed ' + await _implicitPubGet('../bar/pubspec.yaml has changed ' 'since the pubspec.lock file was generated.'); }); From 1abcf86ba4066fefa157aec618c47c77bfd4f893 Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Tue, 21 Mar 2023 14:36:01 +0000 Subject: [PATCH 4/4] Fix windows directory delimiters in test2 --- test/embedding/ensure_pubspec_resolved.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/embedding/ensure_pubspec_resolved.dart b/test/embedding/ensure_pubspec_resolved.dart index 16b90a6db..ea4daba4b 100644 --- a/test/embedding/ensure_pubspec_resolved.dart +++ b/test/embedding/ensure_pubspec_resolved.dart @@ -80,7 +80,7 @@ void testEnsurePubspecResolved() { .deleteSync(); await _implicitPubGet( - 'No .dart_tool${p.separator}package_config.json file found', + 'No .dart_tool/package_config.json file found', ); }); @@ -378,8 +378,7 @@ void testEnsurePubspecResolved() { ), ]).create(); - await _implicitPubGet( - '${p.join('..', 'bar', 'pubspec.yaml')} has changed ' + await _implicitPubGet('../bar/pubspec.yaml has changed ' 'since the pubspec.lock file was generated.'); }); });