Warm cache with all transitive dependencies in `flutter update-packages` command (#96258)

diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart
index defc86c..2f5e9ed 100644
--- a/packages/flutter_tools/lib/src/cache.dart
+++ b/packages/flutter_tools/lib/src/cache.dart
@@ -153,7 +153,7 @@
     platform ??= FakePlatform(environment: <String, String>{});
     logger ??= BufferLogger.test();
     return Cache(
-      rootOverride: rootOverride ??= fileSystem.directory('cache'),
+      rootOverride: rootOverride ?? fileSystem.directory('cache'),
       artifacts: artifacts ?? <ArtifactSet>[],
       logger: logger,
       fileSystem: fileSystem,
diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart
index c293a11..6c54eff 100644
--- a/packages/flutter_tools/lib/src/commands/update_packages.dart
+++ b/packages/flutter_tools/lib/src/commands/update_packages.dart
@@ -97,6 +97,12 @@
         help: 'For Flutter CLI testing only, forces this command to throw an unhandled exception.',
         defaultsTo: false,
         negatable: false,
+      )
+      ..addOption(
+        'jobs',
+        abbr: 'j',
+        help: 'Causes the "pub get" runs to happen concurrently on this many '
+              'CPUs. Defaults to the number of CPUs that this machine has.',
       );
   }
 
@@ -141,19 +147,19 @@
   Future<FlutterCommandResult> runCommand() async {
     final List<Directory> packages = runner.getRepoPackages();
 
-    final bool upgrade = boolArg('force-upgrade');
+    final bool forceUpgrade = boolArg('force-upgrade');
     final bool isPrintPaths = boolArg('paths');
     final bool isPrintTransitiveClosure = boolArg('transitive-closure');
     final bool isVerifyOnly = boolArg('verify-only');
     final bool isConsumerOnly = boolArg('consumer-only');
     final bool offline = boolArg('offline');
-    final bool crash = boolArg('crash');
+    final bool doUpgrade = forceUpgrade || isPrintPaths || isPrintTransitiveClosure;
 
-    if (crash) {
+    if (boolArg('crash')) {
       throw StateError('test crash please ignore.');
     }
 
-    if (upgrade && offline) {
+    if (forceUpgrade && offline) {
       throwToolExit(
           '--force-upgrade cannot be used with the --offline flag'
       );
@@ -177,60 +183,10 @@
     }
 
     if (isVerifyOnly) {
-      bool needsUpdate = false;
-      globals.printStatus('Verifying pubspecs...');
-      for (final Directory directory in packages) {
-        PubspecYaml pubspec;
-        try {
-          pubspec = PubspecYaml(directory);
-        } on String catch (message) {
-          throwToolExit(message);
-        }
-        globals.printTrace('Reading pubspec.yaml from ${directory.path}');
-        if (pubspec.checksum.value == null) {
-          // If the checksum is invalid or missing, we can just ask them run to run
-          // upgrade again to compute it.
-          globals.printWarning(
-            'Warning: pubspec in ${directory.path} has out of date dependencies. '
-            'Please run "flutter update-packages --force-upgrade" to update them correctly.'
-          );
-          needsUpdate = true;
-        }
-        // all dependencies in the pubspec sorted lexically.
-        final Map<String, String> checksumDependencies = <String, String>{};
-        for (final PubspecLine data in pubspec.inputData) {
-          if (data is PubspecDependency && data.kind == DependencyKind.normal) {
-            checksumDependencies[data.name] = data.version;
-          }
-        }
-        final String checksum = _computeChecksum(checksumDependencies.keys, (String name) => checksumDependencies[name]);
-        if (checksum != pubspec.checksum.value) {
-          // If the checksum doesn't match, they may have added or removed some dependencies.
-          // we need to run update-packages to recapture the transitive deps.
-          globals.printWarning(
-            'Warning: pubspec in ${directory.path} has updated or new dependencies. '
-            'Please run "flutter update-packages --force-upgrade" to update them correctly '
-            '(checksum ${pubspec.checksum.value} != $checksum).'
-          );
-          needsUpdate = true;
-        } else {
-          // everything is correct in the pubspec.
-          globals.printTrace('pubspec in ${directory.path} is up to date!');
-        }
-      }
-      if (needsUpdate) {
-        throwToolExit(
-          'Warning: one or more pubspecs have invalid dependencies. '
-          'Please run "flutter update-packages --force-upgrade" to update them correctly.',
-          exitCode: 1,
-        );
-      }
-      globals.printStatus('All pubspecs were up to date.');
+      _verifyPubspecs(packages);
       return FlutterCommandResult.success();
     }
 
-    final Map<String, PubspecDependency> dependencies = <String, PubspecDependency>{};
-    final bool doUpgrade = upgrade || isPrintPaths || isPrintTransitiveClosure;
     if (doUpgrade) {
       // This feature attempts to collect all the packages used across all the
       // pubspec.yamls in the repo (including via transitive dependencies), and
@@ -239,9 +195,116 @@
       globals.printStatus('Upgrading packages...');
     }
 
-    // First, collect up the explicit dependencies:
+    // First, collect the dependencies:
     final List<PubspecYaml> pubspecs = <PubspecYaml>[];
+    final Map<String, PubspecDependency> explicitDependencies = <String, PubspecDependency>{};
+    final Map<String, PubspecDependency> allDependencies = <String, PubspecDependency>{};
     final Set<String> specialDependencies = <String>{};
+    _collectDependencies(
+      packages: packages,
+      pubspecs: pubspecs,
+      explicitDependencies: explicitDependencies,
+      allDependencies: allDependencies,
+      specialDependencies: specialDependencies,
+      doUpgrade: doUpgrade,
+    );
+
+    // Now that we have all the dependencies we care about, we are going to
+    // create a fake package and then run either "pub upgrade", if requested,
+    // followed by "pub get" on it. If upgrading, the pub tool will attempt to
+    // bring these dependencies up to the most recent possible versions while
+    // honoring all their constraints. If not upgrading the pub tool will only
+    // attempt to download any necessary package versions to the pub cache to
+    // warm the cache.
+    final PubDependencyTree tree = PubDependencyTree(); // object to collect results
+    final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_update_packages.');
+    await _generateFakePackage(
+      tempDir: tempDir,
+      dependencies: doUpgrade ? explicitDependencies.values : allDependencies.values,
+      pubspecs: pubspecs,
+      tree: tree,
+      doUpgrade: doUpgrade,
+    );
+
+    if (doUpgrade) {
+      final bool done = _upgradePubspecs(
+        tree: tree,
+        pubspecs: pubspecs,
+        explicitDependencies: explicitDependencies,
+        specialDependencies: specialDependencies,
+      );
+
+      if (done) {
+        // Complete early if we were just printing data.
+        return FlutterCommandResult.success();
+      }
+    }
+
+    await _runPubGetOnPackages(packages);
+
+    return FlutterCommandResult.success();
+  }
+
+  void _verifyPubspecs(List<Directory> packages) {
+    bool needsUpdate = false;
+    globals.printStatus('Verifying pubspecs...');
+    for (final Directory directory in packages) {
+      PubspecYaml pubspec;
+      try {
+        pubspec = PubspecYaml(directory);
+      } on String catch (message) {
+        throwToolExit(message);
+      }
+      globals.printTrace('Reading pubspec.yaml from ${directory.path}');
+      if (pubspec.checksum.value == null) {
+        // If the checksum is invalid or missing, we can just ask them run to run
+        // upgrade again to compute it.
+        globals.printWarning(
+            'Warning: pubspec in ${directory.path} has out of date dependencies. '
+            'Please run "flutter update-packages --force-upgrade" to update them correctly.'
+        );
+        needsUpdate = true;
+      }
+      // all dependencies in the pubspec sorted lexically.
+      final Map<String, String> checksumDependencies = <String, String>{};
+      for (final PubspecLine data in pubspec.inputData) {
+        if (data is PubspecDependency && data.kind == DependencyKind.normal) {
+          checksumDependencies[data.name] = data.version;
+        }
+      }
+      final String checksum = _computeChecksum(checksumDependencies.keys, (String name) => checksumDependencies[name]);
+      if (checksum != pubspec.checksum.value) {
+        // If the checksum doesn't match, they may have added or removed some dependencies.
+        // we need to run update-packages to recapture the transitive deps.
+        globals.printWarning(
+            'Warning: pubspec in ${directory.path} has updated or new dependencies. '
+            'Please run "flutter update-packages --force-upgrade" to update them correctly '
+            '(checksum ${pubspec.checksum.value} != $checksum).'
+        );
+        needsUpdate = true;
+      } else {
+        // everything is correct in the pubspec.
+        globals.printTrace('pubspec in ${directory.path} is up to date!');
+      }
+    }
+    if (needsUpdate) {
+      throwToolExit(
+        'Warning: one or more pubspecs have invalid dependencies. '
+        'Please run "flutter update-packages --force-upgrade" to update them correctly.',
+        exitCode: 1,
+      );
+    }
+    globals.printStatus('All pubspecs were up to date.');
+  }
+
+  void _collectDependencies({
+    @required List<Directory> packages,
+    @required List<PubspecYaml> pubspecs,
+    @required Set<String> specialDependencies,
+    @required Map<String, PubspecDependency> explicitDependencies,
+    @required Map<String, PubspecDependency> allDependencies,
+    @required bool doUpgrade,
+  }) {
     // Visit all the directories with pubspec.yamls we care about.
     for (final Directory directory in packages) {
       if (doUpgrade) {
@@ -254,8 +317,8 @@
         throwToolExit(message);
       }
       pubspecs.add(pubspec); // remember it for later
-      for (final PubspecDependency dependency in pubspec.allDependencies) { // this is all the explicit dependencies
-        if (dependencies.containsKey(dependency.name)) {
+      for (final PubspecDependency dependency in pubspec.allDependencies) {
+        if (allDependencies.containsKey(dependency.name)) {
           // If we've seen the dependency before, make sure that we are
           // importing it the same way. There's several ways to import a
           // dependency. Hosted (from pub via version number), by path (e.g.
@@ -266,17 +329,47 @@
           // This makes sure that we don't import a package in two different
           // ways, e.g. by saying "sdk: flutter" in one pubspec.yaml and
           // saying "path: ../../..." in another.
-          final PubspecDependency previous = dependencies[dependency.name];
+          final PubspecDependency previous = allDependencies[dependency.name];
           if (dependency.kind != previous.kind || dependency.lockTarget != previous.lockTarget) {
             throwToolExit(
-              'Inconsistent requirements around ${dependency.name}; '
-              'saw ${dependency.kind} (${dependency.lockTarget}) in "${dependency.sourcePath}" '
-              'and ${previous.kind} (${previous.lockTarget}) in "${previous.sourcePath}".'
+                'Inconsistent requirements around ${dependency.name}; '
+                    'saw ${dependency.kind} (${dependency.lockTarget}) in "${dependency.sourcePath}" '
+                    'and ${previous.kind} (${previous.lockTarget}) in "${previous.sourcePath}".'
+            );
+          }
+          if (dependency.version != previous.version) {
+            globals.printError(
+                'Requiring multiple versions: multiple versions required by ${dependency.name}; '
+                    'saw ${dependency.version} in "${dependency.sourcePath}" '
+                    'and ${previous.version} in "${previous.sourcePath}".'
+            );
+          }
+        }
+        allDependencies[dependency.name] = dependency;
+      }
+      for (final PubspecDependency dependency in pubspec.allExplicitDependencies) {
+        if (explicitDependencies.containsKey(dependency.name)) {
+          // If we've seen the dependency before, make sure that we are
+          // importing it the same way. There's several ways to import a
+          // dependency. Hosted (from pub via version number), by path (e.g.
+          // pointing at the version of a package we get from the Dart SDK
+          // that we download with Flutter), by SDK (e.g. the "flutter"
+          // package is explicitly from "sdk: flutter").
+          //
+          // This makes sure that we don't import a package in two different
+          // ways, e.g. by saying "sdk: flutter" in one pubspec.yaml and
+          // saying "path: ../../..." in another.
+          final PubspecDependency previous = explicitDependencies[dependency.name];
+          if (dependency.kind != previous.kind || dependency.lockTarget != previous.lockTarget) {
+            throwToolExit(
+                'Inconsistent requirements around ${dependency.name}; '
+                'saw ${dependency.kind} (${dependency.lockTarget}) in "${dependency.sourcePath}" '
+                'and ${previous.kind} (${previous.lockTarget}) in "${previous.sourcePath}".'
             );
           }
         }
         // Remember this dependency by name so we can look it up again.
-        dependencies[dependency.name] = dependency;
+        explicitDependencies[dependency.name] = dependency;
         // Normal dependencies are those we get from pub. The others we
         // already implicitly pin since we pull down one version of the
         // Flutter and Dart SDKs, so we track which those are here so that we
@@ -286,29 +379,28 @@
         }
       }
     }
+  }
 
-    // Now that we have all the dependencies we explicitly care about, we are
-    // going to create a fake package and then run either "pub upgrade" or "pub
-    // get" on it, depending on whether we are upgrading or not. If upgrading,
-    // the pub tool will attempt to bring these dependencies up to the most
-    // recent possible versions while honoring all their constraints. If not
-    // upgrading the pub tool will attempt to download any necessary package
-    // versions to the pub cache to warm the cache.
-    final PubDependencyTree tree = PubDependencyTree(); // object to collect results
-    final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_update_packages.');
+  Future<void> _generateFakePackage({
+    Directory tempDir,
+    Iterable<PubspecDependency> dependencies,
+    List<PubspecYaml> pubspecs,
+    PubDependencyTree tree,
+    bool doUpgrade,
+  }) async {
     try {
       final File fakePackage = _pubspecFor(tempDir);
       fakePackage.createSync();
       fakePackage.writeAsStringSync(
         _generateFakePubspec(
-          dependencies.values,
+          dependencies,
           useAnyVersion: doUpgrade,
         ),
       );
       // Create a synthetic flutter SDK so that transitive flutter SDK
       // constraints are not affected by this upgrade.
       Directory temporaryFlutterSdk;
-      if (upgrade) {
+      if (doUpgrade) {
         temporaryFlutterSdk = createTemporaryFlutterSdk(
           globals.logger,
           globals.fs,
@@ -317,17 +409,14 @@
         );
       }
 
-      // Next we run "pub upgrade" on this generated package, if we're doing
-      // an upgrade. Otherwise, we just run a regular "pub get" on it in order
-      // to force the download of any needed packages to the pub cache.
+      // Next we run "pub get" on it in order to force the download of any
+      // needed packages to the pub cache, upgrading if requested.
       await pub.get(
         context: PubContext.updatePackages,
         directory: tempDir.path,
         upgrade: doUpgrade,
-        offline: offline,
-        flutterRootOverride: upgrade
-          ? temporaryFlutterSdk.path
-          : null,
+        offline: boolArg('offline'),
+        flutterRootOverride: doUpgrade ? temporaryFlutterSdk.path : null,
         generateSyntheticPackage: false,
       );
       // Cleanup the temporary SDK
@@ -354,54 +443,57 @@
     } finally {
       tempDir.deleteSync(recursive: true);
     }
+  }
 
-    if (doUpgrade) {
-      // The transitive dependency tree for the fake package does not contain
-      // dependencies between Flutter SDK packages and pub packages. We add them
-      // here.
-      for (final PubspecYaml pubspec in pubspecs) {
-        final String package = pubspec.name;
-        specialDependencies.add(package);
-        tree._versions[package] = pubspec.version;
-        assert(!tree._dependencyTree.containsKey(package));
-        tree._dependencyTree[package] = <String>{};
-        for (final PubspecDependency dependency in pubspec.dependencies) {
-          if (dependency.kind == DependencyKind.normal) {
-            tree._dependencyTree[package].add(dependency.name);
-          }
+  bool _upgradePubspecs({
+    @required PubDependencyTree tree,
+    @required List<PubspecYaml> pubspecs,
+    @required Set<String> specialDependencies,
+    @required Map<String, PubspecDependency> explicitDependencies,
+  }) {
+    // The transitive dependency tree for the fake package does not contain
+    // dependencies between Flutter SDK packages and pub packages. We add them
+    // here.
+    for (final PubspecYaml pubspec in pubspecs) {
+      final String package = pubspec.name;
+      specialDependencies.add(package);
+      tree._versions[package] = pubspec.version;
+      assert(!tree._dependencyTree.containsKey(package));
+      tree._dependencyTree[package] = <String>{};
+      for (final PubspecDependency dependency in pubspec.dependencies) {
+        if (dependency.kind == DependencyKind.normal) {
+          tree._dependencyTree[package].add(dependency.name);
         }
       }
-
-      if (isPrintTransitiveClosure) {
-        tree._dependencyTree.forEach((String from, Set<String> to) {
-          globals.printStatus('$from -> $to');
-        });
-        return FlutterCommandResult.success();
-      }
-
-      if (isPrintPaths) {
-        showDependencyPaths(from: stringArg('from'), to: stringArg('to'), tree: tree);
-        return FlutterCommandResult.success();
-      }
-
-      // Now that we have collected all the data, we can apply our dependency
-      // versions to each pubspec.yaml that we collected. This mutates the
-      // pubspec.yaml files.
-      //
-      // The specialDependencies argument is the set of package names to not pin
-      // to specific versions because they are explicitly pinned by their
-      // constraints. Here we list the names we earlier established we didn't
-      // need to pin because they come from the Dart or Flutter SDKs.
-      for (final PubspecYaml pubspec in pubspecs) {
-        pubspec.apply(tree, specialDependencies);
-      }
-
-      // Now that the pubspec.yamls are updated, we run "pub get" on each one so
-      // that the various packages are ready to use. This is what "flutter
-      // update-packages" does without --force-upgrade, so we can just fall into
-      // the regular code path.
     }
 
+    if (boolArg('transitive-closure')) {
+      tree._dependencyTree.forEach((String from, Set<String> to) {
+        globals.printStatus('$from -> $to');
+      });
+      return true;
+    }
+
+    if (boolArg('paths')) {
+      showDependencyPaths(from: stringArg('from'), to: stringArg('to'), tree: tree);
+      return true;
+    }
+
+    // Now that we have collected all the data, we can apply our dependency
+    // versions to each pubspec.yaml that we collected. This mutates the
+    // pubspec.yaml files.
+    //
+    // The specialDependencies argument is the set of package names to not pin
+    // to specific versions because they are explicitly pinned by their
+    // constraints. Here we list the names we earlier established we didn't
+    // need to pin because they come from the Dart or Flutter SDKs.
+    for (final PubspecYaml pubspec in pubspecs) {
+      pubspec.apply(tree, specialDependencies);
+    }
+    return false;
+  }
+
+  Future<void> _runPubGetOnPackages(List<Directory> packages) async {
     final Stopwatch timer = Stopwatch()..start();
     int count = 0;
 
@@ -416,7 +508,7 @@
       'Running "flutter pub get" in affected packages...',
     );
     try {
-      final TaskQueue<void> queue = TaskQueue<void>();
+      final TaskQueue<void> queue = TaskQueue<void>(maxJobs: intArg('jobs'));
       for (final Directory dir in packages) {
         unawaited(queue.add(() async {
           final Stopwatch stopwatch = Stopwatch();
@@ -424,7 +516,9 @@
           await pub.get(
             context: PubContext.updatePackages,
             directory: dir.path,
-            offline: offline,
+            // All dependencies should already have been downloaded by the fake
+            // package, so the concurrent checks can all happen offline.
+            offline: true,
             generateSyntheticPackage: false,
             printProgress: false,
           );
@@ -452,8 +546,6 @@
 
     final double seconds = timer.elapsedMilliseconds / 1000.0;
     globals.printStatus("\nRan 'pub get' $count time${count == 1 ? "" : "s"} and fetched coverage data in ${seconds.toStringAsFixed(1)}s.");
-
-    return FlutterCommandResult.success();
   }
 
   void showDependencyPaths({
@@ -752,12 +844,17 @@
   }
 
   /// This returns all regular dependencies and all dev dependencies.
-  Iterable<PubspecDependency> get allDependencies {
+  Iterable<PubspecDependency> get allExplicitDependencies {
     return inputData
         .whereType<PubspecDependency>()
         .where((PubspecDependency data) => data.kind != DependencyKind.overridden && !data.isTransitive);
   }
 
+  /// This returns all dependencies.
+  Iterable<PubspecDependency> get allDependencies {
+    return inputData.whereType<PubspecDependency>();
+  }
+
   /// Take a dependency graph with explicit version numbers, and apply them to
   /// the pubspec.yaml, ignoring any that we know are special dependencies (those
   /// that depend on the Flutter or Dart SDK directly and are thus automatically
@@ -814,7 +911,8 @@
                 // We output data that matches the format that
                 // PubspecDependency.parse can handle. The data.suffix is any
                 // previously-specified trailing comment.
-                assert(versions.contains(data.name));
+                assert(versions.contains(data.name),
+                       "versions doesn't contain ${data.name}");
                 output.add('  ${data.name}: ${versions.versionFor(data.name)}${data.suffix}');
               } else {
                 // If it wasn't a regular dependency, then we output the line
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 96bff4a..6a69357 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -1473,12 +1473,15 @@
 
   ApplicationPackageFactory? applicationPackages;
 
-  /// Gets the parsed command-line option named [name] as `bool`.
+  /// Gets the parsed command-line option named [name] as a `bool`.
   bool boolArg(String name) => argResults?[name] as bool? ?? false;
 
-  /// Gets the parsed command-line option named [name] as `String`.
+  /// Gets the parsed command-line option named [name] as a `String`.
   String? stringArg(String name) => argResults?[name] as String?;
 
+  /// Gets the parsed command-line option named [name] as an `int`.
+  int? intArg(String name) => argResults?[name] as int?;
+
   /// Gets the parsed command-line option named [name] as `List<String>`.
   List<String> stringsArg(String name) => argResults?[name] as List<String>? ?? <String>[];
 }
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart
index c8f2bc3..78018b4 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart
@@ -4,9 +4,75 @@
 
 // @dart = 2.8
 
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/commands/update_packages.dart';
+import 'package:flutter_tools/src/dart/pub.dart';
+import 'package:meta/meta.dart';
+import 'package:test/fake.dart';
 
 import '../../src/common.dart';
+import '../../src/context.dart';
+import '../../src/test_flutter_command_runner.dart';
+
+// An example pubspec.yaml from flutter, not necessary for it to be up to date.
+const String kFlutterPubspecYaml = r'''
+name: flutter
+description: A framework for writing Flutter applications
+homepage: http://flutter.dev
+
+environment:
+  sdk: ">=2.2.2 <3.0.0"
+
+dependencies:
+  # To update these, use "flutter update-packages --force-upgrade".
+  collection: 1.14.11
+  meta: 1.1.8
+  typed_data: 1.1.6
+  vector_math: 2.0.8
+
+  sky_engine:
+    sdk: flutter
+
+  gallery:
+    git:
+      url: https://github.com/flutter/gallery.git
+      ref: d00362e6bdd0f9b30bba337c358b9e4a6e4ca950
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  flutter_goldens:
+    sdk: flutter
+
+  archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+
+# PUBSPEC CHECKSUM: 1234
+''';
+
+// An example pubspec.yaml, not necessary for it to be up to date.
+const String kExamplesPubspecYaml = r'''
+name: examples
+description: Examples for flutter
+homepage: http://flutter.dev
+
+version: 1.0.0
+
+environment:
+  sdk: ">=2.14.0-383.0.dev <3.0.0"
+  flutter: ">=2.5.0-6.0.pre.30 <3.0.0"
+
+dependencies:
+  cupertino_icons: 1.0.4
+  flutter:
+    sdk: flutter
+
+  archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+
+# PUBSPEC CHECKSUM: 6543
+''';
 
 void main() {
   testWithoutContext('kManuallyPinnedDependencies pins are actually pins', () {
@@ -16,4 +82,148 @@
       reason: 'Version pins in kManuallyPinnedDependencies must be specific pins, not ranges.',
     );
   });
+
+  group('update-packages', () {
+    FileSystem fileSystem;
+    Directory flutterSdk;
+    Directory flutter;
+    FakePub pub;
+
+    setUpAll(() {
+      Cache.disableLocking();
+    });
+
+    setUp(() {
+      fileSystem = MemoryFileSystem.test();
+      flutterSdk = fileSystem.directory('flutter')..createSync();
+      flutterSdk.childFile('version').writeAsStringSync('1.2.3');
+      flutter = flutterSdk.childDirectory('packages').childDirectory('flutter')
+        ..createSync(recursive: true);
+      flutterSdk.childDirectory('dev').createSync(recursive: true);
+      flutterSdk.childDirectory('examples').childFile('pubspec.yaml')
+        ..createSync(recursive: true)
+        ..writeAsStringSync(kExamplesPubspecYaml);
+      flutter.childFile('pubspec.yaml').writeAsStringSync(kFlutterPubspecYaml);
+      Cache.flutterRoot = flutterSdk.absolute.path;
+      pub = FakePub(fileSystem);
+    });
+
+    testUsingContext('updates packages', () async {
+      final UpdatePackagesCommand command = UpdatePackagesCommand();
+      await createTestCommandRunner(command).run(<String>['update-packages']);
+      expect(pub.pubGetDirectories, equals(<String>[
+        '/.tmp_rand0/flutter_update_packages.rand0',
+        '/flutter/examples',
+        '/flutter/packages/flutter',
+      ]));
+      expect(pub.pubBatchDirectories, isEmpty);
+    }, overrides: <Type, Generator>{
+      Pub: () => pub,
+      FileSystem: () => fileSystem,
+      ProcessManager: () => FakeProcessManager.any(),
+      Cache: () => Cache.test(
+        processManager: FakeProcessManager.any(),
+      ),
+    });
+
+    testUsingContext('force updates packages', () async {
+      final UpdatePackagesCommand command = UpdatePackagesCommand();
+      await createTestCommandRunner(command).run(<String>[
+        'update-packages',
+        '--force-upgrade',
+      ]);
+      expect(pub.pubGetDirectories, equals(<String>[
+        '/.tmp_rand0/flutter_update_packages.rand0',
+        '/flutter/examples',
+        '/flutter/packages/flutter',
+      ]));
+      expect(pub.pubBatchDirectories, equals(<String>[
+        '/.tmp_rand0/flutter_update_packages.rand0',
+      ]));
+    }, overrides: <Type, Generator>{
+      Pub: () => pub,
+      FileSystem: () => fileSystem,
+      ProcessManager: () => FakeProcessManager.any(),
+      Cache: () => Cache.test(
+        processManager: FakeProcessManager.any(),
+      ),
+    });
+  });
+}
+
+class FakePub extends Fake implements Pub {
+  FakePub(this.fileSystem);
+
+  final FileSystem fileSystem;
+  final List<String> pubGetDirectories = <String>[];
+  final List<String> pubBatchDirectories = <String>[];
+
+  @override
+  Future<void> get({
+    @required PubContext context,
+    String directory,
+    bool skipIfAbsent = false,
+    bool upgrade = false,
+    bool offline = false,
+    bool generateSyntheticPackage = false,
+    String flutterRootOverride,
+    bool checkUpToDate = false,
+    bool shouldSkipThirdPartyGenerator = true,
+    bool printProgress = true,
+  }) async {
+    pubGetDirectories.add(directory);
+    fileSystem.directory(directory).childFile('pubspec.lock')
+      ..createSync(recursive: true)
+      ..writeAsStringSync('''
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  async:
+    dependency: "direct dev"
+    description:
+      name: async
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.8.2"
+sdks:
+  dart: ">=2.14.0 <3.0.0"
+''');
+    fileSystem.currentDirectory
+        .childDirectory('.dart_tool')
+        .childFile('package_config.json')
+      ..createSync(recursive: true)
+      ..writeAsStringSync('{"configVersion":2,"packages":[]}');
+  }
+
+  @override
+  Future<void> batch(
+      List<String> arguments, {
+        @required PubContext context,
+        String directory,
+        MessageFilter filter,
+        String failureMessage = 'pub failed',
+        @required bool retry,
+        bool showTraceForErrors,
+      }) async {
+    pubBatchDirectories.add(directory);
+
+'''
+Dart SDK 2.16.0-144.0.dev
+Flutter SDK 2.9.0-1.0.pre.263
+flutter_api_samples 1.0.0
+
+dependencies:
+- cupertino_icons 1.0.4
+- collection 1.15.0
+- meta 1.7.0
+- typed_data 1.3.0 [collection]
+- vector_math 2.1.1
+
+dev dependencies:
+
+transitive dependencies:
+- platform 3.1.0
+- process 4.2.4 [file path platform]
+'''.split('\n').forEach(filter);
+  }
 }
diff --git a/packages/flutter_tools/test/general.shard/update_packages_test.dart b/packages/flutter_tools/test/general.shard/update_packages_test.dart
index 537786f..74e191c 100644
--- a/packages/flutter_tools/test/general.shard/update_packages_test.dart
+++ b/packages/flutter_tools/test/general.shard/update_packages_test.dart
@@ -79,36 +79,36 @@
 ''';
 
 void main() {
-  testWithoutContext('createTemporaryFlutterSdk creates an unpinned flutter SDK', () {
-    final FileSystem fileSystem = MemoryFileSystem.test();
+  FileSystem fileSystem;
+  Directory flutterSdk;
+  Directory flutter;
 
+  setUp(() {
+    fileSystem = MemoryFileSystem.test();
     // Setup simplified Flutter SDK.
-    final Directory flutterSdk = fileSystem.directory('flutter')
-      ..createSync();
+    flutterSdk = fileSystem.directory('flutter')..createSync();
     // Create version file
     flutterSdk.childFile('version').writeAsStringSync('1.2.3');
     // Create a pubspec file
-    final Directory flutter = flutterSdk
-      .childDirectory('packages')
-      .childDirectory('flutter')
+    flutter = flutterSdk.childDirectory('packages').childDirectory('flutter')
       ..createSync(recursive: true);
-    flutter
-      .childFile('pubspec.yaml')
-      .writeAsStringSync(kFlutterPubspecYaml);
+    flutter.childFile('pubspec.yaml').writeAsStringSync(kFlutterPubspecYaml);
+  });
 
+  testWithoutContext(
+      'createTemporaryFlutterSdk creates an unpinned flutter SDK', () {
     // A stray extra package should not cause a crash.
     final Directory extra = flutterSdk
-      .childDirectory('packages')
-      .childDirectory('extra')
+        .childDirectory('packages')
+        .childDirectory('extra')
       ..createSync(recursive: true);
-    extra
-      .childFile('pubspec.yaml')
-      .writeAsStringSync(kExtraPubspecYaml);
+    extra.childFile('pubspec.yaml').writeAsStringSync(kExtraPubspecYaml);
 
     // Create already parsed pubspecs.
     final PubspecYaml flutterPubspec = PubspecYaml(flutter);
 
-    final PubspecDependency gitDependency = flutterPubspec.dependencies.firstWhere((PubspecDependency dep) => dep.kind == DependencyKind.git);
+    final PubspecDependency gitDependency = flutterPubspec.dependencies
+        .firstWhere((PubspecDependency dep) => dep.kind == DependencyKind.git);
     expect(
       gitDependency.lockLine,
       '''
@@ -152,25 +152,57 @@
   });
 
   testWithoutContext('Throws a StateError on a malformed git: reference', () {
-    final FileSystem fileSystem = MemoryFileSystem.test();
-
-    // Setup simplified Flutter SDK.
-    final Directory flutterSdk = fileSystem.directory('flutter')
-      ..createSync();
-    // Create version file
-    flutterSdk.childFile('version').writeAsStringSync('1.2.3');
-    // Create a pubspec file
-    final Directory flutter = flutterSdk
-      .childDirectory('packages')
-      .childDirectory('flutter')
-      ..createSync(recursive: true);
-    flutter
-      .childFile('pubspec.yaml')
-      .writeAsStringSync(kInvalidGitPubspec);
+    // Create an invalid pubspec file.
+    flutter.childFile('pubspec.yaml').writeAsStringSync(kInvalidGitPubspec);
 
     expect(
       () => PubspecYaml(flutter),
       throwsStateError,
     );
   });
+
+  testWithoutContext('PubspecYaml Loads dependencies', () async {
+    final PubspecYaml pubspecYaml = PubspecYaml(flutter);
+    expect(
+        pubspecYaml.allDependencies
+            .map<String>((PubspecDependency dependency) => '${dependency.name}: ${dependency.version}')
+            .toSet(),
+        equals(<String>{
+          'collection: 1.14.11',
+          'meta: 1.1.8',
+          'typed_data: 1.1.6',
+          'vector_math: 2.0.8',
+          'sky_engine: ',
+          'gallery: ',
+          'flutter_test: ',
+          'flutter_goldens: ',
+          'archive: 2.0.11',
+        }));
+    expect(
+        pubspecYaml.allExplicitDependencies
+            .map<String>((PubspecDependency dependency) => '${dependency.name}: ${dependency.version}')
+            .toSet(),
+        equals(<String>{
+          'collection: 1.14.11',
+          'meta: 1.1.8',
+          'typed_data: 1.1.6',
+          'vector_math: 2.0.8',
+          'sky_engine: ',
+          'gallery: ',
+          'flutter_test: ',
+          'flutter_goldens: '
+        }));
+    expect(
+        pubspecYaml.dependencies
+            .map<String>((PubspecDependency dependency) => '${dependency.name}: ${dependency.version}')
+            .toSet(),
+        equals(<String>{
+          'collection: 1.14.11',
+          'meta: 1.1.8',
+          'typed_data: 1.1.6',
+          'vector_math: 2.0.8',
+          'sky_engine: ',
+          'gallery: '
+        }));
+  });
 }