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 : /// An implementation of [LiveTest] that's controlled by a [LiveTestController].
18 : class _LiveTest extends LiveTest {
19 : final LiveTestController _controller;
20 :
21 10 : Suite get suite => _controller._suite;
22 :
23 0 : List<Group> get groups => _controller._groups;
24 :
25 10 : Test get test => _controller._test;
26 :
27 10 : State get state => _controller._state;
28 :
29 : Stream<State> get onStateChange =>
30 15 : _controller._onStateChangeController.stream;
31 :
32 0 : List<AsyncError> get errors => new UnmodifiableListView(_controller._errors);
33 :
34 15 : Stream<AsyncError> get onError => _controller._onErrorController.stream;
35 :
36 15 : Stream<Message> get onMessage => _controller._onMessageController.stream;
37 :
38 15 : Future get onComplete => _controller.completer.future;
39 :
40 10 : Future run() => _controller._run();
41 :
42 0 : Future close() => _controller._close();
43 :
44 5 : _LiveTest(this._controller);
45 : }
46 :
47 : /// A controller that drives a [LiveTest].
48 : ///
49 : /// This is a utility class to make it easier for implementors of [Test] to
50 : /// create the [LiveTest] returned by [Test.load]. The [LiveTest] is accessible
51 : /// through [LiveTestController.liveTest].
52 : ///
53 : /// This automatically handles some of [LiveTest]'s guarantees, but for the most
54 : /// part it's the caller's responsibility to make sure everything gets
55 : /// dispatched in the correct order.
56 : class LiveTestController {
57 : /// The [LiveTest] controlled by [this].
58 5 : LiveTest get liveTest => _liveTest;
59 : LiveTest _liveTest;
60 :
61 : /// The test suite that's running [this].
62 : final Suite _suite;
63 :
64 : /// The groups containing [this].
65 : final List<Group> _groups;
66 :
67 : /// The test that's being run.
68 : final Test _test;
69 :
70 : /// The function that will actually start the test running.
71 : final Function _onRun;
72 :
73 : /// A function to run when the test is closed.
74 : ///
75 : /// This may be `null`.
76 : final Function _onClose;
77 :
78 : /// The list of errors caught by the test.
79 : final _errors = new List<AsyncError>();
80 :
81 : /// The current state of the test.
82 : var _state = const State(Status.pending, Result.success);
83 :
84 : /// The controller for [LiveTest.onStateChange].
85 : ///
86 : /// This is synchronous to ensure that events are well-ordered across multiple
87 : /// streams.
88 : final _onStateChangeController =
89 : new StreamController<State>.broadcast(sync: true);
90 :
91 : /// The controller for [LiveTest.onError].
92 : ///
93 : /// This is synchronous to ensure that events are well-ordered across multiple
94 : /// streams.
95 : final _onErrorController =
96 : new StreamController<AsyncError>.broadcast(sync: true);
97 :
98 : /// The controller for [LiveTest.onMessage].
99 : ///
100 : /// This is synchronous to ensure that events are well-ordered across multiple
101 : /// streams.
102 : final _onMessageController =
103 : new StreamController<Message>.broadcast(sync: true);
104 :
105 : /// The completer for [LiveTest.onComplete];
106 : final completer = new Completer();
107 :
108 : /// Whether [run] has been called.
109 : var _runCalled = false;
110 :
111 : /// Whether [close] has been called.
112 10 : bool get _isClosed => _onErrorController.isClosed;
113 :
114 : /// Creates a new controller for a [LiveTest].
115 : ///
116 : /// [test] is the test being run; [suite] is the suite that contains it.
117 : ///
118 : /// [onRun] is a function that's called from [LiveTest.run]. It should start
119 : /// the test running. The controller takes care of ensuring that
120 : /// [LiveTest.run] isn't called more than once and that [LiveTest.onComplete]
121 : /// is returned.
122 : ///
123 : /// [onClose] is a function that's called the first time [LiveTest.close] is
124 : /// called. It should clean up any resources that have been allocated for the
125 : /// test and ensure that the test finishes quickly if it's still running. It
126 : /// will only be called if [onRun] has been called first.
127 : ///
128 : /// If [groups] is passed, it's used to populate the list of groups that
129 : /// contain this test. Otherwise, `suite.group` is used.
130 : LiveTestController(Suite suite, this._test, void onRun(), void onClose(),
131 : {Iterable<Group> groups})
132 : : _suite = suite,
133 : _onRun = onRun,
134 : _onClose = onClose,
135 : _groups =
136 10 : groups == null ? [suite.group] : new List.unmodifiable(groups) {
137 10 : _liveTest = new _LiveTest(this);
138 : }
139 :
140 : /// Adds an error to the [LiveTest].
141 : ///
142 : /// This both adds the error to [LiveTest.errors] and emits it via
143 : /// [LiveTest.onError]. [stackTrace] is automatically converted into a [Chain]
144 : /// if it's not one already.
145 : void addError(error, StackTrace stackTrace) {
146 0 : if (_isClosed) return;
147 :
148 0 : var asyncError = new AsyncError(error, new Chain.forTrace(stackTrace));
149 0 : _errors.add(asyncError);
150 0 : _onErrorController.add(asyncError);
151 : }
152 :
153 : /// Sets the current state of the [LiveTest] to [newState].
154 : ///
155 : /// If [newState] is different than the old state, this both sets
156 : /// [LiveTest.state] and emits the new state via [LiveTest.onStateChanged]. If
157 : /// it's not different, this does nothing.
158 : void setState(State newState) {
159 5 : if (_isClosed) return;
160 10 : if (_state == newState) return;
161 :
162 5 : _state = newState;
163 10 : _onStateChangeController.add(newState);
164 : }
165 :
166 : /// Emits message over [LiveTest.onMessage].
167 : void message(Message message) {
168 2 : if (_onMessageController.hasListener) {
169 2 : _onMessageController.add(message);
170 : } else {
171 : // Make sure all messages get surfaced one way or another to aid in
172 : // debugging.
173 0 : Zone.ROOT.print(message.text);
174 : }
175 : }
176 :
177 : /// A wrapper for [_onRun] that ensures that it follows the guarantees for
178 : /// [LiveTest.run].
179 : Future _run() {
180 5 : if (_runCalled) {
181 0 : throw new StateError("LiveTest.run() may not be called more than once.");
182 5 : } else if (_isClosed) {
183 0 : throw new StateError("LiveTest.run() may not be called for a closed "
184 : "test.");
185 : }
186 5 : _runCalled = true;
187 :
188 10 : _onRun();
189 10 : return liveTest.onComplete;
190 : }
191 :
192 : /// A wrapper for [_onClose] that ensures that all controllers are closed.
193 : Future _close() {
194 0 : if (_isClosed) return completer.future;
195 :
196 0 : _onStateChangeController.close();
197 0 : _onErrorController.close();
198 :
199 0 : if (_runCalled) {
200 0 : _onClose();
201 : } else {
202 0 : completer.complete();
203 : }
204 :
205 0 : return completer.future;
206 : }
207 : }
|