Api entrypoint for ensuring resolution is up-to-date (#3844)

diff --git a/lib/pub.dart b/lib/pub.dart
index fc79917..b1239f7 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 @@
   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<void> 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 b34e33e..0fe86c2 100644
--- a/lib/src/command/deps.dart
+++ b/lib/src/command/deps.dart
@@ -72,7 +72,7 @@
   @override
   Future<void> 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 @@
       if (argResults.wasParsed('style')) {
         usageException('Cannot combine --json and --style.');
       }
+      await entrypoint.ensureUpToDate();
       final visited = <String>[];
       final toVisit = [entrypoint.root.name];
       final packagesJson = <dynamic>[];
diff --git a/lib/src/command/list_package_dirs.dart b/lib/src/command/list_package_dirs.dart
deleted file mode 100644
index b572a9e..0000000
--- 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 <dir>.',
-      valueHelp: 'dir',
-    );
-  }
-
-  @override
-  Future<void> 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 9a01f9b..f92ff26 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -169,7 +169,7 @@
   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 @@
     }
   }
 
-  /// 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<void> 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 @@
       //  * 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 @@
       // 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 @@
     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 @@
 
       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 @@
     //
     // 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 @@
 
       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 @@
     });
   }
 
-  /// 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 = <String, String>{};
 
     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 @@
             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 accde6e..5789665 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -18,7 +18,6 @@
 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 @@
   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 @@
   }
   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 8a7d59b..4b8e7ac 100644
--- a/test/embedding/embedding_test.dart
+++ b/test/embedding/embedding_test.dart
@@ -15,6 +15,7 @@
 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 @@
   List<String> args,
   StringBuffer buffer, {
   String? workingDirectory,
-  Map<String, String>? environment,
+  Map<String, String?>? 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 @@
       );
     }
   });
+
+  testEnsurePubspecResolved();
 }
 
 String _filter(String input) {
diff --git a/test/embedding/ensure_pubspec_resolved.dart b/test/embedding/ensure_pubspec_resolved.dart
new file mode 100644
index 0000000..ea4daba
--- /dev/null
+++ b/test/embedding/ensure_pubspec_resolved.dart
@@ -0,0 +1,566 @@
+// 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/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/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('../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('../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<void> _implicitPubGet(
+  String message, {
+  Map<String, String?>? 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<void> _noImplicitPubGet({
+  Map<String, String?>? 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);
+}
diff --git a/test/must_pub_get_test.dart b/test/must_pub_get_test.dart
deleted file mode 100644
index 4eace86..0000000
--- 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 f350065..d4ff758 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 @@
 [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 @@
    | 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 8e8a8ca..9192079 100644
--- a/tool/test-bin/pub_command_runner.dart
+++ b/tool/test-bin/pub_command_runner.dart
@@ -33,6 +33,24 @@
   }
 }
 
+// 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<int> runProtected() async {
+    await ensurePubspecResolved('.');
+    return 0;
+  }
+}
+
 class RunCommand extends Command<int> {
   @override
   String get name => 'run';
@@ -70,7 +88,8 @@
         : null;
     addCommand(
       pubCommand(analytics: analytics, isVerbose: () => _options['verbose'])
-        ..addSubcommand(ThrowingCommand()),
+        ..addSubcommand(ThrowingCommand())
+        ..addSubcommand(EnsurePubspecResolvedCommand()),
     );
     addCommand(RunCommand());
     argParser.addFlag('verbose');