Line data Source code
1 : // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 : // for details. All rights reserved. Use of this source code is governed by a 3 : // BSD-style license that can be found in the LICENSE file. 4 : 5 : import 'dart:async'; 6 : import 'dart:collection'; 7 : 8 : import 'package:stack_trace/stack_trace.dart'; 9 : 10 : import 'group.dart'; 11 : import 'live_test.dart'; 12 : import 'message.dart'; 13 : import 'state.dart'; 14 : import 'suite.dart'; 15 : import 'test.dart'; 16 : 17 : /// A concrete [LiveTest] that enforces some lifecycle guarantees. 18 : /// 19 : /// This automatically handles some of [LiveTest]'s guarantees, but for the most 20 : /// part it's the caller's responsibility to make sure everything gets 21 : /// dispatched in the correct order. 22 : class LiveTestController extends LiveTest { 23 0 : @Deprecated('Use this instance instead') 24 : LiveTest get liveTest => this; 25 : 26 : @override 27 : final Suite suite; 28 : 29 : @override 30 : final List<Group> groups; 31 : 32 : @override 33 : final Test test; 34 : 35 : /// The function that will actually start the test running. 36 : final void Function() _onRun; 37 : 38 : /// A function to run when the test is closed. 39 : /// 40 : /// This may be `null`. 41 : final void Function() _onClose; 42 : 43 : /// The list of errors caught by the test. 44 : final _errors = <AsyncError>[]; 45 : 46 0 : @override 47 0 : List<AsyncError> get errors => UnmodifiableListView(_errors); 48 : 49 : /// The current state of the test. 50 : @override 51 : var state = const State(Status.pending, Result.success); 52 : 53 : /// The controller for [onStateChange]. 54 : /// 55 : /// This is synchronous to ensure that events are well-ordered across multiple 56 : /// streams. 57 : final _onStateChange = StreamController<State>.broadcast(sync: true); 58 11 : @override 59 22 : Stream<State> get onStateChange => _onStateChange.stream; 60 : 61 : /// The controller for [onError]. 62 : /// 63 : /// This is synchronous to ensure that events are well-ordered across multiple 64 : /// streams. 65 : final _onError = StreamController<AsyncError>.broadcast(sync: true); 66 11 : @override 67 22 : Stream<AsyncError> get onError => _onError.stream; 68 : 69 : /// The controller for [onMessage]. 70 : /// 71 : /// This is synchronous to ensure that events are well-ordered across multiple 72 : /// streams. 73 : final _onMessage = StreamController<Message>.broadcast(sync: true); 74 11 : @override 75 22 : Stream<Message> get onMessage => _onMessage.stream; 76 : 77 : final completer = Completer<void>(); 78 : 79 : /// Whether [run] has been called. 80 : var _runCalled = false; 81 : 82 : /// Whether [close] has been called. 83 33 : bool get _isClosed => _onError.isClosed; 84 : 85 : /// Creates a new controller for a [LiveTest]. 86 : /// 87 : /// [test] is the test being run; [suite] is the suite that contains it. 88 : /// 89 : /// [onRun] is a function that's called from [LiveTest.run]. It should start 90 : /// the test running. The controller takes care of ensuring that 91 : /// [LiveTest.run] isn't called more than once and that [LiveTest.onComplete] 92 : /// is returned. 93 : /// 94 : /// [onClose] is a function that's called the first time [LiveTest.close] is 95 : /// called. It should clean up any resources that have been allocated for the 96 : /// test and ensure that the test finishes quickly if it's still running. It 97 : /// will only be called if [onRun] has been called first. 98 : /// 99 : /// If [groups] is passed, it's used to populate the list of groups that 100 : /// contain this test. Otherwise, `suite.group` is used. 101 11 : LiveTestController(this.suite, this.test, this._onRun, this._onClose, 102 : {Iterable<Group>? groups}) 103 11 : : groups = groups == null ? [suite.group] : List.unmodifiable(groups); 104 : 105 : /// Adds an error to the [LiveTest]. 106 : /// 107 : /// This both adds the error to [LiveTest.errors] and emits it via 108 : /// [LiveTest.onError]. [stackTrace] is automatically converted into a [Chain] 109 : /// if it's not one already. 110 0 : void addError(Object error, StackTrace? stackTrace) { 111 0 : if (_isClosed) return; 112 : 113 0 : var asyncError = AsyncError( 114 0 : error, Chain.forTrace(stackTrace ?? StackTrace.fromString(''))); 115 0 : _errors.add(asyncError); 116 0 : _onError.add(asyncError); 117 : } 118 : 119 : /// Sets the current state of the [LiveTest] to [newState]. 120 : /// 121 : /// If [newState] is different than the old state, this both sets 122 : /// [LiveTest.state] and emits the new state via [LiveTest.onStateChanged]. If 123 : /// it's not different, this does nothing. 124 11 : void setState(State newState) { 125 11 : if (_isClosed) return; 126 22 : if (state == newState) return; 127 : 128 11 : state = newState; 129 22 : _onStateChange.add(newState); 130 : } 131 : 132 : /// Emits message over [LiveTest.onMessage]. 133 0 : void message(Message message) { 134 0 : if (_onMessage.hasListener) { 135 0 : _onMessage.add(message); 136 : } else { 137 : // Make sure all messages get surfaced one way or another to aid in 138 : // debugging. 139 0 : Zone.root.print(message.text); 140 : } 141 : } 142 : 143 11 : @override 144 : Future<void> run() { 145 11 : if (_runCalled) { 146 0 : throw StateError('LiveTest.run() may not be called more than once.'); 147 11 : } else if (_isClosed) { 148 0 : throw StateError('LiveTest.run() may not be called for a closed ' 149 : 'test.'); 150 : } 151 11 : _runCalled = true; 152 : 153 11 : _onRun(); 154 11 : return onComplete; 155 : } 156 : 157 : /// Returns a future that completes when the test is complete. 158 : /// 159 : /// We also wait for the state to transition to Status.complete. 160 11 : @override 161 22 : Future<void> get onComplete => completer.future; 162 : 163 0 : @override 164 : Future<void> close() { 165 0 : if (_isClosed) return onComplete; 166 : 167 0 : _onStateChange.close(); 168 0 : _onError.close(); 169 : 170 0 : if (_runCalled) { 171 0 : _onClose(); 172 : } else { 173 0 : completer.complete(); 174 : } 175 : 176 0 : return onComplete; 177 : } 178 : }