|  | // 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); | 
|  | } |