| // 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, | |
| } |