From 65b0ce50320d73b875971c2e73895aa32aac116e Mon Sep 17 00:00:00 2001 From: Erick Zanardo Date: Thu, 14 Aug 2025 15:07:47 -0300 Subject: [PATCH 1/3] fix: coverage on dart test --- lib/src/cli/cli.dart | 1 + lib/src/cli/test_cli_runner.dart | 53 +++++++++++++++++++++++++++++++- pubspec.yaml | 1 + 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/lib/src/cli/cli.dart b/lib/src/cli/cli.dart index bf1b750e7..eeae77f9b 100644 --- a/lib/src/cli/cli.dart +++ b/lib/src/cli/cli.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math'; import 'package:collection/collection.dart'; +import 'package:coverage/coverage.dart' as coverage; import 'package:glob/glob.dart'; import 'package:lcov_parser/lcov_parser.dart'; import 'package:mason/mason.dart'; diff --git a/lib/src/cli/test_cli_runner.dart b/lib/src/cli/test_cli_runner.dart index d6437f1c5..f2cb15e02 100644 --- a/lib/src/cli/test_cli_runner.dart +++ b/lib/src/cli/test_cli_runner.dart @@ -142,6 +142,7 @@ class TestCLIRunner { cwd: cwd, collectCoverage: collectCoverage, testRunner: testRunner, + testType: testType, arguments: [ ...?arguments, if (randomSeed != null) ...[ @@ -158,6 +159,38 @@ class TestCLIRunner { await _cleanupOptimizerFile(cwd); } + // Dart don't directly generate lcov files, so we need + // to read the json that is generates and convert it to lcov. + if (testType == TestRunType.dart && collectCoverage) { + final files = _dartCoverageFilesToProcess( + p.join(cwd, 'coverage'), + ); + + final packagesPath = p.join( + '.dart_tool', + 'package_config.json', + ); + final hitmap = await coverage.HitMap.parseFiles( + files, + packagePath: packagesPath, + ); + + final resolver = await coverage.Resolver.create( + packagesPath: packagesPath, + packagePath: packagesPath, + ); + + final output = hitmap.formatLcov( + resolver, + reportOn: ['lib'], + basePath: cwd, + ); + + // Write the lcov output to the file. + await lcovFile.create(recursive: true); + await lcovFile.writeAsString(output); + } + if (collectCoverage) { assert( lcovFile.existsSync(), @@ -214,12 +247,24 @@ class TestCLIRunner { '''Expected coverage >= ${minCoverage.toStringAsFixed(decimalPlaces)}% but actual is ${e.coverage.toStringAsFixed(decimalPlaces)}%.''', ); } + + static List _dartCoverageFilesToProcess(String absPath) { + if (FileSystemEntity.isDirectorySync(absPath)) { + return Directory(absPath) + .listSync(recursive: true) + .whereType() + .where((e) => e.path.endsWith('.json')) + .toList(); + } + return [File(absPath)]; + } } Future _testCommand({ required void Function(String) stdout, required void Function(String) stderr, required VeryGoodTestRunner testRunner, + required TestRunType testType, String cwd = '.', bool collectCoverage = false, List? arguments, @@ -273,7 +318,13 @@ Future _testCommand({ subscription = testRunner( workingDirectory: cwd, - arguments: [if (collectCoverage) '--coverage', ...?arguments], + arguments: [ + if (collectCoverage) + testType == TestRunType.flutter + ? '--coverage' + : '--coverage=coverage', + ...?arguments, + ], runInShell: true, ).listen( (event) { diff --git a/pubspec.yaml b/pubspec.yaml index 6141371b6..20519d370 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: args: ^2.6.0 cli_completion: ^0.5.1 collection: ^1.19.0 + coverage: ^1.15.0 equatable: ^2.0.5 glob: ^2.1.2 lcov_parser: ^0.1.2 From 8f2e70ed021aaa28d66f0481903a9b0b0d98ecb8 Mon Sep 17 00:00:00 2001 From: Erick Zanardo Date: Thu, 14 Aug 2025 15:54:42 -0300 Subject: [PATCH 2/3] adding missing test --- test/src/cli/test_runner_cli_test.dart | 46 +++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/test/src/cli/test_runner_cli_test.dart b/test/src/cli/test_runner_cli_test.dart index 8384157c2..cf2c13319 100644 --- a/test/src/cli/test_runner_cli_test.dart +++ b/test/src/cli/test_runner_cli_test.dart @@ -684,7 +684,7 @@ void main() { ); }); - test('runs tests w/coverage', () async { + test('runs flutter tests w/coverage', () async { final tempDirectory = Directory.systemTemp.createTempSync(); addTearDown(() => tempDirectory.deleteSync(recursive: true)); @@ -728,6 +728,50 @@ void main() { expect(testRunnerArgs, equals(['--coverage'])); }); + test('runs flutter tests w/coverage', () async { + final tempDirectory = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDirectory.deleteSync(recursive: true)); + + final lcovFile = File( + p.join(tempDirectory.path, 'coverage', 'lcov.info'), + ); + File(p.join(tempDirectory.path, 'pubspec.yaml')).createSync(); + Directory(p.join(tempDirectory.path, 'test')).createSync(); + lcovFile.createSync(recursive: true); + + await expectLater( + TestCLIRunner.test( + testType: TestRunType.dart, + cwd: tempDirectory.path, + collectCoverage: true, + stdout: stdoutLogs.add, + stderr: stderrLogs.add, + overrideTestRunner: testRunner( + Stream.fromIterable( + [ + const DoneTestEvent(success: true, time: 0), + const ExitTestEvent(exitCode: 0, time: 0), + ], + ), + onStart: () { + expect(lcovFile.existsSync(), isFalse); + lcovFile.createSync(recursive: true); + }, + ), + logger: logger, + ), + completion(equals([ExitCode.success.code])), + ); + expect( + stdoutLogs, + equals([ + 'Running "dart test" in . ...\n', + contains('All tests passed!'), + ]), + ); + expect(testRunnerArgs, equals(['--coverage=coverage'])); + }); + test('runs tests w/coverage + min-coverage 100 (pass)', () async { final tempDirectory = Directory.systemTemp.createTempSync(); addTearDown(() => tempDirectory.deleteSync(recursive: true)); From 3c8fd53a70457b36b9400972ccdab1b671534c25 Mon Sep 17 00:00:00 2001 From: Erick Zanardo Date: Thu, 14 Aug 2025 16:15:19 -0300 Subject: [PATCH 3/3] simplyfying method --- lib/src/cli/test_cli_runner.dart | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/src/cli/test_cli_runner.dart b/lib/src/cli/test_cli_runner.dart index f2cb15e02..42123716a 100644 --- a/lib/src/cli/test_cli_runner.dart +++ b/lib/src/cli/test_cli_runner.dart @@ -249,14 +249,11 @@ class TestCLIRunner { } static List _dartCoverageFilesToProcess(String absPath) { - if (FileSystemEntity.isDirectorySync(absPath)) { - return Directory(absPath) - .listSync(recursive: true) - .whereType() - .where((e) => e.path.endsWith('.json')) - .toList(); - } - return [File(absPath)]; + return Directory(absPath) + .listSync(recursive: true) + .whereType() + .where((e) => e.path.endsWith('.json')) + .toList(); } }