[coverage] Support workspaces in coverage filters (#2574)

diff --git a/pkgs/test/test/runner/coverage_test.dart b/pkgs/test/test/runner/coverage_test.dart
index 222e76c..3645972 100644
--- a/pkgs/test/test/runner/coverage_test.dart
+++ b/pkgs/test/test/runner/coverage_test.dart
@@ -179,7 +179,7 @@
 ''');
     });
 
-    test('gathers coverage for tests in multiple pacakges', () async {
+    test('gathers coverage for tests in multiple packages', () async {
       final clientPkgDir = p.join(d.sandbox, 'fake_client');
       await (await runPub([
         'get',
@@ -214,6 +214,75 @@
 ''');
     });
 
+    test('gathers coverage for package in a workspace', () async {
+      await d.dir(d.sandbox, [
+        d.dir('fake_workspace', [
+          d.file('pubspec.yaml', '''
+name: fake_workspace
+version: 1.0.0
+environment:
+  sdk: ^3.5.0
+workspace:
+  - workspace_package
+          '''),
+          d.dir('workspace_package', [
+            d.file('pubspec.yaml', '''
+name: workspace_package
+version: 1.0.0
+environment:
+  sdk: ^3.5.0
+resolution: workspace
+dev_dependencies:
+  test: ^1.26.2
+            '''),
+            d.dir('lib', [
+              d.file('calculate.dart', '''
+              int calculate(int x) {
+                if (x % 2 == 0) {
+                  return x * 2;
+                } else {
+                  return x * 3;
+                }
+              }
+              '''),
+            ]),
+            d.dir('test', [
+              d.file('test.dart', '''
+              import 'package:workspace_package/calculate.dart';
+              import 'package:test/test.dart';
+
+              void main() {
+                test('test 1', () {
+                  expect(calculate(6), 12);
+                });
+              }
+              '''),
+            ]),
+          ]),
+        ]),
+      ]).create();
+
+      final pkgDir = p.join(d.sandbox, 'fake_workspace', 'workspace_package');
+      await (await runPub(['get'], workingDirectory: pkgDir)).shouldExit(0);
+      final lcovFile = p.join(coverageDirectory.path, 'lcov.info');
+      var test = await runTest(
+        ['--coverage-path', lcovFile, 'test/test.dart'],
+        packageConfig: p.join(pkgDir, '../.dart_tool/package_config.json'),
+        workingDirectory: pkgDir,
+      );
+      await validateTest(test);
+      expect(File(lcovFile).readAsStringSync(), '''
+SF:${p.join(pkgDir, 'lib', 'calculate.dart')}
+DA:1,1
+DA:2,2
+DA:3,1
+DA:5,0
+LF:4
+LH:3
+end_of_record
+''');
+    });
+
     test('gathers coverage for Chrome tests', () async {
       await (await runPub(['get'], workingDirectory: pkgDir)).shouldExit(0);
       var test = await runTest(
diff --git a/pkgs/test_core/CHANGELOG.md b/pkgs/test_core/CHANGELOG.md
index e36d0ec..bb35860 100644
--- a/pkgs/test_core/CHANGELOG.md
+++ b/pkgs/test_core/CHANGELOG.md
@@ -4,6 +4,8 @@
   packages using RegExps.
 * Require a function definition named `main` directly in a test suite and
   provide a more direct error message than a failing compiler output.
+* Fix default coverage filter when running in a workspace package. Default
+  filter now includes all the workspace's package.
 
 ## 0.6.14
 
diff --git a/pkgs/test_core/lib/src/runner/vm/platform.dart b/pkgs/test_core/lib/src/runner/vm/platform.dart
index 19d5f9e..53e2aae 100644
--- a/pkgs/test_core/lib/src/runner/vm/platform.dart
+++ b/pkgs/test_core/lib/src/runner/vm/platform.dart
@@ -458,7 +458,7 @@
   List<RegExp>? coveragePackages,
 ) async {
   if (coveragePackages == null || coveragePackages.isEmpty) {
-    return {(await currentPackage).name};
+    return workspacePackageNames(await currentPackage);
   } else {
     return (await currentPackageConfig).packages
         .map((package) => package.name)
diff --git a/pkgs/test_core/lib/src/util/package_config.dart b/pkgs/test_core/lib/src/util/package_config.dart
index 53ad07c..042cb31 100644
--- a/pkgs/test_core/lib/src/util/package_config.dart
+++ b/pkgs/test_core/lib/src/util/package_config.dart
@@ -7,6 +7,7 @@
 
 import 'package:package_config/package_config.dart';
 import 'package:path/path.dart' as p;
+import 'package:pubspec_parse/pubspec_parse.dart';
 
 /// The [PackageConfig] parsed from the current isolates package config file.
 final Future<PackageConfig> currentPackageConfig = () async {
@@ -42,3 +43,24 @@
 final Future<Package> currentPackage = () async {
   return (await currentPackageConfig).packageOf(await packageConfigUri)!;
 }();
+
+/// Returns the names of all the packages in the workspace.
+///
+/// The returned set includes the package's own name (if the package is not a
+/// workspace, that will be the only name in the set).
+Set<String> workspacePackageNames(Package package) =>
+    _allWorkspaceNames(package.root, <String>{});
+
+Set<String> _allWorkspaceNames(Uri packageRoot, Set<String> results) {
+  final pubspecUri = packageRoot.resolve('pubspec.yaml');
+  final pubspecFile = File(pubspecUri.toFilePath());
+  if (pubspecFile.existsSync()) {
+    final yaml = pubspecFile.readAsStringSync();
+    final pubspec = Pubspec.parse(yaml, sourceUrl: pubspecUri);
+    results.add(pubspec.name);
+    for (final package in pubspec.workspace ?? const <String>[]) {
+      _allWorkspaceNames(packageRoot.resolve('$package/'), results);
+    }
+  }
+  return results;
+}
diff --git a/pkgs/test_core/pubspec.yaml b/pkgs/test_core/pubspec.yaml
index e90d36f..c45a920 100644
--- a/pkgs/test_core/pubspec.yaml
+++ b/pkgs/test_core/pubspec.yaml
@@ -22,6 +22,7 @@
   package_config: ^2.0.0
   path: ^1.8.0
   pool: ^1.5.0
+  pubspec_parse: ^1.5.0
   source_map_stack_trace: ^2.1.0
   source_maps: ^0.10.10
   source_span: ^1.8.0