Fix mixed mode tests (#1504)

Fixes an issue where all tests were compiled in either sound or unsound mode, depending on which type of test was encountered first.

We now create a separate compiler for each language version that we see in tests.
diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml
index dace49d..3c4b16c 100644
--- a/.github/workflows/dart.yml
+++ b/.github/workflows/dart.yml
@@ -38,16 +38,16 @@
       - name: mono_repo self validate
         run: pub global run mono_repo generate --validate
   job_002:
-    name: "analyze_and_format; Dart dev; PKGS: integration_tests/nnbd_opted_out, pkgs/test, pkgs/test_api, pkgs/test_core; `dartfmt -n --set-exit-if-changed .`, `dartanalyzer --fatal-infos --fatal-warnings .`"
+    name: "analyze_and_format; Dart dev; PKGS: integration_tests/nnbd_opted_in, integration_tests/nnbd_opted_out, pkgs/test, pkgs/test_api, pkgs/test_core; `dartfmt -n --set-exit-if-changed .`, `dartanalyzer --fatal-infos --fatal-warnings .`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
         uses: actions/cache@v2
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:integration_tests/nnbd_opted_out-pkgs/test-pkgs/test_api-pkgs/test_core;commands:dartfmt-dartanalyzer"
+          key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:integration_tests/nnbd_opted_in-integration_tests/nnbd_opted_out-pkgs/test-pkgs/test_api-pkgs/test_core;commands:dartfmt-dartanalyzer"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:integration_tests/nnbd_opted_out-pkgs/test-pkgs/test_api-pkgs/test_core
+            os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:integration_tests/nnbd_opted_in-integration_tests/nnbd_opted_out-pkgs/test-pkgs/test_api-pkgs/test_core
             os:ubuntu-latest;pub-cache-hosted;dart:dev
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
@@ -56,6 +56,19 @@
           sdk: dev
       - id: checkout
         uses: actions/checkout@v2
+      - id: integration_tests_nnbd_opted_in_pub_upgrade
+        name: "integration_tests/nnbd_opted_in; pub upgrade --no-precompile"
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: integration_tests/nnbd_opted_in
+        run: pub upgrade --no-precompile
+      - name: "integration_tests/nnbd_opted_in; dartfmt -n --set-exit-if-changed ."
+        if: "always() && steps.integration_tests_nnbd_opted_in_pub_upgrade.conclusion == 'success'"
+        working-directory: integration_tests/nnbd_opted_in
+        run: dartfmt -n --set-exit-if-changed .
+      - name: "integration_tests/nnbd_opted_in; dartanalyzer --fatal-infos --fatal-warnings ."
+        if: "always() && steps.integration_tests_nnbd_opted_in_pub_upgrade.conclusion == 'success'"
+        working-directory: integration_tests/nnbd_opted_in
+        run: dartanalyzer --fatal-infos --fatal-warnings .
       - id: integration_tests_nnbd_opted_out_pub_upgrade
         name: "integration_tests/nnbd_opted_out; pub upgrade --no-precompile"
         if: "always() && steps.checkout.conclusion == 'success'"
@@ -109,6 +122,37 @@
         working-directory: pkgs/test_core
         run: dartanalyzer --fatal-infos --fatal-warnings .
   job_003:
+    name: "unit_test; Dart dev; PKG: integration_tests/nnbd_opted_in; `pub run test -p chrome,vm,node`"
+    runs-on: ubuntu-latest
+    steps:
+      - name: Cache Pub hosted dependencies
+        uses: actions/cache@v2
+        with:
+          path: "~/.pub-cache/hosted"
+          key: "os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:integration_tests/nnbd_opted_in;commands:test"
+          restore-keys: |
+            os:ubuntu-latest;pub-cache-hosted;dart:dev;packages:integration_tests/nnbd_opted_in
+            os:ubuntu-latest;pub-cache-hosted;dart:dev
+            os:ubuntu-latest;pub-cache-hosted
+            os:ubuntu-latest
+      - uses: dart-lang/setup-dart@v1.0
+        with:
+          sdk: dev
+      - id: checkout
+        uses: actions/checkout@v2
+      - id: integration_tests_nnbd_opted_in_pub_upgrade
+        name: "integration_tests/nnbd_opted_in; pub upgrade --no-precompile"
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: integration_tests/nnbd_opted_in
+        run: pub upgrade --no-precompile
+      - name: "integration_tests/nnbd_opted_in; pub run test -p chrome,vm,node"
+        if: "always() && steps.integration_tests_nnbd_opted_in_pub_upgrade.conclusion == 'success'"
+        working-directory: integration_tests/nnbd_opted_in
+        run: "pub run test -p chrome,vm,node"
+    needs:
+      - job_001
+      - job_002
+  job_004:
     name: "unit_test; Dart dev; PKG: integration_tests/nnbd_opted_out; `pub run test -p chrome,vm,node`"
     runs-on: ubuntu-latest
     steps:
@@ -139,7 +183,38 @@
     needs:
       - job_001
       - job_002
-  job_004:
+  job_005:
+    name: "unit_test; Dart stable; PKG: integration_tests/nnbd_opted_in; `pub run test -p chrome,vm,node`"
+    runs-on: ubuntu-latest
+    steps:
+      - name: Cache Pub hosted dependencies
+        uses: actions/cache@v2
+        with:
+          path: "~/.pub-cache/hosted"
+          key: "os:ubuntu-latest;pub-cache-hosted;dart:stable;packages:integration_tests/nnbd_opted_in;commands:test"
+          restore-keys: |
+            os:ubuntu-latest;pub-cache-hosted;dart:stable;packages:integration_tests/nnbd_opted_in
+            os:ubuntu-latest;pub-cache-hosted;dart:stable
+            os:ubuntu-latest;pub-cache-hosted
+            os:ubuntu-latest
+      - uses: dart-lang/setup-dart@v1.0
+        with:
+          sdk: stable
+      - id: checkout
+        uses: actions/checkout@v2
+      - id: integration_tests_nnbd_opted_in_pub_upgrade
+        name: "integration_tests/nnbd_opted_in; pub upgrade --no-precompile"
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: integration_tests/nnbd_opted_in
+        run: pub upgrade --no-precompile
+      - name: "integration_tests/nnbd_opted_in; pub run test -p chrome,vm,node"
+        if: "always() && steps.integration_tests_nnbd_opted_in_pub_upgrade.conclusion == 'success'"
+        working-directory: integration_tests/nnbd_opted_in
+        run: "pub run test -p chrome,vm,node"
+    needs:
+      - job_001
+      - job_002
+  job_006:
     name: "unit_test; Dart stable; PKG: integration_tests/nnbd_opted_out; `pub run test -p chrome,vm,node`"
     runs-on: ubuntu-latest
     steps:
@@ -170,7 +245,7 @@
     needs:
       - job_001
       - job_002
-  job_005:
+  job_007:
     name: "unit_test; Dart dev; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" pub run test --preset travis --total-shards 5 --shard-index 0`"
     runs-on: ubuntu-latest
     steps:
@@ -201,7 +276,7 @@
     needs:
       - job_001
       - job_002
-  job_006:
+  job_008:
     name: "unit_test; Dart stable; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" pub run test --preset travis --total-shards 5 --shard-index 0`"
     runs-on: ubuntu-latest
     steps:
@@ -232,7 +307,7 @@
     needs:
       - job_001
       - job_002
-  job_007:
+  job_009:
     name: "unit_test; Dart dev; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" pub run test --preset travis --total-shards 5 --shard-index 1`"
     runs-on: ubuntu-latest
     steps:
@@ -263,7 +338,7 @@
     needs:
       - job_001
       - job_002
-  job_008:
+  job_010:
     name: "unit_test; Dart stable; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" pub run test --preset travis --total-shards 5 --shard-index 1`"
     runs-on: ubuntu-latest
     steps:
@@ -294,7 +369,7 @@
     needs:
       - job_001
       - job_002
-  job_009:
+  job_011:
     name: "unit_test; Dart dev; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" pub run test --preset travis --total-shards 5 --shard-index 2`"
     runs-on: ubuntu-latest
     steps:
@@ -325,7 +400,7 @@
     needs:
       - job_001
       - job_002
-  job_010:
+  job_012:
     name: "unit_test; Dart stable; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" pub run test --preset travis --total-shards 5 --shard-index 2`"
     runs-on: ubuntu-latest
     steps:
@@ -356,7 +431,7 @@
     needs:
       - job_001
       - job_002
-  job_011:
+  job_013:
     name: "unit_test; Dart dev; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" pub run test --preset travis --total-shards 5 --shard-index 3`"
     runs-on: ubuntu-latest
     steps:
@@ -387,7 +462,7 @@
     needs:
       - job_001
       - job_002
-  job_012:
+  job_014:
     name: "unit_test; Dart stable; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" pub run test --preset travis --total-shards 5 --shard-index 3`"
     runs-on: ubuntu-latest
     steps:
@@ -418,7 +493,7 @@
     needs:
       - job_001
       - job_002
-  job_013:
+  job_015:
     name: "unit_test; Dart dev; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" pub run test --preset travis --total-shards 5 --shard-index 4`"
     runs-on: ubuntu-latest
     steps:
@@ -449,7 +524,7 @@
     needs:
       - job_001
       - job_002
-  job_014:
+  job_016:
     name: "unit_test; Dart stable; PKG: pkgs/test; `xvfb-run -s \"-screen 0 1024x768x24\" pub run test --preset travis --total-shards 5 --shard-index 4`"
     runs-on: ubuntu-latest
     steps:
@@ -480,7 +555,7 @@
     needs:
       - job_001
       - job_002
-  job_015:
+  job_017:
     name: "unit_test; Dart dev; PKG: pkgs/test_api; `pub run test --preset travis -x browser`"
     runs-on: ubuntu-latest
     steps:
@@ -511,7 +586,7 @@
     needs:
       - job_001
       - job_002
-  job_016:
+  job_018:
     name: "unit_test; Dart stable; PKG: pkgs/test_api; `pub run test --preset travis -x browser`"
     runs-on: ubuntu-latest
     steps:
@@ -542,7 +617,7 @@
     needs:
       - job_001
       - job_002
-  job_017:
+  job_019:
     name: Notify failure
     runs-on: ubuntu-latest
     if: "(github.event_name == 'push' || github.event_name == 'schedule') && failure()"
@@ -570,3 +645,5 @@
       - job_014
       - job_015
       - job_016
+      - job_017
+      - job_018
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 30c8ff3..0e2f415 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -6,8 +6,6 @@
     # There are a number of deprecated members used through this package
     deprecated_member_use_from_same_package: ignore
     unsafe_html: ignore
-  enable-experiment:
-    - non-nullable
 linter:
   rules:
     - avoid_private_typedef_functions
diff --git a/integration_tests/nnbd_opted_in/mono_pkg.yaml b/integration_tests/nnbd_opted_in/mono_pkg.yaml
new file mode 100644
index 0000000..b79bf54
--- /dev/null
+++ b/integration_tests/nnbd_opted_in/mono_pkg.yaml
@@ -0,0 +1,13 @@
+dart:
+  - dev
+  - stable
+
+stages:
+    - analyze_and_format:
+      - group:
+        - dartfmt: sdk
+        - dartanalyzer: --fatal-infos --fatal-warnings .
+        dart:
+        - dev
+    - unit_test:
+      - test: -p chrome,vm,node
diff --git a/integration_tests/nnbd_opted_in/pubspec.yaml b/integration_tests/nnbd_opted_in/pubspec.yaml
new file mode 100644
index 0000000..182d9d6
--- /dev/null
+++ b/integration_tests/nnbd_opted_in/pubspec.yaml
@@ -0,0 +1,12 @@
+name: nnbd_opted_in
+environment:
+  sdk: '>=2.12.0 <3.0.0'
+dev_dependencies:
+  test: any
+dependency_overrides:
+  test:
+    path: ../../pkgs/test
+  test_api:
+    path: ../../pkgs/test_api
+  test_core:
+    path: ../../pkgs/test_core
diff --git a/integration_tests/nnbd_opted_in/test/common/is_opted_out.dart b/integration_tests/nnbd_opted_in/test/common/is_opted_out.dart
new file mode 100644
index 0000000..b661c9d
--- /dev/null
+++ b/integration_tests/nnbd_opted_in/test/common/is_opted_out.dart
@@ -0,0 +1,5 @@
+// Copyright (c) 2021, 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.
+
+final bool isOptedOut = <int?>[] is List<int>;
diff --git a/integration_tests/nnbd_opted_in/test/opted_in_test.dart b/integration_tests/nnbd_opted_in/test/opted_in_test.dart
new file mode 100644
index 0000000..6f8eb9a
--- /dev/null
+++ b/integration_tests/nnbd_opted_in/test/opted_in_test.dart
@@ -0,0 +1,13 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'common/is_opted_out.dart';
+
+void main() {
+  test('sound behavior', () async {
+    expect(isOptedOut, false);
+  });
+}
diff --git a/integration_tests/nnbd_opted_in/test/opted_out_test.dart b/integration_tests/nnbd_opted_in/test/opted_out_test.dart
new file mode 100644
index 0000000..291647f
--- /dev/null
+++ b/integration_tests/nnbd_opted_in/test/opted_out_test.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2021, 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.
+
+// @dart=2.9
+
+import 'package:test/test.dart';
+
+import 'common/is_opted_out.dart';
+
+void main() {
+  test('unsound behavior', () async {
+    expect(isOptedOut, true);
+  });
+}
diff --git a/integration_tests/nnbd_opted_out/test/common/is_opted_out.dart b/integration_tests/nnbd_opted_out/test/common/is_opted_out.dart
new file mode 100644
index 0000000..7f4b628
--- /dev/null
+++ b/integration_tests/nnbd_opted_out/test/common/is_opted_out.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, 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.
+//
+// @dart=2.12
+
+final bool isOptedOut = <int?>[] is List<int>;
diff --git a/integration_tests/nnbd_opted_out/test/opted_in_test.dart b/integration_tests/nnbd_opted_out/test/opted_in_test.dart
new file mode 100644
index 0000000..1d2e5b1
--- /dev/null
+++ b/integration_tests/nnbd_opted_out/test/opted_in_test.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2021, 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.
+//
+// @dart=2.12
+
+import 'package:test/test.dart';
+
+import 'common/is_opted_out.dart';
+
+void main() {
+  test('is opted in to sound null safety', () {
+    expect(isOptedOut, isFalse);
+  });
+}
diff --git a/integration_tests/nnbd_opted_out/test/opted_out_test.dart b/integration_tests/nnbd_opted_out/test/opted_out_test.dart
new file mode 100644
index 0000000..1f1277d
--- /dev/null
+++ b/integration_tests/nnbd_opted_out/test/opted_out_test.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2021, 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.
+//
+// @dart=2.9
+
+import 'package:test/test.dart';
+
+import 'common/is_opted_out.dart';
+
+void main() {
+  test('is opted out of sound null safety', () {
+    expect(isOptedOut, isTrue);
+  });
+}
diff --git a/integration_tests/nnbd_opted_out/test/spawn_hybrid_code_test.dart b/integration_tests/nnbd_opted_out/test/spawn_hybrid_code_test.dart
index 46829cc..5a933d4 100644
--- a/integration_tests/nnbd_opted_out/test/spawn_hybrid_code_test.dart
+++ b/integration_tests/nnbd_opted_out/test/spawn_hybrid_code_test.dart
@@ -8,28 +8,21 @@
   group('spawnHybridCode', () {
     test('uses the current package language version by default', () async {
       final channel = spawnHybridCode(_hybridMain);
-      expect(await channel.stream.single, equals(false));
+      expect(await channel.stream.single, equals(true));
     });
 
     test('can set the language version with a marker', () async {
       final channel = spawnHybridCode('// @dart=2.12\n$_hybridMain');
-      expect(await channel.stream.single, equals(true));
+      expect(await channel.stream.single, equals(false));
     });
   });
 }
 
 const _hybridMain = '''
-bool runningWithNullSafety() {
-  try {
-    null as String;
-    return false;
-  } catch (_) {
-    return true;
-  }
-}
+final isOptedOut = <int?>[] is List<int>;
 
 void hybridMain(dynamic channel) async {
-  channel.sink.add(runningWithNullSafety());
+  channel.sink.add(isOptedOut);
   channel.sink.close();
 }
 ''';
diff --git a/pkgs/test/CHANGELOG.md b/pkgs/test/CHANGELOG.md
index f91319c..d596b2d 100644
--- a/pkgs/test/CHANGELOG.md
+++ b/pkgs/test/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 1.17.1
+
+* Fix an issue where you couldn't have tests compiled in both sound and
+  unsound null safety modes.
+
 ## 1.17.0
 
 * Change the default way VM tests are launched and ran to greatly speed up
diff --git a/pkgs/test/pubspec.yaml b/pkgs/test/pubspec.yaml
index 4742fee..b088643 100644
--- a/pkgs/test/pubspec.yaml
+++ b/pkgs/test/pubspec.yaml
@@ -1,5 +1,5 @@
 name: test
-version: 1.17.0
+version: 1.17.1
 description: >-
   A full featured library for writing and running Dart tests across platforms.
 repository: https://github.com/dart-lang/test/blob/master/pkgs/test
@@ -34,7 +34,7 @@
   yaml: ^3.0.0
   # Use an exact version until the test_api and test_core package are stable.
   test_api: 0.4.0
-  test_core: 0.3.20
+  test_core: 0.3.21
 
 dev_dependencies:
   fake_async: ^1.0.0
diff --git a/pkgs/test_core/CHANGELOG.md b/pkgs/test_core/CHANGELOG.md
index 7bed808..355159a 100644
--- a/pkgs/test_core/CHANGELOG.md
+++ b/pkgs/test_core/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.3.21
+
+* Fix an issue where you couldn't have tests compiled in both sound and
+  unsound null safety modes.
+
 ## 0.3.20
 
 * Add library `scaffolding.dart` to allow importing a subset of the normal
diff --git a/pkgs/test_core/lib/src/runner/vm/platform.dart b/pkgs/test_core/lib/src/runner/vm/platform.dart
index 8f52f89..0083272 100644
--- a/pkgs/test_core/lib/src/runner/vm/platform.dart
+++ b/pkgs/test_core/lib/src/runner/vm/platform.dart
@@ -35,8 +35,8 @@
 class VMPlatform extends PlatformPlugin {
   /// The test runner configuration.
   final _config = Configuration.current;
-  final _compiler =
-      TestCompiler(p.join(p.current, '.dart_tool', 'pkg_test_kernel.bin'));
+  final _compiler = TestCompiler(
+      p.join(p.current, '.dart_tool', 'test', 'incremental_kernel'));
   final _closeMemo = AsyncMemoizer<void>();
 
   VMPlatform();
diff --git a/pkgs/test_core/lib/src/runner/vm/test_compiler.dart b/pkgs/test_core/lib/src/runner/vm/test_compiler.dart
index 0fb9554..69ad919 100644
--- a/pkgs/test_core/lib/src/runner/vm/test_compiler.dart
+++ b/pkgs/test_core/lib/src/runner/vm/test_compiler.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:async/async.dart';
@@ -27,49 +28,59 @@
 }
 
 class TestCompiler {
-  final _compilePool = Pool(1);
-  final String _dillCachePath;
-  final Directory _outputDillDirectory;
-
-  late final _outputDill =
-      File(p.join(_outputDillDirectory.path, 'output.dill'));
-  FrontendServerClient? _frontendServerClient;
-
   final _closeMemo = AsyncMemoizer<void>();
 
+  /// Each language version that appears in test files gets its own compiler,
+  /// to ensure that all language modes are supported (such as sound and
+  /// unsound null safety).
+  final _compilerForLanguageVersion =
+      <String, _TestCompilerForLanguageVersion>{};
+
+  /// A prefix used for the dill files for each compiler that is created.
+  final String _dillCachePrefix;
+
   /// No work is done until the first call to [compile] is recieved, at which
   /// point the compiler process is started.
-  TestCompiler(this._dillCachePath)
-      : _outputDillDirectory =
-            Directory.systemTemp.createTempSync('dart_test.');
+  TestCompiler(this._dillCachePrefix);
 
-  /// Enqueues a request to compile [mainDart] and returns the result.
-  ///
-  /// This request may need to wait for ongoing compilations.
-  ///
-  /// If [dispose] has already been called, then this immediately returns a
-  /// failed response indicating the compiler was shut down.
-  ///
-  /// The entrypoint [mainDart] is wrapped in a script which bootstraps it with
-  /// a call to `internalBootstrapVmTest`.
+  /// Compiles [mainDart], using a separate compiler per language version of
+  /// the tests.
   Future<CompilationResponse> compile(Uri mainDart, Metadata metadata) async {
-    if (_compilePool.isClosed) return CompilationResponse._wasShutdown;
-    return _compilePool.withResource(() => _compile(mainDart, metadata));
+    if (_closeMemo.hasRun) return CompilationResponse._wasShutdown;
+    var languageVersionComment = metadata.languageVersionComment ??
+        await rootPackageLanguageVersionComment;
+    var compiler = _compilerForLanguageVersion.putIfAbsent(
+        languageVersionComment,
+        () => _TestCompilerForLanguageVersion(
+            _dillCachePrefix, languageVersionComment));
+    return compiler.compile(mainDart);
   }
 
-  Future<void> dispose() => _closeMemo.runOnce(() async {
-        await _compilePool.close();
-        _frontendServerClient?.kill();
-        _frontendServerClient = null;
-        if (_outputDillDirectory.existsSync()) {
-          _outputDillDirectory.deleteSync(recursive: true);
-        }
-      });
+  Future<void> dispose() => _closeMemo.runOnce(() => Future.wait([
+        for (var compiler in _compilerForLanguageVersion.values)
+          compiler.dispose(),
+      ]));
+}
 
-  Future<String> _generateEntrypoint(
-      Uri testUri, Metadata suiteMetadata) async {
+class _TestCompilerForLanguageVersion {
+  final _closeMemo = AsyncMemoizer();
+  final _compilePool = Pool(1);
+  final String _dillCachePath;
+  FrontendServerClient? _frontendServerClient;
+  final String _languageVersionComment;
+  late final _outputDill =
+      File(p.join(_outputDillDirectory.path, 'output.dill'));
+  final _outputDillDirectory =
+      Directory.systemTemp.createTempSync('dart_test.');
+
+  _TestCompilerForLanguageVersion(
+      String dillCachePrefix, this._languageVersionComment)
+      : _dillCachePath =
+            '$dillCachePrefix.${base64.encode(utf8.encode(_languageVersionComment.replaceAll(' ', '')))}';
+
+  String _generateEntrypoint(Uri testUri) {
     return '''
-        ${suiteMetadata.languageVersionComment ?? await rootPackageLanguageVersionComment}
+    $_languageVersionComment
     import "dart:isolate";
 
     import "package:test_core/src/bootstrap/vm.dart";
@@ -82,13 +93,15 @@
   ''';
   }
 
-  Future<CompilationResponse> _compile(Uri mainUri, Metadata metadata) async {
+  Future<CompilationResponse> compile(Uri mainUri) =>
+      _compilePool.withResource(() => _compile(mainUri));
+
+  Future<CompilationResponse> _compile(Uri mainUri) async {
     if (_closeMemo.hasRun) return CompilationResponse._wasShutdown;
     var firstCompile = false;
     CompileResult? compilerOutput;
-    final contents = await _generateEntrypoint(mainUri, metadata);
     final tempFile = File(p.join(_outputDillDirectory.path, 'test.dart'))
-      ..writeAsStringSync(contents);
+      ..writeAsStringSync(_generateEntrypoint(mainUri));
 
     try {
       if (_frontendServerClient == null) {
@@ -151,4 +164,13 @@
     );
     return client.compile();
   }
+
+  Future<void> dispose() => _closeMemo.runOnce(() async {
+        await _compilePool.close();
+        _frontendServerClient?.kill();
+        _frontendServerClient = null;
+        if (_outputDillDirectory.existsSync()) {
+          _outputDillDirectory.deleteSync(recursive: true);
+        }
+      });
 }
diff --git a/pkgs/test_core/pubspec.yaml b/pkgs/test_core/pubspec.yaml
index 5b51cd1..594f315 100644
--- a/pkgs/test_core/pubspec.yaml
+++ b/pkgs/test_core/pubspec.yaml
@@ -1,5 +1,5 @@
 name: test_core
-version: 0.3.20
+version: 0.3.21
 description: A basic library for writing tests and running them on the VM.
 homepage: https://github.com/dart-lang/test/blob/master/pkgs/test_core