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 |