diff --git a/doc/configuration.md b/doc/configuration.md index ae6502b4ecb3ac1a7c641e62cc15097de78732d2..7f294429b4859f2f38dcda447e8571c9b7109db8 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -30,6 +30,7 @@ tags: * [`chain_stack_traces`](#chain_stack_traces) * [`js_trace`](#js_trace) * [`skip`](#skip) + * [`retry`](#retry) * [`test_on`](#test_on) * [Runner Configuration](#runner-configuration) * [`paths`](#paths) @@ -139,6 +140,19 @@ tags: This field is not supported in the [global configuration file](#global-configuration). +### `retry` + +This int field controls how many times a test is retried upon failure. + +```yaml +tags: + chrome: + retry: 3 # Retry chrome failures 3 times. +``` + +This field is not supported in the +[global configuration file](#global-configuration). + ### `test_on` This field declares which platforms a test supports. It takes a diff --git a/lib/src/runner/configuration.dart b/lib/src/runner/configuration.dart index 1d89e30c217c7aa1a1e2d9310782a38e86f8e086..3356f5370d96bf429c9f076545599b35c96938ef 100644 --- a/lib/src/runner/configuration.dart +++ b/lib/src/runner/configuration.dart @@ -216,6 +216,7 @@ class Configuration { bool verboseTrace, bool chainStackTraces, bool skip, + int retry, String skipReason, PlatformSelector testOn, Iterable<String> addTags}) { @@ -253,6 +254,7 @@ class Configuration { verboseTrace: verboseTrace, chainStackTraces: chainStackTraces, skip: skip, + retry: retry, skipReason: skipReason, testOn: testOn, addTags: addTags)); diff --git a/lib/src/runner/configuration/load.dart b/lib/src/runner/configuration/load.dart index 597fb35629bc735f310f3998d9598f4a6c2cf22f..00e89ad60ac8c1bb8b1952fb17b572c6c81bbb46 100644 --- a/lib/src/runner/configuration/load.dart +++ b/lib/src/runner/configuration/load.dart @@ -124,6 +124,7 @@ class _ConfigurationLoader { Configuration _loadLocalTestConfig() { if (_global) { _disallow("skip"); + _disallow("retry"); _disallow("test_on"); _disallow("add_tags"); _disallow("tags"); @@ -150,8 +151,11 @@ class _ConfigurationLoader { value: (valueNode) => _nestedConfig(valueNode, "tag value", runnerConfig: false)); + var retry = _getNonNegativeInt("retry"); + return new Configuration( skip: skip, + retry: retry, skipReason: skipReason, testOn: testOn, addTags: addTags) @@ -290,6 +294,10 @@ class _ConfigurationLoader { /// Asserts that [field] is an int and returns its value. int _getInt(String field) => _getValue(field, "int", (value) => value is int); + /// Asserts that [field] is a non-negative int and returns its value. + int _getNonNegativeInt(String field) => _getValue( + field, "non-negative int", (value) => value is int && value >= 0); + /// Asserts that [field] is a boolean and returns its value. bool _getBool(String field) => _getValue(field, "boolean", (value) => value is bool); diff --git a/lib/src/runner/configuration/suite.dart b/lib/src/runner/configuration/suite.dart index d4541e8059d4bcb02e9d4040f0f62f8cdd29138e..47aae094efa4b18dd7ec5781549ca5f0d4b21a6b 100644 --- a/lib/src/runner/configuration/suite.dart +++ b/lib/src/runner/configuration/suite.dart @@ -135,6 +135,7 @@ class SuiteConfiguration { bool verboseTrace, bool chainStackTraces, bool skip, + int retry, String skipReason, PlatformSelector testOn, Iterable<String> addTags}) { @@ -154,6 +155,7 @@ class SuiteConfiguration { verboseTrace: verboseTrace, chainStackTraces: chainStackTraces, skip: skip, + retry: retry, skipReason: skipReason, testOn: testOn, tags: addTags)); @@ -258,6 +260,7 @@ class SuiteConfiguration { bool verboseTrace, bool chainStackTraces, bool skip, + int retry, String skipReason, PlatformSelector testOn, Iterable<String> addTags}) { @@ -277,6 +280,7 @@ class SuiteConfiguration { verboseTrace: verboseTrace, chainStackTraces: chainStackTraces, skip: skip, + retry: retry, skipReason: skipReason, testOn: testOn, tags: addTags)); diff --git a/pubspec.yaml b/pubspec.yaml index 944856f6deeeed10e28eb6cff0d71865283eea1c..343694da368a6fde64e15e9bcaff7308dd839624 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: test -version: 0.12.21 +version: 0.12.22-dev author: Dart Team <misc@dartlang.org> description: A library for writing dart unit tests. homepage: https://github.com/dart-lang/test diff --git a/test/runner/configuration/global_test.dart b/test/runner/configuration/global_test.dart index 8984ab791b5d74831306afb78c1d345c343c0108..69d290131b6a29472edfafaacda683da8835f1d7 100644 --- a/test/runner/configuration/global_test.dart +++ b/test/runner/configuration/global_test.dart @@ -105,6 +105,7 @@ void main() { group("disallows local-only configuration:", () { for (var field in [ "skip", + "retry", "test_on", "paths", "filename", diff --git a/test/runner/configuration/top_level_error_test.dart b/test/runner/configuration/top_level_error_test.dart index b770d94fc668200874247208b0008bde1c2ae12e..086ccfb6a8cb419585c7b9da33c146461162c193 100644 --- a/test/runner/configuration/top_level_error_test.dart +++ b/test/runner/configuration/top_level_error_test.dart @@ -45,6 +45,24 @@ void main() { test.shouldExit(exit_codes.data); }); + test("rejects an invalid retry", () { + d.file("dart_test.yaml", JSON.encode({"retry": "flup"})).create(); + + var test = runTest(["test.dart"]); + test.stderr.expect( + containsInOrder(["retry must be a non-negative int", "^^^^^^"])); + test.shouldExit(exit_codes.data); + }); + + test("rejects an negative retry values", () { + d.file("dart_test.yaml", JSON.encode({"retry": -1})).create(); + + var test = runTest(["test.dart"]); + test.stderr + .expect(containsInOrder(["retry must be a non-negative int", "^^"])); + test.shouldExit(exit_codes.data); + }); + test("rejects an invalid js_trace", () { d.file("dart_test.yaml", JSON.encode({"js_trace": "flup"})).create(); diff --git a/test/runner/configuration/top_level_test.dart b/test/runner/configuration/top_level_test.dart index 92fc36ed6fc9f75dc0fafd4bc0cc8c1c17a96888..b55e32cad410da3c08129e30c90b619d9f12e155 100644 --- a/test/runner/configuration/top_level_test.dart +++ b/test/runner/configuration/top_level_test.dart @@ -205,6 +205,34 @@ void main() { test.shouldExit(1); }, tags: 'chrome'); + test("retries tests with retry: 1", () { + d.file("dart_test.yaml", JSON.encode({"retry": 1})).create(); + + d + .file( + "test.dart", + """ + import 'package:test/test.dart'; + import 'dart:async'; + + var attempt = 0; + void main() { + test("test", () { + attempt++; + if(attempt <= 1) { + throw 'Failure!'; + } + }); + } + + """) + .create(); + + var test = runTest(["test.dart"]); + test.stdout.expect(consumeThrough(contains('+1: All tests passed'))); + test.shouldExit(0); + }); + test("skips tests with skip: true", () { d.file("dart_test.yaml", JSON.encode({"skip": true})).create();