Restrict imports across layers of test_api (#1492)

Allow splitting `test_api` into multiple targets in bazel. Fix imports
that would cause cycles in the targets, and shuffle some code to make
globbing easier and better reflect the use cases and library coupling.

Add more exports in `backend.dart` of types that are related to how
platforms communicate with the runner, `RemoteException`,
`RemoteListener`, `StackTraceFormatter`, and `StackTraceMapper`. Where
convenient, update imports from `test_api/src/*` to
`test_api/backend.dart` with a `show` clause.
Not all of `src/backend` is exported, including the details that are
currently used from `test_core` and `test` since future work may be able
to keep those details from leaking.

Add `pumpEventQueue` to `hooks.dart`. This allows `expect` to rely only
on `hooks` and avoid a dependency on `scaffolding`. It is an API that
should may be useful in general for async matching frameworks.

Add a test which uses the analyzer to crawl imports and enforce some
aspects of the required structure:
- `backend.dart` and `src/backend/**` may not import any other
  parts of the packages, so `backend` can be a leaf target with a simple
  glob.
- `expect` may not be imported from any of the focused entrypoints
  (`hooks.dart` or `scaffolding.dart`) and may not import any of the
  implementation details other than `hooks.dart`.

Shuffle around code and update a few imports to more narrow libraries to
fit the above criteria.
- Move the annotations like `Timeout` etc to `src/backend/configuration`
  to fix some imports that would be to `src/scaffolding` and re-export
  from `scaffolding.dart`. Not all of these are used, but they are all
  moved for consistency.
- Move `TestFailure` into backend and re-export from `hooks.dart`.
- Move `remote_exception.dart`, `remote_listener.dart`, and
  `stack_trace_mapper.dart` to `src/backend/` since they are now
  exported.
- Move `suite_channel_manager.dart` and `pretty_print.dart` into
  `src/backend/`.
- Move `anchoredHyphenatedIdentifier` into a separate utility library
  under `src/backend/`. Inline the unused private regex and update the
  doc comment avoid referencing it.
diff --git a/pkgs/test/lib/src/runner/browser/browser_manager.dart b/pkgs/test/lib/src/runner/browser/browser_manager.dart
index 207aaa6..a35fc77 100644
--- a/pkgs/test/lib/src/runner/browser/browser_manager.dart
+++ b/pkgs/test/lib/src/runner/browser/browser_manager.dart
@@ -8,8 +8,8 @@
 import 'package:async/async.dart';
 import 'package:pool/pool.dart';
 import 'package:stream_channel/stream_channel.dart';
-import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
-import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
+// ignore: deprecated_member_use
+import 'package:test_api/backend.dart' show Runtime, StackTraceMapper;
 import 'package:test_core/src/runner/application_exception.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/environment.dart'; // ignore: implementation_imports
diff --git a/pkgs/test/lib/src/runner/browser/platform.dart b/pkgs/test/lib/src/runner/browser/platform.dart
index 7158944..46b62d9 100644
--- a/pkgs/test/lib/src/runner/browser/platform.dart
+++ b/pkgs/test/lib/src/runner/browser/platform.dart
@@ -16,9 +16,9 @@
 import 'package:shelf_static/shelf_static.dart';
 import 'package:shelf_web_socket/shelf_web_socket.dart';
 import 'package:stream_channel/stream_channel.dart';
-import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
-import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
+// ignore: deprecated_member_use
+import 'package:test_api/backend.dart'
+    show Runtime, StackTraceMapper, SuitePlatform;
 import 'package:test_core/src/runner/compiler_pool.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/load_exception.dart'; // ignore: implementation_imports
diff --git a/pkgs/test/lib/src/runner/node/platform.dart b/pkgs/test/lib/src/runner/node/platform.dart
index 3a5b733..f6f32c0 100644
--- a/pkgs/test/lib/src/runner/node/platform.dart
+++ b/pkgs/test/lib/src/runner/node/platform.dart
@@ -12,9 +12,9 @@
 import 'package:path/path.dart' as p;
 import 'package:pedantic/pedantic.dart';
 import 'package:stream_channel/stream_channel.dart';
-import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
-import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
+// ignore: deprecated_member_use
+import 'package:test_api/backend.dart'
+    show Runtime, StackTraceMapper, SuitePlatform;
 import 'package:test_core/src/runner/application_exception.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/compiler_pool.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports
diff --git a/pkgs/test_api/CHANGELOG.md b/pkgs/test_api/CHANGELOG.md
index ec25191..a81ea77 100644
--- a/pkgs/test_api/CHANGELOG.md
+++ b/pkgs/test_api/CHANGELOG.md
@@ -7,6 +7,8 @@
 * Add examples to `throwsA` and make top-level `throws...` matchers refer to it.
 * Disable stack trace chaining by default.
 * Fix `expectAsync` function type checks.
+* Add `RemoteException`, `RemoteListener`, `StackTraceFormatter`, and
+  `StackTraceMapper` to `backend.dart`.
 * **Breaking** remove `Runtime.phantomJS`
 * **Breaking** Add callback to get the suite channel in the `beforeLoad`
   callback of `RemoteListener.start`. This is now used in place of using zones
diff --git a/pkgs/test_api/lib/backend.dart b/pkgs/test_api/lib/backend.dart
index 9fb8ac1..bb80812 100644
--- a/pkgs/test_api/lib/backend.dart
+++ b/pkgs/test_api/lib/backend.dart
@@ -8,5 +8,9 @@
 
 export 'src/backend/metadata.dart' show Metadata;
 export 'src/backend/platform_selector.dart' show PlatformSelector;
+export 'src/backend/remote_exception.dart' show RemoteException;
+export 'src/backend/remote_listener.dart' show RemoteListener;
 export 'src/backend/runtime.dart' show Runtime;
+export 'src/backend/stack_trace_formatter.dart' show StackTraceFormatter;
+export 'src/backend/stack_trace_mapper.dart' show StackTraceMapper;
 export 'src/backend/suite_platform.dart' show SuitePlatform;
diff --git a/pkgs/test_api/lib/hooks.dart b/pkgs/test_api/lib/hooks.dart
index cdfff57..b7e1455 100644
--- a/pkgs/test_api/lib/hooks.dart
+++ b/pkgs/test_api/lib/hooks.dart
@@ -10,6 +10,9 @@
 import 'src/backend/invoker.dart';
 import 'src/backend/stack_trace_formatter.dart';
 
+export 'src/backend/test_failure.dart' show TestFailure;
+export 'src/scaffolding/utils.dart' show pumpEventQueue;
+
 class TestHandle {
   /// Returns handle for the currently running test.
   ///
@@ -77,13 +80,3 @@
 }
 
 class OutsideTestException implements Exception {}
-
-/// An exception thrown when a test assertion fails.
-class TestFailure {
-  final String? message;
-
-  TestFailure(this.message);
-
-  @override
-  String toString() => message.toString();
-}
diff --git a/pkgs/test_api/lib/scaffolding.dart b/pkgs/test_api/lib/scaffolding.dart
index 4979ba3..7276047 100644
--- a/pkgs/test_api/lib/scaffolding.dart
+++ b/pkgs/test_api/lib/scaffolding.dart
@@ -6,14 +6,14 @@
     'Please use package:test.')
 library test_api.scaffolding;
 
-export 'src/scaffolding/on_platform.dart' show OnPlatform;
-export 'src/scaffolding/retry.dart' show Retry;
-export 'src/scaffolding/skip.dart' show Skip;
+export 'src/backend/configuration/on_platform.dart' show OnPlatform;
+export 'src/backend/configuration/retry.dart' show Retry;
+export 'src/backend/configuration/skip.dart' show Skip;
+export 'src/backend/configuration/tags.dart' show Tags;
+export 'src/backend/configuration/test_on.dart' show TestOn;
+export 'src/backend/configuration/timeout.dart' show Timeout;
 export 'src/scaffolding/spawn_hybrid.dart' show spawnHybridUri, spawnHybridCode;
-export 'src/scaffolding/tags.dart' show Tags;
-export 'src/scaffolding/test_on.dart' show TestOn;
-export 'src/scaffolding/timeout.dart' show Timeout;
-export 'src/scaffolding/utils.dart'
-    show pumpEventQueue, printOnFailure, markTestSkipped;
 export 'src/scaffolding/test_structure.dart'
     show group, test, setUp, setUpAll, tearDown, tearDownAll, addTearDown;
+export 'src/scaffolding/utils.dart'
+    show pumpEventQueue, printOnFailure, markTestSkipped;
diff --git a/pkgs/test_api/lib/src/scaffolding/on_platform.dart b/pkgs/test_api/lib/src/backend/configuration/on_platform.dart
similarity index 100%
rename from pkgs/test_api/lib/src/scaffolding/on_platform.dart
rename to pkgs/test_api/lib/src/backend/configuration/on_platform.dart
diff --git a/pkgs/test_api/lib/src/scaffolding/retry.dart b/pkgs/test_api/lib/src/backend/configuration/retry.dart
similarity index 100%
rename from pkgs/test_api/lib/src/scaffolding/retry.dart
rename to pkgs/test_api/lib/src/backend/configuration/retry.dart
diff --git a/pkgs/test_api/lib/src/scaffolding/skip.dart b/pkgs/test_api/lib/src/backend/configuration/skip.dart
similarity index 100%
rename from pkgs/test_api/lib/src/scaffolding/skip.dart
rename to pkgs/test_api/lib/src/backend/configuration/skip.dart
diff --git a/pkgs/test_api/lib/src/scaffolding/tags.dart b/pkgs/test_api/lib/src/backend/configuration/tags.dart
similarity index 100%
rename from pkgs/test_api/lib/src/scaffolding/tags.dart
rename to pkgs/test_api/lib/src/backend/configuration/tags.dart
diff --git a/pkgs/test_api/lib/src/scaffolding/test_on.dart b/pkgs/test_api/lib/src/backend/configuration/test_on.dart
similarity index 100%
rename from pkgs/test_api/lib/src/scaffolding/test_on.dart
rename to pkgs/test_api/lib/src/backend/configuration/test_on.dart
diff --git a/pkgs/test_api/lib/src/scaffolding/timeout.dart b/pkgs/test_api/lib/src/backend/configuration/timeout.dart
similarity index 99%
rename from pkgs/test_api/lib/src/scaffolding/timeout.dart
rename to pkgs/test_api/lib/src/backend/configuration/timeout.dart
index 075d986..49c239b 100644
--- a/pkgs/test_api/lib/src/scaffolding/timeout.dart
+++ b/pkgs/test_api/lib/src/backend/configuration/timeout.dart
@@ -2,8 +2,8 @@
 // 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:string_scanner/string_scanner.dart';
 import 'package:meta/meta_meta.dart';
+import 'package:string_scanner/string_scanner.dart';
 
 /// A regular expression that matches text until a letter or whitespace.
 ///
diff --git a/pkgs/test_api/lib/src/backend/declarer.dart b/pkgs/test_api/lib/src/backend/declarer.dart
index 1c299ce..56f2382 100644
--- a/pkgs/test_api/lib/src/backend/declarer.dart
+++ b/pkgs/test_api/lib/src/backend/declarer.dart
@@ -7,7 +7,7 @@
 import 'package:collection/collection.dart';
 import 'package:stack_trace/stack_trace.dart';
 
-import '../scaffolding/timeout.dart';
+import 'configuration/timeout.dart';
 import 'group.dart';
 import 'group_entry.dart';
 import 'invoker.dart';
diff --git a/pkgs/test_api/lib/src/backend/invoker.dart b/pkgs/test_api/lib/src/backend/invoker.dart
index cfdc4b5..bc6aa8d 100644
--- a/pkgs/test_api/lib/src/backend/invoker.dart
+++ b/pkgs/test_api/lib/src/backend/invoker.dart
@@ -6,8 +6,6 @@
 
 import 'package:stack_trace/stack_trace.dart';
 
-import '../../hooks.dart' show TestFailure;
-import '../util/pretty_print.dart';
 import 'closed_exception.dart';
 import 'declarer.dart';
 import 'group.dart';
@@ -19,6 +17,8 @@
 import 'suite.dart';
 import 'suite_platform.dart';
 import 'test.dart';
+import 'test_failure.dart';
+import 'util/pretty_print.dart';
 
 /// A test in this isolate.
 class LocalTest extends Test {
diff --git a/pkgs/test_api/lib/src/backend/metadata.dart b/pkgs/test_api/lib/src/backend/metadata.dart
index ef93153..6c459e2 100644
--- a/pkgs/test_api/lib/src/backend/metadata.dart
+++ b/pkgs/test_api/lib/src/backend/metadata.dart
@@ -5,12 +5,12 @@
 import 'package:boolean_selector/boolean_selector.dart';
 import 'package:collection/collection.dart';
 
-import '../scaffolding/skip.dart';
-import '../scaffolding/timeout.dart';
-import '../util/pretty_print.dart';
-import '../utils.dart';
+import 'configuration/skip.dart';
+import 'configuration/timeout.dart';
 import 'platform_selector.dart';
 import 'suite_platform.dart';
+import 'util/identifier_regex.dart';
+import 'util/pretty_print.dart';
 
 /// Metadata for a test or test suite.
 ///
diff --git a/pkgs/test_api/lib/src/util/remote_exception.dart b/pkgs/test_api/lib/src/backend/remote_exception.dart
similarity index 98%
rename from pkgs/test_api/lib/src/util/remote_exception.dart
rename to pkgs/test_api/lib/src/backend/remote_exception.dart
index a38104e..ef47ea1 100644
--- a/pkgs/test_api/lib/src/util/remote_exception.dart
+++ b/pkgs/test_api/lib/src/backend/remote_exception.dart
@@ -6,7 +6,7 @@
 
 import 'package:stack_trace/stack_trace.dart';
 
-import '../../hooks.dart' show TestFailure;
+import 'test_failure.dart';
 
 /// An exception that was thrown remotely.
 ///
diff --git a/pkgs/test_api/lib/src/remote_listener.dart b/pkgs/test_api/lib/src/backend/remote_listener.dart
similarity index 90%
rename from pkgs/test_api/lib/src/remote_listener.dart
rename to pkgs/test_api/lib/src/backend/remote_listener.dart
index f6f292e..095ae19 100644
--- a/pkgs/test_api/lib/src/remote_listener.dart
+++ b/pkgs/test_api/lib/src/backend/remote_listener.dart
@@ -8,18 +8,17 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:term_glyph/term_glyph.dart' as glyph;
 
-import 'package:test_api/src/backend/declarer.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/invoker.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/metadata.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/stack_trace_formatter.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
-import 'package:test_api/src/util/remote_exception.dart'; // ignore: implementation_imports
-
+import 'declarer.dart';
+import 'group.dart';
+import 'invoker.dart';
+import 'live_test.dart';
+import 'metadata.dart';
+import 'remote_exception.dart';
+import 'stack_trace_formatter.dart';
+import 'suite.dart';
 import 'suite_channel_manager.dart';
+import 'suite_platform.dart';
+import 'test.dart';
 
 class RemoteListener {
   /// The test suite to run.
diff --git a/pkgs/test_api/lib/src/backend/stack_trace_formatter.dart b/pkgs/test_api/lib/src/backend/stack_trace_formatter.dart
index 8d621f5..d200143 100644
--- a/pkgs/test_api/lib/src/backend/stack_trace_formatter.dart
+++ b/pkgs/test_api/lib/src/backend/stack_trace_formatter.dart
@@ -6,8 +6,8 @@
 
 import 'package:stack_trace/stack_trace.dart';
 
-import '../util/stack_trace_mapper.dart';
 import 'invoker.dart';
+import 'stack_trace_mapper.dart';
 
 /// The key used to look up [StackTraceFormatter.current] in a zone.
 final _currentKey = Object();
diff --git a/pkgs/test_api/lib/src/util/stack_trace_mapper.dart b/pkgs/test_api/lib/src/backend/stack_trace_mapper.dart
similarity index 100%
rename from pkgs/test_api/lib/src/util/stack_trace_mapper.dart
rename to pkgs/test_api/lib/src/backend/stack_trace_mapper.dart
diff --git a/pkgs/test_api/lib/src/suite_channel_manager.dart b/pkgs/test_api/lib/src/backend/suite_channel_manager.dart
similarity index 100%
rename from pkgs/test_api/lib/src/suite_channel_manager.dart
rename to pkgs/test_api/lib/src/backend/suite_channel_manager.dart
diff --git a/pkgs/test_api/lib/src/backend/test_failure.dart b/pkgs/test_api/lib/src/backend/test_failure.dart
new file mode 100644
index 0000000..b41c006
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/test_failure.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.
+
+/// An exception thrown when a test assertion fails.
+class TestFailure {
+  final String? message;
+
+  TestFailure(this.message);
+
+  @override
+  String toString() => message.toString();
+}
diff --git a/pkgs/test_api/lib/src/backend/util/identifier_regex.dart b/pkgs/test_api/lib/src/backend/util/identifier_regex.dart
new file mode 100644
index 0000000..6426641
--- /dev/null
+++ b/pkgs/test_api/lib/src/backend/util/identifier_regex.dart
@@ -0,0 +1,9 @@
+// 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.
+
+/// A regular expression matching a full string as a hyphenated identifier.
+///
+/// This is like a standard Dart identifier, except that it can also contain
+/// hyphens.
+final anchoredHyphenatedIdentifier = RegExp(r'^[a-zA-Z_-][a-zA-Z0-9_-]*$');
diff --git a/pkgs/test_api/lib/src/util/pretty_print.dart b/pkgs/test_api/lib/src/backend/util/pretty_print.dart
similarity index 100%
rename from pkgs/test_api/lib/src/util/pretty_print.dart
rename to pkgs/test_api/lib/src/backend/util/pretty_print.dart
diff --git a/pkgs/test_api/lib/src/expect/future_matchers.dart b/pkgs/test_api/lib/src/expect/future_matchers.dart
index d6d39c0..284249b 100644
--- a/pkgs/test_api/lib/src/expect/future_matchers.dart
+++ b/pkgs/test_api/lib/src/expect/future_matchers.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:matcher/matcher.dart';
-import 'package:test_api/test_api.dart' show pumpEventQueue;
+import 'package:test_api/hooks.dart' show pumpEventQueue;
 
 import 'async_matcher.dart';
 import 'expect.dart';
diff --git a/pkgs/test_api/lib/src/expect/never_called.dart b/pkgs/test_api/lib/src/expect/never_called.dart
index d950650..4b93bd7 100644
--- a/pkgs/test_api/lib/src/expect/never_called.dart
+++ b/pkgs/test_api/lib/src/expect/never_called.dart
@@ -6,7 +6,6 @@
 
 import 'package:stack_trace/stack_trace.dart';
 import 'package:test_api/hooks.dart';
-import 'package:test_api/test_api.dart' show pumpEventQueue;
 
 import 'expect.dart';
 import 'future_matchers.dart';
diff --git a/pkgs/test_api/lib/src/scaffolding/spawn_hybrid.dart b/pkgs/test_api/lib/src/scaffolding/spawn_hybrid.dart
index 102298e..ef2d86f 100644
--- a/pkgs/test_api/lib/src/scaffolding/spawn_hybrid.dart
+++ b/pkgs/test_api/lib/src/scaffolding/spawn_hybrid.dart
@@ -8,9 +8,9 @@
 import 'package:async/async.dart';
 import 'package:stream_channel/stream_channel.dart';
 
-import '../../test_api.dart';
-import '../util/remote_exception.dart';
+import '../backend/remote_exception.dart';
 import '../utils.dart';
+import 'test_structure.dart' show addTearDown;
 
 /// A transformer that handles messages from the spawned isolate and ensures
 /// that messages sent to it are JSON-encodable.
diff --git a/pkgs/test_api/lib/src/scaffolding/test_structure.dart b/pkgs/test_api/lib/src/scaffolding/test_structure.dart
index 1a99435..7ec282d 100644
--- a/pkgs/test_api/lib/src/scaffolding/test_structure.dart
+++ b/pkgs/test_api/lib/src/scaffolding/test_structure.dart
@@ -6,9 +6,9 @@
 
 import 'package:meta/meta.dart';
 
+import '../backend/configuration/timeout.dart';
 import '../backend/declarer.dart';
 import '../backend/invoker.dart';
-import '../scaffolding/timeout.dart';
 
 // test_core does not support running tests directly, so the Declarer should
 // always be on the Zone.
diff --git a/pkgs/test_api/lib/src/utils.dart b/pkgs/test_api/lib/src/utils.dart
index 99f33e3..3bf7518 100644
--- a/pkgs/test_api/lib/src/utils.dart
+++ b/pkgs/test_api/lib/src/utils.dart
@@ -2,17 +2,6 @@
 // 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.
 
-/// A regular expression matching a hyphenated identifier.
-///
-/// This is like a standard Dart identifier, except that it can also contain
-/// hyphens.
-final _hyphenatedIdentifier = RegExp(r'[a-zA-Z_-][a-zA-Z0-9_-]*');
-
-/// Like [_hyphenatedIdentifier], but anchored so that it must match the entire
-/// string.
-final anchoredHyphenatedIdentifier =
-    RegExp('^${_hyphenatedIdentifier.pattern}\$');
-
 /// Throws an [ArgumentError] if [message] isn't recursively JSON-safe.
 void ensureJsonEncodable(Object? message) {
   if (message == null ||
diff --git a/pkgs/test_api/pubspec.yaml b/pkgs/test_api/pubspec.yaml
index da7d4ec..5ac3f25 100644
--- a/pkgs/test_api/pubspec.yaml
+++ b/pkgs/test_api/pubspec.yaml
@@ -22,7 +22,9 @@
   matcher: '>=0.12.10 <0.12.11'
 
 dev_dependencies:
+  glob: ^2.0.0
   fake_async: ^1.2.0
+  graphs: ^2.0.0
   pedantic: ^1.10.0
   test: any
   test_core: any
diff --git a/pkgs/test_api/test/import_restrictions_test.dart b/pkgs/test_api/test/import_restrictions_test.dart
new file mode 100644
index 0000000..fc52be5
--- /dev/null
+++ b/pkgs/test_api/test/import_restrictions_test.dart
@@ -0,0 +1,158 @@
+// 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 'dart:async';
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:analyzer/dart/analysis/analysis_context.dart';
+import 'package:analyzer/dart/analysis/context_builder.dart';
+import 'package:analyzer/dart/analysis/context_locator.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:glob/glob.dart';
+import 'package:glob/list_local_fs.dart';
+import 'package:graphs/graphs.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+
+void main() {
+  late _ImportCheck importCheck;
+  setUpAll(() async {
+    importCheck = await _ImportCheck.create();
+  });
+  group('backend', () {
+    test('must not import from other subdirectories', () async {
+      final entryPoints = [
+        _testApiLibrary('backend.dart'),
+        ...(await _ImportCheck.findEntrypointsUnder(
+            _testApiLibrary('src/backend')))
+      ];
+      await for (final source
+          in importCheck.transitiveSamePackageSources(entryPoints)) {
+        for (final import in source.imports) {
+          expect(import.pathSegments.skip(1).take(2), ['src', 'backend'],
+              reason: 'Invalid import from ${source.uri} : $import');
+        }
+      }
+    });
+  });
+
+  group('expect', () {
+    test('must not be imported from any other library', () async {
+      final entryPoints = [
+        _testApiLibrary('hooks.dart'),
+        _testApiLibrary('scaffolding.dart'),
+        _testApiLibrary('fake.dart')
+      ];
+      await for (final source
+          in importCheck.transitiveSamePackageSources(entryPoints)) {
+        for (final import in source.imports) {
+          expect(import.path, isNot(contains('test_api.dart')),
+              reason: 'Invalid import from ${source.uri} : $import.');
+          expect(import.path, isNot(contains('expect')),
+              reason: 'Invalid import from ${source.uri} : $import.');
+        }
+      }
+    });
+
+    test('may only import hooks', () async {
+      final entryPoint = _testApiLibrary('expect.dart');
+      await for (final source
+          in importCheck.transitiveSamePackageSources([entryPoint])) {
+        // Transitive imports through `hooks.dart` don't follow this restrction
+        if (!source.uri.path.contains('expect')) continue;
+        for (final import in source.imports) {
+          expect(import.path,
+              anyOf(['test_api/hooks.dart', startsWith('test_api/src/expect')]),
+              reason: 'Invalid import from ${source.uri} : $import');
+        }
+      }
+    });
+  });
+}
+
+Uri _testApiLibrary(String path) => Uri.parse('package:test_api/$path');
+
+class _ImportCheck {
+  final AnalysisContext _context;
+
+  static Future<Iterable<Uri>> findEntrypointsUnder(Uri uri) async {
+    if (!uri.path.endsWith('/')) {
+      uri = uri.replace(path: '${uri.path}/');
+    }
+    final directory = p.fromUri(await Isolate.resolvePackageUri(uri));
+    return Glob('./**')
+        .listSync(root: directory)
+        .whereType<File>()
+        .map((f) => uri.resolve(p.url.relative(f.path, from: directory)));
+  }
+
+  static Future<_ImportCheck> create() async {
+    final context = await _createAnalysisContext();
+    return _ImportCheck._(context);
+  }
+
+  static Future<AnalysisContext> _createAnalysisContext() async {
+    final libUri = Uri.parse('package:graphs/');
+    final libPath = await _pathForUri(libUri);
+    final packagePath = p.dirname(libPath);
+
+    final roots = ContextLocator().locateRoots(includedPaths: [packagePath]);
+    if (roots.length != 1) {
+      throw StateError('Expected to find exactly one context root, got $roots');
+    }
+    return ContextBuilder().createContext(contextRoot: roots[0]);
+  }
+
+  static Future<String> _pathForUri(Uri uri) async {
+    final fileUri = await Isolate.resolvePackageUri(uri);
+    if (fileUri == null || !fileUri.isScheme('file')) {
+      throw StateError('Expected to resolve $uri to a file URI, got $fileUri');
+    }
+    return p.fromUri(fileUri);
+  }
+
+  _ImportCheck._(this._context);
+
+  Stream<_Source> transitiveSamePackageSources(Iterable<Uri> entryPoints) {
+    assert(entryPoints.every((e) => e.scheme == 'package'));
+    final package = entryPoints.first.pathSegments.first;
+    assert(entryPoints.skip(1).every((e) => e.pathSegments.first == package));
+    return crawlAsync<Uri, _Source>(
+        entryPoints,
+        (uri) async => _Source(uri, await _findImports(uri, package)),
+        (_, source) => source.imports);
+  }
+
+  Future<Set<Uri>> _findImports(Uri uri, String restrictToPackage) async {
+    var path = await _pathForUri(uri);
+    var analysisSession = _context.currentSession;
+    var parseResult = analysisSession.getParsedUnit(path);
+    assert(parseResult.content.isNotEmpty,
+        'Tried to read an invalid library $uri');
+    return parseResult.unit.directives
+        .whereType<UriBasedDirective>()
+        .map((d) => d.uri.stringValue!)
+        .where((uri) => !uri.startsWith('dart:'))
+        .map((import) => _resolveImport(import, uri))
+        .where((import) => import.pathSegments.first == restrictToPackage)
+        .toSet();
+  }
+
+  static Uri _resolveImport(String import, Uri from) {
+    if (import.startsWith('package:')) return Uri.parse(import);
+    assert(from.scheme == 'package');
+    final package = from.pathSegments.first;
+    final fromPath = p.joinAll(from.pathSegments.skip(1));
+    final path = p.normalize(p.join(p.dirname(fromPath), import));
+    return Uri.parse('package:${p.join(package, path)}');
+  }
+}
+
+class _Source {
+  final Uri uri;
+  final Set<Uri> imports;
+
+  _Source(this.uri, this.imports);
+}
diff --git a/pkgs/test_core/lib/src/executable.dart b/pkgs/test_core/lib/src/executable.dart
index 12f13ec..309bded 100644
--- a/pkgs/test_core/lib/src/executable.dart
+++ b/pkgs/test_core/lib/src/executable.dart
@@ -9,12 +9,12 @@
 import 'package:path/path.dart' as p;
 import 'package:source_span/source_span.dart';
 import 'package:stack_trace/stack_trace.dart';
-import 'package:test_api/src/util/pretty_print.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/util/pretty_print.dart'; // ignore: implementation_imports
 
-import 'runner.dart';
 import 'runner/application_exception.dart';
 import 'runner/configuration.dart';
 import 'runner/version.dart';
+import 'runner.dart';
 import 'util/errors.dart';
 import 'util/exit_codes.dart' as exit_codes;
 import 'util/io.dart';
diff --git a/pkgs/test_core/lib/src/runner.dart b/pkgs/test_core/lib/src/runner.dart
index 250b99f..3f3af5c 100644
--- a/pkgs/test_core/lib/src/runner.dart
+++ b/pkgs/test_core/lib/src/runner.dart
@@ -6,15 +6,15 @@
 import 'dart:io';
 
 import 'package:async/async.dart';
+// ignore: deprecated_member_use
+import 'package:test_api/backend.dart'
+    show PlatformSelector, Runtime, SuitePlatform;
 import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/group_entry.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/operating_system.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/platform_selector.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
-import 'package:test_api/src/util/pretty_print.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/util/pretty_print.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/reporter/multiplex.dart';
 
 import 'runner/application_exception.dart';
diff --git a/pkgs/test_core/lib/src/runner/configuration/load.dart b/pkgs/test_core/lib/src/runner/configuration/load.dart
index 9a23915..18b741a 100644
--- a/pkgs/test_core/lib/src/runner/configuration/load.dart
+++ b/pkgs/test_core/lib/src/runner/configuration/load.dart
@@ -14,7 +14,7 @@
         Timeout;
 import 'package:test_api/src/backend/operating_system.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/platform_selector.dart'; // ignore: implementation_imports
-import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/util/identifier_regex.dart'; // ignore: implementation_imports
 import 'package:yaml/yaml.dart';
 
 import '../../util/errors.dart';
diff --git a/pkgs/test_core/lib/src/runner/hybrid_listener.dart b/pkgs/test_core/lib/src/runner/hybrid_listener.dart
index 16e36c8..f7fdb86 100644
--- a/pkgs/test_core/lib/src/runner/hybrid_listener.dart
+++ b/pkgs/test_core/lib/src/runner/hybrid_listener.dart
@@ -9,7 +9,8 @@
 import 'package:stack_trace/stack_trace.dart';
 import 'package:stream_channel/isolate_channel.dart';
 import 'package:stream_channel/stream_channel.dart';
-import 'package:test_api/src/util/remote_exception.dart'; // ignore: implementation_imports
+// ignore: deprecated_member_use
+import 'package:test_api/backend.dart' show RemoteException;
 import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
 
 /// A sink transformer that wraps data and error events so that errors can be
diff --git a/pkgs/test_core/lib/src/runner/parse_metadata.dart b/pkgs/test_core/lib/src/runner/parse_metadata.dart
index a68422b..e155f34 100644
--- a/pkgs/test_core/lib/src/runner/parse_metadata.dart
+++ b/pkgs/test_core/lib/src/runner/parse_metadata.dart
@@ -11,7 +11,7 @@
         Timeout;
 import 'package:test_api/src/backend/metadata.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/platform_selector.dart'; // ignore: implementation_imports
-import 'package:test_api/src/utils.dart'; // ignore: implementation_imports
+import 'package:test_api/src/backend/util/identifier_regex.dart'; // ignore: implementation_imports
 
 import '../util/dart.dart';
 import '../util/pair.dart';
diff --git a/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart b/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart
index 4d63bdf..a8c7f21 100644
--- a/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart
+++ b/pkgs/test_core/lib/src/runner/plugin/platform_helpers.dart
@@ -7,11 +7,11 @@
 
 import 'package:stack_trace/stack_trace.dart';
 import 'package:stream_channel/stream_channel.dart';
+// ignore: deprecated_member_use
+import 'package:test_api/backend.dart'
+    show Metadata, RemoteException, SuitePlatform;
 import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/metadata.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
-import 'package:test_api/src/util/remote_exception.dart'; // ignore: implementation_imports
 
 import '../configuration.dart';
 import '../environment.dart';
diff --git a/pkgs/test_core/lib/src/runner/plugin/remote_platform_helpers.dart b/pkgs/test_core/lib/src/runner/plugin/remote_platform_helpers.dart
index e98046c..51c3e9d 100644
--- a/pkgs/test_core/lib/src/runner/plugin/remote_platform_helpers.dart
+++ b/pkgs/test_core/lib/src/runner/plugin/remote_platform_helpers.dart
@@ -3,11 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:stream_channel/stream_channel.dart';
-
-import 'package:test_api/src/backend/stack_trace_formatter.dart'; // ignore: implementation_imports
-import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
-
-import 'package:test_api/src/remote_listener.dart'; // ignore: implementation_imports
+// ignore: deprecated_member_use
+import 'package:test_api/backend.dart'
+    show RemoteListener, StackTraceFormatter, StackTraceMapper;
 
 /// Returns a channel that will emit a serialized representation of the tests
 /// defined in [getMain].
diff --git a/pkgs/test_core/lib/src/runner/runner_test.dart b/pkgs/test_core/lib/src/runner/runner_test.dart
index 5f3b0f7..6b59632 100644
--- a/pkgs/test_core/lib/src/runner/runner_test.dart
+++ b/pkgs/test_core/lib/src/runner/runner_test.dart
@@ -5,16 +5,16 @@
 import 'package:pedantic/pedantic.dart';
 import 'package:stack_trace/stack_trace.dart';
 import 'package:stream_channel/stream_channel.dart';
+// ignore: deprecated_member_use
+import 'package:test_api/backend.dart'
+    show Metadata, RemoteException, SuitePlatform;
 import 'package:test_api/src/backend/group.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/live_test.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/live_test_controller.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/message.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/metadata.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/state.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
-import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
 import 'package:test_api/src/backend/test.dart'; // ignore: implementation_imports
-import 'package:test_api/src/util/remote_exception.dart'; // ignore: implementation_imports
 
 import 'spawn_hybrid.dart';
 
diff --git a/pkgs/test_core/lib/src/runner/spawn_hybrid.dart b/pkgs/test_core/lib/src/runner/spawn_hybrid.dart
index 2ec757c..2892828 100644
--- a/pkgs/test_core/lib/src/runner/spawn_hybrid.dart
+++ b/pkgs/test_core/lib/src/runner/spawn_hybrid.dart
@@ -10,14 +10,14 @@
 import 'package:path/path.dart' as p;
 import 'package:stream_channel/isolate_channel.dart';
 import 'package:stream_channel/stream_channel.dart';
+// ignore: deprecated_member_use
+import 'package:test_api/backend.dart' show RemoteException;
+import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
 
 import '../util/dart.dart' as dart;
 import '../util/package_config.dart';
 import 'package_version.dart';
 
-import 'package:test_api/src/backend/suite.dart'; // ignore: implementation_imports
-import 'package:test_api/src/util/remote_exception.dart'; // ignore: implementation_imports
-
 /// Spawns a hybrid isolate from [url] with the given [message], and returns a
 /// [StreamChannel] that communicates with it.
 ///
diff --git a/pkgs/test_core/lib/src/util/stack_trace_mapper.dart b/pkgs/test_core/lib/src/util/stack_trace_mapper.dart
index f55de3b..9e8650a 100644
--- a/pkgs/test_core/lib/src/util/stack_trace_mapper.dart
+++ b/pkgs/test_core/lib/src/util/stack_trace_mapper.dart
@@ -4,7 +4,8 @@
 
 import 'package:source_map_stack_trace/source_map_stack_trace.dart' as mapper;
 import 'package:source_maps/source_maps.dart';
-import 'package:test_api/src/util/stack_trace_mapper.dart'; // ignore: implementation_imports
+// ignore: deprecated_member_use
+import 'package:test_api/backend.dart' show StackTraceMapper;
 
 /// A class for mapping JS stack traces to Dart stack traces using source maps.
 class JSStackTraceMapper extends StackTraceMapper {