| // Copyright (c) 2023, 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 'src/backend/group.dart'; |
| import 'src/backend/invoker.dart'; |
| import 'src/backend/live_test.dart'; |
| import 'src/backend/metadata.dart'; |
| import 'src/backend/runtime.dart'; |
| import 'src/backend/state.dart'; |
| import 'src/backend/suite.dart'; |
| import 'src/backend/suite_platform.dart'; |
| |
| @Deprecated('These classes are unused for monitoring tests. Use `State`.') |
| export 'src/backend/state.dart' show Result, Status; |
| export 'src/backend/test_failure.dart' show TestFailure; |
| |
| /// A monitor for the behavior of a callback when it is run as the body of a |
| /// test case. |
| /// |
| /// Allows running a callback as the body of a local test case and querying for |
| /// the current [state], and [errors], and subscribing to future errors. |
| /// |
| /// Use [run] to run a test body and query for the success or failure. |
| /// |
| /// Use [start] to start a test and query for whether it has finished running. |
| final class TestCaseMonitor { |
| final LiveTest _liveTest; |
| final _done = Completer<void>(); |
| TestCaseMonitor._(FutureOr<void> Function() body) |
| : _liveTest = _createTest(body); |
| |
| /// Run [body] as a test case and return a [TestCaseMonitor] with the result. |
| /// |
| /// The [state] will either [State.passed], [State.skipped], or |
| /// [State.failed], the test will no longer be running. |
| /// |
| /// {@template result-late-fail} |
| /// Note that a test can change state from [State.passed] to [State.failed] |
| /// if the test surfaces an unawaited asynchronous error. |
| /// {@endtemplate} |
| /// |
| /// ```dart |
| /// final monitor = await TestCaseMonitor.run(() { |
| /// fail('oh no!'); |
| /// }); |
| /// assert(monitor.state == State.failed); |
| /// assert((monitor.errors.single.error as TestFailure).message == 'oh no!'); |
| /// ``` |
| static Future<TestCaseMonitor> run(FutureOr<void> Function() body) async { |
| final monitor = TestCaseMonitor.start(body); |
| await monitor.onDone; |
| return monitor; |
| } |
| |
| /// Start [body] as a test case and return a [TestCaseMonitor] with the status |
| /// and result. |
| /// |
| /// The [state] will start as [State.pending] if queried synchronously, but it |
| /// will switch to [State.running]. After `onDone` completes the state will be |
| /// one of [State.passed], [State.skipped], or [State.failed]. |
| /// |
| /// {@macro result-late-fail} |
| /// |
| /// ```dart |
| /// late void Function() completeWork; |
| /// final monitor = TestCaseMonitor.start(() { |
| /// final outstandingWork = TestHandle.current.markPending(); |
| /// completeWork = outstandingWork.complete; |
| /// }); |
| /// await pumpEventQueue(); |
| /// assert(monitor.state == State.running); |
| /// completeWork(); |
| /// await monitor.onDone; |
| /// assert(monitor.state == State.passed); |
| /// ``` |
| static TestCaseMonitor start(FutureOr<void> Function() body) => |
| TestCaseMonitor._(body).._start(); |
| |
| void _start() { |
| _liveTest.run().whenComplete(_done.complete); |
| } |
| |
| /// A future that completes after this test has finished running, or has |
| /// surfaced an error. |
| Future<void> get onDone => _done.future; |
| |
| /// The running and success or failure status for the test case. |
| State get state { |
| final status = _liveTest.state.status; |
| if (status == Status.pending) return State.pending; |
| if (status == Status.running) return State.running; |
| final result = _liveTest.state.result; |
| if (result == Result.skipped) return State.skipped; |
| if (result == Result.success) return State.passed; |
| // result == Result.failure || result == Result.error |
| return State.failed; |
| } |
| |
| /// The errors surfaced by the test. |
| /// |
| /// A test with any errors will have a [state] of [State.failed]. |
| /// |
| /// {@macro result-late-fail} |
| /// |
| /// A test may have more than one error if there were unhandled asynchronous |
| /// errors surfaced after the test is done. |
| Iterable<AsyncError> get errors => _liveTest.errors; |
| |
| /// A stream of errors surfaced by the test. |
| /// |
| /// This stream will not close, asynchronous errors may be surfaced within the |
| /// test's error zone at any point. |
| Stream<AsyncError> get onError => _liveTest.onError; |
| } |
| |
| /// Returns a local [LiveTest] that runs [body]. |
| LiveTest _createTest(FutureOr<void> Function() body) { |
| var test = LocalTest('test', Metadata(chainStackTraces: true), body); |
| var suite = Suite(Group.root([test]), _suitePlatform, ignoreTimeouts: false); |
| return test.load(suite); |
| } |
| |
| /// A dummy suite platform to use for testing suites. |
| final _suitePlatform = |
| SuitePlatform(Runtime.vm, compiler: Runtime.vm.defaultCompiler); |
| |
| /// The running and success state of a test monitored by a [TestCaseMonitor]. |
| enum State { |
| /// The test is has not yet started. |
| pending, |
| |
| /// The test is running and has not yet failed. |
| running, |
| |
| /// The test has completed without any error. |
| /// |
| /// This implies that the test body has completed, and no error has surfaced |
| /// *yet*. However, it this doesn't mean that the test won't fail in the |
| /// future. |
| passed, |
| |
| /// The test, or some part of it, has been skipped. |
| /// |
| /// This does not imply that the test has not had an error, but if there are |
| /// errors they are ignored. |
| skipped, |
| |
| /// The test has failed. |
| /// |
| /// An test fails when any exception, typically a [TestFailure], is thrown in |
| /// the test's zone. A test that has failed may still have additional errors |
| /// that surface as unhandled asynchronous errors. |
| failed, |
| } |