Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --fail-fast flag #2040

Merged
merged 8 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pkgs/test/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.24.6-wip

* Add support for discontinuing after the first failing test with `--fail-fast`.

## 1.24.5

* Change "compiling <path>" to "loading <path>" message in all cases. Surface
Expand Down
4 changes: 2 additions & 2 deletions pkgs/test/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: test
version: 1.24.5
version: 1.24.6-wip
description: >-
A full featured library for writing and running Dart tests across platforms.
repository: https://github.com/dart-lang/test/tree/master/pkgs/test
Expand Down Expand Up @@ -35,7 +35,7 @@ dependencies:

# Use an exact version until the test_api and test_core package are stable.
test_api: 0.6.1
test_core: 0.5.5
test_core: 0.5.6

typed_data: ^1.3.0
web_socket_channel: ^2.0.0
Expand Down
13 changes: 13 additions & 0 deletions pkgs/test/test/runner/engine_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,19 @@ void main() {
expect(engine.run(), completion(isFalse));
});

test('.run() does not run more tests after failure for stopOnFirstFailure',
() async {
var secondTestRan = false;
var engine = declareEngine(() {
test('failure', () => throw 'oh no');
test('subsequent', () {
secondTestRan = true;
});
}, stopOnFirstFailure: true);
await expectLater(engine.run(), completion(isFalse));
expect(secondTestRan, false);
});

test('.run() may not be called more than once', () {
var engine = Engine.withSuites([]);
expect(engine.run(), completes);
Expand Down
1 change: 1 addition & 0 deletions pkgs/test/test/runner/runner_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ $_runtimeCompilers
Must be a 32bit unsigned integer or "random".
If "random", pick a random seed to use.
If not passed, do not randomize test case execution order.
--[no-]fail-fast Stop running tests after the first failure.

Output:
-r, --reporter=<option> Set how to print test results.
Expand Down
27 changes: 18 additions & 9 deletions pkgs/test/test/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -195,16 +195,24 @@ List<GroupEntry> declare(void Function() body) {
}

/// Runs [body] with a declarer and returns an engine that runs those tests.
Engine declareEngine(void Function() body,
{bool runSkipped = false, String? coverage}) {
Engine declareEngine(
void Function() body, {
bool runSkipped = false,
String? coverage,
bool stopOnFirstFailure = false,
}) {
var declarer = Declarer()..declare(body);
return Engine.withSuites([
RunnerSuite(
const PluginEnvironment(),
SuiteConfiguration.runSkipped(runSkipped),
declarer.build(),
suitePlatform)
], coverage: coverage);
return Engine.withSuites(
[
RunnerSuite(
const PluginEnvironment(),
SuiteConfiguration.runSkipped(runSkipped),
declarer.build(),
suitePlatform)
],
coverage: coverage,
stopOnFirstFailure: stopOnFirstFailure,
);
}

/// Returns a [RunnerSuite] with a default environment and configuration.
Expand Down Expand Up @@ -348,6 +356,7 @@ Configuration configuration(
tags: tags,
onPlatform: onPlatform,
testRandomizeOrderingSeed: testRandomizeOrderingSeed,
stopOnFirstFailure: false,
timeout: timeout,
verboseTrace: verboseTrace,
chainStackTraces: chainStackTraces,
Expand Down
4 changes: 4 additions & 0 deletions pkgs/test_core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.6-wip

* Add support for discontinuing after the first failing test with `--fail-fast`.

## 0.5.5

* Change "compiling <path>" to "loading <path>" message in all cases. Surface
Expand Down
8 changes: 5 additions & 3 deletions pkgs/test_core/lib/src/runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ class Runner {
/// Creates a new runner based on [configuration].
factory Runner(Configuration config) => config.asCurrent(() {
var engine = Engine(
concurrency: config.concurrency,
coverage: config.coverage,
testRandomizeOrderingSeed: config.testRandomizeOrderingSeed);
concurrency: config.concurrency,
coverage: config.coverage,
testRandomizeOrderingSeed: config.testRandomizeOrderingSeed,
stopOnFirstFailure: config.stopOnFirstFailure,
);

var sinks = <IOSink>[];
Reporter createFileReporter(String reporterName, String filepath) {
Expand Down
18 changes: 18 additions & 0 deletions pkgs/test_core/lib/src/runner/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ class Configuration {
/// The same seed will shuffle the tests in the same way every time.
final int? testRandomizeOrderingSeed;

final bool? _stopOnFirstFailure;

/// Whether to stop running subsequent tests after a test fails.
bool get stopOnFirstFailure => _stopOnFirstFailure ?? false;

/// Returns the current configuration, or a default configuration if no
/// current configuration is set.
///
Expand Down Expand Up @@ -270,6 +275,7 @@ class Configuration {
required Map<String, CustomRuntime>? defineRuntimes,
required bool? noRetry,
required int? testRandomizeOrderingSeed,
required bool? stopOnFirstFailure,

// Suite-level configuration
required bool? allowDuplicateTestNames,
Expand Down Expand Up @@ -322,6 +328,7 @@ class Configuration {
defineRuntimes: defineRuntimes,
noRetry: noRetry,
testRandomizeOrderingSeed: testRandomizeOrderingSeed,
stopOnFirstFailure: stopOnFirstFailure,
includeTags: includeTags,
excludeTags: excludeTags,
globalPatterns: globalPatterns,
Expand Down Expand Up @@ -379,6 +386,7 @@ class Configuration {
Map<String, CustomRuntime>? defineRuntimes,
bool? noRetry,
int? testRandomizeOrderingSeed,
bool? stopOnFirstFailure,

// Suite-level configuration
bool? allowDuplicateTestNames,
Expand Down Expand Up @@ -430,6 +438,7 @@ class Configuration {
defineRuntimes: defineRuntimes,
noRetry: noRetry,
testRandomizeOrderingSeed: testRandomizeOrderingSeed,
stopOnFirstFailure: stopOnFirstFailure,
allowDuplicateTestNames: allowDuplicateTestNames,
allowTestRandomization: allowTestRandomization,
jsTrace: jsTrace,
Expand Down Expand Up @@ -496,6 +505,7 @@ class Configuration {
defineRuntimes: null,
noRetry: null,
testRandomizeOrderingSeed: null,
stopOnFirstFailure: null,
ignoreTimeouts: null,
allowDuplicateTestNames: null,
allowTestRandomization: null,
Expand Down Expand Up @@ -563,6 +573,7 @@ class Configuration {
defineRuntimes: null,
noRetry: null,
testRandomizeOrderingSeed: null,
stopOnFirstFailure: null,
jsTrace: null,
runSkipped: null,
dart2jsArgs: null,
Expand Down Expand Up @@ -627,6 +638,7 @@ class Configuration {
defineRuntimes: null,
noRetry: null,
testRandomizeOrderingSeed: null,
stopOnFirstFailure: null,
allowDuplicateTestNames: null,
allowTestRandomization: null,
jsTrace: null,
Expand Down Expand Up @@ -689,6 +701,7 @@ class Configuration {
overrideRuntimes: null,
noRetry: null,
testRandomizeOrderingSeed: null,
stopOnFirstFailure: null,
allowDuplicateTestNames: null,
allowTestRandomization: null,
jsTrace: null,
Expand Down Expand Up @@ -754,6 +767,7 @@ class Configuration {
required Map<String, CustomRuntime>? defineRuntimes,
required bool? noRetry,
required this.testRandomizeOrderingSeed,
required bool? stopOnFirstFailure,
required BooleanSelector? includeTags,
required BooleanSelector? excludeTags,
required Iterable<Pattern>? globalPatterns,
Expand Down Expand Up @@ -786,6 +800,7 @@ class Configuration {
globalPatterns = globalPatterns == null
? const {}
: UnmodifiableSetView(globalPatterns.toSet()),
_stopOnFirstFailure = stopOnFirstFailure,
suiteDefaults = (() {
var config = suiteDefaults ?? SuiteConfiguration.empty;
if (pauseAfterLoad == true) {
Expand Down Expand Up @@ -839,6 +854,7 @@ class Configuration {
defineRuntimes: null,
noRetry: null,
testRandomizeOrderingSeed: null,
stopOnFirstFailure: null,
includeTags: null,
excludeTags: null,
);
Expand Down Expand Up @@ -944,6 +960,7 @@ class Configuration {
noRetry: other._noRetry ?? _noRetry,
testRandomizeOrderingSeed:
other.testRandomizeOrderingSeed ?? testRandomizeOrderingSeed,
stopOnFirstFailure: other._stopOnFirstFailure ?? _stopOnFirstFailure,
includeTags: includeTags.intersection(other.includeTags),
excludeTags: excludeTags.union(other.excludeTags),
globalPatterns: globalPatterns.union(other.globalPatterns),
Expand Down Expand Up @@ -1034,6 +1051,7 @@ class Configuration {
noRetry: noRetry ?? _noRetry,
testRandomizeOrderingSeed:
testRandomizeOrderingSeed ?? this.testRandomizeOrderingSeed,
stopOnFirstFailure: _stopOnFirstFailure,
includeTags: includeTags,
excludeTags: excludeTags,
globalPatterns: globalPatterns,
Expand Down
3 changes: 3 additions & 0 deletions pkgs/test_core/lib/src/runner/configuration/args.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ final ArgParser _parser = (() {
'Must be a 32bit unsigned integer or "random".\n'
'If "random", pick a random seed to use.\n'
'If not passed, do not randomize test case execution order.');
parser.addFlag('fail-fast',
help: 'Stop running tests after the first failure.\n');

var reporterDescriptions = <String, String>{};
for (var reporter in allReporters.keys) {
Expand Down Expand Up @@ -348,6 +350,7 @@ class _Parser {
noRetry: _ifParsed('no-retry'),
testRandomizeOrderingSeed: testRandomizeOrderingSeed,
ignoreTimeouts: _ifParsed('ignore-timeouts'),
stopOnFirstFailure: _ifParsed('fail-fast'),
// Config that isn't supported on the command line
addTags: null,
allowTestRandomization: null,
Expand Down
28 changes: 23 additions & 5 deletions pkgs/test_core/lib/src/runner/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ class Engine {
/// The same seed will shuffle the tests in the same way every time.
int? testRandomizeOrderingSeed;

/// Whether to stop running tests after a failure.
bool _stopOnFirstFailure;

/// A pool that limits the number of test suites running concurrently.
final Pool _runPool;

Expand Down Expand Up @@ -202,8 +205,16 @@ class Engine {
/// Omitting this argument or passing `0` disables shuffling.
///
/// [coverage] specifies a directory to output coverage information.
Engine({int? concurrency, String? coverage, this.testRandomizeOrderingSeed})
: _runPool = Pool(concurrency ?? 1),
///
/// If [stopOnFirstFailure] then a single failing test will cause the engine
/// to [close] and stop ruunning further tests.
Engine({
int? concurrency,
String? coverage,
this.testRandomizeOrderingSeed,
bool stopOnFirstFailure = false,
}) : _runPool = Pool(concurrency ?? 1),
_stopOnFirstFailure = stopOnFirstFailure,
_coverage = coverage {
_group.future.then((_) {
_onTestStartedGroup.close();
Expand All @@ -222,8 +233,12 @@ class Engine {
/// [concurrency] controls how many suites are run at once. If [runSkipped] is
/// `true`, skipped tests will be run as though they weren't skipped.
factory Engine.withSuites(List<RunnerSuite> suites,
{int? concurrency, String? coverage}) {
var engine = Engine(concurrency: concurrency, coverage: coverage);
{int? concurrency, String? coverage, bool stopOnFirstFailure = false}) {
var engine = Engine(
concurrency: concurrency,
coverage: coverage,
stopOnFirstFailure: stopOnFirstFailure,
);
for (var suite in suites) {
engine.suiteSink.add(suite);
}
Expand Down Expand Up @@ -371,7 +386,10 @@ class Engine {
// loop pump to avoid starving non-microtask events.
await Future(() {});

if (!_restarted.contains(liveTest)) return;
if (!_restarted.contains(liveTest)) {
if (_stopOnFirstFailure && liveTest.state.result.isFailing) close();
return;
}
await _runLiveTest(suiteController, liveTest.copy(),
countSuccess: countSuccess);
_restarted.remove(liveTest);
Expand Down
2 changes: 1 addition & 1 deletion pkgs/test_core/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: test_core
version: 0.5.5
version: 0.5.6-wip
description: A basic library for writing tests and running them on the VM.
repository: https://github.com/dart-lang/test/tree/master/pkgs/test_core

Expand Down