f8086c81ae
Collects files from `package:async_helper` and `tests/language` that are generally useful, so that all test-related helpers are in `package:expect`. Moves the two libraries from `package:async_helper` into `package:expect`, and the `tests/language/static_type_helper.dart` file too. Deprecates `async_minitest.dart`, to follow `minitest.dart`, expecting the Flutter use of it to have been fixed to not break on deprecation (I believe Flutter no longer breaks builds on deprecations at all). Patch 1 is the actual change. Patch 2+4+8 is changing all existing references to the files. Patch 6 ignores deprecation in files still using `async_minitest.dart`. 3+5+7+9 are updating this text to make the numbers match. Then it's just test-expectations and small tweaks from there. Change-Id: I1b665135b5fef9b9a0c3b340ffe9daf874d0174c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373120 Reviewed-by: Nate Bosch <nbosch@google.com> Reviewed-by: Devon Carew <devoncarew@google.com> Commit-Queue: Lasse Nielsen <lrn@google.com>
170 lines
6.1 KiB
Dart
170 lines
6.1 KiB
Dart
// Copyright (c) 2013, 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.
|
|
|
|
/// This library is used for testing asynchronous tests.
|
|
/// If a test is asynchronous, it needs to notify the testing driver
|
|
/// about this (otherwise tests may get reported as passing after main()
|
|
/// finished even if the asynchronous operations fail).
|
|
///
|
|
/// This library provides the following methods
|
|
/// - `asyncStart()`: Needs to be called before an asynchronous operation is
|
|
/// scheduled.
|
|
/// - `asyncEnd()`: Needs to be called as soon as the asynchronous operation
|
|
/// ended.
|
|
/// - `asyncSuccess(_)`: Variant of asyncEnd useful together with Future.then.
|
|
/// - `asyncTest(f)`: Helper method that wraps a computation that returns a
|
|
/// `Future` with matching calls to `asyncStart()` and
|
|
/// `asyncSuccess(_)`.
|
|
/// - `asyncExpectThrows<E>(f)`: Checks that the `Future` `f` completes
|
|
/// with an error of type `E`. Calls `asyncStart()`
|
|
/// first and `asyncEnd()` when the future completes with
|
|
/// the expected error.
|
|
/// - `asyncExpectThrowsWhen<E>(condition, f)`:
|
|
/// Checks that the `Future` `f` completes with an error of
|
|
/// type `E` if `condition` is true, and does not fail
|
|
/// if `condition` is false. Calls `asyncStart()`
|
|
/// first and `asyncEnd()` when the future completes with
|
|
/// the expected error, or with no error if the condition
|
|
/// is false.
|
|
///
|
|
/// After every `asyncStart()` called is matched with a corresponding
|
|
/// `asyncEnd()` or `asyncSuccess(_)` call, the testing driver will be notified
|
|
/// that the tests is done.
|
|
library async_helper;
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:expect/expect.dart';
|
|
|
|
bool _initialized = false;
|
|
int _asyncLevel = 0;
|
|
|
|
Exception _buildException(String msg) {
|
|
return Exception('Fatal: $msg. This is most likely a bug in your test.');
|
|
}
|
|
|
|
/// Call this method before an asynchronous test is created.
|
|
///
|
|
/// If [count] is provided, expect [count] [asyncEnd] calls instead of just one.
|
|
void asyncStart([int count = 1]) {
|
|
if (count <= 0) return;
|
|
if (_initialized && _asyncLevel == 0) {
|
|
throw _buildException('asyncStart() was called even though we are done '
|
|
'with testing.');
|
|
}
|
|
if (!_initialized) {
|
|
print('unittest-suite-wait-for-done');
|
|
_initialized = true;
|
|
}
|
|
_asyncLevel += count;
|
|
}
|
|
|
|
/// Call this after an asynchronous test has ended successfully.
|
|
void asyncEnd() {
|
|
if (_asyncLevel <= 0) {
|
|
if (!_initialized) {
|
|
throw _buildException('asyncEnd() was called before asyncStart().');
|
|
} else {
|
|
throw _buildException('asyncEnd() was called more often than '
|
|
'asyncStart().');
|
|
}
|
|
}
|
|
_asyncLevel--;
|
|
if (_asyncLevel == 0) {
|
|
print('unittest-suite-success');
|
|
}
|
|
}
|
|
|
|
/// Call this after an asynchronous test has ended successfully.
|
|
///
|
|
/// This is a helper for calling [asyncEnd] as a callback.
|
|
/// This method intentionally has a signature that matches `Future.then` as a
|
|
/// convenience for calling [asyncEnd] when a `Future` completes without error,
|
|
/// like this:
|
|
/// ```dart
|
|
/// asyncStart();
|
|
/// Future result = test();
|
|
/// result.then(asyncSuccess);
|
|
/// ```
|
|
void asyncSuccess(void _) {
|
|
asyncEnd();
|
|
}
|
|
|
|
/// Helper method for performing asynchronous tests involving `Future`.
|
|
///
|
|
/// The function [test] must return a `Future` which completes without error
|
|
/// when the test is successful.
|
|
Future<void> asyncTest(Future<void> Function() test) {
|
|
asyncStart();
|
|
return test().then(asyncSuccess);
|
|
}
|
|
|
|
/// Verifies that the asynchronous [result] throws a [T].
|
|
///
|
|
/// Fails if [result] completes with a value, or it completes with
|
|
/// an error which is not a [T].
|
|
///
|
|
/// Returns the accepted thrown object.
|
|
/// For example, to check the content of the thrown object,
|
|
/// you could write this:
|
|
/// ```
|
|
/// var e = await asyncExpectThrows<MyException>(asyncExpression)
|
|
/// Expect.isTrue(e.myMessage.contains("WARNING"));
|
|
/// ```
|
|
/// If `result` completes with an [ExpectException] error from another
|
|
/// failed test expectation, that error cannot be caught and accepted.
|
|
Future<T> asyncExpectThrows<T extends Object>(Future<void> result,
|
|
[String reason = ""]) {
|
|
// Delay computing the header text until the test has failed.
|
|
// The header computation uses complicated language features,
|
|
// and language tests should avoid doing complicated things
|
|
// until after the actual test has had a chance to succeed.
|
|
String header() {
|
|
// Handle null being passed in from legacy code
|
|
// while also avoiding producing an unnecessary null check warning here.
|
|
if ((reason as dynamic) == null) reason = "";
|
|
// Only include the type in the message if it's not `Object` or a top type.
|
|
var type = Object() is! T ? "<$T>" : "";
|
|
return "asyncExpectThrows$type($reason):";
|
|
}
|
|
|
|
// Unsound null-safety check.
|
|
if ((result as dynamic) == null) {
|
|
Expect.testError("${header()} result Future must not be null.");
|
|
}
|
|
|
|
// TODO(rnystrom): It might useful to validate that T is not bound to
|
|
// ExpectException since that won't work.
|
|
|
|
asyncStart();
|
|
return result.then<T>((_) {
|
|
throw ExpectException("${header()} Did not throw.");
|
|
}, onError: (error, stack) {
|
|
// A test failure doesn't count as throwing. Rethrow it.
|
|
if (error is ExpectException) throw error;
|
|
|
|
if (error is! T) {
|
|
// Throws something unexpected.
|
|
throw ExpectException(
|
|
"${header()} Unexpected '${Error.safeToString(error)}'\n$stack");
|
|
}
|
|
|
|
asyncEnd();
|
|
return error;
|
|
});
|
|
}
|
|
|
|
/// Checks that the asynchronous [result] throws a [T] if and only if
|
|
/// [condition] is `true`.
|
|
///
|
|
/// When [condition] is `false`, [result] is expected to complete without
|
|
/// errors.
|
|
Future<T?> asyncExpectThrowsWhen<T extends Object>(
|
|
bool condition, Future<void> result,
|
|
[String reason = ""]) {
|
|
return condition
|
|
? asyncExpectThrows<T>(result, reason)
|
|
: result.then<T?>((_) => null);
|
|
}
|