Make coverage collection aware of workspaces (#166389)

By default, `CoverageCollector.libraryNames` was being set to the name
of the current `FlutterProject`. This means that only tests in the
current project are included in the coverage report. So if the project
is a workspace, tests in its subprojects were being excluded. I've
changed it so that it also allows tests that are part of any of the
subprojects.

Fixes #159390
This commit is contained in:
Liam Appelbe 2025-04-07 08:27:08 +10:00 committed by GitHub
parent 7982d392aa
commit 09367adce2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 188 additions and 2 deletions

View File

@ -727,8 +727,15 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
FlutterProject flutterProject,
PackageConfig packageConfig,
) {
final String projectName = flutterProject.manifest.appName;
final Set<String> packagesToInclude = <String>{if (packagesRegExps.isEmpty) projectName};
final Set<String> packagesToInclude = <String>{};
if (packagesRegExps.isEmpty) {
void addProject(FlutterProject project) {
packagesToInclude.add(project.manifest.appName);
project.workspaceProjects.forEach(addProject);
}
addProject(flutterProject);
}
try {
for (final String regExpStr in packagesRegExps) {
final RegExp regExp = RegExp(regExpStr);

View File

@ -140,6 +140,10 @@ class FlutterManifest {
return dependencies != null ? <String>{...dependencies.keys.cast<String>()} : <String>{};
}
/// List of all the entries in the workspace field of the `pubspec.yaml` file.
List<String> get workspace =>
(_descriptor['workspace'] as YamlList?)?.cast<String>() ?? <String>[];
// Flag to avoid printing multiple invalid version messages.
bool _hasShowInvalidVersionMsg = false;

View File

@ -131,6 +131,16 @@ class FlutterProject {
/// The manifest of the example sub-project of this project.
final FlutterManifest _exampleManifest;
/// List of [FlutterProject]s corresponding to the workspace entries.
List<FlutterProject> get workspaceProjects =>
manifest.workspace
.map(
(String entry) => FlutterProject.fromDirectory(
directory.childDirectory(directory.fileSystem.path.normalize(entry)),
),
)
.toList();
/// The set of organization names found in this project as
/// part of iOS product bundle identifier, Android application ID, or
/// Gradle group ID.

View File

@ -358,6 +358,98 @@ dev_dependencies:
},
);
testUsingContext(
'Coverage provides current library name and workspace names to Coverage Collector by default',
() async {
final Directory package = fs.currentDirectory;
package.childFile('pubspec.yaml').writeAsStringSync('''
name: my_app
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
workspace:
- child1
- child2
''');
package.childDirectory('child1').childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: child1
resolution: workspace
''');
package.childDirectory('child2').childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: child2
resolution: workspace
workspace:
- example
''');
package.childDirectory('child2').childDirectory('example').childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: child2_example
resolution: workspace
''');
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
FakeVmServiceRequest(
method: 'getVM',
jsonResponse:
(VM.parse(<String, Object>{})!
..isolates = <IsolateRef>[
IsolateRef.parse(<String, Object>{'id': '1'})!,
])
.toJson(),
),
FakeVmServiceRequest(
method: 'getSourceReport',
args: <String, Object>{
'isolateId': '1',
'reports': <Object>['Coverage'],
'forceCompile': true,
'reportLines': true,
'libraryFilters': <String>[
'package:my_app/',
'package:child1/',
'package:child2/',
'package:child2_example/',
],
'librariesAlreadyCompiled': <Object>[],
},
jsonResponse: SourceReport(ranges: <SourceReportRange>[]).toJson(),
),
],
);
final FakeFlutterTestRunner testRunner = FakeFlutterTestRunner(0, null, fakeVmServiceHost);
final TestCommand testCommand = TestCommand(testRunner: testRunner);
final CommandRunner<void> commandRunner = createTestCommandRunner(testCommand);
await commandRunner.run(const <String>[
'test',
'--no-pub',
'--coverage',
'--',
'test/some_test.dart',
]);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
expect((testRunner.lastTestWatcher! as CoverageCollector).libraryNames, <String>{
'my_app',
'child1',
'child2',
'child2_example',
});
},
overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
},
);
testUsingContext(
'Coverage provides library names matching regexps to Coverage Collector',
() async {

View File

@ -1547,6 +1547,35 @@ flutter:
args:
- deferredComponentArg''');
});
testWithoutContext('FlutterManifest can parse workspace', () async {
const String manifest = '''
name: test
workspace:
- pkgs/bar
- pkgs/foo
''';
final FlutterManifest? flutterManifest = FlutterManifest.createFromString(
manifest,
logger: BufferLogger.test(),
);
expect(flutterManifest, isNotNull);
expect(flutterManifest!.workspace, <String>['pkgs/bar', 'pkgs/foo']);
});
testWithoutContext('FlutterManifest can parse empty workspace', () async {
const String manifest = '''
name: test
''';
final FlutterManifest? flutterManifest = FlutterManifest.createFromString(
manifest,
logger: BufferLogger.test(),
);
expect(flutterManifest, isNotNull);
expect(flutterManifest!.workspace, isEmpty);
});
}
Matcher matchesManifest({String? appVersion, String? buildName, String? buildNumber}) {

View File

@ -1522,6 +1522,50 @@ plugins {
expect(updatedPubspecContents, validPubspecWithDependenciesAndNullValues);
});
});
group('workspaces', () {
_testInMemory('fails on invalid pubspec.yaml', () async {
final Directory directory = globals.fs.directory('myproject');
directory.childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: parent
flutter:
workspace:
- child1
- child2
- child2/example
''');
directory.childDirectory('child1').childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: child1
flutter:
resolution: workspace
''');
directory.childDirectory('child2').childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: child2
flutter:
resolution: workspace
''');
directory.childDirectory('child2').childDirectory('example').childFile('pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: child2_example
flutter:
resolution: workspace
''');
expect(
FlutterProject.fromDirectory(directory).workspaceProjects
.map((FlutterProject subproject) => subproject.manifest.appName)
.toList(),
<String>['child1', 'child2', 'child2_example'],
);
});
});
});
group('watch companion', () {