Merge pull request #721 from dart-lang/never-called
Add flushEventQueue() and neverCalled
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0e14a4..cd365d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.12.28
+
+* Add a `pumpEventQueue()` function to make it easy to wait until all
+ asynchronous tasks are complete.
+
+* Add a `neverCalled` getter that returns a function that causes the test to
+ fail if it's ever called.
+
## 0.12.27+1
* Increase the timeout for loading tests to 12 minutes.
diff --git a/lib/src/frontend/expect_async.dart b/lib/src/frontend/expect_async.dart
index 9a37bdf..85f4d0a 100644
--- a/lib/src/frontend/expect_async.dart
+++ b/lib/src/frontend/expect_async.dart
@@ -5,11 +5,9 @@
import 'dart:async';
import '../backend/invoker.dart';
+import '../util/placeholder.dart';
import 'expect.dart';
-/// An object used to detect unpassed arguments.
-const _PLACEHOLDER = const Object();
-
// Function types returned by expectAsync# methods.
typedef T Func0<T>();
@@ -157,39 +155,39 @@
// argument count of zero.
T max0() => max6();
- T max1([Object a0 = _PLACEHOLDER]) => max6(a0);
+ T max1([Object a0 = placeholder]) => max6(a0);
- T max2([Object a0 = _PLACEHOLDER, Object a1 = _PLACEHOLDER]) => max6(a0, a1);
+ T max2([Object a0 = placeholder, Object a1 = placeholder]) => max6(a0, a1);
T max3(
- [Object a0 = _PLACEHOLDER,
- Object a1 = _PLACEHOLDER,
- Object a2 = _PLACEHOLDER]) =>
+ [Object a0 = placeholder,
+ Object a1 = placeholder,
+ Object a2 = placeholder]) =>
max6(a0, a1, a2);
T max4(
- [Object a0 = _PLACEHOLDER,
- Object a1 = _PLACEHOLDER,
- Object a2 = _PLACEHOLDER,
- Object a3 = _PLACEHOLDER]) =>
+ [Object a0 = placeholder,
+ Object a1 = placeholder,
+ Object a2 = placeholder,
+ Object a3 = placeholder]) =>
max6(a0, a1, a2, a3);
T max5(
- [Object a0 = _PLACEHOLDER,
- Object a1 = _PLACEHOLDER,
- Object a2 = _PLACEHOLDER,
- Object a3 = _PLACEHOLDER,
- Object a4 = _PLACEHOLDER]) =>
+ [Object a0 = placeholder,
+ Object a1 = placeholder,
+ Object a2 = placeholder,
+ Object a3 = placeholder,
+ Object a4 = placeholder]) =>
max6(a0, a1, a2, a3, a4);
T max6(
- [Object a0 = _PLACEHOLDER,
- Object a1 = _PLACEHOLDER,
- Object a2 = _PLACEHOLDER,
- Object a3 = _PLACEHOLDER,
- Object a4 = _PLACEHOLDER,
- Object a5 = _PLACEHOLDER]) =>
- _run([a0, a1, a2, a3, a4, a5].where((a) => a != _PLACEHOLDER));
+ [Object a0 = placeholder,
+ Object a1 = placeholder,
+ Object a2 = placeholder,
+ Object a3 = placeholder,
+ Object a4 = placeholder,
+ Object a5 = 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) {
diff --git a/lib/src/frontend/future_matchers.dart b/lib/src/frontend/future_matchers.dart
index 0284a4d..31f78ac 100644
--- a/lib/src/frontend/future_matchers.dart
+++ b/lib/src/frontend/future_matchers.dart
@@ -9,6 +9,7 @@
import '../utils.dart';
import 'async_matcher.dart';
import 'expect.dart';
+import 'utils.dart';
/// Matches a [Future] that completes successfully with a value.
///
@@ -86,31 +87,23 @@
/// Note that this creates an asynchronous expectation. The call to
/// `expect()` that includes this will return immediately and execution will
/// continue.
-final Matcher doesNotComplete = const _DoesNotComplete(20);
+final Matcher doesNotComplete = const _DoesNotComplete();
class _DoesNotComplete extends Matcher {
- final int _timesToPump;
- const _DoesNotComplete(this._timesToPump);
-
- // TODO(grouma) - Make this a top level function
- Future _pumpEventQueue(times) {
- if (times == 0) return new Future.value();
- return new Future(() => _pumpEventQueue(times - 1));
- }
+ const _DoesNotComplete();
Description describe(Description description) {
description.add("does not complete");
return description;
}
- @override
bool matches(item, Map matchState) {
if (item is! Future) return false;
item.then((value) {
fail('Future was not expected to complete but completed with a value of '
'$value');
});
- expect(_pumpEventQueue(_timesToPump), completes);
+ expect(pumpEventQueue(), completes);
return true;
}
diff --git a/lib/src/frontend/never_called.dart b/lib/src/frontend/never_called.dart
new file mode 100644
index 0000000..f56029e
--- /dev/null
+++ b/lib/src/frontend/never_called.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2017, 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.
+
+import 'dart:async';
+
+import 'package:stack_trace/stack_trace.dart';
+
+import '../util/placeholder.dart';
+import '../utils.dart';
+import 'expect.dart';
+import 'future_matchers.dart';
+import 'utils.dart';
+
+/// Returns a function that causes the test to fail if it's called.
+///
+/// This can safely be passed in place of any callback that takes ten or fewer
+/// positional parameters. For example:
+///
+/// ```
+/// // Asserts that the stream never emits an event.
+/// stream.listen(neverCalled);
+/// ```
+///
+/// This also ensures that the test doesn't complete until a call to
+/// [pumpEventQueue] finishes, so that the callback has a chance to be called.
+T Function<T>(
+ [Object,
+ Object,
+ Object,
+ Object,
+ Object,
+ Object,
+ Object,
+ Object,
+ Object,
+ Object]) get neverCalled {
+ // Make sure the test stays alive long enough to call the function if it's
+ // going to.
+ expect(pumpEventQueue(), completes);
+
+ var zone = Zone.current;
+ return <T>(
+ [a1 = placeholder,
+ a2 = placeholder,
+ a3 = placeholder,
+ a4 = placeholder,
+ a5 = placeholder,
+ a6 = placeholder,
+ a7 = placeholder,
+ a8 = placeholder,
+ a9 = placeholder,
+ a10 = placeholder]) {
+ var arguments = [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]
+ .where((argument) => argument != placeholder)
+ .toList();
+
+ zone.handleUncaughtError(
+ new TestFailure(
+ "Callback should never have been called, but it was called with" +
+ (arguments.isEmpty
+ ? " no arguments."
+ : ":\n${bullet(arguments.map(prettyPrint))}")),
+ zone.run(() => new Chain.current()));
+ return null as T;
+ };
+}
diff --git a/lib/src/frontend/utils.dart b/lib/src/frontend/utils.dart
new file mode 100644
index 0000000..209c4d1
--- /dev/null
+++ b/lib/src/frontend/utils.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2017, 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.
+
+import 'dart:async';
+
+/// Returns a [Future] that completes after the [event loop][] has run the given
+/// number of [times] (20 by default).
+///
+/// [event loop]: https://webdev.dartlang.org/articles/performance/event-loop#darts-event-loop-and-queues
+///
+/// Awaiting this approximates waiting until all asynchronous work (other than
+/// work that's waiting for external resources) completes.
+Future pumpEventQueue({int times}) {
+ 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: times - 1));
+}
diff --git a/lib/src/util/placeholder.dart b/lib/src/util/placeholder.dart
new file mode 100644
index 0000000..862964f
--- /dev/null
+++ b/lib/src/util/placeholder.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2017, 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.
+
+/// A class that's used as a default argument to detect whether an argument was
+/// passed.
+///
+/// We use a custom class for this rather than just `const Object()` so that
+/// callers can't accidentally pass the placeholder value.
+class _Placeholder {
+ const _Placeholder();
+}
+
+/// A placeholder to use as a default argument value to detect whether an
+/// argument was passed.
+const placeholder = const _Placeholder();
diff --git a/lib/test.dart b/lib/test.dart
index 8d159ad..39e4cbc 100644
--- a/lib/test.dart
+++ b/lib/test.dart
@@ -23,6 +23,7 @@
export 'src/frontend/expect_async.dart';
export 'src/frontend/future_matchers.dart';
export 'src/frontend/on_platform.dart';
+export 'src/frontend/never_called.dart';
export 'src/frontend/prints_matcher.dart';
export 'src/frontend/skip.dart';
export 'src/frontend/spawn_hybrid.dart';
@@ -33,6 +34,7 @@
export 'src/frontend/throws_matcher.dart';
export 'src/frontend/throws_matchers.dart';
export 'src/frontend/timeout.dart';
+export 'src/frontend/utils.dart';
/// The global declarer.
///
diff --git a/pubspec.yaml b/pubspec.yaml
index 96ac403..8c006dc 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: test
-version: 0.12.27+1
+version: 0.12.28
author: Dart Team <misc@dartlang.org>
description: A library for writing dart unit tests.
homepage: https://github.com/dart-lang/test
diff --git a/test/frontend/matcher/completion_test.dart b/test/frontend/matcher/completion_test.dart
index 5054e1f..ca89152 100644
--- a/test/frontend/matcher/completion_test.dart
+++ b/test/frontend/matcher/completion_test.dart
@@ -55,7 +55,7 @@
var completer = new Completer();
expect(completer.future, doesNotComplete);
new Future(() async {
- await pumpEventQueue(10);
+ await pumpEventQueue(times: 10);
}).then(completer.complete);
});
diff --git a/test/frontend/never_called_test.dart b/test/frontend/never_called_test.dart
new file mode 100644
index 0000000..f830e8f
--- /dev/null
+++ b/test/frontend/never_called_test.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2017, 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.
+
+import 'dart:async';
+
+import 'package:term_glyph/term_glyph.dart' as glyph;
+import 'package:test/src/backend/state.dart';
+import 'package:test/test.dart';
+
+import '../utils.dart';
+
+void main() {
+ setUpAll(() {
+ glyph.ascii = true;
+ });
+
+ test("doesn't throw if it isn't called", () async {
+ var liveTest = await runTestBody(() {
+ const Stream.empty().listen(neverCalled);
+ });
+
+ expectTestPassed(liveTest);
+ });
+
+ group("if it's called", () {
+ test("throws", () async {
+ var liveTest = await runTestBody(() {
+ neverCalled();
+ });
+
+ expectTestFailed(
+ liveTest,
+ "Callback should never have been called, but it was called with no "
+ "arguments.");
+ });
+
+ test("pretty-prints arguments", () async {
+ var liveTest = await runTestBody(() {
+ neverCalled(1, "foo\nbar");
+ });
+
+ expectTestFailed(
+ liveTest,
+ "Callback should never have been called, but it was called with:\n"
+ "* <1>\n"
+ "* 'foo\\n'\n"
+ " 'bar'");
+ });
+
+ test("keeps the test alive", () async {
+ var liveTest = await runTestBody(() {
+ pumpEventQueue(times: 10).then(neverCalled);
+ });
+
+ expectTestFailed(
+ liveTest,
+ 'Callback should never have been called, but it was called with:\n'
+ '* <null>');
+ });
+
+ test("can't be caught", () async {
+ var liveTest = await runTestBody(() {
+ try {
+ neverCalled();
+ } catch (_) {
+ // Do nothing.
+ }
+ });
+
+ expectTestFailed(
+ liveTest,
+ 'Callback should never have been called, but it was called with '
+ 'no arguments.');
+ });
+ });
+}
diff --git a/test/utils.dart b/test/utils.dart
index c2b062a..ccc5c13 100644
--- a/test/utils.dart
+++ b/test/utils.dart
@@ -223,20 +223,6 @@
}
}
-/// 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);