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 // TODO(nweiz): This is under lib so that it can be used by the unittest dummy | 5 // TODO(nweiz): This is under lib so that it can be used by the unittest dummy |
6 // package. Once that package is no longer being updated, move this back into | 6 // package. Once that package is no longer being updated, move this back into |
7 // bin. | 7 // bin. |
8 library test.executable; | 8 library test.executable; |
9 | 9 |
10 import 'dart:async'; | 10 import 'dart:async'; |
11 import 'dart:io'; | 11 import 'dart:io'; |
12 import 'dart:math' as math; | 12 import 'dart:math' as math; |
13 | 13 |
14 import 'package:args/args.dart'; | 14 import 'package:args/args.dart'; |
| 15 import 'package:async/async.dart'; |
15 import 'package:stack_trace/stack_trace.dart'; | 16 import 'package:stack_trace/stack_trace.dart'; |
16 import 'package:yaml/yaml.dart'; | 17 import 'package:yaml/yaml.dart'; |
17 | 18 |
18 import 'backend/metadata.dart'; | 19 import 'backend/metadata.dart'; |
19 import 'backend/test_platform.dart'; | 20 import 'backend/test_platform.dart'; |
20 import 'runner/engine.dart'; | 21 import 'runner/engine.dart'; |
21 import 'runner/application_exception.dart'; | 22 import 'runner/application_exception.dart'; |
22 import 'runner/load_exception.dart'; | 23 import 'runner/load_exception.dart'; |
23 import 'runner/load_exception_suite.dart'; | 24 import 'runner/load_suite.dart'; |
24 import 'runner/loader.dart'; | 25 import 'runner/loader.dart'; |
25 import 'runner/reporter/compact.dart'; | 26 import 'runner/reporter/compact.dart'; |
26 import 'runner/reporter/expanded.dart'; | 27 import 'runner/reporter/expanded.dart'; |
27 import 'util/exit_codes.dart' as exit_codes; | 28 import 'util/exit_codes.dart' as exit_codes; |
28 import 'util/io.dart'; | 29 import 'util/io.dart'; |
29 import 'utils.dart'; | 30 import 'utils.dart'; |
30 | 31 |
31 /// The argument parser used to parse the executable arguments. | 32 /// The argument parser used to parse the executable arguments. |
32 final _parser = new ArgParser(allowTrailingOptions: true); | 33 final _parser = new ArgParser(allowTrailingOptions: true); |
33 | 34 |
(...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
204 | 205 |
205 var metadata = new Metadata(verboseTrace: options["verbose-trace"]); | 206 var metadata = new Metadata(verboseTrace: options["verbose-trace"]); |
206 var platforms = options["platform"].map(TestPlatform.find); | 207 var platforms = options["platform"].map(TestPlatform.find); |
207 var loader = new Loader(platforms, | 208 var loader = new Loader(platforms, |
208 pubServeUrl: pubServeUrl, | 209 pubServeUrl: pubServeUrl, |
209 packageRoot: options["package-root"], | 210 packageRoot: options["package-root"], |
210 color: color, | 211 color: color, |
211 metadata: metadata, | 212 metadata: metadata, |
212 jsTrace: options["js-trace"]); | 213 jsTrace: options["js-trace"]); |
213 | 214 |
| 215 var closed = false; |
214 var signalSubscription; | 216 var signalSubscription; |
215 signalSubscription = _signals.listen((_) { | 217 signalSubscription = _signals.listen((_) { |
| 218 closed = true; |
216 signalSubscription.cancel(); | 219 signalSubscription.cancel(); |
217 loader.close(); | 220 loader.close(); |
218 }); | 221 }); |
219 | 222 |
220 try { | 223 try { |
221 var engine = new Engine(concurrency: concurrency); | 224 var engine = new Engine(concurrency: concurrency); |
222 | 225 |
223 var watch = options["reporter"] == "compact" | 226 var watch = options["reporter"] == "compact" |
224 ? CompactReporter.watch | 227 ? CompactReporter.watch |
225 : ExpandedReporter.watch; | 228 : ExpandedReporter.watch; |
226 | 229 |
227 watch( | 230 watch( |
228 engine, | 231 engine, |
229 color: color, | 232 color: color, |
230 verboseTrace: options["verbose-trace"], | 233 verboseTrace: options["verbose-trace"], |
231 printPath: paths.length > 1 || | 234 printPath: paths.length > 1 || |
232 new Directory(paths.single).existsSync(), | 235 new Directory(paths.single).existsSync(), |
233 printPlatform: platforms.length > 1); | 236 printPlatform: platforms.length > 1); |
234 | 237 |
235 // Override the signal handler to close [reporter]. [loader] will still be | 238 // Override the signal handler to close [reporter]. [loader] will still be |
236 // closed in the [whenComplete] below. | 239 // closed in the [whenComplete] below. |
237 signalSubscription.onData((_) async { | 240 signalSubscription.onData((_) async { |
| 241 closed = true; |
238 signalSubscription.cancel(); | 242 signalSubscription.cancel(); |
239 | 243 |
240 // Wait a bit to print this message, since printing it eagerly looks weird | 244 // Wait a bit to print this message, since printing it eagerly looks weird |
241 // if the tests then finish immediately. | 245 // if the tests then finish immediately. |
242 var timer = new Timer(new Duration(seconds: 1), () { | 246 var timer = new Timer(new Duration(seconds: 1), () { |
243 // Print a blank line first to ensure that this doesn't interfere with | 247 // Print a blank line first to ensure that this doesn't interfere with |
244 // the compact reporter's unfinished line. | 248 // the compact reporter's unfinished line. |
245 print(""); | 249 print(""); |
246 print("Waiting for current test(s) to finish."); | 250 print("Waiting for current test(s) to finish."); |
247 print("Press Control-C again to terminate immediately."); | 251 print("Press Control-C again to terminate immediately."); |
248 }); | 252 }); |
249 | 253 |
| 254 // Make sure we close the engine *before* the loader. Otherwise, |
| 255 // LoadSuites provided by the loader may get into bad states. |
250 await engine.close(); | 256 await engine.close(); |
251 timer.cancel(); | 257 timer.cancel(); |
252 await loader.close(); | 258 await loader.close(); |
253 }); | 259 }); |
254 | 260 |
255 try { | 261 try { |
256 var results = await Future.wait([ | 262 var results = await Future.wait([ |
257 _loadSuites(paths, pattern, loader, engine), | 263 _loadSuites(paths, pattern, loader, engine), |
258 engine.run() | 264 engine.run() |
259 ], eagerError: true); | 265 ], eagerError: true); |
260 | 266 |
| 267 if (closed) return; |
| 268 |
261 // Explicitly check "== true" here because [engine.run] can return `null` | 269 // Explicitly check "== true" here because [engine.run] can return `null` |
262 // if the engine was closed prematurely. | 270 // if the engine was closed prematurely. |
263 exitCode = results.last == true ? 0 : 1; | 271 exitCode = results.last == true ? 0 : 1; |
264 } finally { | 272 } finally { |
265 signalSubscription.cancel(); | 273 signalSubscription.cancel(); |
266 await engine.close(); | 274 await engine.close(); |
267 } | 275 } |
268 | 276 |
269 if (engine.passed.length == 0 && engine.failed.length == 0 && | 277 if (engine.passed.length == 0 && engine.failed.length == 0 && |
270 engine.skipped.length == 0 && pattern != null) { | 278 engine.skipped.length == 0 && pattern != null) { |
(...skipping 23 matching lines...) Expand all Loading... |
294 } | 302 } |
295 } | 303 } |
296 | 304 |
297 /// Load the test suites in [paths] that match [pattern] and pass them to | 305 /// Load the test suites in [paths] that match [pattern] and pass them to |
298 /// [engine]. | 306 /// [engine]. |
299 /// | 307 /// |
300 /// This completes once all the tests have been added to the engine. It does not | 308 /// This completes once all the tests have been added to the engine. It does not |
301 /// run the engine. | 309 /// run the engine. |
302 Future _loadSuites(List<String> paths, Pattern pattern, Loader loader, | 310 Future _loadSuites(List<String> paths, Pattern pattern, Loader loader, |
303 Engine engine) async { | 311 Engine engine) async { |
304 var completer = new Completer(); | 312 var group = new FutureGroup(); |
305 | 313 |
306 mergeStreams(paths.map((path) { | 314 mergeStreams(paths.map((path) { |
307 if (new Directory(path).existsSync()) return loader.loadDir(path); | 315 if (new Directory(path).existsSync()) return loader.loadDir(path); |
308 if (new File(path).existsSync()) return loader.loadFile(path); | 316 if (new File(path).existsSync()) return loader.loadFile(path); |
309 | 317 |
310 return new Stream.fromFuture(new Future.error( | 318 return new Stream.fromIterable([ |
311 new LoadException(path, 'Does not exist.'), | 319 new LoadSuite("loading $path", () => |
312 new Trace.current())); | 320 throw new LoadException(path, 'Does not exist.')) |
313 })).map((suite) { | 321 ]); |
314 if (pattern == null) return suite; | 322 })).listen((loadSuite) { |
315 return suite.change( | 323 group.add(new Future.sync(() async { |
316 tests: suite.tests.where((test) => test.name.contains(pattern))); | 324 engine.suiteSink.add(loadSuite); |
317 }).listen((suite) => engine.suiteSink.add(suite), | |
318 onError: (error, stackTrace) { | |
319 if (error is LoadException) { | |
320 engine.suiteSink.add(new LoadExceptionSuite(error, stackTrace)); | |
321 } else if (!completer.isCompleted) { | |
322 completer.completeError(error, stackTrace); | |
323 } | |
324 }, onDone: () => completer.complete()); | |
325 | 325 |
326 await completer.future; | 326 var suite = await loadSuite.suite; |
| 327 if (suite == null) return; |
| 328 if (pattern != null) { |
| 329 suite = suite.change( |
| 330 tests: suite.tests.where((test) => test.name.contains(pattern))); |
| 331 } |
| 332 |
| 333 engine.suiteSink.add(suite); |
| 334 })); |
| 335 }, onError: (error, stackTrace) { |
| 336 group.add(new Future.error(error, stackTrace)); |
| 337 }, onDone: group.close); |
| 338 |
| 339 await group.future; |
327 | 340 |
328 // Once we've loaded all the suites, notify the engine that no more will be | 341 // Once we've loaded all the suites, notify the engine that no more will be |
329 // coming. | 342 // coming. |
330 engine.suiteSink.close(); | 343 engine.suiteSink.close(); |
331 } | 344 } |
332 | 345 |
333 /// Print usage information for this command. | 346 /// Print usage information for this command. |
334 /// | 347 /// |
335 /// If [error] is passed, it's used in place of the usage message and the whole | 348 /// If [error] is passed, it's used in place of the usage message and the whole |
336 /// thing is printed to stderr instead of stdout. | 349 /// thing is printed to stderr instead of stdout. |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
401 if (description is! Map) return false; | 414 if (description is! Map) return false; |
402 var path = description["path"]; | 415 var path = description["path"]; |
403 if (path is! String) return false; | 416 if (path is! String) return false; |
404 | 417 |
405 print("$version (from $path)"); | 418 print("$version (from $path)"); |
406 return true; | 419 return true; |
407 | 420 |
408 default: return false; | 421 default: return false; |
409 } | 422 } |
410 } | 423 } |
OLD | NEW |