OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:async'; | 5 import 'dart:async'; |
6 import 'dart:collection'; | 6 import 'dart:collection'; |
7 | 7 |
8 import 'package:async/async.dart' hide Result; | 8 import 'package:async/async.dart' hide Result; |
9 import 'package:collection/collection.dart'; | 9 import 'package:collection/collection.dart'; |
10 import 'package:pool/pool.dart'; | 10 import 'package:pool/pool.dart'; |
11 | 11 |
12 import '../backend/group.dart'; | 12 import '../backend/group.dart'; |
13 import '../backend/group_entry.dart'; | 13 import '../backend/group_entry.dart'; |
14 import '../backend/invoker.dart'; | 14 import '../backend/invoker.dart'; |
15 import '../backend/live_test.dart'; | 15 import '../backend/live_test.dart'; |
16 import '../backend/live_test_controller.dart'; | 16 import '../backend/live_test_controller.dart'; |
17 import '../backend/state.dart'; | 17 import '../backend/state.dart'; |
18 import '../backend/test.dart'; | 18 import '../backend/test.dart'; |
| 19 import '../util/iterable_set.dart'; |
| 20 import 'live_suite.dart'; |
| 21 import 'live_suite_controller.dart'; |
19 import 'load_suite.dart'; | 22 import 'load_suite.dart'; |
20 import 'runner_suite.dart'; | 23 import 'runner_suite.dart'; |
21 | 24 |
22 /// An [Engine] manages a run that encompasses multiple test suites. | 25 /// An [Engine] manages a run that encompasses multiple test suites. |
23 /// | 26 /// |
24 /// Test suites are provided by passing them into [suiteSink]. Once all suites | 27 /// Test suites are provided by passing them into [suiteSink]. Once all suites |
25 /// have been provided, the user should close [suiteSink] to indicate this. | 28 /// have been provided, the user should close [suiteSink] to indicate this. |
26 /// [run] won't terminate until [suiteSink] is closed. Suites will be run in the | 29 /// [run] won't terminate until [suiteSink] is closed. Suites will be run in the |
27 /// order they're provided to [suiteSink]. Tests within those suites will | 30 /// order they're provided to [suiteSink]. Tests within those suites will |
28 /// likewise be run in the order they're declared. | 31 /// likewise be run in the order they're declared. |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
95 Sink<RunnerSuite> get suiteSink => new DelegatingSink(_suiteController.sink); | 98 Sink<RunnerSuite> get suiteSink => new DelegatingSink(_suiteController.sink); |
96 final _suiteController = new StreamController<RunnerSuite>(); | 99 final _suiteController = new StreamController<RunnerSuite>(); |
97 | 100 |
98 /// All the [RunnerSuite]s added to [suiteSink] so far. | 101 /// All the [RunnerSuite]s added to [suiteSink] so far. |
99 /// | 102 /// |
100 /// Note that if a [LoadSuite] is added, this will only contain that suite, | 103 /// Note that if a [LoadSuite] is added, this will only contain that suite, |
101 /// not the suite it loads. | 104 /// not the suite it loads. |
102 Set<RunnerSuite> get addedSuites => new UnmodifiableSetView(_addedSuites); | 105 Set<RunnerSuite> get addedSuites => new UnmodifiableSetView(_addedSuites); |
103 final _addedSuites = new Set<RunnerSuite>(); | 106 final _addedSuites = new Set<RunnerSuite>(); |
104 | 107 |
105 /// A broadcast that emits each [RunnerSuite] as it's added to the engine via | 108 /// A broadcast stream that emits each [RunnerSuite] as it's added to the |
106 /// [suiteSink]. | 109 /// engine via [suiteSink]. |
107 /// | 110 /// |
108 /// Note that if a [LoadSuite] is added, this will only return that suite, not | 111 /// Note that if a [LoadSuite] is added, this will only return that suite, not |
109 /// the suite it loads. | 112 /// the suite it loads. |
110 /// | 113 /// |
111 /// This is guaranteed to fire after the suite is added to [addedSuites]. | 114 /// This is guaranteed to fire after the suite is added to [addedSuites]. |
112 Stream<RunnerSuite> get onSuiteAdded => _onSuiteAddedController.stream; | 115 Stream<RunnerSuite> get onSuiteAdded => _onSuiteAddedController.stream; |
113 final _onSuiteAddedController = new StreamController<RunnerSuite>.broadcast(); | 116 final _onSuiteAddedController = new StreamController<RunnerSuite>.broadcast(); |
114 | 117 |
115 /// All the currently-known tests that have run, are running, or will run. | 118 /// All the currently-known suites that have run or are running. |
| 119 /// |
| 120 /// These are [LiveSuite]s, representing the in-progress state of each suite |
| 121 /// as its component tests are being run. |
| 122 /// |
| 123 /// Note that unlike [addedSuites], for suites that are loaded using |
| 124 /// [LoadSuite]s, both the [LoadSuite] and the suite it loads will eventually |
| 125 /// be in this set. |
| 126 Set<LiveSuite> get liveSuites => new UnmodifiableSetView(_liveSuites); |
| 127 final _liveSuites = new Set<LiveSuite>(); |
| 128 |
| 129 /// A broadcast stream that emits each [LiveSuite] as it's loaded. |
| 130 /// |
| 131 /// Note that unlike [onSuiteAdded], for suites that are loaded using |
| 132 /// [LoadSuite]s, both the [LoadSuite] and the suite it loads will eventually |
| 133 /// be emitted by this stream. |
| 134 Stream<LiveSuite> get onSuiteStarted => _onSuiteStartedController.stream; |
| 135 final _onSuiteStartedController = new StreamController<LiveSuite>.broadcast(); |
| 136 |
| 137 /// All the currently-known tests that have run or are running. |
116 /// | 138 /// |
117 /// These are [LiveTest]s, representing the in-progress state of each test. | 139 /// These are [LiveTest]s, representing the in-progress state of each test. |
118 /// Tests that have not yet begun running are marked [Status.pending]; tests | 140 /// Tests that have not yet begun running are marked [Status.pending]; tests |
119 /// that have finished are marked [Status.complete]. | 141 /// that have finished are marked [Status.complete]. |
120 /// | 142 /// |
121 /// This is guaranteed to contain the same tests as the union of [passed], | 143 /// This is guaranteed to contain the same tests as the union of [passed], |
122 /// [skipped], [failed], and [active]. | 144 /// [skipped], [failed], and [active]. |
123 /// | 145 /// |
124 /// [LiveTest.run] must not be called on these tests. | 146 /// [LiveTest.run] must not be called on these tests. |
125 List<LiveTest> get liveTests => new UnmodifiableListView(_liveTests); | 147 Set<LiveTest> get liveTests => new GroupSet.from( |
126 final _liveTests = new List<LiveTest>(); | 148 [passed, skipped, failed, new IterableSet(active)], |
| 149 disjoint: true); |
127 | 150 |
128 /// A stream that emits each [LiveTest] as it's about to start running. | 151 /// A stream that emits each [LiveTest] as it's about to start running. |
129 /// | 152 /// |
130 /// This is guaranteed to fire before [LiveTest.onStateChange] first fires. | 153 /// This is guaranteed to fire before [LiveTest.onStateChange] first fires. |
131 Stream<LiveTest> get onTestStarted => _onTestStartedController.stream; | 154 Stream<LiveTest> get onTestStarted => _onTestStartedGroup.stream; |
132 final _onTestStartedController = new StreamController<LiveTest>.broadcast(); | 155 final _onTestStartedGroup = new StreamGroup<LiveTest>.broadcast(); |
133 | 156 |
134 /// The set of tests that have completed and been marked as passing. | 157 /// The set of tests that have completed and been marked as passing. |
135 Set<LiveTest> get passed => new UnmodifiableSetView(_passed); | 158 Set<LiveTest> get passed => _passedGroup.set; |
136 final _passed = new Set<LiveTest>(); | 159 final _passedGroup = new SetGroup<LiveTest>(disjoint: true); |
137 | 160 |
138 /// The set of tests that have completed and been marked as skipped. | 161 /// The set of tests that have completed and been marked as skipped. |
139 Set<LiveTest> get skipped => new UnmodifiableSetView(_skipped); | 162 Set<LiveTest> get skipped => _skippedGroup.set; |
140 final _skipped = new Set<LiveTest>(); | 163 final _skippedGroup = new SetGroup<LiveTest>(disjoint: true); |
141 | 164 |
142 /// The set of tests that have completed and been marked as failing or error. | 165 /// The set of tests that have completed and been marked as failing or error. |
143 Set<LiveTest> get failed => new UnmodifiableSetView(_failed); | 166 Set<LiveTest> get failed => _failedGroup.set; |
144 final _failed = new Set<LiveTest>(); | 167 final _failedGroup = new SetGroup<LiveTest>(disjoint: true); |
145 | 168 |
146 /// The tests that are still running, in the order they begain running. | 169 /// The tests that are still running, in the order they begain running. |
147 List<LiveTest> get active => new UnmodifiableListView(_active); | 170 List<LiveTest> get active => new UnmodifiableListView(_active); |
148 final _active = new QueueList<LiveTest>(); | 171 final _active = new QueueList<LiveTest>(); |
149 | 172 |
150 /// The set of tests that have been marked for restarting. | 173 /// The set of tests that have been marked for restarting. |
151 /// | 174 /// |
152 /// This is always a subset of [active]. Once a test in here has finished | 175 /// This is always a subset of [active]. Once a test in here has finished |
153 /// running, it's run again. | 176 /// running, it's run again. |
154 final _restarted = new Set<LiveTest>(); | 177 final _restarted = new Set<LiveTest>(); |
(...skipping 15 matching lines...) Expand all Loading... |
170 /// | 193 /// |
171 /// [concurrency] controls how many suites are run at once, and defaults to 1. | 194 /// [concurrency] controls how many suites are run at once, and defaults to 1. |
172 /// [maxSuites] controls how many suites are *loaded* at once, and defaults to | 195 /// [maxSuites] controls how many suites are *loaded* at once, and defaults to |
173 /// four times [concurrency]. | 196 /// four times [concurrency]. |
174 Engine({int concurrency, int maxSuites}) | 197 Engine({int concurrency, int maxSuites}) |
175 : _runPool = new Pool(concurrency == null ? 1 : concurrency), | 198 : _runPool = new Pool(concurrency == null ? 1 : concurrency), |
176 _loadPool = new Pool(maxSuites == null | 199 _loadPool = new Pool(maxSuites == null |
177 ? (concurrency == null ? 2 : concurrency * 2) | 200 ? (concurrency == null ? 2 : concurrency * 2) |
178 : maxSuites) { | 201 : maxSuites) { |
179 _group.future.then((_) { | 202 _group.future.then((_) { |
| 203 _onTestStartedGroup.close(); |
| 204 _onSuiteStartedController.close(); |
180 if (_closedBeforeDone == null) _closedBeforeDone = false; | 205 if (_closedBeforeDone == null) _closedBeforeDone = false; |
181 }).catchError((_) { | 206 }).catchError((_) { |
182 // Don't top-level errors. They'll be thrown via [success] anyway. | 207 // Don't top-level errors. They'll be thrown via [success] anyway. |
183 }); | 208 }); |
184 } | 209 } |
185 | 210 |
186 /// Creates an [Engine] that will run all tests in [suites]. | 211 /// Creates an [Engine] that will run all tests in [suites]. |
187 /// | 212 /// |
188 /// [concurrency] controls how many suites are run at once. An engine | 213 /// [concurrency] controls how many suites are run at once. An engine |
189 /// constructed this way will automatically close its [suiteSink], meaning | 214 /// constructed this way will automatically close its [suiteSink], meaning |
(...skipping 16 matching lines...) Expand all Loading... |
206 } | 231 } |
207 _runCalled = true; | 232 _runCalled = true; |
208 | 233 |
209 _suiteController.stream.listen((suite) { | 234 _suiteController.stream.listen((suite) { |
210 _addedSuites.add(suite); | 235 _addedSuites.add(suite); |
211 _onSuiteAddedController.add(suite); | 236 _onSuiteAddedController.add(suite); |
212 | 237 |
213 _group.add(new Future.sync(() async { | 238 _group.add(new Future.sync(() async { |
214 var loadResource = await _loadPool.request(); | 239 var loadResource = await _loadPool.request(); |
215 | 240 |
| 241 var controller; |
216 if (suite is LoadSuite) { | 242 if (suite is LoadSuite) { |
217 suite = await _addLoadSuite(suite); | 243 controller = await _addLoadSuite(suite); |
218 if (suite == null) { | 244 if (controller == null) { |
219 loadResource.release(); | 245 loadResource.release(); |
220 return; | 246 return; |
221 } | 247 } |
| 248 } else { |
| 249 controller = new LiveSuiteController(suite); |
222 } | 250 } |
223 | 251 |
| 252 _addLiveSuite(controller.liveSuite); |
| 253 |
224 await _runPool.withResource(() async { | 254 await _runPool.withResource(() async { |
225 if (_closed) return; | 255 if (_closed) return; |
226 await _runGroup(suite, suite.group, []); | 256 await _runGroup(controller, controller.liveSuite.suite.group, []); |
227 loadResource.allowRelease(() => suite.close()); | 257 loadResource.allowRelease(() => controller.close()); |
228 }); | 258 }); |
229 })); | 259 })); |
230 }, onDone: () { | 260 }, onDone: () { |
231 _onSuiteAddedController.close(); | 261 _onSuiteAddedController.close(); |
232 _group.close(); | 262 _group.close(); |
233 }); | 263 }); |
234 | 264 |
235 return success; | 265 return success; |
236 } | 266 } |
237 | 267 |
238 /// Runs all the entries in [entries] in sequence. | 268 /// Runs all the entries in [group] in sequence. |
239 /// | 269 /// |
| 270 /// [suiteController] is the controller fo the suite that contains [group]. |
240 /// [parents] is a list of groups that contain [group]. It may be modified, | 271 /// [parents] is a list of groups that contain [group]. It may be modified, |
241 /// but it's guaranteed to be in its original state once this function has | 272 /// but it's guaranteed to be in its original state once this function has |
242 /// finished. | 273 /// finished. |
243 Future _runGroup(RunnerSuite suite, Group group, List<Group> parents) async { | 274 Future _runGroup(LiveSuiteController suiteController, Group group, |
| 275 List<Group> parents) async { |
244 parents.add(group); | 276 parents.add(group); |
245 try { | 277 try { |
246 if (group.metadata.skip) { | 278 if (group.metadata.skip) { |
247 await _runLiveTest(_skippedTest(suite, group, parents)); | 279 await _runSkippedTest(suiteController, group, parents); |
248 return; | 280 return; |
249 } | 281 } |
250 | 282 |
251 var setUpAllSucceeded = true; | 283 var setUpAllSucceeded = true; |
252 if (group.setUpAll != null) { | 284 if (group.setUpAll != null) { |
253 var liveTest = group.setUpAll.load(suite, groups: parents); | 285 var liveTest = group.setUpAll.load(suiteController.liveSuite.suite, |
254 await _runLiveTest(liveTest, countSuccess: false); | 286 groups: parents); |
| 287 await _runLiveTest(suiteController, liveTest, countSuccess: false); |
255 setUpAllSucceeded = liveTest.state.result == Result.success; | 288 setUpAllSucceeded = liveTest.state.result == Result.success; |
256 } | 289 } |
257 | 290 |
258 if (!_closed && setUpAllSucceeded) { | 291 if (!_closed && setUpAllSucceeded) { |
259 for (var entry in group.entries) { | 292 for (var entry in group.entries) { |
260 if (_closed) return; | 293 if (_closed) return; |
261 | 294 |
262 if (entry is Group) { | 295 if (entry is Group) { |
263 await _runGroup(suite, entry, parents); | 296 await _runGroup(suiteController, entry, parents); |
264 } else if (entry.metadata.skip) { | 297 } else if (entry.metadata.skip) { |
265 await _runLiveTest(_skippedTest(suite, entry, parents)); | 298 await _runSkippedTest(suiteController, entry, parents); |
266 } else { | 299 } else { |
267 var test = entry as Test; | 300 var test = entry as Test; |
268 await _runLiveTest(test.load(suite, groups: parents)); | 301 await _runLiveTest( |
| 302 suiteController, |
| 303 test.load(suiteController.liveSuite.suite, groups: parents)); |
269 } | 304 } |
270 } | 305 } |
271 } | 306 } |
272 | 307 |
273 // Even if we're closed or setUpAll failed, we want to run all the | 308 // Even if we're closed or setUpAll failed, we want to run all the |
274 // teardowns to ensure that any state is properly cleaned up. | 309 // teardowns to ensure that any state is properly cleaned up. |
275 if (group.tearDownAll != null) { | 310 if (group.tearDownAll != null) { |
276 var liveTest = group.tearDownAll.load(suite, groups: parents); | 311 var liveTest = group.tearDownAll.load(suiteController.liveSuite.suite, |
277 await _runLiveTest(liveTest, countSuccess: false); | 312 groups: parents); |
| 313 await _runLiveTest(suiteController, liveTest, countSuccess: false); |
278 if (_closed) await liveTest.close(); | 314 if (_closed) await liveTest.close(); |
279 } | 315 } |
280 } finally { | 316 } finally { |
281 parents.remove(group); | 317 parents.remove(group); |
282 } | 318 } |
283 } | 319 } |
284 | 320 |
285 /// Returns a dummy [LiveTest] for a test or group marked as "skip". | 321 /// Runs [liveTest] using [suiteController]. |
286 /// | |
287 /// [parents] is a list of groups that contain [entry]. | |
288 LiveTest _skippedTest(RunnerSuite suite, GroupEntry entry, | |
289 List<Group> parents) { | |
290 // The netry name will be `null` for the root group. | |
291 var test = new LocalTest(entry.name ?? "(suite)", entry.metadata, () {}); | |
292 | |
293 var controller; | |
294 controller = new LiveTestController(suite, test, () { | |
295 controller.setState(const State(Status.running, Result.success)); | |
296 controller.setState(const State(Status.complete, Result.success)); | |
297 controller.completer.complete(); | |
298 }, () {}, groups: parents); | |
299 return controller.liveTest; | |
300 } | |
301 | |
302 /// Runs [liveTest]. | |
303 /// | 322 /// |
304 /// If [countSuccess] is `true` (the default), the test is put into [passed] | 323 /// If [countSuccess] is `true` (the default), the test is put into [passed] |
305 /// if it succeeds. Otherwise, it's removed from [liveTests] entirely. | 324 /// if it succeeds. Otherwise, it's removed from [liveTests] entirely. |
306 Future _runLiveTest(LiveTest liveTest, {bool countSuccess: true}) async { | 325 Future _runLiveTest(LiveSuiteController suiteController, LiveTest liveTest, |
307 _liveTests.add(liveTest); | 326 {bool countSuccess: true}) async { |
308 _active.add(liveTest); | 327 _active.add(liveTest); |
309 | 328 |
310 // If there were no active non-load tests, the current active test would | 329 // If there were no active non-load tests, the current active test would |
311 // have been a load test. In that case, remove it, since now we have a | 330 // have been a load test. In that case, remove it, since now we have a |
312 // non-load test to add. | 331 // non-load test to add. |
313 if (_active.isNotEmpty && _active.first.suite is LoadSuite) { | 332 if (_active.first.suite is LoadSuite) _active.removeFirst(); |
314 _liveTests.remove(_active.removeFirst()); | |
315 } | |
316 | 333 |
317 liveTest.onStateChange.listen((state) { | 334 liveTest.onStateChange.listen((state) { |
318 if (state.status != Status.complete) return; | 335 if (state.status != Status.complete) return; |
319 _active.remove(liveTest); | 336 _active.remove(liveTest); |
320 | 337 |
321 // If we're out of non-load tests, surface a load test. | 338 // If we're out of non-load tests, surface a load test. |
322 if (_active.isEmpty && _activeLoadTests.isNotEmpty) { | 339 if (_active.isEmpty && _activeLoadTests.isNotEmpty) { |
323 _active.add(_activeLoadTests.first); | 340 _active.add(_activeLoadTests.first); |
324 _liveTests.add(_activeLoadTests.first); | |
325 } | |
326 | |
327 if (state.result != Result.success) { | |
328 _passed.remove(liveTest); | |
329 _failed.add(liveTest); | |
330 } else if (liveTest.test.metadata.skip) { | |
331 _skipped.add(liveTest); | |
332 } else if (countSuccess) { | |
333 _passed.add(liveTest); | |
334 } else { | |
335 _liveTests.remove(liveTest); | |
336 } | 341 } |
337 }); | 342 }); |
338 | 343 |
339 _onTestStartedController.add(liveTest); | 344 suiteController.reportLiveTest(liveTest, countSuccess: countSuccess); |
340 | 345 |
341 // First, schedule a microtask to ensure that [onTestStarted] fires before | 346 // Schedule a microtask to ensure that [onTestStarted] fires before the |
342 // the first [LiveTest.onStateChange] event. Once the test finishes, use | 347 // first [LiveTest.onStateChange] event. |
343 // [new Future] to do a coarse-grained event loop pump to avoid starving | |
344 // non-microtask events. | |
345 await new Future.microtask(liveTest.run); | 348 await new Future.microtask(liveTest.run); |
| 349 |
| 350 // Once the test finishes, use [new Future] to do a coarse-grained event |
| 351 // loop pump to avoid starving non-microtask events. |
346 await new Future(() {}); | 352 await new Future(() {}); |
347 | 353 |
348 if (!_restarted.contains(liveTest)) return; | 354 if (!_restarted.contains(liveTest)) return; |
349 await _runLiveTest(liveTest.copy(), countSuccess: countSuccess); | 355 await _runLiveTest(suiteController, liveTest.copy(), |
| 356 countSuccess: countSuccess); |
350 _restarted.remove(liveTest); | 357 _restarted.remove(liveTest); |
351 } | 358 } |
352 | 359 |
| 360 /// Runs a dummy [LiveTest] for a test or group marked as "skip". |
| 361 /// |
| 362 /// [suiteController] is the controller for the suite that contains [entry]. |
| 363 /// [parents] is a list of groups that contain [entry]. |
| 364 Future _runSkippedTest(LiveSuiteController suiteController, GroupEntry entry, |
| 365 List<Group> parents) { |
| 366 // The netry name will be `null` for the root group. |
| 367 var test = new LocalTest(entry.name ?? "(suite)", entry.metadata, () {}); |
| 368 |
| 369 var controller; |
| 370 controller = new LiveTestController( |
| 371 suiteController.liveSuite.suite, test, () { |
| 372 controller.setState(const State(Status.running, Result.success)); |
| 373 controller.setState(const State(Status.complete, Result.success)); |
| 374 controller.completer.complete(); |
| 375 }, () {}, groups: parents); |
| 376 |
| 377 return _runLiveTest(suiteController, controller.liveTest); |
| 378 } |
| 379 |
353 /// Closes [liveTest] and tells the engine to re-run it once it's done | 380 /// Closes [liveTest] and tells the engine to re-run it once it's done |
354 /// running. | 381 /// running. |
355 /// | 382 /// |
356 /// Returns the same future as [LiveTest.close]. | 383 /// Returns the same future as [LiveTest.close]. |
357 Future restartTest(LiveTest liveTest) async { | 384 Future restartTest(LiveTest liveTest) async { |
358 if (_activeLoadTests.contains(liveTest)) { | 385 if (_activeLoadTests.contains(liveTest)) { |
359 throw new ArgumentError("Can't restart a load test."); | 386 throw new ArgumentError("Can't restart a load test."); |
360 } | 387 } |
361 | 388 |
362 if (!_active.contains(liveTest)) { | 389 if (!_active.contains(liveTest)) { |
363 throw new StateError("Can't restart inactive test " | 390 throw new StateError("Can't restart inactive test " |
364 "\"${liveTest.test.name}\"."); | 391 "\"${liveTest.test.name}\"."); |
365 } | 392 } |
366 | 393 |
367 _restarted.add(liveTest); | 394 _restarted.add(liveTest); |
368 _active.remove(liveTest); | 395 _active.remove(liveTest); |
369 await liveTest.close(); | 396 await liveTest.close(); |
370 } | 397 } |
371 | 398 |
372 /// Adds listeners for [suite]. | 399 /// Runs [suite] and returns the [LiveSuiteController] for the suite it loads. |
373 /// | 400 /// |
374 /// Load suites have specific logic apart from normal test suites. | 401 /// Returns `null` if the suite fails to load. |
375 Future<RunnerSuite> _addLoadSuite(LoadSuite suite) async { | 402 Future<LiveSuiteController> _addLoadSuite(LoadSuite suite) async { |
| 403 var controller = new LiveSuiteController(suite); |
| 404 _addLiveSuite(controller.liveSuite); |
| 405 |
376 var liveTest = await suite.test.load(suite); | 406 var liveTest = await suite.test.load(suite); |
377 | |
378 _activeLoadTests.add(liveTest); | 407 _activeLoadTests.add(liveTest); |
379 | 408 |
380 // Only surface the load test if there are no other tests currently running. | 409 // Only surface the load test if there are no other tests currently running. |
381 if (_active.isEmpty) { | 410 if (_active.isEmpty) _active.add(liveTest); |
382 _liveTests.add(liveTest); | |
383 _active.add(liveTest); | |
384 } | |
385 | 411 |
386 liveTest.onStateChange.listen((state) { | 412 liveTest.onStateChange.listen((state) { |
387 if (state.status != Status.complete) return; | 413 if (state.status != Status.complete) return; |
388 _activeLoadTests.remove(liveTest); | 414 _activeLoadTests.remove(liveTest); |
389 | 415 |
390 // Only one load test will be active at any given time, and it will always | 416 // Only one load test will be active at any given time, and it will always |
391 // be the only active test. Remove it and, if possible, surface another | 417 // be the only active test. Remove it and, if possible, surface another |
392 // load test. | 418 // load test. |
393 if (_active.isNotEmpty && _active.first.suite == suite) { | 419 if (_active.isNotEmpty && _active.first.suite == suite) { |
394 _active.remove(liveTest); | 420 _active.remove(liveTest); |
395 _liveTests.remove(liveTest); | 421 if (_activeLoadTests.isNotEmpty) _active.add(_activeLoadTests.last); |
396 | |
397 if (_activeLoadTests.isNotEmpty) { | |
398 _active.add(_activeLoadTests.last); | |
399 _liveTests.add(_activeLoadTests.last); | |
400 } | |
401 } | 422 } |
402 | |
403 // Surface the load test if it fails so that the user can see the failure. | |
404 if (state.result == Result.success) return; | |
405 _failed.add(liveTest); | |
406 _liveTests.add(liveTest); | |
407 }); | 423 }); |
408 | 424 |
409 // Run the test immediately. We don't want loading to be blocked on suites | 425 controller.reportLiveTest(liveTest, countSuccess: false); |
410 // that are already running. | 426 controller.noMoreLiveTests(); |
411 _onTestStartedController.add(liveTest); | |
412 await liveTest.run(); | |
413 | 427 |
414 return suite.suite; | 428 // Schedule a microtask to ensure that [onTestStarted] fires before the |
| 429 // first [LiveTest.onStateChange] event. |
| 430 new Future.microtask(liveTest.run); |
| 431 |
| 432 var innerSuite = await suite.suite; |
| 433 if (innerSuite == null) return null; |
| 434 |
| 435 var innerController = new LiveSuiteController(innerSuite); |
| 436 innerController.liveSuite.onClose.then((_) { |
| 437 // When the main suite is closed, close the load suite and its test as |
| 438 // well. This doesn't release any resources, but it does close streams |
| 439 // which indicates that the load test won't experience an error in the |
| 440 // future. |
| 441 liveTest.close(); |
| 442 controller.close(); |
| 443 }); |
| 444 |
| 445 return innerController; |
| 446 } |
| 447 |
| 448 /// Add [liveSuite] and the information it exposes to the engine's |
| 449 /// informational streams and collections. |
| 450 void _addLiveSuite(LiveSuite liveSuite) { |
| 451 _liveSuites.add(liveSuite); |
| 452 _onSuiteStartedController.add(liveSuite); |
| 453 |
| 454 _onTestStartedGroup.add(liveSuite.onTestStarted); |
| 455 _passedGroup.add(liveSuite.passed); |
| 456 _skippedGroup.add(liveSuite.skipped); |
| 457 _failedGroup.add(liveSuite.failed); |
415 } | 458 } |
416 | 459 |
417 /// Signals that the caller is done paying attention to test results and the | 460 /// Signals that the caller is done paying attention to test results and the |
418 /// engine should release any resources it has allocated. | 461 /// engine should release any resources it has allocated. |
419 /// | 462 /// |
420 /// Any actively-running tests are also closed. VM tests are allowed to finish | 463 /// Any actively-running tests are also closed. VM tests are allowed to finish |
421 /// running so that any modifications they've made to the filesystem can be | 464 /// running so that any modifications they've made to the filesystem can be |
422 /// cleaned up. | 465 /// cleaned up. |
423 /// | 466 /// |
424 /// **Note that closing the engine is not the same as closing [suiteSink].** | 467 /// **Note that closing the engine is not the same as closing [suiteSink].** |
(...skipping 11 matching lines...) Expand all Loading... |
436 var futures = allLiveTests.map((liveTest) => liveTest.close()).toList(); | 479 var futures = allLiveTests.map((liveTest) => liveTest.close()).toList(); |
437 | 480 |
438 // Closing the load pool will close the test suites as soon as their tests | 481 // Closing the load pool will close the test suites as soon as their tests |
439 // are done. For browser suites this is effectively immediate since their | 482 // are done. For browser suites this is effectively immediate since their |
440 // tests shut down as soon as they're closed, but for VM suites we may need | 483 // tests shut down as soon as they're closed, but for VM suites we may need |
441 // to wait for tearDowns or tearDownAlls to run. | 484 // to wait for tearDowns or tearDownAlls to run. |
442 futures.add(_loadPool.close()); | 485 futures.add(_loadPool.close()); |
443 await Future.wait(futures, eagerError: true); | 486 await Future.wait(futures, eagerError: true); |
444 } | 487 } |
445 } | 488 } |
OLD | NEW |