[CFE] Run coverage update on weekly bot and print a diff

This CL will make the weekly bot run tests with coverage and update the
coverage, then printing the diff.

It runs:
 * All front_end suits
 * python3 tools/test.py -cfasta -mrelease -rnone language
 * All "_test.dart" files inside
   pkg/{_fe_analyzer_shared,front_end,frontend_server,kernel}/test

We can add more later if we find it useful, although I get something
like

```
$ git diff --stat
[...]
104 files changed, 359 insertions(+), 979 deletions(-)

$ git diff | wc -l
7118
```

so I'm unsure if it will be interesting or just too much data.

Change-Id: Ia0438e618371eff6739b8787accbc8d1425ad548
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/448582
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
diff --git a/pkg/front_end/test/run_all_coverage.dart b/pkg/front_end/test/run_all_coverage.dart
index 9f92387..a80d853 100644
--- a/pkg/front_end/test/run_all_coverage.dart
+++ b/pkg/front_end/test/run_all_coverage.dart
@@ -12,6 +12,22 @@
 final Uri repoDirUri = computeRepoDirUri();
 
 Future<void> main() async {
+  Directory coverageTmpDir = await runAllCoverageTests(silent: false);
+
+  // Don't include the not-compiled stuff as we've (mostly) asked the VM to
+  // force compile everything and that the remaining stuff is (mostly) mixins
+  // and const classes etc that shouldn't (necessarily) be compiled but is
+  // potentially covered in other ways.
+  await coverageMerger.mergeFromDirUri(
+    repoDirUri.resolve(".dart_tool/package_config.json"),
+    coverageTmpDir.uri,
+    silent: false,
+    extraCoverageIgnores: const [],
+    extraCoverageBlockIgnores: const [],
+  );
+}
+
+Future<Directory> runAllCoverageTests({required bool silent}) async {
   String dartExtension = "";
   if (Platform.isWindows) {
     dartExtension = ".exe";
@@ -24,19 +40,24 @@
     "cfe_coverage",
   );
   print("Using $coverageTmpDir for coverage.");
-  List<List<String>> runThese = [];
-  void addSuiteSkipVm(String suitePath) {
-    runThese.add([
-      dart.toFilePath(),
-      "--enable-asserts",
-      suitePath,
-      "-DskipVm=true",
-      "--coverage=${coverageTmpDir.path}/",
-    ]);
+  List<List<String>> runTheseSeveralAtATime = [];
+  List<List<String>> runTheseOneAtATime = [];
+  void addSuiteSkipVm(String suitePath, {required int shards}) {
+    for (int shard = 1; shard <= shards; shard++) {
+      runTheseSeveralAtATime.add([
+        dart.toFilePath(),
+        "--enable-asserts",
+        suitePath,
+        "-DskipVm=true",
+        "--coverage=${coverageTmpDir.path}/",
+        "--shards=$shards",
+        "--shard=$shard",
+      ]);
+    }
   }
 
   void addWithCoverageArgument(String script) {
-    runThese.add([
+    runTheseSeveralAtATime.add([
       dart.toFilePath(),
       "--enable-asserts",
       script,
@@ -44,8 +65,8 @@
     ]);
   }
 
-  addSuiteSkipVm("pkg/front_end/test/strong_suite.dart");
-  addSuiteSkipVm("pkg/front_end/test/modular_suite.dart");
+  addSuiteSkipVm("pkg/front_end/test/strong_suite.dart", shards: 4);
+  addSuiteSkipVm("pkg/front_end/test/modular_suite.dart", shards: 1);
 
   addWithCoverageArgument("pkg/front_end/test/messages_suite.dart");
   addWithCoverageArgument("pkg/front_end/test/outline_suite.dart");
@@ -68,7 +89,8 @@
   addWithCoverageArgument("pkg/front_end/test/spelling_test_src_suite.dart");
   addWithCoverageArgument("pkg/front_end/test/compile_platform_coverage.dart");
 
-  runThese.add([
+  // These two each use all available CPUs.
+  runTheseOneAtATime.add([
     "python3",
     "tools/test.py",
     "-cfasta",
@@ -76,7 +98,7 @@
     "-rnone",
     "language",
   ]);
-  runThese.add([
+  runTheseOneAtATime.add([
     dart.toFilePath(),
     repoDirUri
         .resolve("pkg/front_end/test/run_our_tests_with_coverage.dart")
@@ -88,35 +110,63 @@
   );
   environment["CFE_COVERAGE"] = "${coverageTmpDir.path}/";
 
-  for (List<String> runThis in runThese) {
+  final int processes = Platform.numberOfProcessors;
+  int processesLeft = processes;
+  int processesRunning = 0;
+  Completer<void> completer = new Completer();
+
+  for (List<String> runThis in runTheseSeveralAtATime) {
+    while (processesLeft <= 0) {
+      await completer.future;
+    }
+    processesLeft--;
+    processesRunning++;
     print("Starting $runThis");
-    Process p = await Process.start(
-      runThis.first,
-      runThis.skip(1).toList(),
-      environment: environment,
+    unawaited(
+      _run(silent, runThis, environment).then((runExitCode) {
+        print("$runThis finished with exit code $runExitCode.");
+        processesRunning--;
+        processesLeft++;
+        Completer<void> oldCompleter = completer;
+        completer = new Completer();
+        oldCompleter.complete();
+      }),
     );
-    p.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen((
-      String line,
-    ) {
-      print("stdout> $line");
-    });
-    p.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen((
-      String line,
-    ) {
-      print("stderr> $line");
-    });
-    print("Exit code = ${await p.exitCode}");
+  }
+  while (processesRunning > 0) {
+    await completer.future;
   }
 
-  // Don't include the not-compiled stuff as we've (mostly) asked the VM to
-  // force compile everything and that the remaining stuff is (mostly) mixins
-  // and const classes etc that shouldn't (necessarily) be compiled but is
-  // potentially covered in other ways.
-  await coverageMerger.mergeFromDirUri(
-    repoDirUri.resolve(".dart_tool/package_config.json"),
-    coverageTmpDir.uri,
-    silent: false,
-    extraCoverageIgnores: const [],
-    extraCoverageBlockIgnores: const [],
+  for (List<String> runThis in runTheseOneAtATime) {
+    print("Starting $runThis");
+    print(
+      "Finished with exit code "
+      "${await _run(silent, runThis, environment)}",
+    );
+  }
+
+  return coverageTmpDir;
+}
+
+Future<int> _run(
+  bool silent,
+  List<String> runThis,
+  Map<String, String> environment,
+) async {
+  Process p = await Process.start(
+    runThis.first,
+    runThis.skip(1).toList(),
+    environment: environment,
   );
+  p.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen((
+    String line,
+  ) {
+    if (!silent) print("stdout> $line");
+  });
+  p.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen((
+    String line,
+  ) {
+    print("stderr> $line");
+  });
+  return await p.exitCode;
 }
diff --git a/pkg/front_end/test/run_all_coverage_update.dart b/pkg/front_end/test/run_all_coverage_update.dart
new file mode 100644
index 0000000..619665c
--- /dev/null
+++ b/pkg/front_end/test/run_all_coverage_update.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2025, 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:io' show Directory;
+
+import '../tool/coverage_merger.dart' show mergeFromDirUri;
+import 'run_all_coverage.dart' show runAllCoverageTests;
+
+Future<void> main() async {
+  Directory coverageTmpDir = await runAllCoverageTests(silent: true);
+
+  await mergeFromDirUri(
+    Uri.base.resolve(".dart_tool/package_config.json"),
+    coverageTmpDir.uri,
+    silent: true,
+    extraCoverageIgnores: ["coverage-ignore(suite):"],
+    extraCoverageBlockIgnores: ["coverage-ignore-block(suite):"],
+    addAndRemoveCommentsInFiles: true,
+  );
+}
diff --git a/pkg/front_end/test/weekly_tester.dart b/pkg/front_end/test/weekly_tester.dart
index 22a613f4..5807b70 100644
--- a/pkg/front_end/test/weekly_tester.dart
+++ b/pkg/front_end/test/weekly_tester.dart
@@ -4,7 +4,7 @@
 
 import 'dart:async' show Future;
 import 'dart:convert' show LineSplitter, utf8;
-import 'dart:io' show File, Platform, Process;
+import 'dart:io' show File, Platform, Process, ProcessResult;
 
 Future<void> main(List<String> args) async {
   // General idea: Launch - in separate processes - whatever we want to run
@@ -109,6 +109,15 @@
     }
     shouldThrow = true;
   }
+
+  // Now run all coverage and print a diff.
+  WrappedProcess coverageProcess = await run([
+    Platform.script.resolve("run_all_coverage_update.dart").toString(),
+  ], "coverage update");
+  await coverageProcess.process.exitCode;
+  ProcessResult coverageDiffResult = Process.runSync("git", ["diff"]);
+  print(coverageDiffResult.stdout);
+
   if (shouldThrow) throw "There were failures!";
 }