| 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:isolate'; | |
| 13 import 'dart:math' as math; | 12 import 'dart:math' as math; |
| 14 | 13 |
| 15 import 'package:args/args.dart'; | 14 import 'package:args/args.dart'; |
| 16 import 'package:stack_trace/stack_trace.dart'; | 15 import 'package:stack_trace/stack_trace.dart'; |
| 17 import 'package:yaml/yaml.dart'; | 16 import 'package:yaml/yaml.dart'; |
| 18 | 17 |
| 19 import 'backend/test_platform.dart'; | 18 import 'backend/test_platform.dart'; |
| 20 import 'runner/reporter/compact.dart'; | 19 import 'runner/reporter/compact.dart'; |
| 21 import 'runner/load_exception.dart'; | 20 import 'runner/load_exception.dart'; |
| 21 import 'runner/load_exception_suite.dart'; |
| 22 import 'runner/loader.dart'; | 22 import 'runner/loader.dart'; |
| 23 import 'util/exit_codes.dart' as exit_codes; | 23 import 'util/exit_codes.dart' as exit_codes; |
| 24 import 'util/io.dart'; | 24 import 'util/io.dart'; |
| 25 import 'utils.dart'; | 25 import 'utils.dart'; |
| 26 | 26 |
| 27 /// The argument parser used to parse the executable arguments. | 27 /// The argument parser used to parse the executable arguments. |
| 28 final _parser = new ArgParser(allowTrailingOptions: true); | 28 final _parser = new ArgParser(allowTrailingOptions: true); |
| 29 | 29 |
| 30 /// The default number of test suites to run at once. | 30 /// The default number of test suites to run at once. |
| 31 /// | 31 /// |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 144 try { | 144 try { |
| 145 concurrency = int.parse(options["concurrency"]); | 145 concurrency = int.parse(options["concurrency"]); |
| 146 } catch (error) { | 146 } catch (error) { |
| 147 _printUsage('Couldn\'t parse --concurrency "${options["concurrency"]}":' | 147 _printUsage('Couldn\'t parse --concurrency "${options["concurrency"]}":' |
| 148 ' ${error.message}'); | 148 ' ${error.message}'); |
| 149 exitCode = exit_codes.usage; | 149 exitCode = exit_codes.usage; |
| 150 return; | 150 return; |
| 151 } | 151 } |
| 152 } | 152 } |
| 153 | 153 |
| 154 var paths = options.rest; |
| 155 if (paths.isEmpty) { |
| 156 if (!new Directory("test").existsSync()) { |
| 157 _printUsage('No test files were passed and the default "test/" ' |
| 158 "directory doesn't exist."); |
| 159 exitCode = exit_codes.data; |
| 160 return; |
| 161 } |
| 162 paths = ["test"]; |
| 163 } |
| 164 |
| 154 var signalSubscription; | 165 var signalSubscription; |
| 155 var closed = false; | 166 var closed = false; |
| 156 signalSubscription = _signals.listen((_) { | 167 signalSubscription = _signals.listen((_) { |
| 157 signalSubscription.cancel(); | 168 signalSubscription.cancel(); |
| 158 closed = true; | 169 closed = true; |
| 159 loader.close(); | 170 loader.close(); |
| 160 }); | 171 }); |
| 161 | 172 |
| 162 new Future.sync(() { | 173 mergeStreams(paths.map((path) { |
| 163 var paths = options.rest; | 174 if (new Directory(path).existsSync()) return loader.loadDir(path); |
| 164 if (paths.isEmpty) { | 175 if (new File(path).existsSync()) return loader.loadFile(path); |
| 165 if (!new Directory("test").existsSync()) { | 176 return new Stream.fromFuture(new Future.error( |
| 166 throw new LoadException("test", | 177 new LoadException(path, 'Does not exist.'), |
| 167 "No test files were passed and the default directory doesn't " | 178 new Trace.current())); |
| 168 "exist."); | 179 })).transform(new StreamTransformer.fromHandlers( |
| 169 } | 180 handleError: (error, stackTrace, sink) { |
| 170 paths = ["test"]; | 181 if (error is! LoadException) { |
| 182 sink.addError(error, stackTrace); |
| 183 } else { |
| 184 sink.add(new LoadExceptionSuite(error)); |
| 171 } | 185 } |
| 172 | 186 })).toList().then((suites) { |
| 173 return Future.wait(paths.map((path) { | |
| 174 if (new Directory(path).existsSync()) return loader.loadDir(path); | |
| 175 if (new File(path).existsSync()) return loader.loadFile(path); | |
| 176 throw new LoadException(path, 'Does not exist.'); | |
| 177 })); | |
| 178 }).then((suites) { | |
| 179 if (closed) return null; | 187 if (closed) return null; |
| 180 suites = flatten(suites); | 188 suites = flatten(suites); |
| 181 | 189 |
| 182 var pattern; | 190 var pattern; |
| 183 if (options["name"] != null) { | 191 if (options["name"] != null) { |
| 184 if (options["plain-name"] != null) { | 192 if (options["plain-name"] != null) { |
| 185 _printUsage("--name and --plain-name may not both be passed."); | 193 _printUsage("--name and --plain-name may not both be passed."); |
| 186 exitCode = exit_codes.data; | 194 exitCode = exit_codes.data; |
| 187 return null; | 195 return null; |
| 188 } | 196 } |
| 189 pattern = new RegExp(options["name"]); | 197 pattern = new RegExp(options["name"]); |
| 190 } else if (options["plain-name"] != null) { | 198 } else if (options["plain-name"] != null) { |
| 191 pattern = options["plain-name"]; | 199 pattern = options["plain-name"]; |
| 192 } | 200 } |
| 193 | 201 |
| 194 if (pattern != null) { | 202 if (pattern != null) { |
| 195 suites = suites.map((suite) { | 203 suites = suites.map((suite) { |
| 204 // Don't ever filter out load errors. |
| 205 if (suite is LoadExceptionSuite) return suite; |
| 196 return suite.change( | 206 return suite.change( |
| 197 tests: suite.tests.where((test) => test.name.contains(pattern))); | 207 tests: suite.tests.where((test) => test.name.contains(pattern))); |
| 198 }).toList(); | 208 }).toList(); |
| 199 | 209 |
| 200 if (suites.every((suite) => suite.tests.isEmpty)) { | 210 if (suites.every((suite) => suite.tests.isEmpty)) { |
| 201 stderr.write('No tests match '); | 211 stderr.write('No tests match '); |
| 202 | 212 |
| 203 if (pattern is RegExp) { | 213 if (pattern is RegExp) { |
| 204 stderr.write('regular expression "${pattern.pattern}".'); | 214 stderr.writeln('regular expression "${pattern.pattern}".'); |
| 205 } else { | 215 } else { |
| 206 stderr.writeln('"$pattern".'); | 216 stderr.writeln('"$pattern".'); |
| 207 } | 217 } |
| 208 exitCode = exit_codes.data; | 218 exitCode = exit_codes.data; |
| 209 return null; | 219 return null; |
| 210 } | 220 } |
| 211 } | 221 } |
| 212 | 222 |
| 213 var reporter = new CompactReporter(flatten(suites), | 223 var reporter = new CompactReporter(flatten(suites), |
| 214 concurrency: concurrency, color: color); | 224 concurrency: concurrency, color: color); |
| (...skipping 17 matching lines...) Expand all Loading... |
| 232 reporter.close().then((_) => timer.cancel()); | 242 reporter.close().then((_) => timer.cancel()); |
| 233 }); | 243 }); |
| 234 | 244 |
| 235 return reporter.run().then((success) { | 245 return reporter.run().then((success) { |
| 236 exitCode = success ? 0 : 1; | 246 exitCode = success ? 0 : 1; |
| 237 }).whenComplete(() { | 247 }).whenComplete(() { |
| 238 signalSubscription.cancel(); | 248 signalSubscription.cancel(); |
| 239 return reporter.close(); | 249 return reporter.close(); |
| 240 }); | 250 }); |
| 241 }).whenComplete(signalSubscription.cancel).catchError((error, stackTrace) { | 251 }).whenComplete(signalSubscription.cancel).catchError((error, stackTrace) { |
| 242 if (error is LoadException) { | 252 stderr.writeln(getErrorMessage(error)); |
| 243 stderr.writeln(error.toString(color: color)); | 253 stderr.writeln(new Trace.from(stackTrace).terse); |
| 244 | 254 stderr.writeln( |
| 245 // Only print stack traces for load errors that come from the user's | 255 "This is an unexpected error. Please file an issue at " |
| 246 if (error.innerError is! IOException && | 256 "http://github.com/dart-lang/test\n" |
| 247 error.innerError is! IsolateSpawnException && | 257 "with the stack trace and instructions for reproducing the error."); |
| 248 error.innerError is! FormatException && | 258 exitCode = exit_codes.software; |
| 249 error.innerError is! String) { | |
| 250 stderr.write(terseChain(stackTrace)); | |
| 251 } | |
| 252 | |
| 253 exitCode = error.innerError is IOException | |
| 254 ? exit_codes.io | |
| 255 : exit_codes.data; | |
| 256 } else { | |
| 257 stderr.writeln(getErrorMessage(error)); | |
| 258 stderr.writeln(new Trace.from(stackTrace).terse); | |
| 259 stderr.writeln( | |
| 260 "This is an unexpected error. Please file an issue at " | |
| 261 "http://github.com/dart-lang/test\n" | |
| 262 "with the stack trace and instructions for reproducing the error."); | |
| 263 exitCode = exit_codes.software; | |
| 264 } | |
| 265 }).whenComplete(() { | 259 }).whenComplete(() { |
| 266 return loader.close().then((_) { | 260 return loader.close().then((_) { |
| 267 // If we're on a Dart version that doesn't support Isolate.kill(), we have | 261 // If we're on a Dart version that doesn't support Isolate.kill(), we have |
| 268 // to manually exit so that dangling isolates don't prevent it. | 262 // to manually exit so that dangling isolates don't prevent it. |
| 269 if (!supportsIsolateKill) exit(exitCode); | 263 if (!supportsIsolateKill) exit(exitCode); |
| 270 }); | 264 }); |
| 271 }); | 265 }); |
| 272 } | 266 } |
| 273 | 267 |
| 274 /// Print usage information for this command. | 268 /// Print usage information for this command. |
| 275 /// | 269 /// |
| 276 /// If [error] is passed, it's used in place of the usage message and the whole | 270 /// If [error] is passed, it's used in place of the usage message and the whole |
| 277 /// thing is printed to stderr instead of stdout. | 271 /// thing is printed to stderr instead of stdout. |
| 278 void _printUsage([String error]) { | 272 void _printUsage([String error]) { |
| 279 var output = stdout; | 273 var output = stdout; |
| 280 | 274 |
| 281 var message = "Runs tests in this package."; | 275 var message = "Runs tests in this package."; |
| 282 if (error != null) { | 276 if (error != null) { |
| 283 message = error; | 277 message = error; |
| 284 output = stderr; | 278 output = stderr; |
| 285 } | 279 } |
| 286 | 280 |
| 287 output.write("""$message | 281 output.write("""$message |
| 288 | 282 |
| 289 Usage: pub run test:test [files or directories...] | 283 Usage: pub run test:test [files or directories...] |
| 290 | 284 |
| 291 ${_parser.usage} | 285 ${_parser.usage} |
| 292 """); | 286 """); |
| 293 } | 287 } |
| OLD | NEW |