From 6d4361cb6762316fef1f35ae9ae7d99f3f0d3fdc Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Tue, 26 May 2026 07:04:40 +0000 Subject: [PATCH 1/6] Add support for the --shard-by flag along with documentation and comprehensive unit tests --- pkgs/matcher/lib/src/core_matchers.dart | 3 +- pkgs/matcher/lib/src/equals_matcher.dart | 6 +- .../matcher/lib/src/expect/async_matcher.dart | 3 +- pkgs/matcher/lib/src/expect/expect.dart | 50 +++-- pkgs/matcher/lib/src/expect/expect_async.dart | 199 ++++++++++-------- .../lib/src/expect/future_matchers.dart | 3 +- pkgs/matcher/lib/src/expect/never_called.dart | 3 +- .../lib/src/expect/stream_matcher.dart | 114 +++++----- .../lib/src/expect/stream_matchers.dart | 19 +- pkgs/matcher/lib/src/feature_matcher.dart | 3 +- pkgs/matcher/lib/src/having_matcher.dart | 23 +- pkgs/matcher/lib/src/interfaces.dart | 3 +- pkgs/matcher/lib/src/iterable_matchers.dart | 49 ++--- pkgs/matcher/lib/src/numeric_matchers.dart | 8 +- pkgs/matcher/lib/src/order_matchers.dart | 12 +- pkgs/matcher/lib/src/string_matchers.dart | 26 +-- pkgs/matcher/lib/src/type_matcher.dart | 7 +- pkgs/matcher/test/core_matchers_test.dart | 9 +- pkgs/matcher/test/custom_matcher_test.dart | 2 +- pkgs/matcher/test/escape_test.dart | 3 +- pkgs/matcher/test/expect_async_test.dart | 3 +- pkgs/matcher/test/having_test.dart | 8 +- pkgs/matcher/test/matcher/prints_test.dart | 3 +- pkgs/test/README.md | 35 ++- .../browser/expanded_reporter_test.dart | 26 ++- .../runner/configuration/platform_test.dart | 48 +++-- pkgs/test/test/runner/node/runner_test.dart | 136 ++++++------ pkgs/test/test/runner/runner_test.dart | 68 +++--- pkgs/test/test/runner/shard_test.dart | 141 +++++++++++++ pkgs/test/test/utils.dart | 2 + .../test/import_restrictions_test.dart | 9 +- pkgs/test_core/lib/src/runner.dart | 88 ++++++-- .../lib/src/runner/configuration.dart | 20 +- .../lib/src/runner/configuration/args.dart | 8 + 34 files changed, 716 insertions(+), 424 deletions(-) diff --git a/pkgs/matcher/lib/src/core_matchers.dart b/pkgs/matcher/lib/src/core_matchers.dart index 7c80804b5..280b5233c 100644 --- a/pkgs/matcher/lib/src/core_matchers.dart +++ b/pkgs/matcher/lib/src/core_matchers.dart @@ -328,7 +328,8 @@ class _In extends FeatureMatcher { Matcher predicate( bool Function(T) f, [ String description = 'satisfies function', -]) => _Predicate(f, description); +]) => + _Predicate(f, description); class _Predicate extends FeatureMatcher { final bool Function(T) _matcher; diff --git a/pkgs/matcher/lib/src/equals_matcher.dart b/pkgs/matcher/lib/src/equals_matcher.dart index 7cb08e1ca..75fe9d038 100644 --- a/pkgs/matcher/lib/src/equals_matcher.dart +++ b/pkgs/matcher/lib/src/equals_matcher.dart @@ -120,7 +120,7 @@ class _DeepMatcher extends Matcher { if (actual is Iterable) { var expectedIterator = expected.iterator; var actualIterator = actual.iterator; - for (var index = 0; ; index++) { + for (var index = 0;; index++) { // Advance in lockstep. var expectedNext = expectedIterator.moveNext(); var actualNext = actualIterator.moveNext(); @@ -379,6 +379,6 @@ class _Mismatch { }); _Mismatch.simple(this.location, this.actual, String problem) - : describeProblem = ((description, verbose) => description.add(problem)), - instead = false; + : describeProblem = ((description, verbose) => description.add(problem)), + instead = false; } diff --git a/pkgs/matcher/lib/src/expect/async_matcher.dart b/pkgs/matcher/lib/src/expect/async_matcher.dart index 13152456f..fba0f1bc5 100644 --- a/pkgs/matcher/lib/src/expect/async_matcher.dart +++ b/pkgs/matcher/lib/src/expect/async_matcher.dart @@ -68,5 +68,6 @@ abstract class AsyncMatcher extends Matcher { Description mismatchDescription, Map matchState, bool verbose, - ) => StringDescription(matchState[this] as String); + ) => + StringDescription(matchState[this] as String); } diff --git a/pkgs/matcher/lib/src/expect/expect.dart b/pkgs/matcher/lib/src/expect/expect.dart index f1e0b9192..4d04ef4ed 100644 --- a/pkgs/matcher/lib/src/expect/expect.dart +++ b/pkgs/matcher/lib/src/expect/expect.dart @@ -21,14 +21,13 @@ import 'util/pretty_print.dart'; /// The type used for functions that can be used to build up error reports /// upon failures in [expect]. @Deprecated('Will be removed in 0.13.0.') -typedef ErrorFormatter = - String Function( - Object? actual, - Matcher matcher, - String? reason, - Map matchState, - bool verbose, - ); +typedef ErrorFormatter = String Function( + Object? actual, + Matcher matcher, + String? reason, + Map matchState, + bool verbose, +); /// Assert that [actual] matches [matcher]. /// @@ -88,7 +87,8 @@ Future expectLater( dynamic matcher, { String? reason, Object? /* String|bool */ skip, -}) => _expect(actual, matcher, reason: reason, skip: skip); +}) => + _expect(actual, matcher, reason: reason, skip: skip); /// The implementation of [expect] and [expectLater]. Future _expect( @@ -149,23 +149,21 @@ Future _expect( fail(formatFailure(matcher, actual, result, reason: reason)); } else if (result is Future) { final outstandingWork = test.markPending(); - return result - .then((realResult) { - if (realResult == null) return; - fail( - formatFailure( - matcher as Matcher, - actual, - realResult as String, - reason: reason, - ), - ); - }) - .whenComplete( - // Always remove this, in case the failure is caught and handled - // gracefully. - outstandingWork.complete, - ); + return result.then((realResult) { + if (realResult == null) return; + fail( + formatFailure( + matcher as Matcher, + actual, + realResult as String, + reason: reason, + ), + ); + }).whenComplete( + // Always remove this, in case the failure is caught and handled + // gracefully. + outstandingWork.complete, + ); } return Future.sync(() {}); diff --git a/pkgs/matcher/lib/src/expect/expect_async.dart b/pkgs/matcher/lib/src/expect/expect_async.dart index 57def17f2..d03492e67 100644 --- a/pkgs/matcher/lib/src/expect/expect_async.dart +++ b/pkgs/matcher/lib/src/expect/expect_async.dart @@ -80,14 +80,13 @@ class _ExpectedFunction { String? id, String? reason, bool Function()? isDone, - }) : _callback = callback, - _minExpectedCalls = minExpected, - _maxExpectedCalls = (maxExpected == 0 && minExpected > 0) - ? minExpected - : maxExpected, - _isDone = isDone, - _reason = reason == null ? '' : '\n$reason', - _id = _makeCallbackId(id, callback) { + }) : _callback = callback, + _minExpectedCalls = minExpected, + _maxExpectedCalls = + (maxExpected == 0 && minExpected > 0) ? minExpected : maxExpected, + _isDone = isDone, + _reason = reason == null ? '' : '\n$reason', + _id = _makeCallbackId(id, callback) { try { _test = TestHandle.current; } on OutsideTestException { @@ -162,14 +161,16 @@ class _ExpectedFunction { Object? a0 = placeholder, Object? a1 = placeholder, Object? a2 = placeholder, - ]) => max6(a0, a1, a2); + ]) => + max6(a0, a1, a2); T max4([ Object? a0 = placeholder, Object? a1 = placeholder, Object? a2 = placeholder, Object? a3 = placeholder, - ]) => max6(a0, a1, a2, a3); + ]) => + max6(a0, a1, a2, a3); T max5([ Object? a0 = placeholder, @@ -177,7 +178,8 @@ class _ExpectedFunction { Object? a2 = placeholder, Object? a3 = placeholder, Object? a4 = placeholder, - ]) => max6(a0, a1, a2, a3, a4); + ]) => + max6(a0, a1, a2, a3, a4); T max6([ Object? a0 = placeholder, @@ -186,7 +188,8 @@ class _ExpectedFunction { Object? a3 = placeholder, Object? a4 = placeholder, Object? a5 = placeholder, - ]) => _run([a0, a1, a2, a3, a4, a5].where((a) => a != placeholder)); + ]) => + _run([a0, a1, a2, a3, a4, a5].where((a) => a != placeholder)); /// Runs the wrapped function with [args] and returns its return value. T _run(Iterable args) { @@ -237,13 +240,14 @@ Function expectAsync( int max = 0, String? id, String? reason, -}) => _ExpectedFunction( - callback, - count, - max, - id: id, - reason: reason, -).func; +}) => + _ExpectedFunction( + callback, + count, + max, + id: id, + reason: reason, + ).func; /// Informs the framework that the given [callback] of arity 0 is expected to be /// called [count] number of times (by default 1). @@ -272,7 +276,8 @@ Func0 expectAsync0( int max = 0, String? id, String? reason, -}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max0; +}) => + _ExpectedFunction(callback, count, max, id: id, reason: reason).max0; /// Informs the framework that the given [callback] of arity 1 is expected to be /// called [count] number of times (by default 1). @@ -301,7 +306,8 @@ Func1 expectAsync1( int max = 0, String? id, String? reason, -}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max1; +}) => + _ExpectedFunction(callback, count, max, id: id, reason: reason).max1; /// Informs the framework that the given [callback] of arity 2 is expected to be /// called [count] number of times (by default 1). @@ -330,7 +336,8 @@ Func2 expectAsync2( int max = 0, String? id, String? reason, -}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max2; +}) => + _ExpectedFunction(callback, count, max, id: id, reason: reason).max2; /// Informs the framework that the given [callback] of arity 3 is expected to be /// called [count] number of times (by default 1). @@ -359,7 +366,8 @@ Func3 expectAsync3( int max = 0, String? id, String? reason, -}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max3; +}) => + _ExpectedFunction(callback, count, max, id: id, reason: reason).max3; /// Informs the framework that the given [callback] of arity 4 is expected to be /// called [count] number of times (by default 1). @@ -388,7 +396,8 @@ Func4 expectAsync4( int max = 0, String? id, String? reason, -}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max4; +}) => + _ExpectedFunction(callback, count, max, id: id, reason: reason).max4; /// Informs the framework that the given [callback] of arity 5 is expected to be /// called [count] number of times (by default 1). @@ -417,7 +426,8 @@ Func5 expectAsync5( int max = 0, String? id, String? reason, -}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max5; +}) => + _ExpectedFunction(callback, count, max, id: id, reason: reason).max5; /// Informs the framework that the given [callback] of arity 6 is expected to be /// called [count] number of times (by default 1). @@ -446,7 +456,8 @@ Func6 expectAsync6( int max = 0, String? id, String? reason, -}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max6; +}) => + _ExpectedFunction(callback, count, max, id: id, reason: reason).max6; /// This function is deprecated because it doesn't work well with strong mode. /// Use [expectAsyncUntil0], [expectAsyncUntil1], @@ -458,14 +469,15 @@ Function expectAsyncUntil( bool Function() isDone, { String? id, String? reason, -}) => _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, -).func; +}) => + _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, + ).func; /// Informs the framework that the given [callback] of arity 0 is expected to be /// called until [isDone] returns true. @@ -489,14 +501,15 @@ Func0 expectAsyncUntil0( bool Function() isDone, { String? id, String? reason, -}) => _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, -).max0; +}) => + _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, + ).max0; /// Informs the framework that the given [callback] of arity 1 is expected to be /// called until [isDone] returns true. @@ -520,14 +533,15 @@ Func1 expectAsyncUntil1( bool Function() isDone, { String? id, String? reason, -}) => _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, -).max1; +}) => + _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, + ).max1; /// Informs the framework that the given [callback] of arity 2 is expected to be /// called until [isDone] returns true. @@ -551,14 +565,15 @@ Func2 expectAsyncUntil2( bool Function() isDone, { String? id, String? reason, -}) => _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, -).max2; +}) => + _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, + ).max2; /// Informs the framework that the given [callback] of arity 3 is expected to be /// called until [isDone] returns true. @@ -582,14 +597,15 @@ Func3 expectAsyncUntil3( bool Function() isDone, { String? id, String? reason, -}) => _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, -).max3; +}) => + _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, + ).max3; /// Informs the framework that the given [callback] of arity 4 is expected to be /// called until [isDone] returns true. @@ -613,14 +629,15 @@ Func4 expectAsyncUntil4( bool Function() isDone, { String? id, String? reason, -}) => _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, -).max4; +}) => + _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, + ).max4; /// Informs the framework that the given [callback] of arity 5 is expected to be /// called until [isDone] returns true. @@ -644,14 +661,15 @@ Func5 expectAsyncUntil5( bool Function() isDone, { String? id, String? reason, -}) => _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, -).max5; +}) => + _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, + ).max5; /// Informs the framework that the given [callback] of arity 6 is expected to be /// called until [isDone] returns true. @@ -675,11 +693,12 @@ Func6 expectAsyncUntil6( bool Function() isDone, { String? id, String? reason, -}) => _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, -).max6; +}) => + _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, + ).max6; diff --git a/pkgs/matcher/lib/src/expect/future_matchers.dart b/pkgs/matcher/lib/src/expect/future_matchers.dart index b22ab375a..b9aaf2283 100644 --- a/pkgs/matcher/lib/src/expect/future_matchers.dart +++ b/pkgs/matcher/lib/src/expect/future_matchers.dart @@ -39,7 +39,8 @@ final Matcher completes = const _Completes(null); Matcher completion( Object? matcher, [ @Deprecated('this parameter is ignored') String? description, -]) => _Completes(wrapMatcher(matcher)); +]) => + _Completes(wrapMatcher(matcher)); class _Completes extends AsyncMatcher { final Matcher? _matcher; diff --git a/pkgs/matcher/lib/src/expect/never_called.dart b/pkgs/matcher/lib/src/expect/never_called.dart index 4da7c5388..75434e2a1 100644 --- a/pkgs/matcher/lib/src/expect/never_called.dart +++ b/pkgs/matcher/lib/src/expect/never_called.dart @@ -35,8 +35,7 @@ Null Function([ Object?, Object?, Object?, -]) -get neverCalled { +]) get neverCalled { // Make sure the test stays alive long enough to call the function if it's // going to. expect(pumpEventQueue(), completes); diff --git a/pkgs/matcher/lib/src/expect/stream_matcher.dart b/pkgs/matcher/lib/src/expect/stream_matcher.dart index bd62b315b..15503de97 100644 --- a/pkgs/matcher/lib/src/expect/stream_matcher.dart +++ b/pkgs/matcher/lib/src/expect/stream_matcher.dart @@ -140,65 +140,61 @@ class _StreamMatcher extends AsyncMatcher implements StreamMatcher { // for an invalid argument type. var transaction = queue.startTransaction(); var copy = transaction.newQueue(); - return matchQueue(copy) - .then( - (result) async { - // Accept the transaction if the result is null, indicating that the - // match succeeded. - if (result == null) { - transaction.commit(copy); - return null; - } - - // Get a list of events emitted by the stream so we can emit them as - // part of the error message. - var replay = transaction.newQueue(); - var events = []; - var subscription = Result.captureStreamTransformer - .bind(replay.rest.cast()) - .listen(events.add, onDone: () => events.add(null)); - - // Wait on a timer tick so all buffered events are emitted. - await Future.delayed(Duration.zero); - _unawaited(subscription.cancel()); - - var eventsString = events - .map((event) { - if (event == null) { - return 'x Stream closed.'; - } else if (event.isValue) { - return addBullet(event.asValue!.value.toString()); - } else { - var error = event.asError!; - var chain = TestHandle.current.formatStackTrace( - error.stackTrace, - ); - var text = '${error.error}\n$chain'; - return indent(text, first: '! '); - } - }) - .join('\n'); - if (eventsString.isEmpty) eventsString = 'no events'; - - transaction.reject(); - - var buffer = StringBuffer(); - buffer.writeln(indent(eventsString, first: 'emitted ')); - if (result.isNotEmpty) { - buffer.writeln(indent(result, first: ' which ')); - } - return buffer.toString().trimRight(); - }, - onError: (Object error) { - transaction.reject(); - // ignore: only_throw_errors - throw error; - }, - ) - .then((result) { - if (shouldCancelQueue) queue.cancel(); - return result; - }); + return matchQueue(copy).then( + (result) async { + // Accept the transaction if the result is null, indicating that the + // match succeeded. + if (result == null) { + transaction.commit(copy); + return null; + } + + // Get a list of events emitted by the stream so we can emit them as + // part of the error message. + var replay = transaction.newQueue(); + var events = []; + var subscription = Result.captureStreamTransformer + .bind(replay.rest.cast()) + .listen(events.add, onDone: () => events.add(null)); + + // Wait on a timer tick so all buffered events are emitted. + await Future.delayed(Duration.zero); + _unawaited(subscription.cancel()); + + var eventsString = events.map((event) { + if (event == null) { + return 'x Stream closed.'; + } else if (event.isValue) { + return addBullet(event.asValue!.value.toString()); + } else { + var error = event.asError!; + var chain = TestHandle.current.formatStackTrace( + error.stackTrace, + ); + var text = '${error.error}\n$chain'; + return indent(text, first: '! '); + } + }).join('\n'); + if (eventsString.isEmpty) eventsString = 'no events'; + + transaction.reject(); + + var buffer = StringBuffer(); + buffer.writeln(indent(eventsString, first: 'emitted ')); + if (result.isNotEmpty) { + buffer.writeln(indent(result, first: ' which ')); + } + return buffer.toString().trimRight(); + }, + onError: (Object error) { + transaction.reject(); + // ignore: only_throw_errors + throw error; + }, + ).then((result) { + if (shouldCancelQueue) queue.cancel(); + return result; + }); } @override diff --git a/pkgs/matcher/lib/src/expect/stream_matchers.dart b/pkgs/matcher/lib/src/expect/stream_matchers.dart index 2e2e494f8..db351d2c8 100644 --- a/pkgs/matcher/lib/src/expect/stream_matchers.dart +++ b/pkgs/matcher/lib/src/expect/stream_matchers.dart @@ -97,8 +97,7 @@ StreamMatcher emitsAnyOf(Iterable matchers) { } if (streamMatchers.length == 1) return streamMatchers.first; - var description = - 'do one of the following:\n' + var description = 'do one of the following:\n' '${bullet(streamMatchers.map((matcher) => matcher.description))}'; return StreamMatcher((queue) async { @@ -175,8 +174,7 @@ StreamMatcher emitsInOrder(Iterable matchers) { var streamMatchers = matchers.map(emits).toList(); if (streamMatchers.length == 1) return streamMatchers.first; - var description = - 'do the following in order:\n' + var description = 'do the following in order:\n' '${bullet(streamMatchers.map((matcher) => matcher.description))}'; return StreamMatcher((queue) async { @@ -208,11 +206,11 @@ StreamMatcher emitsThrough(Object? matcher) { var failures = []; Future tryHere() => queue.withTransaction((copy) async { - var result = await streamMatcher.matchQueue(copy); - if (result == null) return true; - failures.add(result); - return false; - }); + var result = await streamMatcher.matchQueue(copy); + if (result == null) return true; + failures.add(result); + return false; + }); while (await queue.hasNext) { if (await tryHere()) return null; @@ -321,8 +319,7 @@ Future _tryMatch(StreamQueue queue, StreamMatcher matcher) { StreamMatcher emitsInAnyOrder(Iterable matchers) { var streamMatchers = matchers.map(emits).toSet(); if (streamMatchers.length == 1) return streamMatchers.first; - var description = - 'do the following in any order:\n' + var description = 'do the following in any order:\n' '${bullet(streamMatchers.map((matcher) => matcher.description))}'; return StreamMatcher( diff --git a/pkgs/matcher/lib/src/feature_matcher.dart b/pkgs/matcher/lib/src/feature_matcher.dart index 413e7eb40..709f8817f 100644 --- a/pkgs/matcher/lib/src/feature_matcher.dart +++ b/pkgs/matcher/lib/src/feature_matcher.dart @@ -41,5 +41,6 @@ abstract class FeatureMatcher extends TypeMatcher { Description mismatchDescription, Map matchState, bool verbose, - ) => mismatchDescription; + ) => + mismatchDescription; } diff --git a/pkgs/matcher/lib/src/having_matcher.dart b/pkgs/matcher/lib/src/having_matcher.dart index 9cb31318c..5756dfd63 100644 --- a/pkgs/matcher/lib/src/having_matcher.dart +++ b/pkgs/matcher/lib/src/having_matcher.dart @@ -27,22 +27,23 @@ class HavingMatcher implements TypeMatcher { dynamic matcher, Iterable<_FunctionMatcher>? existing, ) : _functionMatchers = [ - ...?existing, - _FunctionMatcher(description, feature, matcher), - ]; + ...?existing, + _FunctionMatcher(description, feature, matcher), + ]; @override TypeMatcher having( Object? Function(T) feature, String description, dynamic matcher, - ) => HavingMatcher._fromExisting( - _parent, - description, - feature, - matcher, - _functionMatchers, - ); + ) => + HavingMatcher._fromExisting( + _parent, + description, + feature, + matcher, + _functionMatchers, + ); @override bool matches(dynamic item, Map matchState) { @@ -84,7 +85,7 @@ class _FunctionMatcher extends CustomMatcher { final Object? Function(T value) _feature; _FunctionMatcher(String name, this._feature, Object? matcher) - : super('`$name`:', '`$name`', matcher); + : super('`$name`:', '`$name`', matcher); @override Object? featureValueOf(covariant T actual) => _feature(actual); diff --git a/pkgs/matcher/lib/src/interfaces.dart b/pkgs/matcher/lib/src/interfaces.dart index 7fc700878..97c817d17 100644 --- a/pkgs/matcher/lib/src/interfaces.dart +++ b/pkgs/matcher/lib/src/interfaces.dart @@ -59,5 +59,6 @@ abstract class Matcher { Description mismatchDescription, Map matchState, bool verbose, - ) => mismatchDescription; + ) => + mismatchDescription; } diff --git a/pkgs/matcher/lib/src/iterable_matchers.dart b/pkgs/matcher/lib/src/iterable_matchers.dart index 087bbd280..b79d16e11 100644 --- a/pkgs/matcher/lib/src/iterable_matchers.dart +++ b/pkgs/matcher/lib/src/iterable_matchers.dart @@ -140,8 +140,8 @@ class _UnorderedEquals extends _UnorderedMatches { final List _expectedValues; _UnorderedEquals(Iterable expected) - : _expectedValues = expected.toList(), - super(expected.map(equals)); + : _expectedValues = expected.toList(), + super(expected.map(equals)); @override Description describe(Description description) => description @@ -168,8 +168,8 @@ class _UnorderedMatches extends _IterableMatcher { final bool _allowUnmatchedValues; _UnorderedMatches(Iterable expected, {bool allowUnmatchedValues = false}) - : _expected = expected.map(wrapMatcher).toList(), - _allowUnmatchedValues = allowUnmatchedValues; + : _expected = expected.map(wrapMatcher).toList(), + _allowUnmatchedValues = allowUnmatchedValues; String? _test(List values) { // Check the lengths are the same. @@ -193,25 +193,21 @@ class _UnorderedMatches extends _IterableMatcher { for (var valueIndex = 0; valueIndex < values.length; valueIndex++) { _findPairing(edges, valueIndex, matched); } - for ( - var matcherIndex = 0; - matcherIndex < _expected.length; - matcherIndex++ - ) { + for (var matcherIndex = 0; + matcherIndex < _expected.length; + matcherIndex++) { if (matched[matcherIndex] == null) { final description = StringDescription() .add('has no match for ') .addDescriptionOf(_expected[matcherIndex]) .add(' at index $matcherIndex'); - final remainingUnmatched = matched - .sublist(matcherIndex + 1) - .where((m) => m == null) - .length; + final remainingUnmatched = + matched.sublist(matcherIndex + 1).where((m) => m == null).length; return remainingUnmatched == 0 ? description.toString() : description - .add(' along with $remainingUnmatched other unmatched') - .toString(); + .add(' along with $remainingUnmatched other unmatched') + .toString(); } } return null; @@ -233,7 +229,8 @@ class _UnorderedMatches extends _IterableMatcher { Description mismatchDescription, Map matchState, bool verbose, - ) => mismatchDescription.add(_test(item.toList())!); + ) => + mismatchDescription.add(_test(item.toList())!); /// Returns `true` if the value at [valueIndex] can be paired with some /// unmatched matcher and updates the state of [matched]. @@ -244,7 +241,8 @@ class _UnorderedMatches extends _IterableMatcher { List> edges, int valueIndex, List matched, - ) => _findPairingInner(edges, valueIndex, matched, {}); + ) => + _findPairingInner(edges, valueIndex, matched, {}); /// Implementation of [_findPairing], tracks [reserved] which are the /// matchers that have been used _during_ this search. @@ -281,7 +279,8 @@ Matcher pairwiseCompare( Iterable expected, bool Function(S, T) comparator, String description, -) => _PairwiseCompare(expected, comparator, description); +) => + _PairwiseCompare(expected, comparator, description); typedef _Comparator = bool Function(S a, T b); @@ -362,8 +361,8 @@ class _ContainsAll extends _UnorderedMatches { final Iterable _unwrappedExpected; _ContainsAll(Iterable expected) - : _unwrappedExpected = expected, - super(expected.map(wrapMatcher), allowUnmatchedValues: true); + : _unwrappedExpected = expected, + super(expected.map(wrapMatcher), allowUnmatchedValues: true); @override Description describe(Description description) => description.add('contains all of ').addDescriptionOf(_unwrappedExpected); @@ -414,7 +413,8 @@ class _ContainsAllInOrder extends _IterableMatcher { Description mismatchDescription, Map matchState, bool verbose, - ) => mismatchDescription.add(_test(item, matchState)!); + ) => + mismatchDescription.add(_test(item, matchState)!); } /// Matches [Iterable]s where exactly one element matches the expected @@ -463,7 +463,8 @@ class _ContainsOnce extends _IterableMatcher { Description mismatchDescription, Map matchState, bool verbose, - ) => mismatchDescription.add(_test(item, matchState)!); + ) => + mismatchDescription.add(_test(item, matchState)!); } /// Matches [Iterable]s which are sorted. @@ -487,8 +488,8 @@ class _IsSorted extends _IterableMatcher { final Comparator _compare; _IsSorted(K Function(T) keyOf, Comparator compare) - : _keyOf = keyOf, - _compare = compare; + : _keyOf = keyOf, + _compare = compare; @override bool typedMatches(Iterable item, Map matchState) { diff --git a/pkgs/matcher/lib/src/numeric_matchers.dart b/pkgs/matcher/lib/src/numeric_matchers.dart index 22c3d7c5a..529a3fea8 100644 --- a/pkgs/matcher/lib/src/numeric_matchers.dart +++ b/pkgs/matcher/lib/src/numeric_matchers.dart @@ -91,8 +91,8 @@ class _InRange extends FeatureMatcher { @override Description describe(Description description) => description.add( - 'be in range from ' - "$_low (${_lowMatchValue ? 'inclusive' : 'exclusive'}) to " - "$_high (${_highMatchValue ? 'inclusive' : 'exclusive'})", - ); + 'be in range from ' + "$_low (${_lowMatchValue ? 'inclusive' : 'exclusive'}) to " + "$_high (${_highMatchValue ? 'inclusive' : 'exclusive'})", + ); } diff --git a/pkgs/matcher/lib/src/order_matchers.dart b/pkgs/matcher/lib/src/order_matchers.dart index d87c74ac7..21b48bf0c 100644 --- a/pkgs/matcher/lib/src/order_matchers.dart +++ b/pkgs/matcher/lib/src/order_matchers.dart @@ -12,12 +12,12 @@ Matcher greaterThan(Object value) => /// Returns a matcher which matches if the match argument is greater /// than or equal to the given [value]. Matcher greaterThanOrEqualTo(Object value) => _OrderingMatcher( - value, - true, - false, - true, - 'a value greater than or equal to', -); + value, + true, + false, + true, + 'a value greater than or equal to', + ); /// Returns a matcher which matches if the match argument is less /// than the given [value]. diff --git a/pkgs/matcher/lib/src/string_matchers.dart b/pkgs/matcher/lib/src/string_matchers.dart index 3d5225226..49c9fdc89 100644 --- a/pkgs/matcher/lib/src/string_matchers.dart +++ b/pkgs/matcher/lib/src/string_matchers.dart @@ -14,8 +14,8 @@ class _IsEqualIgnoringCase extends FeatureMatcher { final String _matchValue; _IsEqualIgnoringCase(String value) - : _value = value, - _matchValue = value.toLowerCase(); + : _value = value, + _matchValue = value.toLowerCase(); @override bool typedMatches(String item, Map matchState) => @@ -50,7 +50,7 @@ class _IsEqualIgnoringWhitespace extends FeatureMatcher { final String _matchValue; _IsEqualIgnoringWhitespace(String value) - : _matchValue = collapseWhitespace(value); + : _matchValue = collapseWhitespace(value); @override bool typedMatches(String item, Map matchState) => @@ -135,11 +135,11 @@ class _StringContainsInOrder extends FeatureMatcher { @override Description describe(Description description) => description.addAll( - 'a string containing ', - ', ', - ' in order', - _substrings, - ); + 'a string containing ', + ', ', + ' in order', + _substrings, + ); } /// Returns a matcher that matches if the match argument is a string and @@ -153,11 +153,11 @@ class _MatchesRegExp extends FeatureMatcher { final RegExp _regexp; _MatchesRegExp(Pattern re) - : _regexp = (re is String) - ? RegExp(re) - : (re is RegExp) - ? re - : throw ArgumentError('matches requires a regexp or string'); + : _regexp = (re is String) + ? RegExp(re) + : (re is RegExp) + ? re + : throw ArgumentError('matches requires a regexp or string'); @override bool typedMatches(String item, Map matchState) => _regexp.hasMatch(item); diff --git a/pkgs/matcher/lib/src/type_matcher.dart b/pkgs/matcher/lib/src/type_matcher.dart index 179509987..57b2105f9 100644 --- a/pkgs/matcher/lib/src/type_matcher.dart +++ b/pkgs/matcher/lib/src/type_matcher.dart @@ -66,8 +66,8 @@ class TypeMatcher extends Matcher { ) String? name, ]) : _name = - // ignore: deprecated_member_use_from_same_package - name; + // ignore: deprecated_member_use_from_same_package + name; /// Returns a new [TypeMatcher] that validates the existing type as well as /// a specific [feature] of the object with the provided [matcher]. @@ -88,7 +88,8 @@ class TypeMatcher extends Matcher { Object? Function(T) feature, String description, dynamic matcher, - ) => HavingMatcher(this, description, feature, matcher); + ) => + HavingMatcher(this, description, feature, matcher); @override Description describe(Description description) { diff --git a/pkgs/matcher/test/core_matchers_test.dart b/pkgs/matcher/test/core_matchers_test.dart index 69866961e..19f5cb075 100644 --- a/pkgs/matcher/test/core_matchers_test.dart +++ b/pkgs/matcher/test/core_matchers_test.dart @@ -243,8 +243,7 @@ void main() { 4, [], ]; - var reason1 = - "Expected: [['foo', 'bar'], ['foo'], 4, []] " + var reason1 = "Expected: [['foo', 'bar'], ['foo'], 4, []] " "Actual: [['foo', 'bar'], ['foo'], 3, []] " 'Which: at location [2] is <3> instead of <4>'; @@ -260,8 +259,7 @@ void main() { 4, [], ]; - var reason2 = - "Expected: [['foo', 'bar'], ['foo'], 4, []] " + var reason2 = "Expected: [['foo', 'bar'], ['foo'], 4, []] " "Actual: [['foo', 'barry'], ['foo'], 4, []] " "Which: at location [0][1] is 'barry' instead of 'bar'"; @@ -277,8 +275,7 @@ void main() { 4, {'foo': 'barry'}, ]; - var reason3 = - "Expected: [['foo', 'bar'], ['foo'], 4, {'foo': 'barry'}] " + var reason3 = "Expected: [['foo', 'bar'], ['foo'], 4, {'foo': 'barry'}] " "Actual: [['foo', 'bar'], ['foo'], 4, {'foo': 'bar'}] " "Which: at location [3]['foo'] is 'bar' instead of 'barry'"; diff --git a/pkgs/matcher/test/custom_matcher_test.dart b/pkgs/matcher/test/custom_matcher_test.dart index 16e2adc49..eb0b4ab30 100644 --- a/pkgs/matcher/test/custom_matcher_test.dart +++ b/pkgs/matcher/test/custom_matcher_test.dart @@ -15,7 +15,7 @@ class _BadCustomMatcher extends CustomMatcher { class _HasPrice extends CustomMatcher { _HasPrice(Object? matcher) - : super('Widget with a price that is', 'price', matcher); + : super('Widget with a price that is', 'price', matcher); @override Object? featureValueOf(Object? actual) => (actual as Widget).price; } diff --git a/pkgs/matcher/test/escape_test.dart b/pkgs/matcher/test/escape_test.dart index 955458e07..ee58daae3 100644 --- a/pkgs/matcher/test/escape_test.dart +++ b/pkgs/matcher/test/escape_test.dart @@ -44,8 +44,7 @@ void _testEscaping(String name, String source, String target) { expect( escaped == target, isTrue, - reason: - 'Expected escaped value: $target\n' + reason: 'Expected escaped value: $target\n' ' Actual escaped value: $escaped', ); }); diff --git a/pkgs/matcher/test/expect_async_test.dart b/pkgs/matcher/test/expect_async_test.dart index bb00951d7..adb54710b 100644 --- a/pkgs/matcher/test/expect_async_test.dart +++ b/pkgs/matcher/test/expect_async_test.dart @@ -178,7 +178,8 @@ void main() { }); group('with count', () { - test("won't allow the test to complete until it's called at least that " + test( + "won't allow the test to complete until it's called at least that " 'many times', () async { late void Function() callback; final monitor = TestCaseMonitor.start(() { diff --git a/pkgs/matcher/test/having_test.dart b/pkgs/matcher/test/having_test.dart index 89396ac5d..d2e02855c 100644 --- a/pkgs/matcher/test/having_test.dart +++ b/pkgs/matcher/test/having_test.dart @@ -132,10 +132,10 @@ Matcher _hasPrice(Object matcher) => const TypeMatcher().having((e) => e.price, 'price', matcher); Matcher _badCustomMatcher() => const TypeMatcher().having( - (e) => throw Exception('bang'), - 'feature', - {1: 'a'}, -); + (e) => throw Exception('bang'), + 'feature', + {1: 'a'}, + ); class CustomRangeError extends RangeError { CustomRangeError.range( diff --git a/pkgs/matcher/test/matcher/prints_test.dart b/pkgs/matcher/test/matcher/prints_test.dart index 6e63e6689..b9d120553 100644 --- a/pkgs/matcher/test/matcher/prints_test.dart +++ b/pkgs/matcher/test/matcher/prints_test.dart @@ -229,7 +229,8 @@ void main() { expectLater(() { scheduleMicrotask(() => print('hello!')); return completer.future; - }, prints('hello!\n')).then((_) { + }, prints('hello!\n')) + .then((_) { fired = true; }), ); diff --git a/pkgs/test/README.md b/pkgs/test/README.md index 9da43d2a7..03ab04969 100644 --- a/pkgs/test/README.md +++ b/pkgs/test/README.md @@ -204,12 +204,35 @@ dart test --total-shards 3 --shard-index 0 path/to/test.dart dart test --total-shards 3 --shard-index 1 path/to/test.dart dart test --total-shards 3 --shard-index 2 path/to/test.dart ``` -Sharding: This refers to the process of splitting up a large test suite into -smaller subsets (called shards) that can be run independently. Sharding is -particularly useful for distributed testing, where multiple machines are used -to run tests simultaneously. By dividing the test suite into smaller subsets, -you can run tests in parallel across multiple machines, which can significantly -reduce the overall testing time. + +By default, sharding is done by `"test"`, meaning individual tests within each +suite are distributed across the shards. You can customize how tests are +distributed using the `--shard-by` flag, which supports the following values: + +* `test` (default): Distribute individual test cases across shards. This + balances the test load at the test case level. Test cases from each suite + are sliced continuously, which minimizes how often a suite is split across + shards and helps maximize the re-use of `setUpAll` and `tearDownAll` setups. +* `file`: Distribute entire test files across shards. This can be faster for + suites with many small files as it avoids loading every file in every shard. + Because test files are not split, any `setUpAll` and `tearDownAll` setups in + a file are guaranteed to run only once (on the shard running that file). + +Sharding is particularly useful for distributed testing, where multiple +machines are used to run tests simultaneously. By dividing the test suite into +smaller subsets, you can run tests in parallel across multiple machines, which +can significantly reduce the overall testing time. + +### Interaction with Test Filters + +The sharding modes interact differently with filters like `--name` or `--tags`: + +* When sharding by `test`, the sharding partition is calculated *after* + applying filters. This guarantees that the matching tests are distributed + as evenly as possible across all shards. +* When sharding by `file`, the files are partitioned *before* they are loaded + and filtered. If a filter only matches tests in a few files, some shards + might run no tests because those files were allocated to other shards. ### Test concurrency diff --git a/pkgs/test/test/runner/browser/expanded_reporter_test.dart b/pkgs/test/test/runner/browser/expanded_reporter_test.dart index 464b67bc1..7c9a4d01c 100644 --- a/pkgs/test/test/runner/browser/expanded_reporter_test.dart +++ b/pkgs/test/test/runner/browser/expanded_reporter_test.dart @@ -13,8 +13,10 @@ import '../../io.dart'; void main() { setUpAll(precompileTestExecutable); - test('prints the platform name when running on multiple platforms', () async { - await d.file('test.dart', ''' + test( + 'prints the platform name when running on multiple platforms', + () async { + await d.file('test.dart', ''' import 'dart:async'; import 'package:test/test.dart'; @@ -24,13 +26,15 @@ void main() { } ''').create(); - var test = await runTest([ - '-r', 'expanded', '-p', 'chrome', '-p', 'vm', '-j', '1', // - 'test.dart', - ]); - - expect(test.stdoutStream(), emitsThrough(contains('[VM, Kernel]'))); - expect(test.stdout, emitsThrough(contains('[Chrome, Dart2Js]'))); - await test.shouldExit(0); - }, tags: ['chrome']); + var test = await runTest([ + '-r', 'expanded', '-p', 'chrome', '-p', 'vm', '-j', '1', // + 'test.dart', + ]); + + expect(test.stdoutStream(), emitsThrough(contains('[VM, Kernel]'))); + expect(test.stdout, emitsThrough(contains('[Chrome, Dart2Js]'))); + await test.shouldExit(0); + }, + tags: ['chrome'], + ); } diff --git a/pkgs/test/test/runner/configuration/platform_test.dart b/pkgs/test/test/runner/configuration/platform_test.dart index 1878d8283..9158a878e 100644 --- a/pkgs/test/test/runner/configuration/platform_test.dart +++ b/pkgs/test/test/runner/configuration/platform_test.dart @@ -18,19 +18,21 @@ void main() { setUpAll(precompileTestExecutable); group('on_platform', () { - test('applies platform-specific configuration to matching tests', () async { - await d - .file( - 'dart_test.yaml', - jsonEncode({ - 'on_platform': { - 'chrome': {'timeout': '0s'}, - }, - }), - ) - .create(); + test( + 'applies platform-specific configuration to matching tests', + () async { + await d + .file( + 'dart_test.yaml', + jsonEncode({ + 'on_platform': { + 'chrome': {'timeout': '0s'}, + }, + }), + ) + .create(); - await d.file('test.dart', ''' + await d.file('test.dart', ''' import 'dart:async'; import 'package:test/test.dart'; @@ -40,16 +42,18 @@ void main() { } ''').create(); - var test = await runTest(['-p', 'chrome,vm', 'test.dart']); - expect( - test.stdout, - containsInOrder([ - '-1: [Chrome, Dart2Js] test [E]', - '+1 -1: Some tests failed.', - ]), - ); - await test.shouldExit(1); - }, tags: ['chrome']); + var test = await runTest(['-p', 'chrome,vm', 'test.dart']); + expect( + test.stdout, + containsInOrder([ + '-1: [Chrome, Dart2Js] test [E]', + '+1 -1: Some tests failed.', + ]), + ); + await test.shouldExit(1); + }, + tags: ['chrome'], + ); test('supports platform selectors', () async { await d diff --git a/pkgs/test/test/runner/node/runner_test.dart b/pkgs/test/test/runner/node/runner_test.dart index 33e1f4c3a..ec0a288a3 100644 --- a/pkgs/test/test/runner/node/runner_test.dart +++ b/pkgs/test/test/runner/node/runner_test.dart @@ -106,33 +106,41 @@ void main() { await test.shouldExit(1); }); - test("a test file doesn't have a main defined", () async { - await d.file('test.dart', 'void foo() {}').create(); - - var test = await runTest(['-p', 'node', 'test.dart']); - expect( - test.stdout, - containsInOrder([ - '-1: loading test.dart [E]', - 'Failed to load "test.dart": No top-level main() function defined.', - ]), - ); - await test.shouldExit(1); - }, skip: 'https://github.com/dart-lang/test/issues/894'); - - test('a test file has a non-function main', () async { - await d.file('test.dart', 'int main;').create(); + test( + "a test file doesn't have a main defined", + () async { + await d.file('test.dart', 'void foo() {}').create(); + + var test = await runTest(['-p', 'node', 'test.dart']); + expect( + test.stdout, + containsInOrder([ + '-1: loading test.dart [E]', + 'Failed to load "test.dart": No top-level main() function defined.', + ]), + ); + await test.shouldExit(1); + }, + skip: 'https://github.com/dart-lang/test/issues/894', + ); - var test = await runTest(['-p', 'node', 'test.dart']); - expect( - test.stdout, - containsInOrder([ - '-1: loading test.dart [E]', - 'Failed to load "test.dart": Top-level main getter is not a function.', - ]), - ); - await test.shouldExit(1); - }, skip: 'https://github.com/dart-lang/test/issues/894'); + test( + 'a test file has a non-function main', + () async { + await d.file('test.dart', 'int main;').create(); + + var test = await runTest(['-p', 'node', 'test.dart']); + expect( + test.stdout, + containsInOrder([ + '-1: loading test.dart [E]', + 'Failed to load "test.dart": Top-level main getter is not a function.', + ]), + ); + await test.shouldExit(1); + }, + skip: 'https://github.com/dart-lang/test/issues/894', + ); test('a test file has a main with arguments', () async { await d.file('test.dart', 'void main(arg) {}').create(); @@ -225,8 +233,10 @@ void main() { await test.shouldExit(1); }); - test('runs failing tests that fail only on node (with dart2wasm)', () async { - await d.file('test.dart', ''' + test( + 'runs failing tests that fail only on node (with dart2wasm)', + () async { + await d.file('test.dart', ''' import 'package:path/path.dart' as p; import 'package:test/test.dart'; @@ -239,27 +249,31 @@ void main() { } ''').create(); - var test = await runTest([ - '-p', - 'node', - '-p', - 'vm', - '-c', - 'dart2js', - '-c', - 'dart2wasm', - 'test.dart', - ]); - expect(test.stdout, emitsThrough(contains('+1 -2: Some tests failed.'))); - await test.shouldExit(1); - }, skip: skipBelowMajorNodeVersion(22)); + var test = await runTest([ + '-p', + 'node', + '-p', + 'vm', + '-c', + 'dart2js', + '-c', + 'dart2wasm', + 'test.dart', + ]); + expect(test.stdout, emitsThrough(contains('+1 -2: Some tests failed.'))); + await test.shouldExit(1); + }, + skip: skipBelowMajorNodeVersion(22), + ); - test('gracefully handles wasm errors on old node versions', () async { - // Old Node.JS versions can't read the WebAssembly modules emitted by - // dart2wasm. The node process exits before connecting to the server - // opened by the test runner, leading to timeouts. So, this is a - // regression test for https://github.com/dart-lang/test/pull/2259#issuecomment-2307868442 - await d.file('test.dart', ''' + test( + 'gracefully handles wasm errors on old node versions', + () async { + // Old Node.JS versions can't read the WebAssembly modules emitted by + // dart2wasm. The node process exits before connecting to the server + // opened by the test runner, leading to timeouts. So, this is a + // regression test for https://github.com/dart-lang/test/pull/2259#issuecomment-2307868442 + await d.file('test.dart', ''' import 'package:test/test.dart'; void main() { @@ -269,18 +283,20 @@ void main() { } ''').create(); - var test = await runTest(['-p', 'node', '-c', 'dart2wasm', 'test.dart']); - expect( - test.stdout, - emitsInOrder([ - emitsThrough( - contains('Node exited before connecting to the test channel.'), - ), - emitsThrough(contains('-1: Some tests failed.')), - ]), - ); - await test.shouldExit(1); - }, skip: skipAboveMajorNodeVersion(21)); + var test = await runTest(['-p', 'node', '-c', 'dart2wasm', 'test.dart']); + expect( + test.stdout, + emitsInOrder([ + emitsThrough( + contains('Node exited before connecting to the test channel.'), + ), + emitsThrough(contains('-1: Some tests failed.')), + ]), + ); + await test.shouldExit(1); + }, + skip: skipAboveMajorNodeVersion(21), + ); test('forwards prints from the Node test', () async { await d.file('test.dart', ''' diff --git a/pkgs/test/test/runner/runner_test.dart b/pkgs/test/test/runner/runner_test.dart index 8a8594f81..7198fdf29 100644 --- a/pkgs/test/test/runner/runner_test.dart +++ b/pkgs/test/test/runner/runner_test.dart @@ -842,23 +842,27 @@ void main() { }); for (var platform in ['vm', 'chrome']) { - test('on the $platform platform', () async { - var test = await runTest( - ['test.dart', '-p', platform], - vmArgs: ['--enable-experiment=non-nullable'], - ); - - await expectLater(test.stdout, emitsThrough(contains('int x;'))); - await test.shouldExit(1); - - // Test that they can be removed on subsequent runs as well - test = await runTest(['test.dart', '-p', platform]); - await expectLater( - test.stdout, - emitsThrough(contains('+1: All tests passed!')), - ); - await test.shouldExit(0); - }, skip: 'https://github.com/dart-lang/test/issues/1813'); + test( + 'on the $platform platform', + () async { + var test = await runTest( + ['test.dart', '-p', platform], + vmArgs: ['--enable-experiment=non-nullable'], + ); + + await expectLater(test.stdout, emitsThrough(contains('int x;'))); + await test.shouldExit(1); + + // Test that they can be removed on subsequent runs as well + test = await runTest(['test.dart', '-p', platform]); + await expectLater( + test.stdout, + emitsThrough(contains('+1: All tests passed!')), + ); + await test.shouldExit(0); + }, + skip: 'https://github.com/dart-lang/test/issues/1813', + ); } }); }); @@ -896,18 +900,22 @@ void main() { await test.shouldExit(0); }); - test('on the browser platform', () async { - var test = await runTest([ - '-p', - 'vm,chrome', - 'a_test.dart', - 'b_test.dart', - ]); - await expectLater( - test.stdout, - emitsThrough(contains('+3: All tests passed!')), - ); - await test.shouldExit(0); - }, skip: 'https://github.com/dart-lang/test/issues/1803'); + test( + 'on the browser platform', + () async { + var test = await runTest([ + '-p', + 'vm,chrome', + 'a_test.dart', + 'b_test.dart', + ]); + await expectLater( + test.stdout, + emitsThrough(contains('+3: All tests passed!')), + ); + await test.shouldExit(0); + }, + skip: 'https://github.com/dart-lang/test/issues/1803', + ); }); } diff --git a/pkgs/test/test/runner/shard_test.dart b/pkgs/test/test/runner/shard_test.dart index b255da9c8..01539fe37 100644 --- a/pkgs/test/test/runner/shard_test.dart +++ b/pkgs/test/test/runner/shard_test.dart @@ -172,6 +172,147 @@ void main() { await test.shouldExit(79); }); + test('shards by file', () async { + await d.file('1_test.dart', ''' + import 'package:test/test.dart'; + + void main() { + test("test 1.1", () {}); + test("test 1.2", () {}); + } + ''').create(); + + await d.file('2_test.dart', ''' + import 'package:test/test.dart'; + + void main() { + test("test 2.1", () {}); + test("test 2.2", () {}); + } + ''').create(); + + var test = await runTest([ + '.', + '--shard-index=0', + '--total-shards=2', + '--shard-by=file', + ]); + expect( + test.stdout, + containsInOrder([ + '+0: ./1_test.dart: test 1.1', + '+1: ./1_test.dart: test 1.2', + '+2: All tests passed!', + ]), + ); + expect(test.stdout, isNot(contains('./2_test.dart'))); + await test.shouldExit(0); + + test = await runTest([ + '.', + '--shard-index=1', + '--total-shards=2', + '--shard-by=file', + ]); + expect( + test.stdout, + containsInOrder([ + '+0: ./2_test.dart: test 2.1', + '+1: ./2_test.dart: test 2.2', + '+2: All tests passed!', + ]), + ); + expect(test.stdout, isNot(contains('./1_test.dart'))); + await test.shouldExit(0); + }); + + test('interaction of --shard-by=file with name filters', () async { + await d.file('1_test.dart', ''' + import 'package:test/test.dart'; + + void main() { + test("match", () {}); + } + ''').create(); + + await d.file('2_test.dart', ''' + import 'package:test/test.dart'; + + void main() { + test("other", () {}); + } + ''').create(); + + // Shard 1 gets 2_test.dart. But 2_test.dart has no tests matching 'match'. + // So shard 1 should report no tests ran and fail. + var test = await runTest([ + '.', + '--shard-index=1', + '--total-shards=2', + '--shard-by=file', + '--name=match', + ]); + expect(test.stdout, emitsThrough('No tests ran.')); + await test.shouldExit(79); + + // Shard 0 gets 1_test.dart. It matches, so it runs. + test = await runTest([ + '.', + '--shard-index=0', + '--total-shards=2', + '--shard-by=file', + '--name=match', + ]); + expect( + test.stdout, + containsInOrder(['+0: ./1_test.dart: match', '+1: All tests passed!']), + ); + await test.shouldExit(0); + }); + + test('interaction of --shard-by=test with name filters', () async { + await d.file('test.dart', ''' + import 'package:test/test.dart'; + + void main() { + test("match 1", () {}); + test("match 2", () {}); + test("other 1", () {}); + test("other 2", () {}); + } + ''').create(); + + // Only tests matching 'match' are sharded. 'match 1' and 'match 2' are active. + // Shard 0 runs 'match 1', Shard 1 runs 'match 2'. + var test = await runTest([ + 'test.dart', + '--shard-index=0', + '--total-shards=2', + '--shard-by=test', + '--name=match', + ]); + expect( + test.stdout, + containsInOrder(['+0: match 1', '+1: All tests passed!']), + ); + expect(test.stdout, isNot(contains('match 2'))); + await test.shouldExit(0); + + test = await runTest([ + 'test.dart', + '--shard-index=1', + '--total-shards=2', + '--shard-by=test', + '--name=match', + ]); + expect( + test.stdout, + containsInOrder(['+0: match 2', '+1: All tests passed!']), + ); + expect(test.stdout, isNot(contains('match 1'))); + await test.shouldExit(0); + }); + group('reports an error if', () { test('--shard-index is provided alone', () async { var test = await runTest(['--shard-index=1']); diff --git a/pkgs/test/test/utils.dart b/pkgs/test/test/utils.dart index 26dbd06c9..e3fdd055d 100644 --- a/pkgs/test/test/utils.dart +++ b/pkgs/test/test/utils.dart @@ -232,6 +232,7 @@ Configuration configuration({ int? concurrency, int? shardIndex, int? totalShards, + String? shardBy, Map>? testSelections, Iterable? foldTraceExcept, Iterable? foldTraceOnly, @@ -286,6 +287,7 @@ Configuration configuration({ concurrency: concurrency, shardIndex: shardIndex, totalShards: totalShards, + shardBy: shardBy, testSelections: testSelections, foldTraceExcept: foldTraceExcept, foldTraceOnly: foldTraceOnly, diff --git a/pkgs/test_api/test/import_restrictions_test.dart b/pkgs/test_api/test/import_restrictions_test.dart index 9256d4c0f..421c065f3 100644 --- a/pkgs/test_api/test/import_restrictions_test.dart +++ b/pkgs/test_api/test/import_restrictions_test.dart @@ -36,10 +36,11 @@ void main() { entryPoints, )) { for (final import in source.imports) { - expect(import.pathSegments.skip(1).take(2), [ - 'src', - 'backend', - ], reason: 'Invalid import from ${source.uri} : $import'); + expect( + import.pathSegments.skip(1).take(2), + ['src', 'backend'], + reason: 'Invalid import from ${source.uri} : $import', + ); } } }); diff --git a/pkgs/test_core/lib/src/runner.dart b/pkgs/test_core/lib/src/runner.dart index 455622f81..18dc7cad3 100644 --- a/pkgs/test_core/lib/src/runner.dart +++ b/pkgs/test_core/lib/src/runner.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'package:async/async.dart'; import 'package:boolean_selector/boolean_selector.dart'; +import 'package:path/path.dart' as p; import 'package:stack_trace/stack_trace.dart'; import 'package:test_api/backend.dart' show PlatformSelector, Runtime, SuitePlatform; @@ -269,25 +270,76 @@ class Runner { /// Only tests that match [_config.patterns] will be included in the /// suites once they're loaded. Stream _loadSuites() { - return StreamGroup.merge( - _config.testSelections.entries.map((pathEntry) { + final Stream source; + if (_config.totalShards != null && _config.shardBy == 'file') { + final allFiles = []; + for (var pathEntry in _config.testSelections.entries) { final testPath = pathEntry.key; - final testSelections = pathEntry.value; - final suiteConfig = _config.suiteDefaults.selectTests(testSelections); if (Directory(testPath).existsSync()) { - return _loader.loadDir(testPath, suiteConfig); + allFiles.addAll( + Directory(testPath) + .listSync(recursive: true) + .whereType() + .map((f) => f.path) + .where((path) => _config.filename.matches(p.basename(path))), + ); } else if (File(testPath).existsSync()) { - return _loader.loadFile(testPath, suiteConfig); + allFiles.add(testPath); } else { - return Stream.fromIterable([ - LoadSuite.forLoadException( - LoadException(testPath, 'Does not exist.'), - suiteConfig, - ), - ]); + allFiles.add(testPath); } - }), - ).map((loadSuite) { + } + + allFiles.sort(); + + final shardSize = allFiles.length / _config.totalShards!; + final shardStart = (shardSize * _config.shardIndex!).round(); + final shardEnd = (shardSize * (_config.shardIndex! + 1)).round(); + final shardFiles = allFiles.sublist(shardStart, shardEnd); + + source = StreamGroup.merge( + shardFiles.map((path) { + final key = _config.testSelections.keys.firstWhere( + (k) => path == k || p.isWithin(k, path), + orElse: () => _config.testSelections.keys.first, + ); + final testSelections = _config.testSelections[key]!; + final suiteConfig = _config.suiteDefaults.selectTests(testSelections); + + if (!File(path).existsSync() && !Directory(path).existsSync()) { + return Stream.fromIterable([ + LoadSuite.forLoadException( + LoadException(path, 'Does not exist.'), + suiteConfig, + ), + ]); + } + return _loader.loadFile(path, suiteConfig); + }), + ); + } else { + source = StreamGroup.merge( + _config.testSelections.entries.map((pathEntry) { + final testPath = pathEntry.key; + final testSelections = pathEntry.value; + final suiteConfig = _config.suiteDefaults.selectTests(testSelections); + if (Directory(testPath).existsSync()) { + return _loader.loadDir(testPath, suiteConfig); + } else if (File(testPath).existsSync()) { + return _loader.loadFile(testPath, suiteConfig); + } else { + return Stream.fromIterable([ + LoadSuite.forLoadException( + LoadException(testPath, 'Does not exist.'), + suiteConfig, + ), + ]); + } + }), + ); + } + + return source.map((loadSuite) { return loadSuite.changeSuite((suite) { _warnForUnknownTags(suite); @@ -482,15 +534,15 @@ class Runner { return 'the suite itself'; } - /// If sharding is enabled, filters [suite] to only include the tests that - /// should be run in this shard. + /// If sharding by "test" is enabled, filters [suite] to only include the + /// tests that should be run in this shard. /// /// We just take a slice of the tests in each suite corresponding to the shard - /// index. This makes the tests pretty tests across shards, and since the + /// index. This makes the tests pretty even across shards, and since the /// tests are continuous, makes us more likely to be able to re-use /// `setUpAll()` logic. RunnerSuite _shardSuite(RunnerSuite suite) { - if (_config.totalShards == null) return suite; + if (_config.totalShards == null || _config.shardBy == 'file') return suite; var shardSize = suite.group.testCount / _config.totalShards!; var shardIndex = _config.shardIndex!; diff --git a/pkgs/test_core/lib/src/runner/configuration.dart b/pkgs/test_core/lib/src/runner/configuration.dart index 3f432ada7..aedd15243 100644 --- a/pkgs/test_core/lib/src/runner/configuration.dart +++ b/pkgs/test_core/lib/src/runner/configuration.dart @@ -130,6 +130,10 @@ class Configuration { /// See [shardIndex] for details. final int? totalShards; + /// How to distribute tests across shards. + String get shardBy => _shardBy ?? 'test'; + final String? _shardBy; + /// The list of packages to fold when producing [StackTrace]s. Set get foldTraceExcept => _foldTraceExcept ?? {}; final Set? _foldTraceExcept; @@ -283,6 +287,7 @@ class Configuration { required int? concurrency, required int? shardIndex, required int? totalShards, + required String? shardBy, required Map>? testSelections, required Iterable? foldTraceExcept, required Iterable? foldTraceOnly, @@ -340,6 +345,7 @@ class Configuration { concurrency: concurrency, shardIndex: shardIndex, totalShards: totalShards, + shardBy: shardBy, testSelections: testSelections, foldTraceExcept: foldTraceExcept, foldTraceOnly: foldTraceOnly, @@ -403,6 +409,7 @@ class Configuration { int? concurrency, int? shardIndex, int? totalShards, + String? shardBy, Map>? testSelections, Iterable? foldTraceExcept, Iterable? foldTraceOnly, @@ -458,6 +465,7 @@ class Configuration { concurrency: concurrency, shardIndex: shardIndex, totalShards: totalShards, + shardBy: shardBy, testSelections: testSelections, foldTraceExcept: foldTraceExcept, foldTraceOnly: foldTraceOnly, @@ -531,6 +539,7 @@ class Configuration { concurrency: null, shardIndex: null, totalShards: null, + shardBy: null, testSelections: null, filename: null, chosenPresets: null, @@ -599,6 +608,7 @@ class Configuration { concurrency: null, shardIndex: null, totalShards: null, + shardBy: null, testSelections: null, foldTraceExcept: null, foldTraceOnly: null, @@ -669,6 +679,7 @@ class Configuration { coveragePackages: null, shardIndex: null, totalShards: null, + shardBy: null, testSelections: null, foldTraceExcept: null, foldTraceOnly: null, @@ -735,6 +746,7 @@ class Configuration { concurrency: null, shardIndex: null, totalShards: null, + shardBy: null, foldTraceExcept: null, foldTraceOnly: null, chosenPresets: null, @@ -806,6 +818,7 @@ class Configuration { required int? concurrency, required this.shardIndex, required this.totalShards, + required String? shardBy, required Map>? testSelections, required Iterable? foldTraceExcept, required Iterable? foldTraceOnly, @@ -821,7 +834,8 @@ class Configuration { required BooleanSelector? excludeTags, required Iterable? globalPatterns, required SuiteConfiguration? suiteDefaults, - }) : _help = help, + }) : _shardBy = shardBy, + _help = help, _version = version, _pauseAfterLoad = pauseAfterLoad, _debug = debug, @@ -899,6 +913,7 @@ class Configuration { concurrency: null, shardIndex: null, totalShards: null, + shardBy: null, testSelections: null, foldTraceExcept: null, foldTraceOnly: null, @@ -1002,6 +1017,7 @@ class Configuration { concurrency: other._concurrency ?? _concurrency, shardIndex: other.shardIndex ?? shardIndex, totalShards: other.totalShards ?? totalShards, + shardBy: other._shardBy ?? _shardBy, testSelections: other._testSelections ?? _testSelections, foldTraceExcept: foldTraceExcept, foldTraceOnly: foldTraceOnly, @@ -1059,6 +1075,7 @@ class Configuration { int? concurrency, int? shardIndex, int? totalShards, + String? shardBy, Map>? testSelections, Iterable? exceptPackages, Iterable? onlyPackages, @@ -1110,6 +1127,7 @@ class Configuration { concurrency: concurrency ?? _concurrency, shardIndex: shardIndex ?? this.shardIndex, totalShards: totalShards ?? this.totalShards, + shardBy: shardBy ?? _shardBy, testSelections: testSelections ?? _testSelections, foldTraceExcept: exceptPackages ?? _foldTraceExcept, foldTraceOnly: onlyPackages ?? _foldTraceOnly, diff --git a/pkgs/test_core/lib/src/runner/configuration/args.dart b/pkgs/test_core/lib/src/runner/configuration/args.dart index 1714784f3..415409a64 100644 --- a/pkgs/test_core/lib/src/runner/configuration/args.dart +++ b/pkgs/test_core/lib/src/runner/configuration/args.dart @@ -129,6 +129,12 @@ final ArgParser _parser = (() { 'shard-index', help: 'The index of this test runner invocation (of --total-shards).', ); + parser.addOption( + 'shard-by', + help: 'How to distribute tests across shards.', + allowed: ['test', 'file'], + defaultsTo: 'test', + ); parser.addOption( 'pub-serve', help: '[Removed] The port of a pub serve instance serving "test/".', @@ -379,6 +385,7 @@ class _Parser { var shardIndex = _parseOption('shard-index', int.parse); var totalShards = _parseOption('total-shards', int.parse); + var shardBy = _ifParsed('shard-by') as String?; if ((shardIndex == null) != (totalShards == null)) { throw const FormatException( '--shard-index and --total-shards may only be passed together.', @@ -477,6 +484,7 @@ class _Parser { concurrency: _parseOption('concurrency', int.parse), shardIndex: shardIndex, totalShards: totalShards, + shardBy: shardBy, timeout: _parseOption('timeout', Timeout.parse), suiteLoadTimeout: _parseOption('suite-load-timeout', Timeout.parse), globalPatterns: patterns, From 123f4045fc366aeeb990324f57ac89433b4f88ad Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Tue, 26 May 2026 07:53:08 +0000 Subject: [PATCH 2/6] dartfmt --- pkgs/matcher/lib/src/core_matchers.dart | 3 +- pkgs/matcher/lib/src/equals_matcher.dart | 6 +- .../matcher/lib/src/expect/async_matcher.dart | 3 +- pkgs/matcher/lib/src/expect/expect.dart | 50 ++--- pkgs/matcher/lib/src/expect/expect_async.dart | 199 ++++++++---------- .../lib/src/expect/future_matchers.dart | 3 +- pkgs/matcher/lib/src/expect/never_called.dart | 3 +- .../lib/src/expect/stream_matcher.dart | 114 +++++----- .../lib/src/expect/stream_matchers.dart | 19 +- pkgs/matcher/lib/src/feature_matcher.dart | 3 +- pkgs/matcher/lib/src/having_matcher.dart | 23 +- pkgs/matcher/lib/src/interfaces.dart | 3 +- pkgs/matcher/lib/src/iterable_matchers.dart | 49 +++-- pkgs/matcher/lib/src/numeric_matchers.dart | 8 +- pkgs/matcher/lib/src/order_matchers.dart | 12 +- pkgs/matcher/lib/src/string_matchers.dart | 26 +-- pkgs/matcher/lib/src/type_matcher.dart | 7 +- pkgs/matcher/test/core_matchers_test.dart | 9 +- pkgs/matcher/test/custom_matcher_test.dart | 2 +- pkgs/matcher/test/escape_test.dart | 3 +- pkgs/matcher/test/expect_async_test.dart | 3 +- pkgs/matcher/test/having_test.dart | 8 +- pkgs/matcher/test/matcher/prints_test.dart | 3 +- 23 files changed, 272 insertions(+), 287 deletions(-) diff --git a/pkgs/matcher/lib/src/core_matchers.dart b/pkgs/matcher/lib/src/core_matchers.dart index 280b5233c..7c80804b5 100644 --- a/pkgs/matcher/lib/src/core_matchers.dart +++ b/pkgs/matcher/lib/src/core_matchers.dart @@ -328,8 +328,7 @@ class _In extends FeatureMatcher { Matcher predicate( bool Function(T) f, [ String description = 'satisfies function', -]) => - _Predicate(f, description); +]) => _Predicate(f, description); class _Predicate extends FeatureMatcher { final bool Function(T) _matcher; diff --git a/pkgs/matcher/lib/src/equals_matcher.dart b/pkgs/matcher/lib/src/equals_matcher.dart index 75fe9d038..7cb08e1ca 100644 --- a/pkgs/matcher/lib/src/equals_matcher.dart +++ b/pkgs/matcher/lib/src/equals_matcher.dart @@ -120,7 +120,7 @@ class _DeepMatcher extends Matcher { if (actual is Iterable) { var expectedIterator = expected.iterator; var actualIterator = actual.iterator; - for (var index = 0;; index++) { + for (var index = 0; ; index++) { // Advance in lockstep. var expectedNext = expectedIterator.moveNext(); var actualNext = actualIterator.moveNext(); @@ -379,6 +379,6 @@ class _Mismatch { }); _Mismatch.simple(this.location, this.actual, String problem) - : describeProblem = ((description, verbose) => description.add(problem)), - instead = false; + : describeProblem = ((description, verbose) => description.add(problem)), + instead = false; } diff --git a/pkgs/matcher/lib/src/expect/async_matcher.dart b/pkgs/matcher/lib/src/expect/async_matcher.dart index fba0f1bc5..13152456f 100644 --- a/pkgs/matcher/lib/src/expect/async_matcher.dart +++ b/pkgs/matcher/lib/src/expect/async_matcher.dart @@ -68,6 +68,5 @@ abstract class AsyncMatcher extends Matcher { Description mismatchDescription, Map matchState, bool verbose, - ) => - StringDescription(matchState[this] as String); + ) => StringDescription(matchState[this] as String); } diff --git a/pkgs/matcher/lib/src/expect/expect.dart b/pkgs/matcher/lib/src/expect/expect.dart index 4d04ef4ed..f1e0b9192 100644 --- a/pkgs/matcher/lib/src/expect/expect.dart +++ b/pkgs/matcher/lib/src/expect/expect.dart @@ -21,13 +21,14 @@ import 'util/pretty_print.dart'; /// The type used for functions that can be used to build up error reports /// upon failures in [expect]. @Deprecated('Will be removed in 0.13.0.') -typedef ErrorFormatter = String Function( - Object? actual, - Matcher matcher, - String? reason, - Map matchState, - bool verbose, -); +typedef ErrorFormatter = + String Function( + Object? actual, + Matcher matcher, + String? reason, + Map matchState, + bool verbose, + ); /// Assert that [actual] matches [matcher]. /// @@ -87,8 +88,7 @@ Future expectLater( dynamic matcher, { String? reason, Object? /* String|bool */ skip, -}) => - _expect(actual, matcher, reason: reason, skip: skip); +}) => _expect(actual, matcher, reason: reason, skip: skip); /// The implementation of [expect] and [expectLater]. Future _expect( @@ -149,21 +149,23 @@ Future _expect( fail(formatFailure(matcher, actual, result, reason: reason)); } else if (result is Future) { final outstandingWork = test.markPending(); - return result.then((realResult) { - if (realResult == null) return; - fail( - formatFailure( - matcher as Matcher, - actual, - realResult as String, - reason: reason, - ), - ); - }).whenComplete( - // Always remove this, in case the failure is caught and handled - // gracefully. - outstandingWork.complete, - ); + return result + .then((realResult) { + if (realResult == null) return; + fail( + formatFailure( + matcher as Matcher, + actual, + realResult as String, + reason: reason, + ), + ); + }) + .whenComplete( + // Always remove this, in case the failure is caught and handled + // gracefully. + outstandingWork.complete, + ); } return Future.sync(() {}); diff --git a/pkgs/matcher/lib/src/expect/expect_async.dart b/pkgs/matcher/lib/src/expect/expect_async.dart index d03492e67..57def17f2 100644 --- a/pkgs/matcher/lib/src/expect/expect_async.dart +++ b/pkgs/matcher/lib/src/expect/expect_async.dart @@ -80,13 +80,14 @@ class _ExpectedFunction { String? id, String? reason, bool Function()? isDone, - }) : _callback = callback, - _minExpectedCalls = minExpected, - _maxExpectedCalls = - (maxExpected == 0 && minExpected > 0) ? minExpected : maxExpected, - _isDone = isDone, - _reason = reason == null ? '' : '\n$reason', - _id = _makeCallbackId(id, callback) { + }) : _callback = callback, + _minExpectedCalls = minExpected, + _maxExpectedCalls = (maxExpected == 0 && minExpected > 0) + ? minExpected + : maxExpected, + _isDone = isDone, + _reason = reason == null ? '' : '\n$reason', + _id = _makeCallbackId(id, callback) { try { _test = TestHandle.current; } on OutsideTestException { @@ -161,16 +162,14 @@ class _ExpectedFunction { Object? a0 = placeholder, Object? a1 = placeholder, Object? a2 = placeholder, - ]) => - max6(a0, a1, a2); + ]) => max6(a0, a1, a2); T max4([ Object? a0 = placeholder, Object? a1 = placeholder, Object? a2 = placeholder, Object? a3 = placeholder, - ]) => - max6(a0, a1, a2, a3); + ]) => max6(a0, a1, a2, a3); T max5([ Object? a0 = placeholder, @@ -178,8 +177,7 @@ class _ExpectedFunction { Object? a2 = placeholder, Object? a3 = placeholder, Object? a4 = placeholder, - ]) => - max6(a0, a1, a2, a3, a4); + ]) => max6(a0, a1, a2, a3, a4); T max6([ Object? a0 = placeholder, @@ -188,8 +186,7 @@ class _ExpectedFunction { Object? a3 = placeholder, Object? a4 = placeholder, Object? a5 = placeholder, - ]) => - _run([a0, a1, a2, a3, a4, a5].where((a) => a != placeholder)); + ]) => _run([a0, a1, a2, a3, a4, a5].where((a) => a != placeholder)); /// Runs the wrapped function with [args] and returns its return value. T _run(Iterable args) { @@ -240,14 +237,13 @@ Function expectAsync( int max = 0, String? id, String? reason, -}) => - _ExpectedFunction( - callback, - count, - max, - id: id, - reason: reason, - ).func; +}) => _ExpectedFunction( + callback, + count, + max, + id: id, + reason: reason, +).func; /// Informs the framework that the given [callback] of arity 0 is expected to be /// called [count] number of times (by default 1). @@ -276,8 +272,7 @@ Func0 expectAsync0( int max = 0, String? id, String? reason, -}) => - _ExpectedFunction(callback, count, max, id: id, reason: reason).max0; +}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max0; /// Informs the framework that the given [callback] of arity 1 is expected to be /// called [count] number of times (by default 1). @@ -306,8 +301,7 @@ Func1 expectAsync1( int max = 0, String? id, String? reason, -}) => - _ExpectedFunction(callback, count, max, id: id, reason: reason).max1; +}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max1; /// Informs the framework that the given [callback] of arity 2 is expected to be /// called [count] number of times (by default 1). @@ -336,8 +330,7 @@ Func2 expectAsync2( int max = 0, String? id, String? reason, -}) => - _ExpectedFunction(callback, count, max, id: id, reason: reason).max2; +}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max2; /// Informs the framework that the given [callback] of arity 3 is expected to be /// called [count] number of times (by default 1). @@ -366,8 +359,7 @@ Func3 expectAsync3( int max = 0, String? id, String? reason, -}) => - _ExpectedFunction(callback, count, max, id: id, reason: reason).max3; +}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max3; /// Informs the framework that the given [callback] of arity 4 is expected to be /// called [count] number of times (by default 1). @@ -396,8 +388,7 @@ Func4 expectAsync4( int max = 0, String? id, String? reason, -}) => - _ExpectedFunction(callback, count, max, id: id, reason: reason).max4; +}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max4; /// Informs the framework that the given [callback] of arity 5 is expected to be /// called [count] number of times (by default 1). @@ -426,8 +417,7 @@ Func5 expectAsync5( int max = 0, String? id, String? reason, -}) => - _ExpectedFunction(callback, count, max, id: id, reason: reason).max5; +}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max5; /// Informs the framework that the given [callback] of arity 6 is expected to be /// called [count] number of times (by default 1). @@ -456,8 +446,7 @@ Func6 expectAsync6( int max = 0, String? id, String? reason, -}) => - _ExpectedFunction(callback, count, max, id: id, reason: reason).max6; +}) => _ExpectedFunction(callback, count, max, id: id, reason: reason).max6; /// This function is deprecated because it doesn't work well with strong mode. /// Use [expectAsyncUntil0], [expectAsyncUntil1], @@ -469,15 +458,14 @@ Function expectAsyncUntil( bool Function() isDone, { String? id, String? reason, -}) => - _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, - ).func; +}) => _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, +).func; /// Informs the framework that the given [callback] of arity 0 is expected to be /// called until [isDone] returns true. @@ -501,15 +489,14 @@ Func0 expectAsyncUntil0( bool Function() isDone, { String? id, String? reason, -}) => - _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, - ).max0; +}) => _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, +).max0; /// Informs the framework that the given [callback] of arity 1 is expected to be /// called until [isDone] returns true. @@ -533,15 +520,14 @@ Func1 expectAsyncUntil1( bool Function() isDone, { String? id, String? reason, -}) => - _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, - ).max1; +}) => _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, +).max1; /// Informs the framework that the given [callback] of arity 2 is expected to be /// called until [isDone] returns true. @@ -565,15 +551,14 @@ Func2 expectAsyncUntil2( bool Function() isDone, { String? id, String? reason, -}) => - _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, - ).max2; +}) => _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, +).max2; /// Informs the framework that the given [callback] of arity 3 is expected to be /// called until [isDone] returns true. @@ -597,15 +582,14 @@ Func3 expectAsyncUntil3( bool Function() isDone, { String? id, String? reason, -}) => - _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, - ).max3; +}) => _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, +).max3; /// Informs the framework that the given [callback] of arity 4 is expected to be /// called until [isDone] returns true. @@ -629,15 +613,14 @@ Func4 expectAsyncUntil4( bool Function() isDone, { String? id, String? reason, -}) => - _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, - ).max4; +}) => _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, +).max4; /// Informs the framework that the given [callback] of arity 5 is expected to be /// called until [isDone] returns true. @@ -661,15 +644,14 @@ Func5 expectAsyncUntil5( bool Function() isDone, { String? id, String? reason, -}) => - _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, - ).max5; +}) => _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, +).max5; /// Informs the framework that the given [callback] of arity 6 is expected to be /// called until [isDone] returns true. @@ -693,12 +675,11 @@ Func6 expectAsyncUntil6( bool Function() isDone, { String? id, String? reason, -}) => - _ExpectedFunction( - callback, - 0, - -1, - id: id, - reason: reason, - isDone: isDone, - ).max6; +}) => _ExpectedFunction( + callback, + 0, + -1, + id: id, + reason: reason, + isDone: isDone, +).max6; diff --git a/pkgs/matcher/lib/src/expect/future_matchers.dart b/pkgs/matcher/lib/src/expect/future_matchers.dart index b9aaf2283..b22ab375a 100644 --- a/pkgs/matcher/lib/src/expect/future_matchers.dart +++ b/pkgs/matcher/lib/src/expect/future_matchers.dart @@ -39,8 +39,7 @@ final Matcher completes = const _Completes(null); Matcher completion( Object? matcher, [ @Deprecated('this parameter is ignored') String? description, -]) => - _Completes(wrapMatcher(matcher)); +]) => _Completes(wrapMatcher(matcher)); class _Completes extends AsyncMatcher { final Matcher? _matcher; diff --git a/pkgs/matcher/lib/src/expect/never_called.dart b/pkgs/matcher/lib/src/expect/never_called.dart index 75434e2a1..4da7c5388 100644 --- a/pkgs/matcher/lib/src/expect/never_called.dart +++ b/pkgs/matcher/lib/src/expect/never_called.dart @@ -35,7 +35,8 @@ Null Function([ Object?, Object?, Object?, -]) get neverCalled { +]) +get neverCalled { // Make sure the test stays alive long enough to call the function if it's // going to. expect(pumpEventQueue(), completes); diff --git a/pkgs/matcher/lib/src/expect/stream_matcher.dart b/pkgs/matcher/lib/src/expect/stream_matcher.dart index 15503de97..bd62b315b 100644 --- a/pkgs/matcher/lib/src/expect/stream_matcher.dart +++ b/pkgs/matcher/lib/src/expect/stream_matcher.dart @@ -140,61 +140,65 @@ class _StreamMatcher extends AsyncMatcher implements StreamMatcher { // for an invalid argument type. var transaction = queue.startTransaction(); var copy = transaction.newQueue(); - return matchQueue(copy).then( - (result) async { - // Accept the transaction if the result is null, indicating that the - // match succeeded. - if (result == null) { - transaction.commit(copy); - return null; - } - - // Get a list of events emitted by the stream so we can emit them as - // part of the error message. - var replay = transaction.newQueue(); - var events = []; - var subscription = Result.captureStreamTransformer - .bind(replay.rest.cast()) - .listen(events.add, onDone: () => events.add(null)); - - // Wait on a timer tick so all buffered events are emitted. - await Future.delayed(Duration.zero); - _unawaited(subscription.cancel()); - - var eventsString = events.map((event) { - if (event == null) { - return 'x Stream closed.'; - } else if (event.isValue) { - return addBullet(event.asValue!.value.toString()); - } else { - var error = event.asError!; - var chain = TestHandle.current.formatStackTrace( - error.stackTrace, - ); - var text = '${error.error}\n$chain'; - return indent(text, first: '! '); - } - }).join('\n'); - if (eventsString.isEmpty) eventsString = 'no events'; - - transaction.reject(); - - var buffer = StringBuffer(); - buffer.writeln(indent(eventsString, first: 'emitted ')); - if (result.isNotEmpty) { - buffer.writeln(indent(result, first: ' which ')); - } - return buffer.toString().trimRight(); - }, - onError: (Object error) { - transaction.reject(); - // ignore: only_throw_errors - throw error; - }, - ).then((result) { - if (shouldCancelQueue) queue.cancel(); - return result; - }); + return matchQueue(copy) + .then( + (result) async { + // Accept the transaction if the result is null, indicating that the + // match succeeded. + if (result == null) { + transaction.commit(copy); + return null; + } + + // Get a list of events emitted by the stream so we can emit them as + // part of the error message. + var replay = transaction.newQueue(); + var events = []; + var subscription = Result.captureStreamTransformer + .bind(replay.rest.cast()) + .listen(events.add, onDone: () => events.add(null)); + + // Wait on a timer tick so all buffered events are emitted. + await Future.delayed(Duration.zero); + _unawaited(subscription.cancel()); + + var eventsString = events + .map((event) { + if (event == null) { + return 'x Stream closed.'; + } else if (event.isValue) { + return addBullet(event.asValue!.value.toString()); + } else { + var error = event.asError!; + var chain = TestHandle.current.formatStackTrace( + error.stackTrace, + ); + var text = '${error.error}\n$chain'; + return indent(text, first: '! '); + } + }) + .join('\n'); + if (eventsString.isEmpty) eventsString = 'no events'; + + transaction.reject(); + + var buffer = StringBuffer(); + buffer.writeln(indent(eventsString, first: 'emitted ')); + if (result.isNotEmpty) { + buffer.writeln(indent(result, first: ' which ')); + } + return buffer.toString().trimRight(); + }, + onError: (Object error) { + transaction.reject(); + // ignore: only_throw_errors + throw error; + }, + ) + .then((result) { + if (shouldCancelQueue) queue.cancel(); + return result; + }); } @override diff --git a/pkgs/matcher/lib/src/expect/stream_matchers.dart b/pkgs/matcher/lib/src/expect/stream_matchers.dart index db351d2c8..2e2e494f8 100644 --- a/pkgs/matcher/lib/src/expect/stream_matchers.dart +++ b/pkgs/matcher/lib/src/expect/stream_matchers.dart @@ -97,7 +97,8 @@ StreamMatcher emitsAnyOf(Iterable matchers) { } if (streamMatchers.length == 1) return streamMatchers.first; - var description = 'do one of the following:\n' + var description = + 'do one of the following:\n' '${bullet(streamMatchers.map((matcher) => matcher.description))}'; return StreamMatcher((queue) async { @@ -174,7 +175,8 @@ StreamMatcher emitsInOrder(Iterable matchers) { var streamMatchers = matchers.map(emits).toList(); if (streamMatchers.length == 1) return streamMatchers.first; - var description = 'do the following in order:\n' + var description = + 'do the following in order:\n' '${bullet(streamMatchers.map((matcher) => matcher.description))}'; return StreamMatcher((queue) async { @@ -206,11 +208,11 @@ StreamMatcher emitsThrough(Object? matcher) { var failures = []; Future tryHere() => queue.withTransaction((copy) async { - var result = await streamMatcher.matchQueue(copy); - if (result == null) return true; - failures.add(result); - return false; - }); + var result = await streamMatcher.matchQueue(copy); + if (result == null) return true; + failures.add(result); + return false; + }); while (await queue.hasNext) { if (await tryHere()) return null; @@ -319,7 +321,8 @@ Future _tryMatch(StreamQueue queue, StreamMatcher matcher) { StreamMatcher emitsInAnyOrder(Iterable matchers) { var streamMatchers = matchers.map(emits).toSet(); if (streamMatchers.length == 1) return streamMatchers.first; - var description = 'do the following in any order:\n' + var description = + 'do the following in any order:\n' '${bullet(streamMatchers.map((matcher) => matcher.description))}'; return StreamMatcher( diff --git a/pkgs/matcher/lib/src/feature_matcher.dart b/pkgs/matcher/lib/src/feature_matcher.dart index 709f8817f..413e7eb40 100644 --- a/pkgs/matcher/lib/src/feature_matcher.dart +++ b/pkgs/matcher/lib/src/feature_matcher.dart @@ -41,6 +41,5 @@ abstract class FeatureMatcher extends TypeMatcher { Description mismatchDescription, Map matchState, bool verbose, - ) => - mismatchDescription; + ) => mismatchDescription; } diff --git a/pkgs/matcher/lib/src/having_matcher.dart b/pkgs/matcher/lib/src/having_matcher.dart index 5756dfd63..9cb31318c 100644 --- a/pkgs/matcher/lib/src/having_matcher.dart +++ b/pkgs/matcher/lib/src/having_matcher.dart @@ -27,23 +27,22 @@ class HavingMatcher implements TypeMatcher { dynamic matcher, Iterable<_FunctionMatcher>? existing, ) : _functionMatchers = [ - ...?existing, - _FunctionMatcher(description, feature, matcher), - ]; + ...?existing, + _FunctionMatcher(description, feature, matcher), + ]; @override TypeMatcher having( Object? Function(T) feature, String description, dynamic matcher, - ) => - HavingMatcher._fromExisting( - _parent, - description, - feature, - matcher, - _functionMatchers, - ); + ) => HavingMatcher._fromExisting( + _parent, + description, + feature, + matcher, + _functionMatchers, + ); @override bool matches(dynamic item, Map matchState) { @@ -85,7 +84,7 @@ class _FunctionMatcher extends CustomMatcher { final Object? Function(T value) _feature; _FunctionMatcher(String name, this._feature, Object? matcher) - : super('`$name`:', '`$name`', matcher); + : super('`$name`:', '`$name`', matcher); @override Object? featureValueOf(covariant T actual) => _feature(actual); diff --git a/pkgs/matcher/lib/src/interfaces.dart b/pkgs/matcher/lib/src/interfaces.dart index 97c817d17..7fc700878 100644 --- a/pkgs/matcher/lib/src/interfaces.dart +++ b/pkgs/matcher/lib/src/interfaces.dart @@ -59,6 +59,5 @@ abstract class Matcher { Description mismatchDescription, Map matchState, bool verbose, - ) => - mismatchDescription; + ) => mismatchDescription; } diff --git a/pkgs/matcher/lib/src/iterable_matchers.dart b/pkgs/matcher/lib/src/iterable_matchers.dart index b79d16e11..087bbd280 100644 --- a/pkgs/matcher/lib/src/iterable_matchers.dart +++ b/pkgs/matcher/lib/src/iterable_matchers.dart @@ -140,8 +140,8 @@ class _UnorderedEquals extends _UnorderedMatches { final List _expectedValues; _UnorderedEquals(Iterable expected) - : _expectedValues = expected.toList(), - super(expected.map(equals)); + : _expectedValues = expected.toList(), + super(expected.map(equals)); @override Description describe(Description description) => description @@ -168,8 +168,8 @@ class _UnorderedMatches extends _IterableMatcher { final bool _allowUnmatchedValues; _UnorderedMatches(Iterable expected, {bool allowUnmatchedValues = false}) - : _expected = expected.map(wrapMatcher).toList(), - _allowUnmatchedValues = allowUnmatchedValues; + : _expected = expected.map(wrapMatcher).toList(), + _allowUnmatchedValues = allowUnmatchedValues; String? _test(List values) { // Check the lengths are the same. @@ -193,21 +193,25 @@ class _UnorderedMatches extends _IterableMatcher { for (var valueIndex = 0; valueIndex < values.length; valueIndex++) { _findPairing(edges, valueIndex, matched); } - for (var matcherIndex = 0; - matcherIndex < _expected.length; - matcherIndex++) { + for ( + var matcherIndex = 0; + matcherIndex < _expected.length; + matcherIndex++ + ) { if (matched[matcherIndex] == null) { final description = StringDescription() .add('has no match for ') .addDescriptionOf(_expected[matcherIndex]) .add(' at index $matcherIndex'); - final remainingUnmatched = - matched.sublist(matcherIndex + 1).where((m) => m == null).length; + final remainingUnmatched = matched + .sublist(matcherIndex + 1) + .where((m) => m == null) + .length; return remainingUnmatched == 0 ? description.toString() : description - .add(' along with $remainingUnmatched other unmatched') - .toString(); + .add(' along with $remainingUnmatched other unmatched') + .toString(); } } return null; @@ -229,8 +233,7 @@ class _UnorderedMatches extends _IterableMatcher { Description mismatchDescription, Map matchState, bool verbose, - ) => - mismatchDescription.add(_test(item.toList())!); + ) => mismatchDescription.add(_test(item.toList())!); /// Returns `true` if the value at [valueIndex] can be paired with some /// unmatched matcher and updates the state of [matched]. @@ -241,8 +244,7 @@ class _UnorderedMatches extends _IterableMatcher { List> edges, int valueIndex, List matched, - ) => - _findPairingInner(edges, valueIndex, matched, {}); + ) => _findPairingInner(edges, valueIndex, matched, {}); /// Implementation of [_findPairing], tracks [reserved] which are the /// matchers that have been used _during_ this search. @@ -279,8 +281,7 @@ Matcher pairwiseCompare( Iterable expected, bool Function(S, T) comparator, String description, -) => - _PairwiseCompare(expected, comparator, description); +) => _PairwiseCompare(expected, comparator, description); typedef _Comparator = bool Function(S a, T b); @@ -361,8 +362,8 @@ class _ContainsAll extends _UnorderedMatches { final Iterable _unwrappedExpected; _ContainsAll(Iterable expected) - : _unwrappedExpected = expected, - super(expected.map(wrapMatcher), allowUnmatchedValues: true); + : _unwrappedExpected = expected, + super(expected.map(wrapMatcher), allowUnmatchedValues: true); @override Description describe(Description description) => description.add('contains all of ').addDescriptionOf(_unwrappedExpected); @@ -413,8 +414,7 @@ class _ContainsAllInOrder extends _IterableMatcher { Description mismatchDescription, Map matchState, bool verbose, - ) => - mismatchDescription.add(_test(item, matchState)!); + ) => mismatchDescription.add(_test(item, matchState)!); } /// Matches [Iterable]s where exactly one element matches the expected @@ -463,8 +463,7 @@ class _ContainsOnce extends _IterableMatcher { Description mismatchDescription, Map matchState, bool verbose, - ) => - mismatchDescription.add(_test(item, matchState)!); + ) => mismatchDescription.add(_test(item, matchState)!); } /// Matches [Iterable]s which are sorted. @@ -488,8 +487,8 @@ class _IsSorted extends _IterableMatcher { final Comparator _compare; _IsSorted(K Function(T) keyOf, Comparator compare) - : _keyOf = keyOf, - _compare = compare; + : _keyOf = keyOf, + _compare = compare; @override bool typedMatches(Iterable item, Map matchState) { diff --git a/pkgs/matcher/lib/src/numeric_matchers.dart b/pkgs/matcher/lib/src/numeric_matchers.dart index 529a3fea8..22c3d7c5a 100644 --- a/pkgs/matcher/lib/src/numeric_matchers.dart +++ b/pkgs/matcher/lib/src/numeric_matchers.dart @@ -91,8 +91,8 @@ class _InRange extends FeatureMatcher { @override Description describe(Description description) => description.add( - 'be in range from ' - "$_low (${_lowMatchValue ? 'inclusive' : 'exclusive'}) to " - "$_high (${_highMatchValue ? 'inclusive' : 'exclusive'})", - ); + 'be in range from ' + "$_low (${_lowMatchValue ? 'inclusive' : 'exclusive'}) to " + "$_high (${_highMatchValue ? 'inclusive' : 'exclusive'})", + ); } diff --git a/pkgs/matcher/lib/src/order_matchers.dart b/pkgs/matcher/lib/src/order_matchers.dart index 21b48bf0c..d87c74ac7 100644 --- a/pkgs/matcher/lib/src/order_matchers.dart +++ b/pkgs/matcher/lib/src/order_matchers.dart @@ -12,12 +12,12 @@ Matcher greaterThan(Object value) => /// Returns a matcher which matches if the match argument is greater /// than or equal to the given [value]. Matcher greaterThanOrEqualTo(Object value) => _OrderingMatcher( - value, - true, - false, - true, - 'a value greater than or equal to', - ); + value, + true, + false, + true, + 'a value greater than or equal to', +); /// Returns a matcher which matches if the match argument is less /// than the given [value]. diff --git a/pkgs/matcher/lib/src/string_matchers.dart b/pkgs/matcher/lib/src/string_matchers.dart index 49c9fdc89..3d5225226 100644 --- a/pkgs/matcher/lib/src/string_matchers.dart +++ b/pkgs/matcher/lib/src/string_matchers.dart @@ -14,8 +14,8 @@ class _IsEqualIgnoringCase extends FeatureMatcher { final String _matchValue; _IsEqualIgnoringCase(String value) - : _value = value, - _matchValue = value.toLowerCase(); + : _value = value, + _matchValue = value.toLowerCase(); @override bool typedMatches(String item, Map matchState) => @@ -50,7 +50,7 @@ class _IsEqualIgnoringWhitespace extends FeatureMatcher { final String _matchValue; _IsEqualIgnoringWhitespace(String value) - : _matchValue = collapseWhitespace(value); + : _matchValue = collapseWhitespace(value); @override bool typedMatches(String item, Map matchState) => @@ -135,11 +135,11 @@ class _StringContainsInOrder extends FeatureMatcher { @override Description describe(Description description) => description.addAll( - 'a string containing ', - ', ', - ' in order', - _substrings, - ); + 'a string containing ', + ', ', + ' in order', + _substrings, + ); } /// Returns a matcher that matches if the match argument is a string and @@ -153,11 +153,11 @@ class _MatchesRegExp extends FeatureMatcher { final RegExp _regexp; _MatchesRegExp(Pattern re) - : _regexp = (re is String) - ? RegExp(re) - : (re is RegExp) - ? re - : throw ArgumentError('matches requires a regexp or string'); + : _regexp = (re is String) + ? RegExp(re) + : (re is RegExp) + ? re + : throw ArgumentError('matches requires a regexp or string'); @override bool typedMatches(String item, Map matchState) => _regexp.hasMatch(item); diff --git a/pkgs/matcher/lib/src/type_matcher.dart b/pkgs/matcher/lib/src/type_matcher.dart index 57b2105f9..179509987 100644 --- a/pkgs/matcher/lib/src/type_matcher.dart +++ b/pkgs/matcher/lib/src/type_matcher.dart @@ -66,8 +66,8 @@ class TypeMatcher extends Matcher { ) String? name, ]) : _name = - // ignore: deprecated_member_use_from_same_package - name; + // ignore: deprecated_member_use_from_same_package + name; /// Returns a new [TypeMatcher] that validates the existing type as well as /// a specific [feature] of the object with the provided [matcher]. @@ -88,8 +88,7 @@ class TypeMatcher extends Matcher { Object? Function(T) feature, String description, dynamic matcher, - ) => - HavingMatcher(this, description, feature, matcher); + ) => HavingMatcher(this, description, feature, matcher); @override Description describe(Description description) { diff --git a/pkgs/matcher/test/core_matchers_test.dart b/pkgs/matcher/test/core_matchers_test.dart index 19f5cb075..69866961e 100644 --- a/pkgs/matcher/test/core_matchers_test.dart +++ b/pkgs/matcher/test/core_matchers_test.dart @@ -243,7 +243,8 @@ void main() { 4, [], ]; - var reason1 = "Expected: [['foo', 'bar'], ['foo'], 4, []] " + var reason1 = + "Expected: [['foo', 'bar'], ['foo'], 4, []] " "Actual: [['foo', 'bar'], ['foo'], 3, []] " 'Which: at location [2] is <3> instead of <4>'; @@ -259,7 +260,8 @@ void main() { 4, [], ]; - var reason2 = "Expected: [['foo', 'bar'], ['foo'], 4, []] " + var reason2 = + "Expected: [['foo', 'bar'], ['foo'], 4, []] " "Actual: [['foo', 'barry'], ['foo'], 4, []] " "Which: at location [0][1] is 'barry' instead of 'bar'"; @@ -275,7 +277,8 @@ void main() { 4, {'foo': 'barry'}, ]; - var reason3 = "Expected: [['foo', 'bar'], ['foo'], 4, {'foo': 'barry'}] " + var reason3 = + "Expected: [['foo', 'bar'], ['foo'], 4, {'foo': 'barry'}] " "Actual: [['foo', 'bar'], ['foo'], 4, {'foo': 'bar'}] " "Which: at location [3]['foo'] is 'bar' instead of 'barry'"; diff --git a/pkgs/matcher/test/custom_matcher_test.dart b/pkgs/matcher/test/custom_matcher_test.dart index eb0b4ab30..16e2adc49 100644 --- a/pkgs/matcher/test/custom_matcher_test.dart +++ b/pkgs/matcher/test/custom_matcher_test.dart @@ -15,7 +15,7 @@ class _BadCustomMatcher extends CustomMatcher { class _HasPrice extends CustomMatcher { _HasPrice(Object? matcher) - : super('Widget with a price that is', 'price', matcher); + : super('Widget with a price that is', 'price', matcher); @override Object? featureValueOf(Object? actual) => (actual as Widget).price; } diff --git a/pkgs/matcher/test/escape_test.dart b/pkgs/matcher/test/escape_test.dart index ee58daae3..955458e07 100644 --- a/pkgs/matcher/test/escape_test.dart +++ b/pkgs/matcher/test/escape_test.dart @@ -44,7 +44,8 @@ void _testEscaping(String name, String source, String target) { expect( escaped == target, isTrue, - reason: 'Expected escaped value: $target\n' + reason: + 'Expected escaped value: $target\n' ' Actual escaped value: $escaped', ); }); diff --git a/pkgs/matcher/test/expect_async_test.dart b/pkgs/matcher/test/expect_async_test.dart index adb54710b..bb00951d7 100644 --- a/pkgs/matcher/test/expect_async_test.dart +++ b/pkgs/matcher/test/expect_async_test.dart @@ -178,8 +178,7 @@ void main() { }); group('with count', () { - test( - "won't allow the test to complete until it's called at least that " + test("won't allow the test to complete until it's called at least that " 'many times', () async { late void Function() callback; final monitor = TestCaseMonitor.start(() { diff --git a/pkgs/matcher/test/having_test.dart b/pkgs/matcher/test/having_test.dart index d2e02855c..89396ac5d 100644 --- a/pkgs/matcher/test/having_test.dart +++ b/pkgs/matcher/test/having_test.dart @@ -132,10 +132,10 @@ Matcher _hasPrice(Object matcher) => const TypeMatcher().having((e) => e.price, 'price', matcher); Matcher _badCustomMatcher() => const TypeMatcher().having( - (e) => throw Exception('bang'), - 'feature', - {1: 'a'}, - ); + (e) => throw Exception('bang'), + 'feature', + {1: 'a'}, +); class CustomRangeError extends RangeError { CustomRangeError.range( diff --git a/pkgs/matcher/test/matcher/prints_test.dart b/pkgs/matcher/test/matcher/prints_test.dart index b9d120553..6e63e6689 100644 --- a/pkgs/matcher/test/matcher/prints_test.dart +++ b/pkgs/matcher/test/matcher/prints_test.dart @@ -229,8 +229,7 @@ void main() { expectLater(() { scheduleMicrotask(() => print('hello!')); return completer.future; - }, prints('hello!\n')) - .then((_) { + }, prints('hello!\n')).then((_) { fired = true; }), ); From f5d7f1937bd4306753408a44aaeb317e50de2e69 Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Tue, 26 May 2026 07:55:48 +0000 Subject: [PATCH 3/6] revert formatting changes --- .../browser/expanded_reporter_test.dart | 26 ++-- .../runner/configuration/platform_test.dart | 48 +++---- pkgs/test/test/runner/node/runner_test.dart | 136 ++++++++---------- pkgs/test/test/runner/runner_test.dart | 68 ++++----- 4 files changed, 123 insertions(+), 155 deletions(-) diff --git a/pkgs/test/test/runner/browser/expanded_reporter_test.dart b/pkgs/test/test/runner/browser/expanded_reporter_test.dart index 7c9a4d01c..464b67bc1 100644 --- a/pkgs/test/test/runner/browser/expanded_reporter_test.dart +++ b/pkgs/test/test/runner/browser/expanded_reporter_test.dart @@ -13,10 +13,8 @@ import '../../io.dart'; void main() { setUpAll(precompileTestExecutable); - test( - 'prints the platform name when running on multiple platforms', - () async { - await d.file('test.dart', ''' + test('prints the platform name when running on multiple platforms', () async { + await d.file('test.dart', ''' import 'dart:async'; import 'package:test/test.dart'; @@ -26,15 +24,13 @@ void main() { } ''').create(); - var test = await runTest([ - '-r', 'expanded', '-p', 'chrome', '-p', 'vm', '-j', '1', // - 'test.dart', - ]); - - expect(test.stdoutStream(), emitsThrough(contains('[VM, Kernel]'))); - expect(test.stdout, emitsThrough(contains('[Chrome, Dart2Js]'))); - await test.shouldExit(0); - }, - tags: ['chrome'], - ); + var test = await runTest([ + '-r', 'expanded', '-p', 'chrome', '-p', 'vm', '-j', '1', // + 'test.dart', + ]); + + expect(test.stdoutStream(), emitsThrough(contains('[VM, Kernel]'))); + expect(test.stdout, emitsThrough(contains('[Chrome, Dart2Js]'))); + await test.shouldExit(0); + }, tags: ['chrome']); } diff --git a/pkgs/test/test/runner/configuration/platform_test.dart b/pkgs/test/test/runner/configuration/platform_test.dart index 9158a878e..1878d8283 100644 --- a/pkgs/test/test/runner/configuration/platform_test.dart +++ b/pkgs/test/test/runner/configuration/platform_test.dart @@ -18,21 +18,19 @@ void main() { setUpAll(precompileTestExecutable); group('on_platform', () { - test( - 'applies platform-specific configuration to matching tests', - () async { - await d - .file( - 'dart_test.yaml', - jsonEncode({ - 'on_platform': { - 'chrome': {'timeout': '0s'}, - }, - }), - ) - .create(); + test('applies platform-specific configuration to matching tests', () async { + await d + .file( + 'dart_test.yaml', + jsonEncode({ + 'on_platform': { + 'chrome': {'timeout': '0s'}, + }, + }), + ) + .create(); - await d.file('test.dart', ''' + await d.file('test.dart', ''' import 'dart:async'; import 'package:test/test.dart'; @@ -42,18 +40,16 @@ void main() { } ''').create(); - var test = await runTest(['-p', 'chrome,vm', 'test.dart']); - expect( - test.stdout, - containsInOrder([ - '-1: [Chrome, Dart2Js] test [E]', - '+1 -1: Some tests failed.', - ]), - ); - await test.shouldExit(1); - }, - tags: ['chrome'], - ); + var test = await runTest(['-p', 'chrome,vm', 'test.dart']); + expect( + test.stdout, + containsInOrder([ + '-1: [Chrome, Dart2Js] test [E]', + '+1 -1: Some tests failed.', + ]), + ); + await test.shouldExit(1); + }, tags: ['chrome']); test('supports platform selectors', () async { await d diff --git a/pkgs/test/test/runner/node/runner_test.dart b/pkgs/test/test/runner/node/runner_test.dart index ec0a288a3..33e1f4c3a 100644 --- a/pkgs/test/test/runner/node/runner_test.dart +++ b/pkgs/test/test/runner/node/runner_test.dart @@ -106,41 +106,33 @@ void main() { await test.shouldExit(1); }); - test( - "a test file doesn't have a main defined", - () async { - await d.file('test.dart', 'void foo() {}').create(); - - var test = await runTest(['-p', 'node', 'test.dart']); - expect( - test.stdout, - containsInOrder([ - '-1: loading test.dart [E]', - 'Failed to load "test.dart": No top-level main() function defined.', - ]), - ); - await test.shouldExit(1); - }, - skip: 'https://github.com/dart-lang/test/issues/894', - ); + test("a test file doesn't have a main defined", () async { + await d.file('test.dart', 'void foo() {}').create(); - test( - 'a test file has a non-function main', - () async { - await d.file('test.dart', 'int main;').create(); - - var test = await runTest(['-p', 'node', 'test.dart']); - expect( - test.stdout, - containsInOrder([ - '-1: loading test.dart [E]', - 'Failed to load "test.dart": Top-level main getter is not a function.', - ]), - ); - await test.shouldExit(1); - }, - skip: 'https://github.com/dart-lang/test/issues/894', - ); + var test = await runTest(['-p', 'node', 'test.dart']); + expect( + test.stdout, + containsInOrder([ + '-1: loading test.dart [E]', + 'Failed to load "test.dart": No top-level main() function defined.', + ]), + ); + await test.shouldExit(1); + }, skip: 'https://github.com/dart-lang/test/issues/894'); + + test('a test file has a non-function main', () async { + await d.file('test.dart', 'int main;').create(); + + var test = await runTest(['-p', 'node', 'test.dart']); + expect( + test.stdout, + containsInOrder([ + '-1: loading test.dart [E]', + 'Failed to load "test.dart": Top-level main getter is not a function.', + ]), + ); + await test.shouldExit(1); + }, skip: 'https://github.com/dart-lang/test/issues/894'); test('a test file has a main with arguments', () async { await d.file('test.dart', 'void main(arg) {}').create(); @@ -233,10 +225,8 @@ void main() { await test.shouldExit(1); }); - test( - 'runs failing tests that fail only on node (with dart2wasm)', - () async { - await d.file('test.dart', ''' + test('runs failing tests that fail only on node (with dart2wasm)', () async { + await d.file('test.dart', ''' import 'package:path/path.dart' as p; import 'package:test/test.dart'; @@ -249,31 +239,27 @@ void main() { } ''').create(); - var test = await runTest([ - '-p', - 'node', - '-p', - 'vm', - '-c', - 'dart2js', - '-c', - 'dart2wasm', - 'test.dart', - ]); - expect(test.stdout, emitsThrough(contains('+1 -2: Some tests failed.'))); - await test.shouldExit(1); - }, - skip: skipBelowMajorNodeVersion(22), - ); + var test = await runTest([ + '-p', + 'node', + '-p', + 'vm', + '-c', + 'dart2js', + '-c', + 'dart2wasm', + 'test.dart', + ]); + expect(test.stdout, emitsThrough(contains('+1 -2: Some tests failed.'))); + await test.shouldExit(1); + }, skip: skipBelowMajorNodeVersion(22)); - test( - 'gracefully handles wasm errors on old node versions', - () async { - // Old Node.JS versions can't read the WebAssembly modules emitted by - // dart2wasm. The node process exits before connecting to the server - // opened by the test runner, leading to timeouts. So, this is a - // regression test for https://github.com/dart-lang/test/pull/2259#issuecomment-2307868442 - await d.file('test.dart', ''' + test('gracefully handles wasm errors on old node versions', () async { + // Old Node.JS versions can't read the WebAssembly modules emitted by + // dart2wasm. The node process exits before connecting to the server + // opened by the test runner, leading to timeouts. So, this is a + // regression test for https://github.com/dart-lang/test/pull/2259#issuecomment-2307868442 + await d.file('test.dart', ''' import 'package:test/test.dart'; void main() { @@ -283,20 +269,18 @@ void main() { } ''').create(); - var test = await runTest(['-p', 'node', '-c', 'dart2wasm', 'test.dart']); - expect( - test.stdout, - emitsInOrder([ - emitsThrough( - contains('Node exited before connecting to the test channel.'), - ), - emitsThrough(contains('-1: Some tests failed.')), - ]), - ); - await test.shouldExit(1); - }, - skip: skipAboveMajorNodeVersion(21), - ); + var test = await runTest(['-p', 'node', '-c', 'dart2wasm', 'test.dart']); + expect( + test.stdout, + emitsInOrder([ + emitsThrough( + contains('Node exited before connecting to the test channel.'), + ), + emitsThrough(contains('-1: Some tests failed.')), + ]), + ); + await test.shouldExit(1); + }, skip: skipAboveMajorNodeVersion(21)); test('forwards prints from the Node test', () async { await d.file('test.dart', ''' diff --git a/pkgs/test/test/runner/runner_test.dart b/pkgs/test/test/runner/runner_test.dart index 7198fdf29..8a8594f81 100644 --- a/pkgs/test/test/runner/runner_test.dart +++ b/pkgs/test/test/runner/runner_test.dart @@ -842,27 +842,23 @@ void main() { }); for (var platform in ['vm', 'chrome']) { - test( - 'on the $platform platform', - () async { - var test = await runTest( - ['test.dart', '-p', platform], - vmArgs: ['--enable-experiment=non-nullable'], - ); - - await expectLater(test.stdout, emitsThrough(contains('int x;'))); - await test.shouldExit(1); - - // Test that they can be removed on subsequent runs as well - test = await runTest(['test.dart', '-p', platform]); - await expectLater( - test.stdout, - emitsThrough(contains('+1: All tests passed!')), - ); - await test.shouldExit(0); - }, - skip: 'https://github.com/dart-lang/test/issues/1813', - ); + test('on the $platform platform', () async { + var test = await runTest( + ['test.dart', '-p', platform], + vmArgs: ['--enable-experiment=non-nullable'], + ); + + await expectLater(test.stdout, emitsThrough(contains('int x;'))); + await test.shouldExit(1); + + // Test that they can be removed on subsequent runs as well + test = await runTest(['test.dart', '-p', platform]); + await expectLater( + test.stdout, + emitsThrough(contains('+1: All tests passed!')), + ); + await test.shouldExit(0); + }, skip: 'https://github.com/dart-lang/test/issues/1813'); } }); }); @@ -900,22 +896,18 @@ void main() { await test.shouldExit(0); }); - test( - 'on the browser platform', - () async { - var test = await runTest([ - '-p', - 'vm,chrome', - 'a_test.dart', - 'b_test.dart', - ]); - await expectLater( - test.stdout, - emitsThrough(contains('+3: All tests passed!')), - ); - await test.shouldExit(0); - }, - skip: 'https://github.com/dart-lang/test/issues/1803', - ); + test('on the browser platform', () async { + var test = await runTest([ + '-p', + 'vm,chrome', + 'a_test.dart', + 'b_test.dart', + ]); + await expectLater( + test.stdout, + emitsThrough(contains('+3: All tests passed!')), + ); + await test.shouldExit(0); + }, skip: 'https://github.com/dart-lang/test/issues/1803'); }); } From bf785f185cf5efb13339c9193aaaafefa210f26f Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Tue, 26 May 2026 07:56:22 +0000 Subject: [PATCH 4/6] revert formatting changes2 --- pkgs/test_api/test/import_restrictions_test.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkgs/test_api/test/import_restrictions_test.dart b/pkgs/test_api/test/import_restrictions_test.dart index 421c065f3..9256d4c0f 100644 --- a/pkgs/test_api/test/import_restrictions_test.dart +++ b/pkgs/test_api/test/import_restrictions_test.dart @@ -36,11 +36,10 @@ void main() { entryPoints, )) { for (final import in source.imports) { - expect( - import.pathSegments.skip(1).take(2), - ['src', 'backend'], - reason: 'Invalid import from ${source.uri} : $import', - ); + expect(import.pathSegments.skip(1).take(2), [ + 'src', + 'backend', + ], reason: 'Invalid import from ${source.uri} : $import'); } } }); From fa129020a3e5d526a674e6dba3c54f67f94f1d6b Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Tue, 26 May 2026 08:27:21 +0000 Subject: [PATCH 5/6] CHANGELOGS --- pkgs/test/CHANGELOG.md | 1 + pkgs/test_core/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/pkgs/test/CHANGELOG.md b/pkgs/test/CHANGELOG.md index 7e369fa86..377fb733d 100644 --- a/pkgs/test/CHANGELOG.md +++ b/pkgs/test/CHANGELOG.md @@ -11,6 +11,7 @@ all tests with OS `'windows'` would previously still run browser tests on windows, but will now skip all tests including browser tests. * Use a DevTools URL instead of a defunct observatory URL. +* Add flag `--shard-by` to control sharding strategy. ## 1.31.1 diff --git a/pkgs/test_core/CHANGELOG.md b/pkgs/test_core/CHANGELOG.md index 6907873b9..d419b4424 100644 --- a/pkgs/test_core/CHANGELOG.md +++ b/pkgs/test_core/CHANGELOG.md @@ -2,6 +2,7 @@ * Support using the OS platform selector to configure browser tests. * Use a DevTools URL instead of a defunct observatory URL. +* Add flag `--shard-by` to control sharding strategy. ## 0.6.18 From 7de7f39379b2dc2e7a1c91f1adbde37060f08c3e Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Tue, 26 May 2026 09:07:42 +0000 Subject: [PATCH 6/6] Update test expectation --- pkgs/test/test/runner/runner_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/test/test/runner/runner_test.dart b/pkgs/test/test/runner/runner_test.dart index 8a8594f81..c8d573b5a 100644 --- a/pkgs/test/test/runner/runner_test.dart +++ b/pkgs/test/test/runner/runner_test.dart @@ -84,6 +84,8 @@ $_runtimeCompilers (defaults to "$_defaultConcurrency") --total-shards The total number of invocations of the test runner being run. --shard-index The index of this test runner invocation (of --total-shards). + --shard-by How to distribute tests across shards. + [test (default), file] --timeout The default test timeout. For example: 15s, 2x, none (defaults to "30s") --suite-load-timeout The timeout for loading a test suite. Loading the test suite includes compiling the test suite. For example: 15s, 2m, none