| 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 |