diff --git a/lib/src/cli/dart_cli.dart b/lib/src/cli/dart_cli.dart index 2bd10e1ce..13aa07ed8 100644 --- a/lib/src/cli/dart_cli.dart +++ b/lib/src/cli/dart_cli.dart @@ -99,11 +99,12 @@ class Dart { required Logger logger, String cwd = '.', bool recursive = false, + bool checkIgnore = true, + bool showUncovered = false, bool collectCoverage = false, bool optimizePerformance = false, Set ignore = const {}, double? minCoverage, - bool showUncovered = false, String? excludeFromCoverage, CoverageCollectionMode collectCoverageFrom = CoverageCollectionMode.imports, String? randomSeed, @@ -119,11 +120,12 @@ class Dart { testType: TestRunType.dart, cwd: cwd, recursive: recursive, + checkIgnore: checkIgnore, + showUncovered: showUncovered, collectCoverage: collectCoverage, optimizePerformance: optimizePerformance, ignore: ignore, minCoverage: minCoverage, - showUncovered: showUncovered, excludeFromCoverage: excludeFromCoverage, collectCoverageFrom: collectCoverageFrom, randomSeed: randomSeed, diff --git a/lib/src/cli/test_cli_runner.dart b/lib/src/cli/test_cli_runner.dart index cacbed230..21572b3d5 100644 --- a/lib/src/cli/test_cli_runner.dart +++ b/lib/src/cli/test_cli_runner.dart @@ -104,6 +104,7 @@ class TestCLIRunner { void Function(String)? stderr, GeneratorBuilder buildGenerator = MasonGenerator.fromBundle, String? reportOn, + bool checkIgnore = false, @visibleForTesting VeryGoodTestRunner? overrideTestRunner, }) async { final initialCwd = cwd; @@ -206,6 +207,7 @@ class TestCLIRunner { final hitmap = await coverage.HitMap.parseFiles( files, packagePath: packagesPath, + checkIgnoredLines: checkIgnore, ); final resolver = await coverage.Resolver.create( diff --git a/lib/src/commands/dart/commands/dart_test_command.dart b/lib/src/commands/dart/commands/dart_test_command.dart index fd41e6515..96b15e664 100644 --- a/lib/src/commands/dart/commands/dart_test_command.dart +++ b/lib/src/commands/dart/commands/dart_test_command.dart @@ -26,6 +26,8 @@ class DartTestOptions { required this.platform, required this.rest, required this.reportOn, + required this.runSkipped, + required this.checkIgnore, }); /// Parses [ArgResults] into a [DartTestOptions] instance. @@ -54,6 +56,8 @@ class DartTestOptions { final forceAnsi = argResults['force-ansi'] as bool?; final platform = argResults['platform'] as String?; final reportOn = argResults['report-on'] as String?; + final runSkipped = argResults['run-skipped'] as bool; + final checkIgnore = argResults['check-ignore'] as bool; final rest = argResults.rest; return DartTestOptions._( @@ -71,6 +75,8 @@ class DartTestOptions { forceAnsi: forceAnsi, platform: platform, reportOn: reportOn, + runSkipped: runSkipped, + checkIgnore: checkIgnore, rest: rest, ); } @@ -118,6 +124,12 @@ class DartTestOptions { /// An optional file path to report coverage information to. final String? reportOn; + /// Whether to run skipped tests instead of skipping them. + final bool runSkipped; + + /// Whether to check for and respect coverage ignore comments. + final bool checkIgnore; + /// The remaining arguments passed to the `dart test` command. final List rest; } @@ -143,6 +155,7 @@ typedef DartTestCommandCall = void Function(String)? stdout, void Function(String)? stderr, String? reportOn, + bool checkIgnore, }); /// {@template dart_test_command} @@ -253,6 +266,18 @@ class DartTestCommand extends Command { 'platform', help: 'The platform to run tests on. ', valueHelp: 'chrome|vm', + ) + ..addFlag( + 'run-skipped', + help: 'Run skipped tests instead of skipping them.', + negatable: false, + ) + ..addFlag( + 'check-ignore', + defaultsTo: true, + help: + 'Whether to check for and respect coverage ignore comments ' + '(e.g. // coverage:ignore-line).', ); } @@ -316,11 +341,13 @@ This command should be run from the root of your Dart project.'''); if (options.excludeTags != null) ...['-x', options.excludeTags!], if (options.tags != null) ...['-t', options.tags!], if (options.failFast) '--fail-fast', + if (options.runSkipped) '--run-skipped', if (options.platform != null) ...['--platform', options.platform!], if (options.platform == null) ...['-j', options.concurrency], ...options.rest, ], reportOn: options.reportOn, + checkIgnore: options.checkIgnore, ); if (results.any((code) => code != ExitCode.success.code)) { return ExitCode.unavailable.code; diff --git a/lib/src/mcp/mcp_server.dart b/lib/src/mcp/mcp_server.dart index d22d28141..cd2956d17 100644 --- a/lib/src/mcp/mcp_server.dart +++ b/lib/src/mcp/mcp_server.dart @@ -205,6 +205,17 @@ The available values are: chrome, vm, android, ios. Only one value can be selected. ''', ), + 'run_skipped': BooleanSchema( + description: + 'Run skipped tests instead of skipping them. ' + 'Only applies to Dart tests (dart: true).', + ), + 'check_ignore': BooleanSchema( + description: + 'Whether to check for and respect coverage ignore comments ' + '(e.g. // coverage:ignore-line). ' + 'Only applies to Dart tests (dart: true).', + ), }, ), ), @@ -359,6 +370,12 @@ Only one value can be selected. if (args['platform'] != null) { cliArgs.addAll(['--platform', args['platform']! as String]); } + if (args['run_skipped'] == true) { + cliArgs.add('--run-skipped'); + } + if (args['check_ignore'] == true) { + cliArgs.add('--check-ignore'); + } return cliArgs; } diff --git a/test/src/cli/test_cli_runner_test.dart b/test/src/cli/test_cli_runner_test.dart index 7ccadb267..264f51464 100644 --- a/test/src/cli/test_cli_runner_test.dart +++ b/test/src/cli/test_cli_runner_test.dart @@ -780,12 +780,10 @@ void main() { stdout: stdoutLogs.add, stderr: stderrLogs.add, overrideTestRunner: testRunner( - Stream.fromIterable( - [ - const DoneTestEvent(success: true, time: 0), - const ExitTestEvent(exitCode: 0, time: 0), - ], - ), + Stream.fromIterable([ + const DoneTestEvent(success: true, time: 0), + const ExitTestEvent(exitCode: 0, time: 0), + ]), onStart: () { expect(lcovFile.existsSync(), isFalse); lcovFile.createSync(recursive: true); @@ -805,6 +803,42 @@ void main() { expect(testRunnerArgs, equals(['--coverage=coverage'])); }); + test('runs dart tests w/coverage and checkIgnore', () 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, + logger: logger, + checkIgnore: true, + 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); + }, + ), + ), + completion(equals([ExitCode.success.code])), + ); + 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)); @@ -816,8 +850,9 @@ void main() { TestCLIRunner.test( testType: TestRunType.flutter, cwd: tempDirectory.path, - collectCoverage: true, + logger: logger, minCoverage: 100, + collectCoverage: true, stdout: stdoutLogs.add, stderr: stderrLogs.add, overrideTestRunner: testRunner( @@ -833,7 +868,6 @@ void main() { ..writeAsStringSync(lcov100); }, ), - logger: logger, ), completion(equals([ExitCode.success.code])), ); diff --git a/test/src/commands/dart/commands/dart_test_test.dart b/test/src/commands/dart/commands/dart_test_test.dart index 1408d0d18..489fed9cf 100644 --- a/test/src/commands/dart/commands/dart_test_test.dart +++ b/test/src/commands/dart/commands/dart_test_test.dart @@ -45,6 +45,9 @@ const expectedTestUsage = [ ' --force-ansi Whether to force ansi output. If not specified, it will maintain the default behavior based on stdout and stderr.\n' ' --report-on= An optional file path to report coverage information to. This should be a path relative to the current working directory.\n' ' --platform= The platform to run tests on. \n' + ' --run-skipped Run skipped tests instead of skipping them.\n' + ' --[no-]check-ignore Whether to check for and respect coverage ignore comments (e.g. // coverage:ignore-line).\n' + ' (defaults to on)\n' '\n' 'Run "very_good help" to see global options.', ]; @@ -56,10 +59,11 @@ abstract class DartTestCommandCall { Future> call({ String cwd = '.', bool recursive = false, + bool checkIgnore = true, + bool showUncovered = false, bool collectCoverage = false, bool optimizePerformance = false, double? minCoverage, - bool showUncovered = false, String? excludeFromCoverage, CoverageCollectionMode collectCoverageFrom = CoverageCollectionMode.imports, String? randomSeed, @@ -102,10 +106,11 @@ void main() { () => dartTest( cwd: any(named: 'cwd'), recursive: any(named: 'recursive'), + checkIgnore: any(named: 'checkIgnore'), + showUncovered: any(named: 'showUncovered'), collectCoverage: any(named: 'collectCoverage'), optimizePerformance: any(named: 'optimizePerformance'), minCoverage: any(named: 'minCoverage'), - showUncovered: any(named: 'showUncovered'), excludeFromCoverage: any(named: 'excludeFromCoverage'), collectCoverageFrom: any(named: 'collectCoverageFrom'), randomSeed: any(named: 'randomSeed'), @@ -120,8 +125,10 @@ void main() { when(() => argResults['concurrency']).thenReturn(concurrency); when(() => argResults['recursive']).thenReturn(false); when(() => argResults['coverage']).thenReturn(false); + when(() => argResults['check-ignore']).thenReturn(true); when(() => argResults['show-uncovered']).thenReturn(false); when(() => argResults['fail-fast']).thenReturn(false); + when(() => argResults['run-skipped']).thenReturn(false); when(() => argResults['optimization']).thenReturn(true); when(() => argResults['platform']).thenReturn(null); when( @@ -222,6 +229,7 @@ void main() { logger: any(named: 'logger'), stdout: any(named: 'stdout'), stderr: any(named: 'stderr'), + checkIgnore: any(named: 'checkIgnore'), ), ).thenAnswer( (_) async => [ExitCode.success.code, ExitCode.unavailable.code], @@ -495,6 +503,7 @@ void main() { logger: any(named: 'logger'), stdout: any(named: 'stdout'), stderr: any(named: 'stderr'), + checkIgnore: any(named: 'checkIgnore'), ), ).thenThrow(exception); final result = await testCommand.run(); @@ -540,6 +549,7 @@ void main() { logger: any(named: 'logger'), stdout: any(named: 'stdout'), stderr: any(named: 'stderr'), + checkIgnore: any(named: 'checkIgnore'), ), ).thenThrow(exception); final result = await testCommand.run(); @@ -574,6 +584,7 @@ void main() { logger: any(named: 'logger'), stdout: any(named: 'stdout'), stderr: any(named: 'stderr'), + checkIgnore: any(named: 'checkIgnore'), ), ).thenThrow(exception); final result = await testCommand.run(); @@ -654,6 +665,7 @@ void main() { logger: any(named: 'logger'), stdout: any(named: 'stdout'), stderr: any(named: 'stderr'), + checkIgnore: any(named: 'checkIgnore'), ), ).thenThrow(exception); final result = await testCommand.run(); @@ -732,5 +744,36 @@ void main() { ).called(1); }, ); + + test('completes normally --run-skipped', () async { + when(() => argResults['run-skipped']).thenReturn(true); + final result = await testCommand.run(); + expect(result, equals(ExitCode.success.code)); + verify( + () => dartTest( + optimizePerformance: true, + arguments: ['--run-skipped', ...defaultArguments], + logger: logger, + stdout: logger.write, + stderr: logger.err, + ), + ).called(1); + }); + + test('completes normally --no-check-ignore', () async { + when(() => argResults['check-ignore']).thenReturn(false); + final result = await testCommand.run(); + expect(result, equals(ExitCode.success.code)); + verify( + () => dartTest( + optimizePerformance: true, + arguments: defaultArguments, + logger: logger, + stdout: logger.write, + stderr: logger.err, + checkIgnore: false, + ), + ).called(1); + }); }); } diff --git a/test/src/mcp/mcp_server_test.dart b/test/src/mcp/mcp_server_test.dart index da528cec4..e5e53ad44 100644 --- a/test/src/mcp/mcp_server_test.dart +++ b/test/src/mcp/mcp_server_test.dart @@ -335,6 +335,8 @@ void main() { 'dart-define': 'foo=bar', 'dart-define-from-file': 'my_file.json', 'platform': 'chrome', + 'run_skipped': true, + 'check_ignore': true, }, ), ), @@ -368,6 +370,8 @@ void main() { 'my_file.json', '--platform', 'chrome', + '--run-skipped', + '--check-ignore', ]); });