Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1011)

Side by Side Diff: mojo/public/dart/third_party/test/lib/src/runner/engine.dart

Issue 1346773002: Stop running pub get at gclient sync time and fix build bugs (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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 library test.runner.engine;
6
7 import 'dart:async';
8 import 'dart:collection';
9
10 import 'package:async/async.dart' hide Result;
11 import 'package:collection/collection.dart';
12 import 'package:pool/pool.dart';
13
14 import '../backend/live_test.dart';
15 import '../backend/live_test_controller.dart';
16 import '../backend/state.dart';
17 import '../backend/test.dart';
18 import 'load_suite.dart';
19 import 'runner_suite.dart';
20
21 /// An [Engine] manages a run that encompasses multiple test suites.
22 ///
23 /// Test suites are provided by passing them into [suiteSink]. Once all suites
24 /// have been provided, the user should close [suiteSink] to indicate this.
25 /// [run] won't terminate until [suiteSink] is closed. Suites will be run in the
26 /// order they're provided to [suiteSink]. Tests within those suites will
27 /// likewise be run in the order of [Suite.tests].
28 ///
29 /// The current status of every test is visible via [liveTests]. [onTestStarted]
30 /// can also be used to be notified when a test is about to be run.
31 ///
32 /// The engine has some special logic for [LoadSuite]s and the tests they
33 /// contain, referred to as "load tests". Load tests exist to provide visibility
34 /// into the process of loading test files, but as long as that process is
35 /// proceeding normally users usually don't care about it, so the engine only
36 /// surfaces running load tests (that is, includes them in [liveTests] and other
37 /// collections) under specific circumstances.
38 ///
39 /// If only load tests are running, exactly one load test will be in [active]
40 /// and [liveTests]. If this test passes, it will be removed from both [active]
41 /// and [liveTests] and *will not* be added to [passed]. If at any point a load
42 /// test fails, it will be added to [failed] and [liveTests].
43 ///
44 /// The test suite loaded by a load suite will be automatically be run by the
45 /// engine; it doesn't need to be added to [suiteSink] manually.
46 ///
47 /// Load tests will always be emitted through [onTestStarted] so users can watch
48 /// their event streams once they start running.
49 class Engine {
50 /// Whether [run] has been called yet.
51 var _runCalled = false;
52
53 /// Whether [close] has been called.
54 var _closed = false;
55
56 /// Whether [close] was called before all the tests finished running.
57 ///
58 /// This is `null` if close hasn't been called and the tests are still
59 /// running, `true` if close was called before the tests finished running, and
60 /// `false` if the tests finished running before close was called.
61 var _closedBeforeDone;
62
63 /// A pool that limits the number of test suites running concurrently.
64 final Pool _runPool;
65
66 /// A pool that limits the number of test suites loaded concurrently.
67 ///
68 /// Once this reaches its limit, loading any additional test suites will cause
69 /// previous suites to be unloaded in the order they completed.
70 final Pool _loadPool;
71
72 /// Whether all tests passed.
73 ///
74 /// This fires once all tests have completed and [suiteSink] has been closed.
75 /// This will be `null` if [close] was called before all the tests finished
76 /// running.
77 Future<bool> get success async {
78 await _group.future;
79 if (_closedBeforeDone) return null;
80 return liveTests.every((liveTest) =>
81 liveTest.state.result == Result.success);
82 }
83
84 /// A group of futures for each test suite.
85 final _group = new FutureGroup();
86
87 /// A sink used to pass [RunnerSuite]s in to the engine to run.
88 ///
89 /// Suites may be added as quickly as they're available; the Engine will only
90 /// run as many as necessary at a time based on its concurrency settings.
91 ///
92 /// Suites added to the sink will be closed by the engine based on its
93 /// internal logic.
94 Sink<RunnerSuite> get suiteSink => new DelegatingSink(_suiteController.sink);
95 final _suiteController = new StreamController<RunnerSuite>();
96
97 /// All the currently-known tests that have run, are running, or will run.
98 ///
99 /// These are [LiveTest]s, representing the in-progress state of each test.
100 /// Tests that have not yet begun running are marked [Status.pending]; tests
101 /// that have finished are marked [Status.complete].
102 ///
103 /// This is guaranteed to contain the same tests as the union of [passed],
104 /// [skipped], [failed], and [active].
105 ///
106 /// [LiveTest.run] must not be called on these tests.
107 List<LiveTest> get liveTests => new UnmodifiableListView(_liveTests);
108 final _liveTests = new List<LiveTest>();
109
110 /// A stream that emits each [LiveTest] as it's about to start running.
111 ///
112 /// This is guaranteed to fire before [LiveTest.onStateChange] first fires.
113 Stream<LiveTest> get onTestStarted => _onTestStartedController.stream;
114 final _onTestStartedController = new StreamController<LiveTest>.broadcast();
115
116 /// The set of tests that have completed and been marked as passing.
117 Set<LiveTest> get passed => new UnmodifiableSetView(_passed);
118 final _passed = new Set<LiveTest>();
119
120 /// The set of tests that have completed and been marked as skipped.
121 Set<LiveTest> get skipped => new UnmodifiableSetView(_skipped);
122 final _skipped = new Set<LiveTest>();
123
124 /// The set of tests that have completed and been marked as failing or error.
125 Set<LiveTest> get failed => new UnmodifiableSetView(_failed);
126 final _failed = new Set<LiveTest>();
127
128 /// The tests that are still running, in the order they begain running.
129 List<LiveTest> get active => new UnmodifiableListView(_active);
130 final _active = new QueueList<LiveTest>();
131
132 /// The tests from [LoadSuite]s that are still running, in the order they
133 /// began running.
134 ///
135 /// This is separate from [active] because load tests aren't always surfaced.
136 final _activeLoadTests = new List<LiveTest>();
137
138 /// Whether this engine is idle—that is, not currently executing a test.
139 bool get isIdle => _group.isIdle;
140
141 /// A broadcast stream that fires an event whenever [isIdle] switches from
142 /// `false` to `true`.
143 Stream get onIdle => _group.onIdle;
144
145 /// Creates an [Engine] that will run all tests provided via [suiteSink].
146 ///
147 /// [concurrency] controls how many suites are run at once, and defaults to 1.
148 /// [maxSuites] controls how many suites are *loaded* at once, and defaults to
149 /// four times [concurrency].
150 Engine({int concurrency, int maxSuites})
151 : _runPool = new Pool(concurrency == null ? 1 : concurrency),
152 _loadPool = new Pool(maxSuites == null
153 ? (concurrency == null ? 2 : concurrency * 2)
154 : maxSuites) {
155 _group.future.then((_) {
156 if (_closedBeforeDone == null) _closedBeforeDone = false;
157 }).catchError((_) {
158 // Don't top-level errors. They'll be thrown via [success] anyway.
159 });
160 }
161
162 /// Creates an [Engine] that will run all tests in [suites].
163 ///
164 /// [concurrency] controls how many suites are run at once. An engine
165 /// constructed this way will automatically close its [suiteSink], meaning
166 /// that no further suites may be provided.
167 factory Engine.withSuites(List<RunnerSuite> suites, {int concurrency}) {
168 var engine = new Engine(concurrency: concurrency);
169 for (var suite in suites) engine.suiteSink.add(suite);
170 engine.suiteSink.close();
171 return engine;
172 }
173
174 /// Runs all tests in all suites defined by this engine.
175 ///
176 /// This returns `true` if all tests succeed, and `false` otherwise. It will
177 /// only return once all tests have finished running and [suiteSink] has been
178 /// closed.
179 Future<bool> run() {
180 if (_runCalled) {
181 throw new StateError("Engine.run() may not be called more than once.");
182 }
183 _runCalled = true;
184
185 _suiteController.stream.listen((suite) {
186 _group.add(new Future.sync(() async {
187 var loadResource = await _loadPool.request();
188
189 if (suite is LoadSuite) {
190 suite = await _addLoadSuite(suite);
191 if (suite == null) {
192 loadResource.release();
193 return;
194 }
195 }
196
197 await _runPool.withResource(() async {
198 if (_closed) return null;
199
200 // TODO(nweiz): Use a real for loop when issue 23394 is fixed.
201 await Future.forEach(suite.tests, (test) async {
202 if (_closed) return;
203
204 var liveTest = test.metadata.skip
205 ? _skippedTest(suite, test)
206 : test.load(suite);
207 _liveTests.add(liveTest);
208 _active.add(liveTest);
209
210 // If there were no active non-load tests, the current active test
211 // would have been a load test. In that case, remove it, since now w e
212 // have a non-load test to add.
213 if (_active.isNotEmpty && _active.first.suite is LoadSuite) {
214 _liveTests.remove(_active.removeFirst());
215 }
216
217 liveTest.onStateChange.listen((state) {
218 if (state.status != Status.complete) return;
219 _active.remove(liveTest);
220
221 // If we're out of non-load tests, surface a load test.
222 if (_active.isEmpty && _activeLoadTests.isNotEmpty) {
223 _active.add(_activeLoadTests.first);
224 _liveTests.add(_activeLoadTests.first);
225 }
226
227 if (state.result != Result.success) {
228 _passed.remove(liveTest);
229 _failed.add(liveTest);
230 } else if (liveTest.test.metadata.skip) {
231 _skipped.add(liveTest);
232 } else {
233 _passed.add(liveTest);
234 }
235 });
236
237 _onTestStartedController.add(liveTest);
238
239 // First, schedule a microtask to ensure that [onTestStarted] fires
240 // before the first [LiveTest.onStateChange] event. Once the test
241 // finishes, use [new Future] to do a coarse-grained event loop pump
242 // to avoid starving non-microtask events.
243 await new Future.microtask(liveTest.run);
244 await new Future(() {});
245 });
246
247 loadResource.allowRelease(() => suite.close());
248 });
249 }));
250 }, onDone: _group.close);
251
252 return success;
253 }
254
255 /// Returns a dummy [LiveTest] for a test marked as "skip".
256 LiveTest _skippedTest(RunnerSuite suite, Test test) {
257 var controller;
258 controller = new LiveTestController(suite, test, () {
259 controller.setState(const State(Status.running, Result.success));
260 controller.setState(const State(Status.complete, Result.success));
261 controller.completer.complete();
262 }, () {});
263 return controller.liveTest;
264 }
265
266 /// Adds listeners for [suite].
267 ///
268 /// Load suites have specific logic apart from normal test suites.
269 Future<RunnerSuite> _addLoadSuite(LoadSuite suite) async {
270 var liveTest = await suite.tests.single.load(suite);
271
272 _activeLoadTests.add(liveTest);
273
274 // Only surface the load test if there are no other tests currently running.
275 if (_active.isEmpty) {
276 _liveTests.add(liveTest);
277 _active.add(liveTest);
278 }
279
280 liveTest.onStateChange.listen((state) {
281 if (state.status != Status.complete) return;
282 _activeLoadTests.remove(liveTest);
283
284 // Only one load test will be active at any given time, and it will always
285 // be the only active test. Remove it and, if possible, surface another
286 // load test.
287 if (_active.isNotEmpty && _active.first.suite == suite) {
288 _active.remove(liveTest);
289 _liveTests.remove(liveTest);
290
291 if (_activeLoadTests.isNotEmpty) {
292 _active.add(_activeLoadTests.last);
293 _liveTests.add(_activeLoadTests.last);
294 }
295 }
296
297 // Surface the load test if it fails so that the user can see the failure.
298 if (state.result == Result.success) return;
299 _failed.add(liveTest);
300 _liveTests.add(liveTest);
301 });
302
303 // Run the test immediately. We don't want loading to be blocked on suites
304 // that are already running.
305 _onTestStartedController.add(liveTest);
306 await liveTest.run();
307
308 return suite.suite;
309 }
310
311 /// Signals that the caller is done paying attention to test results and the
312 /// engine should release any resources it has allocated.
313 ///
314 /// Any actively-running tests are also closed. VM tests are allowed to finish
315 /// running so that any modifications they've made to the filesystem can be
316 /// cleaned up.
317 ///
318 /// **Note that closing the engine is not the same as closing [suiteSink].**
319 /// Closing [suiteSink] indicates that no more input will be provided, closing
320 /// the engine indicates that no more output should be emitted.
321 Future close() async {
322 _closed = true;
323 if (_closedBeforeDone == null) _closedBeforeDone = true;
324 _suiteController.close();
325
326 // Close the running tests first so that we're sure to wait for them to
327 // finish before we close their suites and cause them to become unloaded.
328 var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests);
329 await Future.wait(allLiveTests.map((liveTest) => liveTest.close()));
330
331 var allSuites = allLiveTests.map((liveTest) => liveTest.suite).toSet();
332 await Future.wait(allSuites.map((suite) => suite.close()));
333 }
334 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698