Clean up test infrastructure (#43030)

See #41880 for history.
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index 9914dd2..fea54fb 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -4,11 +4,11 @@
 
 import 'dart:async';
 import 'dart:io';
+import 'dart:math' as math;
 
 import 'package:googleapis/bigquery/v2.dart' as bq;
 import 'package:googleapis_auth/auth_io.dart' as auth;
 import 'package:http/http.dart' as http;
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
 import 'flutter_compact_formatter.dart';
@@ -30,21 +30,54 @@
 final String pub = path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', Platform.isWindows ? 'pub.bat' : 'pub');
 final String pubCache = path.join(flutterRoot, '.pub-cache');
 final String toolRoot = path.join(flutterRoot, 'packages', 'flutter_tools');
+
+/// The arguments to pass to `flutter test` (typically the local engine
+/// configuration) -- prefilled with the arguments passed to test.dart.
 final List<String> flutterTestArgs = <String>[];
 
 final bool useFlutterTestFormatter = Platform.environment['FLUTTER_TEST_FORMATTER'] == 'true';
 final bool canUseBuildRunner = Platform.environment['FLUTTER_TEST_NO_BUILD_RUNNER'] != 'true';
 
-const Map<String, ShardRunner> _kShards = <String, ShardRunner>{
-  'tests': _runTests,
-  'web_tests': _runWebTests,
-  'tool_tests': _runToolTests,
-  'tool_coverage': _runToolCoverage,
-  'build_tests': _runBuildTests,
-  'coverage': _runCoverage,
-  'integration_tests': _runIntegrationTests,
-  'add2app_test': _runAdd2AppTest,
-};
+/// The number of Cirrus jobs that run host-only devicelab tests in parallel.
+///
+/// WARNING: if you change this number, also change .cirrus.yml
+/// and make sure it runs _all_ shards.
+const int kDeviceLabShardCount = 6;
+
+/// The number of Cirrus jobs that run Web tests in parallel.
+///
+/// WARNING: if you change this number, also change .cirrus.yml
+/// and make sure it runs _all_ shards.
+///
+/// The last shard also runs the Web plugin tests.
+const int kWebShardCount = 6;
+
+/// Maximum number of Web tests to run in a single `flutter test`. We found that
+/// large batches can get flaky, possibly because we reuse a single instance of
+/// the browser, and after many tests the browser's state gets corrupted.
+const int kWebBatchSize = 20;
+
+/// Tests that we don't run on Web for various reasons.
+//
+// TODO(yjbanov): we're getting rid of these blacklists as part of https://github.com/flutter/flutter/projects/60
+const List<String> kWebTestDirectoryBlacklist = <String>[
+  'cupertino',
+  'examples',
+  'material',
+];
+const List<String> kWebTestFileBlacklist = <String>[
+  'test/widgets/heroes_test.dart',
+  'test/widgets/text_test.dart',
+  'test/widgets/selectable_text_test.dart',
+  'test/widgets/color_filter_test.dart',
+  'test/widgets/editable_text_cursor_test.dart',
+  'test/widgets/shadow_test.dart',
+  'test/widgets/raw_keyboard_listener_test.dart',
+  'test/widgets/editable_text_test.dart',
+  'test/widgets/widget_inspector_test.dart',
+  'test/widgets/draggable_test.dart',
+  'test/widgets/shortcuts_test.dart',
+];
 
 /// When you call this, you can pass additional arguments to pass custom
 /// arguments to flutter test. For example, you might want to call this
@@ -53,31 +86,30 @@
 ///
 /// To run the tool_tests part, run it with SHARD=tool_tests
 ///
-/// For example:
+/// Examples:
 /// SHARD=tool_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart
 /// bin/cache/dart-sdk/bin/dart dev/bots/test.dart --local-engine=host_debug_unopt
 Future<void> main(List<String> args) async {
   flutterTestArgs.addAll(args);
-
-  final String shard = Platform.environment['SHARD'];
-  if (shard != null) {
-    if (!_kShards.containsKey(shard)) {
-      print('Invalid shard: $shard');
-      print('The available shards are: ${_kShards.keys.join(", ")}');
-      exit(1);
-    }
-    print('${bold}SHARD=$shard$reset');
-    await _kShards[shard]();
-  } else {
-    for (String currentShard in _kShards.keys) {
-      print('${bold}SHARD=$currentShard$reset');
-      await _kShards[currentShard]();
-      print('');
-    }
-  }
+  if (Platform.environment.containsKey(CIRRUS_TASK_NAME))
+    print('Running task: ${Platform.environment[CIRRUS_TASK_NAME]}');
+  print('═' * 80);
+  await _runSmokeTests();
+  print('═' * 80);
+  await selectShard(const <String, ShardRunner>{
+    'add_to_app_tests': _runAddToAppTests,
+    'build_tests': _runBuildTests,
+    'framework_coverage': _runFrameworkCoverage,
+    'framework_tests': _runFrameworkTests,
+    'hostonly_devicelab_tests': _runHostOnlyDeviceLabTests,
+    'tool_coverage': _runToolCoverage,
+    'tool_tests': _runToolTests,
+    'web_tests': _runWebTests,
+  });
 }
 
 Future<void> _runSmokeTests() async {
+  print('${green}Running smoketests...$reset');
   // Verify that the tests actually return failure on failure and success on
   // success.
   final String automatedTests = path.join(flutterRoot, 'dev', 'automated_tests');
@@ -108,10 +140,11 @@
     script: path.join('test_smoke_test', 'pending_timer_fail_test.dart'),
     expectFailure: true,
     printOutput: false,
-    outputChecker: (CapturedOutput output) =>
-      output.stdout.contains('failingPendingTimerTest')
-      ? null
-      : 'Failed to find the stack trace for the pending Timer.',
+    outputChecker: (CapturedOutput output) {
+      return output.stdout.contains('failingPendingTimerTest')
+        ? null
+        : 'Failed to find the stack trace for the pending Timer.';
+    }
   );
   // We run the remaining smoketests in parallel, because they each take some
   // time to run (e.g. compiling), so we don't want to run them in series,
@@ -153,8 +186,11 @@
   );
 
   // Verify that we correctly generated the version file.
-  final bool validVersion = await verifyVersion(path.join(flutterRoot, 'version'));
-  if (!validVersion) {
+  final String versionError = await verifyVersion(File(path.join(flutterRoot, 'version')));
+  if (versionError != null) {
+    print(redLine);
+    print(versionError);
+    print(redLine);
     exit(1);
   }
 }
@@ -179,7 +215,7 @@
     final http.Client client = await auth.clientViaServiceAccount(accountCredentials, scopes);
     return bq.BigqueryApi(client);
   } catch (e) {
-    print('Failed to get BigQuery API client.');
+    print('${red}Failed to get BigQuery API client.$reset');
     print(e);
     return null;
   }
@@ -203,7 +239,6 @@
 
 Future<void> _runToolTests() async {
   final bq.BigqueryApi bigqueryApi = await _getBigqueryApi();
-  await _runSmokeTests();
 
   const String kDotShard = '.shard';
   const String kTest = 'test';
@@ -232,11 +267,10 @@
   await selectSubshard(subshards);
 }
 
-/// Verifies that AOT, APK, and IPA (if on macOS) builds the
-/// examples apps without crashing. It does not actually
-/// launch the apps. That happens later in the devicelab. This is
-/// just a smoke-test. In particular, this will verify we can build
-/// when there are spaces in the path name for the Flutter SDK and
+/// Verifies that AOT, APK, and IPA (if on macOS) builds the examples apps
+/// without crashing. It does not actually launch the apps. That happens later
+/// in the devicelab. This is just a smoke-test. In particular, this will verify
+/// we can build when there are spaces in the path name for the Flutter SDK and
 /// target app.
 Future<void> _runBuildTests() async {
   final Stream<FileSystemEntity> exampleDirectories = Directory(path.join(flutterRoot, 'examples')).list();
@@ -245,10 +279,11 @@
       continue;
     }
     final String examplePath = fileEntity.path;
-
     await _flutterBuildAot(examplePath);
     await _flutterBuildApk(examplePath);
-    await _flutterBuildIpa(examplePath);
+    if (Platform.isMacOS) {
+      await _flutterBuildIpa(examplePath);
+    }
   }
   // Web compilation tests.
   await _flutterBuildDart2js(path.join('dev', 'integration_tests', 'web'), path.join('lib', 'main.dart'));
@@ -256,12 +291,44 @@
   await _flutterBuildDart2js(path.join('dev', 'integration_tests', 'web_compile_tests'),
     path.join('lib', 'dart_io_import.dart'),
   );
+}
 
-  print('${bold}DONE: All build tests successful.$reset');
+Future<void> _flutterBuildAot(String relativePathToApplication) async {
+  print('${green}Testing AOT build$reset for $cyan$relativePathToApplication$reset...');
+  await runCommand(flutter,
+    <String>['build', 'aot', '-v'],
+    workingDirectory: path.join(flutterRoot, relativePathToApplication),
+  );
+}
+
+Future<void> _flutterBuildApk(String relativePathToApplication) async {
+  print('${green}Testing APK --debug build$reset for $cyan$relativePathToApplication$reset...');
+  await runCommand(flutter,
+    <String>['build', 'apk', '--debug', '-v'],
+    workingDirectory: path.join(flutterRoot, relativePathToApplication),
+  );
+}
+
+Future<void> _flutterBuildIpa(String relativePathToApplication) async {
+  assert(Platform.isMacOS);
+  print('${green}Testing IPA build$reset for $cyan$relativePathToApplication$reset...');
+  // Install Cocoapods.  We don't have these checked in for the examples,
+  // and build ios doesn't take care of it automatically.
+  final File podfile = File(path.join(flutterRoot, relativePathToApplication, 'ios', 'Podfile'));
+  if (podfile.existsSync()) {
+    await runCommand('pod',
+      <String>['install'],
+      workingDirectory: podfile.parent.path,
+    );
+  }
+  await runCommand(flutter,
+    <String>['build', 'ios', '--no-codesign', '--debug', '-v'],
+    workingDirectory: path.join(flutterRoot, relativePathToApplication),
+  );
 }
 
 Future<void> _flutterBuildDart2js(String relativePathToApplication, String target, { bool expectNonZeroExit = false }) async {
-  print('Running Dart2JS build tests...');
+  print('${green}Testing Dart2JS build$reset for $cyan$relativePathToApplication$reset...');
   await runCommand(flutter,
     <String>['build', 'web', '-v', '--target=$target'],
     workingDirectory: path.join(flutterRoot, relativePathToApplication),
@@ -270,211 +337,101 @@
       'FLUTTER_WEB': 'true',
     },
   );
-  print('Done.');
 }
 
-Future<void> _flutterBuildAot(String relativePathToApplication) async {
-  print('Running AOT build tests...');
-  await runCommand(flutter,
-    <String>['build', 'aot', '-v'],
-    workingDirectory: path.join(flutterRoot, relativePathToApplication),
-    expectNonZeroExit: false,
-  );
-  print('Done.');
-}
-
-Future<void> _flutterBuildApk(String relativePathToApplication) async {
-  if (
-        (Platform.environment['ANDROID_HOME']?.isEmpty ?? true) &&
-        (Platform.environment['ANDROID_SDK_ROOT']?.isEmpty ?? true)) {
-    return;
-  }
-  print('Running APK build tests...');
-  await runCommand(flutter,
-    <String>['build', 'apk', '--debug', '-v'],
-    workingDirectory: path.join(flutterRoot, relativePathToApplication),
-    expectNonZeroExit: false,
-  );
-  print('Done.');
-}
-
-Future<void> _flutterBuildIpa(String relativePathToApplication) async {
-  if (!Platform.isMacOS) {
-    return;
-  }
-  print('Running IPA build tests...');
-  // Install Cocoapods.  We don't have these checked in for the examples,
-  // and build ios doesn't take care of it automatically.
-  final File podfile = File(path.join(flutterRoot, relativePathToApplication, 'ios', 'Podfile'));
-  if (podfile.existsSync()) {
-    await runCommand('pod',
-      <String>['install'],
-      workingDirectory: podfile.parent.path,
-      expectNonZeroExit: false,
+Future<void> _runAddToAppTests() async {
+  if (Platform.isMacOS) {
+    print('${green}Running add-to-app iOS integration tests$reset...');
+    final String addToAppDir = path.join(flutterRoot, 'dev', 'integration_tests', 'ios_add2app');
+    await runCommand('./build_and_test.sh',
+      <String>[],
+      workingDirectory: addToAppDir,
     );
   }
-  await runCommand(flutter,
-    <String>['build', 'ios', '--no-codesign', '--debug', '-v'],
-    workingDirectory: path.join(flutterRoot, relativePathToApplication),
-    expectNonZeroExit: false,
-  );
-  print('Done.');
 }
 
-Future<void> _runAdd2AppTest() async {
-  if (!Platform.isMacOS) {
-    return;
-  }
-  print('Running Add2App iOS integration tests...');
-  final String add2AppDir = path.join(flutterRoot, 'dev', 'integration_tests', 'ios_add2app');
-  await runCommand('./build_and_test.sh',
-    <String>[],
-    workingDirectory: add2AppDir,
-    expectNonZeroExit: false,
-  );
-  print('Done.');
-}
-
-Future<void> _runTests() async {
+Future<void> _runFrameworkTests() async {
   final bq.BigqueryApi bigqueryApi = await _getBigqueryApi();
-  await _runSmokeTests();
-  final String subShard = Platform.environment['SUBSHARD'];
 
   Future<void> runWidgets() async {
-    await _runFlutterTest(
-      path.join(flutterRoot, 'packages', 'flutter'),
-      tableData: bigqueryApi?.tabledata,
-      tests: <String>[
-        path.join('test', 'widgets') + path.separator,
-      ],
-    );
-    // Only packages/flutter/test/widgets/widget_inspector_test.dart really
-    // needs to be run with --track-widget-creation but it is nice to run
-    // all of the tests in package:flutter with the flag to ensure that
-    // the Dart kernel transformer triggered by the flag does not break anything.
+    print('${green}Running packages/flutter tests for$reset: ${cyan}test/widgets/$reset');
     await _runFlutterTest(
       path.join(flutterRoot, 'packages', 'flutter'),
       options: <String>['--track-widget-creation'],
       tableData: bigqueryApi?.tabledata,
-      tests: <String>[
-        path.join('test', 'widgets') + path.separator,
-      ],
+      tests: <String>[ path.join('test', 'widgets') + path.separator ],
     );
+    await _runFlutterTest(
+      path.join(flutterRoot, 'packages', 'flutter'),
+      options: <String>['--no-track-widget-creation'],
+      tableData: bigqueryApi?.tabledata,
+      tests: <String>[ path.join('test', 'widgets') + path.separator ],
+    );
+    // Try compiling code outside of the packages/flutter directory with and without --track-widget-creation
+    await _runFlutterTest(path.join(flutterRoot, 'examples', 'flutter_gallery'), options: <String>['--track-widget-creation'], tableData: bigqueryApi?.tabledata);
+    await _runFlutterTest(path.join(flutterRoot, 'examples', 'flutter_gallery'), options: <String>['--no-track-widget-creation'], tableData: bigqueryApi?.tabledata);
   }
 
-  Future<void> runFrameworkOthers() async {
+  Future<void> runLibraries() async {
     final List<String> tests = Directory(path.join(flutterRoot, 'packages', 'flutter', 'test'))
       .listSync(followLinks: false, recursive: false)
       .whereType<Directory>()
       .where((Directory dir) => dir.path.endsWith('widgets') == false)
-      .map((Directory dir) => path.join('test', path.basename(dir.path)) + path.separator)
+      .map<String>((Directory dir) => path.join('test', path.basename(dir.path)) + path.separator)
       .toList();
-
-    print('Running tests for: ${tests.join(';')}');
-
-    await _runFlutterTest(
-      path.join(flutterRoot, 'packages', 'flutter'),
-      tableData: bigqueryApi?.tabledata,
-      tests: tests,
-    );
-    // Only packages/flutter/test/widgets/widget_inspector_test.dart really
-    // needs to be run with --track-widget-creation but it is nice to run
-    // all of the tests in package:flutter with the flag to ensure that
-    // the Dart kernel transformer triggered by the flag does not break anything.
+    print('${green}Running packages/flutter tests$reset for: $cyan${tests.join(", ")}$reset');
     await _runFlutterTest(
       path.join(flutterRoot, 'packages', 'flutter'),
       options: <String>['--track-widget-creation'],
       tableData: bigqueryApi?.tabledata,
       tests: tests,
     );
+    await _runFlutterTest(
+      path.join(flutterRoot, 'packages', 'flutter'),
+      options: <String>['--no-track-widget-creation'],
+      tableData: bigqueryApi?.tabledata,
+      tests: tests,
+    );
   }
 
-  Future<void> runExtras() async {
-    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), tableData: bigqueryApi?.tabledata);
-    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tableData: bigqueryApi?.tabledata);
-    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), tableData: bigqueryApi?.tabledata);
-    await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), tableData: bigqueryApi?.tabledata);
+  Future<void> runMisc() async {
+    print('${green}Running package tests$reset for directories other than packages/flutter');
     await _pubRunTest(path.join(flutterRoot, 'dev', 'bots'), tableData: bigqueryApi?.tabledata);
     await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'), tableData: bigqueryApi?.tabledata);
     await _pubRunTest(path.join(flutterRoot, 'dev', 'snippets'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'), tableData: bigqueryApi?.tabledata);
+    await _runFlutterTest(path.join(flutterRoot, 'examples', 'catalog'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'examples', 'hello_world'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'examples', 'stocks'), tableData: bigqueryApi?.tabledata);
-    await _runFlutterTest(path.join(flutterRoot, 'examples', 'flutter_gallery'), tableData: bigqueryApi?.tabledata);
-    // Regression test to ensure that code outside of package:flutter can run
-    // with --track-widget-creation.
-    await _runFlutterTest(path.join(flutterRoot, 'examples', 'flutter_gallery'), options: <String>['--track-widget-creation'], tableData: bigqueryApi?.tabledata);
-    await _runFlutterTest(path.join(flutterRoot, 'examples', 'catalog'), tableData: bigqueryApi?.tabledata);
-    // Smoke test for code generation.
-    await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'codegen'), tableData: bigqueryApi?.tabledata, environment: <String, String>{
-      'FLUTTER_EXPERIMENTAL_BUILD': 'true',
-    });
-  }
-  switch (subShard) {
-    case 'widgets':
-      await runWidgets();
-      break;
-    case 'framework_other':
-      await runFrameworkOthers();
-      break;
-    case 'extras':
-      runExtras();
-      break;
-    default:
-      print('Unknown sub-shard $subShard, running all tests!');
-      await runWidgets();
-      await runFrameworkOthers();
-      await runExtras();
-
+    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tableData: bigqueryApi?.tabledata);
+    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), tableData: bigqueryApi?.tabledata);
+    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), tableData: bigqueryApi?.tabledata);
+    await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), tableData: bigqueryApi?.tabledata);
+    await _runFlutterTest(
+      path.join(flutterRoot, 'dev', 'integration_tests', 'codegen'),
+      tableData: bigqueryApi?.tabledata,
+      environment: <String, String>{
+        'FLUTTER_EXPERIMENTAL_BUILD': 'true',
+      },
+    );
   }
 
-  print('${bold}DONE: All tests successful.$reset');
+  await selectSubshard(<String, ShardRunner>{
+    'widgets': runWidgets,
+    'libraries': runLibraries,
+    'misc': runMisc,
+  });
 }
 
-// TODO(yjbanov): we're getting rid of these blacklists as part of https://github.com/flutter/flutter/projects/60
-const List<String> kWebTestDirectoryBlacklist = <String>[
-  'test/cupertino',
-  'test/examples',
-  'test/material',
-];
-const List<String> kWebTestFileBlacklist = <String>[
-  'test/widgets/heroes_test.dart',
-  'test/widgets/text_test.dart',
-  'test/widgets/selectable_text_test.dart',
-  'test/widgets/color_filter_test.dart',
-  'test/widgets/editable_text_cursor_test.dart',
-  'test/widgets/shadow_test.dart',
-  'test/widgets/raw_keyboard_listener_test.dart',
-  'test/widgets/editable_text_test.dart',
-  'test/widgets/widget_inspector_test.dart',
-  'test/widgets/draggable_test.dart',
-  'test/widgets/shortcuts_test.dart',
-];
-
-Future<void> _runWebTests() async {
-  final Directory flutterPackageDir = Directory(path.join(flutterRoot, 'packages', 'flutter'));
-  final Directory testDir = Directory(path.join(flutterPackageDir.path, 'test'));
-
-  final List<String> directories = testDir
-    .listSync()
-    .whereType<Directory>()
-    .map<String>((Directory dir) => path.relative(dir.path, from: flutterPackageDir.path))
-    .where((String relativePath) => !kWebTestDirectoryBlacklist.contains(relativePath))
-    .toList();
-
-  await _runFlutterWebTest(flutterPackageDir.path, tests: directories);
-  await _runFlutterWebTest(path.join(flutterRoot, 'packages', 'flutter_web_plugins'), tests: <String>['test']);
-}
-
-Future<void> _runCoverage() async {
+Future<void> _runFrameworkCoverage() async {
   final File coverageFile = File(path.join(flutterRoot, 'packages', 'flutter', 'coverage', 'lcov.info'));
   if (!coverageFile.existsSync()) {
     print('${red}Coverage file not found.$reset');
-    print('Expected to find: ${coverageFile.absolute}');
-    print('This file is normally obtained by running `flutter update-packages`.');
+    print('Expected to find: $cyan${coverageFile.absolute}$reset');
+    print('This file is normally obtained by running `${green}flutter update-packages$reset`.');
     exit(1);
   }
   coverageFile.deleteSync();
@@ -483,11 +440,98 @@
   );
   if (!coverageFile.existsSync()) {
     print('${red}Coverage file not found.$reset');
-    print('Expected to find: ${coverageFile.absolute}');
-    print('This file should have been generated by the `flutter test --coverage` script, but was not.');
+    print('Expected to find: $cyan${coverageFile.absolute}$reset');
+    print('This file should have been generated by the `${green}flutter test --coverage$reset` script, but was not.');
     exit(1);
   }
-  print('${bold}DONE: Coverage collection successful.$reset');
+}
+
+Future<void> _runWebTests() async {
+  final Map<String, ShardRunner> subshards = <String, ShardRunner>{};
+
+  final Directory flutterPackageDirectory = Directory(path.join(flutterRoot, 'packages', 'flutter'));
+  final Directory flutterPackageTestDirectory = Directory(path.join(flutterPackageDirectory.path, 'test'));
+
+  final List<String> allTests = flutterPackageTestDirectory
+    .listSync()
+    .whereType<Directory>()
+    .where((Directory directory) => !kWebTestDirectoryBlacklist.contains(path.basename(directory.path)))
+    .expand((Directory directory) => directory
+      .listSync(recursive: true)
+      .where((FileSystemEntity entity) => entity.path.endsWith('_test.dart'))
+    )
+    .whereType<File>()
+    .map<String>((File file) => path.relative(file.path, from: flutterPackageDirectory.path))
+    .where((String filePath) => !kWebTestFileBlacklist.contains(filePath))
+    .toList()
+    // Finally we shuffle the list because we want the average cost per file to be uniformly
+    // distributed. If the list is not sorted then different shards and batches may have
+    // very different characteristics.
+    // We use a constant seed for repeatability.
+    ..shuffle(math.Random(0));
+
+  assert(kWebShardCount >= 1);
+  final int testsPerShard = (allTests.length / kWebShardCount).ceil();
+  assert(testsPerShard * kWebShardCount >= allTests.length);
+
+  // This for loop computes all but the last shard.
+  for (int index = 0; index < kWebShardCount - 1; index += 1) {
+    subshards['$index'] = () => _runFlutterWebTest(
+      flutterPackageDirectory.path,
+      allTests.sublist(
+        index * testsPerShard,
+        (index + 1) * testsPerShard,
+      ),
+    );
+  }
+
+  // The last shard also runs the flutter_web_plugins tests.
+  //
+  // We make sure the last shard ends in _last so it's easier to catch mismatches
+  // between `.cirrus.yml` and `test.dart`.
+  subshards['${kWebShardCount - 1}_last'] = () async {
+    await _runFlutterWebTest(
+      flutterPackageDirectory.path,
+      allTests.sublist(
+        (kWebShardCount - 1) * testsPerShard,
+        allTests.length,
+      ),
+    );
+    await _runFlutterWebTest(
+      path.join(flutterRoot, 'packages', 'flutter_web_plugins'),
+      <String>['test'],
+    );
+  };
+
+  await selectSubshard(subshards);
+}
+
+Future<void> _runFlutterWebTest(String workingDirectory, List<String> tests) async {
+  final List<String> batch = <String>[];
+  for (int i = 0; i < tests.length; i += 1) {
+    final String testFilePath = tests[i];
+    batch.add(testFilePath);
+    if (batch.length == kWebBatchSize || i == tests.length - 1) {
+      await runCommand(
+        flutter,
+        <String>[
+          'test',
+          if (ciProvider == CiProviders.cirrus)
+            '--concurrency=1',  // do not parallelize on Cirrus, to reduce flakiness
+          '-v',
+          '--platform=chrome',
+          ...?flutterTestArgs,
+          ...batch,
+        ],
+        workingDirectory: workingDirectory,
+        environment: <String, String>{
+          'FLUTTER_WEB': 'true',
+          'FLUTTER_LOW_RESOURCE_MODE': 'true',
+        },
+      );
+      batch.clear();
+    }
+  }
 }
 
 Future<void> _pubRunTest(String workingDirectory, {
@@ -496,14 +540,33 @@
   bool useBuildRunner = false,
   bq.TabledataResourceApi tableData,
 }) async {
-  final List<String> args = <String>['run', '--verbose'];
+  final List<String> args = <String>['run'];
   if (useBuildRunner) {
-    args.addAll(<String>['build_runner', 'test', '--']);
+    final String posixTestPath = path.posix.joinAll(path.split(testPath));
+    args.addAll(<String>[
+      'build_runner',
+      'test',
+      '--build-filter=$posixTestPath/*.dill',
+      '--build-filter=$posixTestPath/**/*.dill',
+      '--',
+    ]);
   } else {
     args.add('test');
   }
   args.add(useFlutterTestFormatter ? '-rjson' : '-rcompact');
-  args.add('-j1'); // TODO(ianh): Scale based on CPUs.
+  int cpus;
+  final String cpuVariable = Platform.environment['CPU']; // CPU is set in cirrus.yml
+  if (cpuVariable != null) {
+    cpus = int.tryParse(cpuVariable, radix: 10);
+    if (cpus == null) {
+      print('${red}The CPU environment variable, if set, must be set to the integer number of available cores.$reset');
+      print('Actual value: "$cpuVariable"');
+      exit(1);
+    }
+  } else {
+    cpus = 2; // Don't default to 1, otherwise we won't catch race conditions.
+  }
+  args.add('-j$cpus');
   if (!hasColor)
     args.add('--no-color');
   if (testPath != null)
@@ -547,238 +610,6 @@
   }
 }
 
-void deleteFile(String path) {
-  // There's a race condition here but in theory we're not racing anyone
-  // while this script runs, so should be ok.
-  final File file = File(path);
-  if (file.existsSync())
-    file.deleteSync();
-}
-
-enum CiProviders {
-  cirrus,
-  luci,
-}
-
-CiProviders _getCiProvider() {
-  if (Platform.environment['CIRRUS_CI'] == 'true') {
-    return CiProviders.cirrus;
-  }
-  if (Platform.environment['LUCI_CONTEXT'] != null) {
-    return CiProviders.luci;
-  }
-  return null;
-}
-
-String _getCiProviderName() {
-  switch(_getCiProvider()) {
-    case CiProviders.cirrus:
-      return 'cirrusci';
-    case CiProviders.luci:
-      return 'luci';
-  }
-  return 'unknown';
-}
-
-int _getPrNumber() {
-  switch(_getCiProvider()) {
-    case CiProviders.cirrus:
-      return Platform.environment['CIRRUS_PR'] == null
-          ? -1
-          : int.tryParse(Platform.environment['CIRRUS_PR']);
-    case CiProviders.luci:
-      return -1; // LUCI doesn't know about this.
-  }
-  return -1;
-}
-
-Future<String> _getAuthors() async {
-  final String exe = Platform.isWindows ? '.exe' : '';
-  final String author = await runAndGetStdout(
-    'git$exe', <String>['-c', 'log.showSignature=false', 'log', _getGitHash(), '--pretty="%an <%ae>"'],
-    workingDirectory: flutterRoot,
-  ).first;
-  return author;
-}
-
-String _getCiUrl() {
-  switch(_getCiProvider()) {
-    case CiProviders.cirrus:
-      return 'https://cirrus-ci.com/task/${Platform.environment['CIRRUS_TASK_ID']}';
-    case CiProviders.luci:
-      return 'https://ci.chromium.org/p/flutter/g/framework/console'; // TODO(dnfield): can we get a direct link to the actual build?
-  }
-  return '';
-}
-
-String _getGitHash() {
-  switch(_getCiProvider()) {
-    case CiProviders.cirrus:
-      return Platform.environment['CIRRUS_CHANGE_IN_REPO'];
-    case CiProviders.luci:
-      return 'HEAD'; // TODO(dnfield): Set this in the env for LUCI.
-  }
-  return '';
-}
-
-Future<void> _processTestOutput(
-  FlutterCompactFormatter formatter,
-  Stream<String> testOutput,
-  bq.TabledataResourceApi tableData,
-) async {
-  final Timer heartbeat = Timer.periodic(const Duration(seconds: 30), (Timer timer) {
-    print('Processing...');
-  });
-
-  await testOutput.forEach(formatter.processRawOutput);
-  heartbeat.cancel();
-  formatter.finish();
-  if (tableData == null || formatter.tests.isEmpty) {
-    return;
-  }
-  final bq.TableDataInsertAllRequest request = bq.TableDataInsertAllRequest();
-  final String authors = await _getAuthors();
-  request.rows = List<bq.TableDataInsertAllRequestRows>.from(
-    formatter.tests.map<bq.TableDataInsertAllRequestRows>((TestResult result) =>
-      bq.TableDataInsertAllRequestRows.fromJson(<String, dynamic> {
-        'json': <String, dynamic>{
-          'source': <String, dynamic>{
-            'provider': _getCiProviderName(),
-            'url': _getCiUrl(),
-            'platform': <String, dynamic>{
-              'os': Platform.operatingSystem,
-              'version': Platform.operatingSystemVersion,
-            },
-          },
-          'test': <String, dynamic>{
-            'name': result.name,
-            'result': result.status.toString(),
-            'file': result.path,
-            'line': result.line,
-            'column': result.column,
-            'time': result.totalTime,
-          },
-          'git': <String, dynamic>{
-            'author': authors,
-            'pull_request': _getPrNumber(),
-            'commit': _getGitHash(),
-            'organization': 'flutter',
-            'repository': 'flutter',
-          },
-          'error': result.status != TestStatus.failed ? null : <String, dynamic>{
-            'message': result.errorMessage,
-            'stack_trace': result.stackTrace,
-          },
-          'information': result.messages,
-        },
-      }),
-    ),
-    growable: false,
-  );
-  final bq.TableDataInsertAllResponse response = await tableData.insertAll(request, 'flutter-infra', 'tests', 'ci');
-  if (response.insertErrors != null && response.insertErrors.isNotEmpty) {
-    print('${red}BigQuery insert errors:');
-    print(response.toJson());
-    print(reset);
-  }
-}
-
-class EvalResult {
-  EvalResult({
-    this.stdout,
-    this.stderr,
-    this.exitCode = 0,
-  });
-
-  final String stdout;
-  final String stderr;
-  final int exitCode;
-}
-
-/// The number of Cirrus jobs that run web tests in parallel.
-///
-/// WARNING: if you change this number, also change .cirrus.yml
-/// and make sure it runs _all_ shards.
-const int _kWebShardCount = 6;
-
-Future<void> _runFlutterWebTest(String workingDirectory, {
-  List<String> tests,
-}) async {
-  List<String> allTests = <String>[];
-  for (String testDirPath in tests) {
-    final Directory testDir = Directory(path.join(workingDirectory, testDirPath));
-    allTests.addAll(
-      testDir.listSync(recursive: true)
-        .whereType<File>()
-        .where((File file) => file.path.endsWith('_test.dart'))
-        .map<String>((File file) => path.relative(file.path, from: workingDirectory))
-        .where((String filePath) => !kWebTestFileBlacklist.contains(filePath)),
-    );
-  }
-
-  // If a shard is specified only run tests in that shard.
-  final int webShard = int.tryParse(Platform.environment['WEB_SHARD'] ?? 'n/a');
-  if (webShard != null) {
-    if (webShard >= _kWebShardCount) {
-      throw 'WEB_SHARD must be <= _kWebShardCount, but was $webShard';
-    }
-    final List<String> shard = <String>[];
-    for (int i = webShard; i < allTests.length; i += _kWebShardCount) {
-      shard.add(allTests[i]);
-    }
-    allTests = shard;
-  }
-
-  print(allTests.join('\n'));
-  print('${allTests.length} tests total');
-
-  // Maximum number of tests to run in a single `flutter test`. We found that
-  // large batches can get flaky, possibly because we reuse a single instance
-  // of the browser, and after many tests the browser's state gets corrupted.
-  const int kBatchSize = 20;
-  List<String> batch = <String>[];
-  for (int i = 0; i < allTests.length; i += 1) {
-    final String testFilePath = allTests[i];
-    batch.add(testFilePath);
-    if (batch.length == kBatchSize || i == allTests.length - 1) {
-      await _runFlutterWebTestBatch(workingDirectory, batch: batch);
-      batch = <String>[];
-    }
-  }
-}
-
-Future<void> _runFlutterWebTestBatch(String workingDirectory, {
-  List<String> batch,
-}) async {
-  final List<String> args = <String>[
-    'test',
-    if (_getCiProvider() == CiProviders.cirrus)
-      '--concurrency=1',  // do not parallelize on Cirrus to reduce flakiness
-    '-v',
-    '--platform=chrome',
-    ...?flutterTestArgs,
-    ...batch,
-  ];
-
-  // TODO(jonahwilliams): fix relative path issues to make this unecessary.
-  final Directory oldCurrent = Directory.current;
-  Directory.current = Directory(path.join(flutterRoot, 'packages', 'flutter'));
-  try {
-    await runCommand(
-      flutter,
-      args,
-      workingDirectory: workingDirectory,
-      expectFlaky: false,
-      environment: <String, String>{
-        'FLUTTER_WEB': 'true',
-        'FLUTTER_LOW_RESOURCE_MODE': 'true',
-      },
-    );
-  } finally {
-    Directory.current = oldCurrent;
-  }
-}
-
 Future<void> _runFlutterTest(String workingDirectory, {
   String script,
   bool expectFailure = false,
@@ -790,8 +621,7 @@
   Map<String, String> environment,
   List<String> tests = const <String>[],
 }) async {
-  assert(!printOutput || outputChecker == null,
-      'Output either can be printed or checked but not both');
+  assert(!printOutput || outputChecker == null, 'Output either can be printed or checked but not both');
 
   final List<String> args = <String>[
     'test',
@@ -800,16 +630,15 @@
   ];
 
   final bool shouldProcessOutput = useFlutterTestFormatter && !expectFailure && !options.contains('--coverage');
-  if (shouldProcessOutput) {
+  if (shouldProcessOutput)
     args.add('--machine');
-  }
 
   if (script != null) {
     final String fullScriptPath = path.join(workingDirectory, script);
     if (!FileSystemEntity.isFileSync(fullScriptPath)) {
-      print('Could not find test: $fullScriptPath');
-      print('Working directory: $workingDirectory');
-      print('Script: $script');
+      print('${red}Could not find test$reset: $green$fullScriptPath$reset');
+      print('Working directory: $cyan$workingDirectory$reset');
+      print('Script: $green$script$reset');
       if (!printOutput)
         print('This is one of the tests that does not normally print output.');
       if (skip)
@@ -876,110 +705,290 @@
   }
 }
 
-// the optional `file` argument is an override for testing
-@visibleForTesting
-Future<bool> verifyVersion(String filename, [File file]) async {
-  final RegExp pattern = RegExp(r'^\d+\.\d+\.\d+(\+hotfix\.\d+)?(-pre\.\d+)?$');
-  file ??= File(filename);
-  final String version = await file.readAsString();
-  if (!file.existsSync()) {
-    print('$redLine');
-    print('The version logic failed to create the Flutter version file.');
-    print('$redLine');
-    return false;
-  }
-  if (version == '0.0.0-unknown') {
-    print('$redLine');
-    print('The version logic failed to determine the Flutter version.');
-    print('$redLine');
-    return false;
-  }
-  if (!version.contains(pattern)) {
-    print('$redLine');
-    print('The version logic generated an invalid version string: "$version".');
-    print('$redLine');
-    return false;
-  }
-  return true;
-}
-
-Future<void> _runIntegrationTests() async {
-  final String subShard = Platform.environment['SUBSHARD'];
-
-  switch (subShard) {
-    case 'gradle1':
-    case 'gradle2':
-      // This runs some gradle integration tests if the subshard is Android.
-      await _androidGradleTests(subShard);
-      break;
-    default:
-      await _runDevicelabTest('dartdocs');
-
-      if (Platform.isLinux) {
-        await _runDevicelabTest('flutter_create_offline_test_linux');
-      } else if (Platform.isWindows) {
-        await _runDevicelabTest('flutter_create_offline_test_windows');
-      } else if (Platform.isMacOS) {
-        await _runDevicelabTest('flutter_create_offline_test_mac');
-        await _runDevicelabTest('plugin_lint_mac');
-// TODO(jmagman): Re-enable once flakiness is resolved.
-//        await _runDevicelabTest('module_test_ios');
-      }
-  }
-}
-
-Future<void> _runDevicelabTest(String testName, {Map<String, String> env}) async {
-  await runCommand(
-    dart,
-    <String>['bin/run.dart', '-t', testName],
-    workingDirectory: path.join(flutterRoot, 'dev', 'devicelab'),
-    environment: env,
-  );
-}
-
-String get androidSdkRoot {
+Map<String, String> _initGradleEnvironment() {
   final String androidSdkRoot = (Platform.environment['ANDROID_HOME']?.isEmpty ?? true)
       ? Platform.environment['ANDROID_SDK_ROOT']
       : Platform.environment['ANDROID_HOME'];
   if (androidSdkRoot == null || androidSdkRoot.isEmpty) {
-    return null;
+    print('${red}Could not find Android SDK; set ANDROID_SDK_ROOT (or ANDROID_HOME).$reset');
+    exit(1);
   }
-  return androidSdkRoot;
-}
-
-Future<void> _androidGradleTests(String subShard) async {
-  // TODO(dnfield): gradlew is crashing on the cirrus image and it's not clear why.
-  if (androidSdkRoot == null || Platform.isWindows) {
-    print('No Android SDK detected or on Windows, skipping Android gradle test.');
-    return;
-  }
-  final Map<String, String> defaultEnv = <String, String>{
+  return <String, String>{
     'ANDROID_HOME': androidSdkRoot,
     'ANDROID_SDK_ROOT': androidSdkRoot,
-    'ENABLE_ANDROID_EMBEDDING_V2': Platform.environment['ENABLE_ANDROID_EMBEDDING_V2'] ?? '',
   };
-  if (subShard == 'gradle1') {
-    await _runDevicelabTest('gradle_plugin_light_apk_test', env: defaultEnv);
-    await _runDevicelabTest('gradle_plugin_fat_apk_test', env: defaultEnv);
-    await _runDevicelabTest('gradle_r8_test', env: defaultEnv);
-    await _runDevicelabTest('gradle_non_android_plugin_test', env: defaultEnv);
-    await _runDevicelabTest('gradle_jetifier_test', env: defaultEnv);
+}
+
+final Map<String, String> gradleEnvironment = _initGradleEnvironment();
+
+Future<void> _runHostOnlyDeviceLabTests() async {
+  if (Platform.isWindows) {
+    // TODO(ianh): remove when https://github.com/flutter/flutter/issues/36311 fixed by https://github.com/flutter/flutter/pull/42709
+    return;
   }
-  if (subShard == 'gradle2') {
-    await _runDevicelabTest('gradle_plugin_bundle_test', env: defaultEnv);
-    await _runDevicelabTest('module_test', env: defaultEnv);
-    await _runDevicelabTest('module_host_with_custom_build_test', env: defaultEnv);
-    await _runDevicelabTest('build_aar_module_test', env: defaultEnv);
-    await _runDevicelabTest('plugin_test', env: defaultEnv);
+
+  // Please don't add more tests here. We should not be using the devicelab
+  // logic to run tests outside devicelab, that's just confusing.
+  // Instead, create tests that are not devicelab tests, and run those.
+
+  // TODO(ianh): Move the tests that are not running on devicelab any more out
+  // of the device lab directory.
+
+  // List the tests to run.
+  // We split these into subshards. The tests are randomly distributed into
+  // those subshards so as to get a uniform distribution of costs, but the
+  // seed is fixed so that issues are reproducible.
+  final List<ShardRunner> tests = <ShardRunner>[
+    // Keep this in alphabetical order.
+    () => _runDevicelabTest('build_aar_module_test', environment: gradleEnvironment, testEmbeddingV2: true),
+    () => _runDevicelabTest('build_aar_module_test', environment: gradleEnvironment, testEmbeddingV2: false),
+    if (Platform.isMacOS) () => _runDevicelabTest('flutter_create_offline_test_mac'),
+    if (Platform.isLinux) () => _runDevicelabTest('flutter_create_offline_test_linux'),
+    if (Platform.isWindows) () => _runDevicelabTest('flutter_create_offline_test_windows'),
+    // TODO(ianh): Fails on macOS looking for "dexdump", https://github.com/flutter/flutter/issues/42494
+    if (!Platform.isMacOS) () => _runDevicelabTest('gradle_jetifier_test', environment: gradleEnvironment, testEmbeddingV2: false),
+    if (!Platform.isMacOS) () => _runDevicelabTest('gradle_jetifier_test', environment: gradleEnvironment, testEmbeddingV2: true),
+    () => _runDevicelabTest('gradle_non_android_plugin_test', environment: gradleEnvironment, testEmbeddingV2: false),
+    () => _runDevicelabTest('gradle_non_android_plugin_test', environment: gradleEnvironment, testEmbeddingV2: true),
+    () => _runDevicelabTest('gradle_plugin_bundle_test', environment: gradleEnvironment, testEmbeddingV2: false),
+    () => _runDevicelabTest('gradle_plugin_bundle_test', environment: gradleEnvironment, testEmbeddingV2: true),
+    () => _runDevicelabTest('gradle_plugin_fat_apk_test', environment: gradleEnvironment, testEmbeddingV2: false),
+    () => _runDevicelabTest('gradle_plugin_fat_apk_test', environment: gradleEnvironment, testEmbeddingV2: true),
+    () => _runDevicelabTest('gradle_plugin_light_apk_test', environment: gradleEnvironment, testEmbeddingV2: false),
+    () => _runDevicelabTest('gradle_plugin_light_apk_test', environment: gradleEnvironment, testEmbeddingV2: true),
+    () => _runDevicelabTest('gradle_r8_test', environment: gradleEnvironment, testEmbeddingV2: false),
+    () => _runDevicelabTest('gradle_r8_test', environment: gradleEnvironment, testEmbeddingV2: true),
+    () => _runDevicelabTest('module_host_with_custom_build_test', environment: gradleEnvironment, testEmbeddingV2: false),
+    () => _runDevicelabTest('module_host_with_custom_build_test', environment: gradleEnvironment, testEmbeddingV2: true),
+    () => _runDevicelabTest('module_test', environment: gradleEnvironment, testEmbeddingV2: false),
+    () => _runDevicelabTest('module_test', environment: gradleEnvironment, testEmbeddingV2: true),
+    // TODO(jmagman): Re-enable once flakiness is resolved, https://github.com/flutter/flutter/issues/37525
+    // if (Platform.isMacOS) () => _runDevicelabTest('module_test_ios'),
+    if (Platform.isMacOS) () => _runDevicelabTest('plugin_lint_mac'),
+    () => _runDevicelabTest('plugin_test', environment: gradleEnvironment, testEmbeddingV2: false),
+    () => _runDevicelabTest('plugin_test', environment: gradleEnvironment, testEmbeddingV2: true),
+  ]..shuffle(math.Random(0));
+
+  final int testsPerShard = tests.length ~/ kDeviceLabShardCount;
+  final Map<String, ShardRunner> subshards = <String, ShardRunner>{};
+
+  for (int subshard = 0; subshard < kDeviceLabShardCount; subshard += 1) {
+    String last = '';
+    List<ShardRunner> sublist;
+    if (subshard < kDeviceLabShardCount - 1) {
+      sublist = tests.sublist(subshard * testsPerShard, (subshard + 1) * testsPerShard);
+    } else {
+      sublist = tests.sublist(subshard * testsPerShard, tests.length);
+      // We make sure the last shard ends in _last so it's easier to catch mismatches
+      // between `.cirrus.yml` and `test.dart`.
+      last = '_last';
+    }
+    subshards['$subshard$last'] = () async {
+      for (ShardRunner test in sublist)
+        await test();
+    };
+  }
+
+  await selectSubshard(subshards);
+}
+
+Future<void> _runDevicelabTest(String testName, {
+  Map<String, String> environment,
+  bool testEmbeddingV2 = false,
+}) async {
+  await runCommand(
+    dart,
+    <String>['bin/run.dart', '-t', testName],
+    workingDirectory: path.join(flutterRoot, 'dev', 'devicelab'),
+    environment: <String, String>{
+      ...?environment,
+      if (testEmbeddingV2)
+        'ENABLE_ANDROID_EMBEDDING_V2': 'true',
+    },
+  );
+}
+
+void deleteFile(String path) {
+  // This is technically a race condition but nobody else should be running
+  // while this script runs, so we should be ok. (Sadly recursive:true does not
+  // obviate the need for existsSync, at least on Windows.)
+  final File file = File(path);
+  if (file.existsSync())
+    file.deleteSync();
+}
+
+enum CiProviders {
+  cirrus,
+  luci,
+}
+
+Future<void> _processTestOutput(
+  FlutterCompactFormatter formatter,
+  Stream<String> testOutput,
+  bq.TabledataResourceApi tableData,
+) async {
+  final Timer heartbeat = Timer.periodic(const Duration(seconds: 30), (Timer timer) {
+    print('Processing...');
+  });
+
+  await testOutput.forEach(formatter.processRawOutput);
+  heartbeat.cancel();
+  formatter.finish();
+  if (tableData == null || formatter.tests.isEmpty) {
+    return;
+  }
+  final bq.TableDataInsertAllRequest request = bq.TableDataInsertAllRequest();
+  final String authors = await _getAuthors();
+  request.rows = List<bq.TableDataInsertAllRequestRows>.from(
+    formatter.tests.map<bq.TableDataInsertAllRequestRows>((TestResult result) =>
+      bq.TableDataInsertAllRequestRows.fromJson(<String, dynamic> {
+        'json': <String, dynamic>{
+          'source': <String, dynamic>{
+            'provider': ciProviderName,
+            'url': ciUrl,
+            'platform': <String, dynamic>{
+              'os': Platform.operatingSystem,
+              'version': Platform.operatingSystemVersion,
+            },
+          },
+          'test': <String, dynamic>{
+            'name': result.name,
+            'result': result.status.toString(),
+            'file': result.path,
+            'line': result.line,
+            'column': result.column,
+            'time': result.totalTime,
+          },
+          'git': <String, dynamic>{
+            'author': authors,
+            'pull_request': prNumber,
+            'commit': gitHash,
+            'organization': 'flutter',
+            'repository': 'flutter',
+          },
+          'error': result.status != TestStatus.failed ? null : <String, dynamic>{
+            'message': result.errorMessage,
+            'stack_trace': result.stackTrace,
+          },
+          'information': result.messages,
+        },
+      }),
+    ),
+    growable: false,
+  );
+  final bq.TableDataInsertAllResponse response = await tableData.insertAll(request, 'flutter-infra', 'tests', 'ci');
+  if (response.insertErrors != null && response.insertErrors.isNotEmpty) {
+    print('${red}BigQuery insert errors:');
+    print(response.toJson());
+    print(reset);
   }
 }
 
-Future<void> selectShard(Map<String, ShardRunner> shards) => _runFromList(shards, 'SHARD', 'shard');
-Future<void> selectSubshard(Map<String, ShardRunner> subshards) => _runFromList(subshards, 'SUBSHARD', 'subshard');
+CiProviders get ciProvider {
+  if (Platform.environment['CIRRUS_CI'] == 'true') {
+    return CiProviders.cirrus;
+  }
+  if (Platform.environment['LUCI_CONTEXT'] != null) {
+    return CiProviders.luci;
+  }
+  return null;
+}
 
-Future<void> _runFromList(Map<String, ShardRunner> items, String key, String name) async {
-  final String item = Platform.environment[key];
-  if (item != null) {
+String get ciProviderName {
+  switch (ciProvider) {
+    case CiProviders.cirrus:
+      return 'cirrusci';
+    case CiProviders.luci:
+      return 'luci';
+  }
+  return 'unknown';
+}
+
+int get prNumber {
+  switch (ciProvider) {
+    case CiProviders.cirrus:
+      return Platform.environment['CIRRUS_PR'] == null
+          ? -1
+          : int.tryParse(Platform.environment['CIRRUS_PR']);
+    case CiProviders.luci:
+      return -1; // LUCI doesn't know about this.
+  }
+  return -1;
+}
+
+Future<String> _getAuthors() async {
+  final String exe = Platform.isWindows ? '.exe' : '';
+  final String author = await runAndGetStdout(
+    'git$exe', <String>['-c', 'log.showSignature=false', 'log', gitHash, '--pretty="%an <%ae>"'],
+    workingDirectory: flutterRoot,
+  ).first;
+  return author;
+}
+
+String get ciUrl {
+  switch (ciProvider) {
+    case CiProviders.cirrus:
+      return 'https://cirrus-ci.com/task/${Platform.environment['CIRRUS_TASK_ID']}';
+    case CiProviders.luci:
+      return 'https://ci.chromium.org/p/flutter/g/framework/console'; // TODO(dnfield): can we get a direct link to the actual build?
+  }
+  return '';
+}
+
+String get gitHash {
+  switch(ciProvider) {
+    case CiProviders.cirrus:
+      return Platform.environment['CIRRUS_CHANGE_IN_REPO'];
+    case CiProviders.luci:
+      return 'HEAD'; // TODO(dnfield): Set this in the env for LUCI.
+  }
+  return '';
+}
+
+/// Checks the given file's contents to determine if they match the allowed
+/// pattern for version strings.
+///
+/// Returns null if the contents are good. Returns a string if they are bad.
+/// The string is an error message.
+Future<String> verifyVersion(File file) async {
+  final RegExp pattern = RegExp(r'^\d+\.\d+\.\d+(\+hotfix\.\d+)?(-pre\.\d+)?$');
+  final String version = await file.readAsString();
+  if (!file.existsSync())
+    return 'The version logic failed to create the Flutter version file.';
+  if (version == '0.0.0-unknown')
+    return 'The version logic failed to determine the Flutter version.';
+  if (!version.contains(pattern))
+    return 'The version logic generated an invalid version string: "$version".';
+  return null;
+}
+
+/// If the CIRRUS_TASK_NAME environment variable exists, we use that to determine
+/// the shard and subshard (parsing it in the form shard-subshard-platform, ignoring
+/// the platform).
+///
+/// However, for local testing you can just set the SHARD and SUBSHARD
+/// environment variables. For example, to run all the framework tests you can
+/// just set SHARD=framework_tests. To run specifically the third subshard of
+/// the Web tests you can set SHARD=web_tests SUBSHARD=2 (it's zero-based).
+Future<void> selectShard(Map<String, ShardRunner> shards) => _runFromList(shards, 'SHARD', 'shard', 0);
+Future<void> selectSubshard(Map<String, ShardRunner> subshards) => _runFromList(subshards, 'SUBSHARD', 'subshard', 1);
+
+const String CIRRUS_TASK_NAME = 'CIRRUS_TASK_NAME';
+
+Future<void> _runFromList(Map<String, ShardRunner> items, String key, String name, int positionInTaskName) async {
+  String item = Platform.environment[key];
+  if (item == null && Platform.environment.containsKey(CIRRUS_TASK_NAME)) {
+    final List<String> parts = Platform.environment[CIRRUS_TASK_NAME].split('-');
+    assert(positionInTaskName < parts.length);
+    item = parts[positionInTaskName];
+  }
+  if (item == null) {
+    for (String currentItem in items.keys) {
+      print('$bold$key=$currentItem$reset');
+      await items[currentItem]();
+      print('');
+    }
+  } else {
     if (!items.containsKey(item)) {
       print('${red}Invalid $name: $item$reset');
       print('The available ${name}s are: ${items.keys.join(", ")}');
@@ -987,11 +996,5 @@
     }
     print('$bold$key=$item$reset');
     await items[item]();
-  } else {
-    for (String currentItem in items.keys) {
-      print('$bold$key=$currentItem$reset');
-      await items[currentItem]();
-      print('');
-    }
   }
 }