Index: mojo/public/dart/third_party/test/test/utils.dart |
diff --git a/mojo/public/dart/third_party/test/test/utils.dart b/mojo/public/dart/third_party/test/test/utils.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5880149aaba33e24b73d15c0eea63f90b2dd8f25 |
--- /dev/null |
+++ b/mojo/public/dart/third_party/test/test/utils.dart |
@@ -0,0 +1,282 @@ |
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library test.test.utils; |
+ |
+import 'dart:async'; |
+import 'dart:collection'; |
+ |
+import 'package:test/src/backend/invoker.dart'; |
+import 'package:test/src/backend/live_test.dart'; |
+import 'package:test/src/backend/metadata.dart'; |
+import 'package:test/src/backend/state.dart'; |
+import 'package:test/src/backend/suite.dart'; |
+import 'package:test/src/runner/application_exception.dart'; |
+import 'package:test/src/runner/load_exception.dart'; |
+import 'package:test/src/util/remote_exception.dart'; |
+import 'package:test/test.dart'; |
+ |
+/// The string representation of an untyped closure with no arguments. |
+/// |
+/// This differs between dart2js and the VM. |
+final String closureString = (() {}).toString(); |
+ |
+// The last state change detected via [expectStates]. |
+State lastState; |
+ |
+/// Asserts that exactly [states] will be emitted via [liveTest.onStateChange]. |
+/// |
+/// The most recent emitted state is stored in [_lastState]. |
+void expectStates(LiveTest liveTest, Iterable<State> statesIter) { |
+ var states = new Queue.from(statesIter); |
+ liveTest.onStateChange.listen(expectAsync((state) { |
+ lastState = state; |
+ expect(state, equals(states.removeFirst())); |
+ }, count: states.length, max: states.length)); |
+} |
+ |
+/// Asserts that errors will be emitted via [liveTest.onError] that match |
+/// [validators], in order. |
+void expectErrors(LiveTest liveTest, Iterable<Function> validatorsIter) { |
+ var validators = new Queue.from(validatorsIter); |
+ liveTest.onError.listen(expectAsync((error) { |
+ validators.removeFirst()(error.error); |
+ }, count: validators.length, max: validators.length)); |
+} |
+ |
+/// Asserts that [liveTest] will have a single failure with message `"oh no"`. |
+void expectSingleFailure(LiveTest liveTest) { |
+ expectStates(liveTest, [ |
+ const State(Status.running, Result.success), |
+ const State(Status.complete, Result.failure) |
+ ]); |
+ |
+ expectErrors(liveTest, [(error) { |
+ expect(lastState.status, equals(Status.complete)); |
+ expect(error, isTestFailure("oh no")); |
+ }]); |
+} |
+ |
+/// Asserts that [liveTest] will have a single error, the string `"oh no"`. |
+void expectSingleError(LiveTest liveTest) { |
+ expectStates(liveTest, [ |
+ const State(Status.running, Result.success), |
+ const State(Status.complete, Result.error) |
+ ]); |
+ |
+ expectErrors(liveTest, [(error) { |
+ expect(lastState.status, equals(Status.complete)); |
+ expect(error, equals("oh no")); |
+ }]); |
+} |
+ |
+/// Returns a matcher that matches a [TestFailure] with the given [message]. |
+/// |
+/// [message] can be a string or a [Matcher]. |
+Matcher isTestFailure(message) => new _IsTestFailure(wrapMatcher(message)); |
+ |
+class _IsTestFailure extends Matcher { |
+ final Matcher _message; |
+ |
+ _IsTestFailure(this._message); |
+ |
+ bool matches(item, Map matchState) => |
+ item is TestFailure && _message.matches(item.message, matchState); |
+ |
+ Description describe(Description description) => |
+ description.add('a TestFailure with message ').addDescriptionOf(_message); |
+ |
+ Description describeMismatch(item, Description mismatchDescription, |
+ Map matchState, bool verbose) { |
+ if (item is! TestFailure) { |
+ return mismatchDescription.addDescriptionOf(item) |
+ .add('is not a TestFailure'); |
+ } else { |
+ return mismatchDescription |
+ .add('message ') |
+ .addDescriptionOf(item.message) |
+ .add(' is not ') |
+ .addDescriptionOf(_message); |
+ } |
+ } |
+} |
+ |
+/// Returns a matcher that matches a [RemoteException] with the given [message]. |
+/// |
+/// [message] can be a string or a [Matcher]. |
+Matcher isRemoteException(message) => |
+ new _IsRemoteException(wrapMatcher(message)); |
+ |
+class _IsRemoteException extends Matcher { |
+ final Matcher _message; |
+ |
+ _IsRemoteException(this._message); |
+ |
+ bool matches(item, Map matchState) => |
+ item is RemoteException && _message.matches(item.message, matchState); |
+ |
+ Description describe(Description description) => |
+ description.add('a RemoteException with message ') |
+ .addDescriptionOf(_message); |
+ |
+ Description describeMismatch(item, Description mismatchDescription, |
+ Map matchState, bool verbose) { |
+ if (item is! RemoteException) { |
+ return mismatchDescription.addDescriptionOf(item) |
+ .add('is not a RemoteException'); |
+ } else { |
+ return mismatchDescription |
+ .add('message ') |
+ .addDescriptionOf(item) |
+ .add(' is not ') |
+ .addDescriptionOf(_message); |
+ } |
+ } |
+} |
+ |
+/// Returns a matcher that matches a [LoadException] with the given |
+/// [innerError]. |
+/// |
+/// [innerError] can be a string or a [Matcher]. |
+Matcher isLoadException(innerError) => |
+ new _IsLoadException(wrapMatcher(innerError)); |
+ |
+class _IsLoadException extends Matcher { |
+ final Matcher _innerError; |
+ |
+ _IsLoadException(this._innerError); |
+ |
+ bool matches(item, Map matchState) => |
+ item is LoadException && _innerError.matches(item.innerError, matchState); |
+ |
+ Description describe(Description description) => |
+ description.add('a LoadException with message ') |
+ .addDescriptionOf(_innerError); |
+ |
+ Description describeMismatch(item, Description mismatchDescription, |
+ Map matchState, bool verbose) { |
+ if (item is! LoadException) { |
+ return mismatchDescription.addDescriptionOf(item) |
+ .add('is not a LoadException'); |
+ } else { |
+ return mismatchDescription |
+ .add('inner error ') |
+ .addDescriptionOf(item) |
+ .add(' is not ') |
+ .addDescriptionOf(_innerError); |
+ } |
+ } |
+} |
+ |
+/// Returns a matcher that matches a [ApplicationException] with the given |
+/// [message]. |
+/// |
+/// [message] can be a string or a [Matcher]. |
+Matcher isApplicationException(message) => |
+ new _IsApplicationException(wrapMatcher(message)); |
+ |
+class _IsApplicationException extends Matcher { |
+ final Matcher _message; |
+ |
+ _IsApplicationException(this._message); |
+ |
+ bool matches(item, Map matchState) => |
+ item is ApplicationException && _message.matches(item.message, matchState); |
+ |
+ Description describe(Description description) => |
+ description.add('a ApplicationException with message ') |
+ .addDescriptionOf(_message); |
+ |
+ Description describeMismatch(item, Description mismatchDescription, |
+ Map matchState, bool verbose) { |
+ if (item is! ApplicationException) { |
+ return mismatchDescription.addDescriptionOf(item) |
+ .add('is not a ApplicationException'); |
+ } else { |
+ return mismatchDescription |
+ .add('message ') |
+ .addDescriptionOf(item) |
+ .add(' is not ') |
+ .addDescriptionOf(_message); |
+ } |
+ } |
+} |
+ |
+/// Returns a [Future] that completes after pumping the event queue [times] |
+/// times. |
+/// |
+/// By default, this should pump the event queue enough times to allow any code |
+/// to run, as long as it's not waiting on some external event. |
+Future pumpEventQueue([int times=20]) { |
+ if (times == 0) return new Future.value(); |
+ // Use [new Future] future to allow microtask events to finish. The [new |
+ // Future.value] constructor uses scheduleMicrotask itself and would therefore |
+ // not wait for microtask callbacks that are scheduled after invoking this |
+ // method. |
+ return new Future(() => pumpEventQueue(times - 1)); |
+} |
+ |
+/// Returns a local [LiveTest] that runs [body]. |
+LiveTest createTest(body()) { |
+ var test = new LocalTest("test", new Metadata(), body); |
+ var suite = new Suite([test]); |
+ return test.load(suite); |
+} |
+ |
+/// Runs [body] as a test. |
+/// |
+/// Once it completes, returns the [LiveTest] used to run it. |
+Future<LiveTest> runTestBody(body()) async { |
+ var liveTest = createTest(body); |
+ await liveTest.run(); |
+ return liveTest; |
+} |
+ |
+/// Asserts that [liveTest] has completed and passed. |
+/// |
+/// If the test had any errors, they're surfaced nicely into the outer test. |
+void expectTestPassed(LiveTest liveTest) { |
+ // Since the test is expected to pass, we forward any current or future errors |
+ // to the outer test, because they're definitely unexpected. |
+ for (var error in liveTest.errors) { |
+ registerException(error.error, error.stackTrace); |
+ } |
+ liveTest.onError.listen((error) { |
+ registerException(error.error, error.stackTrace); |
+ }); |
+ |
+ expect(liveTest.state.status, equals(Status.complete)); |
+ expect(liveTest.state.result, equals(Result.success)); |
+} |
+ |
+/// Asserts that [liveTest] failed with a single [TestFailure] whose message |
+/// matches [message]. |
+void expectTestFailed(LiveTest liveTest, message) { |
+ expect(liveTest.state.status, equals(Status.complete)); |
+ expect(liveTest.state.result, equals(Result.failure)); |
+ expect(liveTest.errors, hasLength(1)); |
+ expect(liveTest.errors.first.error, isTestFailure(message)); |
+} |
+ |
+/// Assert that the [test] callback causes a test to block until [stopBlocking] |
+/// is called at some later time. |
+/// |
+/// [stopBlocking] is passed the return value of [test]. |
+Future expectTestBlocks(test(), stopBlocking(value)) async { |
+ var liveTest; |
+ var future; |
+ liveTest = createTest(() { |
+ var value = test(); |
+ future = pumpEventQueue().then((_) { |
+ expect(liveTest.state.status, equals(Status.running)); |
+ stopBlocking(value); |
+ }); |
+ }); |
+ |
+ await liveTest.run(); |
+ expectTestPassed(liveTest); |
+ // Ensure that the outer test doesn't complete until the inner future |
+ // completes. |
+ return future; |
+} |