| // Copyright (c) 2015, 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 'dart:collection'; | |
| import 'package:stack_trace/stack_trace.dart'; | |
| import 'group.dart'; | |
| import 'live_test.dart'; | |
| import 'message.dart'; | |
| import 'state.dart'; | |
| import 'suite.dart'; | |
| import 'test.dart'; | |
| /// A concrete [LiveTest] that enforces some lifecycle guarantees. | |
| /// | |
| /// This automatically handles some of [LiveTest]'s guarantees, but for the most | |
| /// part it's the caller's responsibility to make sure everything gets | |
| /// dispatched in the correct order. | |
| class LiveTestController extends LiveTest { | |
| @Deprecated('Use this instance instead') | |
| LiveTest get liveTest => this; | |
| @override | |
| final Suite suite; | |
| @override | |
| final List<Group> groups; | |
| @override | |
| final Test test; | |
| /// The function that will actually start the test running. | |
| final void Function() _onRun; | |
| /// A function to run when the test is closed. | |
| /// | |
| /// This may be `null`. | |
| final void Function() _onClose; | |
| /// The list of errors caught by the test. | |
| final _errors = <AsyncError>[]; | |
| @override | |
| List<AsyncError> get errors => UnmodifiableListView(_errors); | |
| /// The current state of the test. | |
| @override | |
| State state = const State(Status.pending, Result.success); | |
| /// The controller for [onStateChange]. | |
| /// | |
| /// This is synchronous to ensure that events are well-ordered across multiple | |
| /// streams. | |
| final _onStateChange = StreamController<State>.broadcast(sync: true); | |
| @override | |
| Stream<State> get onStateChange => _onStateChange.stream; | |
| /// The controller for [onError]. | |
| /// | |
| /// This is synchronous to ensure that events are well-ordered across multiple | |
| /// streams. | |
| final _onError = StreamController<AsyncError>.broadcast(sync: true); | |
| @override | |
| Stream<AsyncError> get onError => _onError.stream; | |
| /// The controller for [onMessage]. | |
| /// | |
| /// This is synchronous to ensure that events are well-ordered across multiple | |
| /// streams. | |
| final _onMessage = StreamController<Message>.broadcast(sync: true); | |
| @override | |
| Stream<Message> get onMessage => _onMessage.stream; | |
| final completer = Completer<void>(); | |
| /// Whether [run] has been called. | |
| var _runCalled = false; | |
| /// Whether [close] has been called. | |
| bool get _isClosed => _onError.isClosed; | |
| /// Creates a new controller for a [LiveTest]. | |
| /// | |
| /// [test] is the test being run; [suite] is the suite that contains it. | |
| /// | |
| /// [onRun] is a function that's called from [LiveTest.run]. It should start | |
| /// the test running. The controller takes care of ensuring that | |
| /// [LiveTest.run] isn't called more than once and that [LiveTest.onComplete] | |
| /// is returned. | |
| /// | |
| /// [onClose] is a function that's called the first time [LiveTest.close] is | |
| /// called. It should clean up any resources that have been allocated for the | |
| /// test and ensure that the test finishes quickly if it's still running. It | |
| /// will only be called if [onRun] has been called first. | |
| /// | |
| /// If [groups] is passed, it's used to populate the list of groups that | |
| /// contain this test. Otherwise, `suite.group` is used. | |
| LiveTestController( | |
| this.suite, | |
| this.test, | |
| this._onRun, | |
| this._onClose, { | |
| Iterable<Group>? groups, | |
| }) : groups = groups == null ? [suite.group] : List.unmodifiable(groups); | |
| /// Adds an error to the [LiveTest]. | |
| /// | |
| /// This both adds the error to [LiveTest.errors] and emits it via | |
| /// [LiveTest.onError]. [stackTrace] is automatically converted into a [Chain] | |
| /// if it's not one already. | |
| void addError(Object error, StackTrace? stackTrace) { | |
| if (_isClosed) return; | |
| var asyncError = AsyncError( | |
| error, | |
| Chain.forTrace(stackTrace ?? StackTrace.fromString('')), | |
| ); | |
| _errors.add(asyncError); | |
| _onError.add(asyncError); | |
| } | |
| /// Sets the current state of the [LiveTest] to [newState]. | |
| /// | |
| /// If [newState] is different than the old state, this both sets | |
| /// [LiveTest.state] and emits the new state via [LiveTest.onStateChanged]. If | |
| /// it's not different, this does nothing. | |
| void setState(State newState) { | |
| if (_isClosed) return; | |
| if (state == newState) return; | |
| state = newState; | |
| _onStateChange.add(newState); | |
| } | |
| /// Emits message over [LiveTest.onMessage]. | |
| void message(Message message) { | |
| if (_onMessage.hasListener) { | |
| _onMessage.add(message); | |
| } else { | |
| // Make sure all messages get surfaced one way or another to aid in | |
| // debugging. | |
| Zone.root.print(message.text); | |
| } | |
| } | |
| @override | |
| Future<void> run() { | |
| if (_runCalled) { | |
| throw StateError('LiveTest.run() may not be called more than once.'); | |
| } else if (_isClosed) { | |
| throw StateError( | |
| 'LiveTest.run() may not be called for a closed ' | |
| 'test.', | |
| ); | |
| } | |
| _runCalled = true; | |
| _onRun(); | |
| return onComplete; | |
| } | |
| /// Returns a future that completes when the test is complete. | |
| /// | |
| /// We also wait for the state to transition to Status.complete. | |
| @override | |
| Future<void> get onComplete => completer.future; | |
| @override | |
| Future<void> close() { | |
| if (_isClosed) return onComplete; | |
| _onStateChange.close(); | |
| _onError.close(); | |
| if (_runCalled) { | |
| _onClose(); | |
| } else { | |
| completer.complete(); | |
| } | |
| return onComplete; | |
| } | |
| } |