Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(339)

Side by Side Diff: lib/src/executable.dart

Issue 1219073003: Add a Configuration class that encapsulates command-line configuration. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Code review changes Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | lib/src/runner/configuration.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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;
13 12
14 import 'package:args/args.dart';
15 import 'package:async/async.dart'; 13 import 'package:async/async.dart';
16 import 'package:stack_trace/stack_trace.dart'; 14 import 'package:stack_trace/stack_trace.dart';
17 import 'package:yaml/yaml.dart'; 15 import 'package:yaml/yaml.dart';
18 16
19 import 'backend/metadata.dart'; 17 import 'backend/metadata.dart';
20 import 'backend/test_platform.dart'; 18 import 'runner/application_exception.dart';
19 import 'runner/configuration.dart';
21 import 'runner/engine.dart'; 20 import 'runner/engine.dart';
22 import 'runner/application_exception.dart';
23 import 'runner/load_exception.dart'; 21 import 'runner/load_exception.dart';
24 import 'runner/load_suite.dart'; 22 import 'runner/load_suite.dart';
25 import 'runner/loader.dart'; 23 import 'runner/loader.dart';
26 import 'runner/reporter/compact.dart'; 24 import 'runner/reporter/compact.dart';
27 import 'runner/reporter/expanded.dart'; 25 import 'runner/reporter/expanded.dart';
28 import 'util/exit_codes.dart' as exit_codes; 26 import 'util/exit_codes.dart' as exit_codes;
29 import 'util/io.dart';
30 import 'utils.dart'; 27 import 'utils.dart';
31 28
32 /// The argument parser used to parse the executable arguments.
33 final _parser = new ArgParser(allowTrailingOptions: true);
34
35 /// The default number of test suites to run at once.
36 ///
37 /// This defaults to half the available processors, since presumably some of
38 /// them will be used for the OS and other processes.
39 final _defaultConcurrency = math.max(1, Platform.numberOfProcessors ~/ 2);
40
41 /// A merged stream of all signals that tell the test runner to shut down 29 /// A merged stream of all signals that tell the test runner to shut down
42 /// gracefully. 30 /// gracefully.
43 /// 31 ///
44 /// Signals will only be captured as long as this has an active subscription. 32 /// Signals will only be captured as long as this has an active subscription.
45 /// Otherwise, they'll be handled by Dart's default signal handler, which 33 /// Otherwise, they'll be handled by Dart's default signal handler, which
46 /// terminates the program immediately. 34 /// terminates the program immediately.
47 final _signals = Platform.isWindows 35 final _signals = Platform.isWindows
48 ? ProcessSignal.SIGINT.watch() 36 ? ProcessSignal.SIGINT.watch()
49 : mergeStreams([ 37 : mergeStreams([
50 ProcessSignal.SIGTERM.watch(), 38 ProcessSignal.SIGTERM.watch(),
(...skipping 20 matching lines...) Expand all
71 if (transformers is! List) return false; 59 if (transformers is! List) return false;
72 60
73 return transformers.any((transformer) { 61 return transformers.any((transformer) {
74 if (transformer is String) return transformer == 'test/pub_serve'; 62 if (transformer is String) return transformer == 'test/pub_serve';
75 if (transformer is! Map) return false; 63 if (transformer is! Map) return false;
76 if (transformer.keys.length != 1) return false; 64 if (transformer.keys.length != 1) return false;
77 return transformer.keys.single == 'test/pub_serve'; 65 return transformer.keys.single == 'test/pub_serve';
78 }); 66 });
79 } 67 }
80 68
69 Configuration _configuration;
70
81 main(List<String> args) async { 71 main(List<String> args) async {
82 var allPlatforms = TestPlatform.all.toList();
83 if (!Platform.isMacOS) allPlatforms.remove(TestPlatform.safari);
84 if (!Platform.isWindows) allPlatforms.remove(TestPlatform.internetExplorer);
85
86 _parser.addFlag("help", abbr: "h", negatable: false,
87 help: "Shows this usage information.");
88 _parser.addFlag("version", negatable: false,
89 help: "Shows the package's version.");
90 _parser.addOption("package-root", hide: true);
91 _parser.addOption("name",
92 abbr: 'n',
93 help: 'A substring of the name of the test to run.\n'
94 'Regular expression syntax is supported.');
95 _parser.addOption("plain-name",
96 abbr: 'N',
97 help: 'A plain-text substring of the name of the test to run.');
98 _parser.addOption("platform",
99 abbr: 'p',
100 help: 'The platform(s) on which to run the tests.',
101 allowed: allPlatforms.map((platform) => platform.identifier).toList(),
102 defaultsTo: 'vm',
103 allowMultiple: true);
104 _parser.addOption("concurrency",
105 abbr: 'j',
106 help: 'The number of concurrent test suites run.\n'
107 '(defaults to $_defaultConcurrency)',
108 valueHelp: 'threads');
109 _parser.addOption("pub-serve",
110 help: 'The port of a pub serve instance serving "test/".',
111 hide: !supportsPubServe,
112 valueHelp: 'port');
113 _parser.addOption("reporter",
114 abbr: 'r',
115 help: 'The runner used to print test results.',
116 allowed: ['compact', 'expanded'],
117 defaultsTo: Platform.isWindows ? 'expanded' : 'compact',
118 allowedHelp: {
119 'compact': 'A single line, updated continuously.',
120 'expanded': 'A separate line for each update.'
121 });
122 _parser.addFlag("verbose-trace", negatable: false,
123 help: 'Whether to emit stack traces with core library frames.');
124 _parser.addFlag("js-trace", negatable: false,
125 help: 'Whether to emit raw JavaScript stack traces for browser tests.');
126 _parser.addFlag("color", defaultsTo: null,
127 help: 'Whether to use terminal colors.\n(auto-detected by default)');
128
129 var options;
130 try { 72 try {
131 options = _parser.parse(args); 73 _configuration = new Configuration.parse(args);
132 } on FormatException catch (error) { 74 } on FormatException catch (error) {
133 _printUsage(error.message); 75 _printUsage(error.message);
134 exitCode = exit_codes.usage; 76 exitCode = exit_codes.usage;
135 return; 77 return;
136 } 78 }
137 79
138 if (options["help"]) { 80 if (_configuration.help) {
139 _printUsage(); 81 _printUsage();
140 return; 82 return;
141 } 83 }
142 84
143 if (options["version"]) { 85 if (_configuration.version) {
144 if (!_printVersion()) { 86 if (!_printVersion()) {
145 stderr.writeln("Couldn't find version number."); 87 stderr.writeln("Couldn't find version number.");
146 exitCode = exit_codes.data; 88 exitCode = exit_codes.data;
147 } 89 }
148 return; 90 return;
149 } 91 }
150 92
151 var color = options["color"]; 93 if (_configuration.pubServeUrl != null && !_usesTransformer) {
152 if (color == null) color = canUseSpecialChars; 94 stderr.write('''
153
154 var pubServeUrl;
155 if (options["pub-serve"] != null) {
156 pubServeUrl = Uri.parse("http://localhost:${options['pub-serve']}");
157 if (!_usesTransformer) {
158 stderr.write('''
159 When using --pub-serve, you must include the "test/pub_serve" transformer in 95 When using --pub-serve, you must include the "test/pub_serve" transformer in
160 your pubspec: 96 your pubspec:
161 97
162 transformers: 98 transformers:
163 - test/pub_serve: 99 - test/pub_serve:
164 \$include: test/**_test.dart 100 \$include: test/**_test.dart
165 '''); 101 ''');
166 exitCode = exit_codes.data; 102 exitCode = exit_codes.data;
167 return; 103 return;
168 }
169 } 104 }
170 105
171 var concurrency = _defaultConcurrency; 106 if (!_configuration.explicitPaths &&
172 if (options["concurrency"] != null) { 107 !new Directory(_configuration.paths.single).existsSync()) {
173 try { 108 _printUsage('No test files were passed and the default "test/" '
174 concurrency = int.parse(options["concurrency"]); 109 "directory doesn't exist.");
175 } catch (error) { 110 exitCode = exit_codes.data;
176 _printUsage('Couldn\'t parse --concurrency "${options["concurrency"]}":' 111 return;
177 ' ${error.message}');
178 exitCode = exit_codes.usage;
179 return;
180 }
181 } 112 }
182 113
183 var paths = options.rest; 114 var metadata = new Metadata(
184 if (paths.isEmpty) { 115 verboseTrace: _configuration.verboseTrace);
185 if (!new Directory("test").existsSync()) { 116 var loader = new Loader(_configuration.platforms,
186 _printUsage('No test files were passed and the default "test/" ' 117 pubServeUrl: _configuration.pubServeUrl,
187 "directory doesn't exist."); 118 packageRoot: _configuration.packageRoot,
188 exitCode = exit_codes.data; 119 color: _configuration.color,
189 return;
190 }
191 paths = ["test"];
192 }
193
194 var pattern;
195 if (options["name"] != null) {
196 if (options["plain-name"] != null) {
197 _printUsage("--name and --plain-name may not both be passed.");
198 exitCode = exit_codes.data;
199 return;
200 }
201 pattern = new RegExp(options["name"]);
202 } else if (options["plain-name"] != null) {
203 pattern = options["plain-name"];
204 }
205
206 var metadata = new Metadata(verboseTrace: options["verbose-trace"]);
207 var platforms = options["platform"].map(TestPlatform.find);
208 var loader = new Loader(platforms,
209 pubServeUrl: pubServeUrl,
210 packageRoot: options["package-root"],
211 color: color,
212 metadata: metadata, 120 metadata: metadata,
213 jsTrace: options["js-trace"]); 121 jsTrace: _configuration.jsTrace);
214 122
215 var closed = false; 123 var closed = false;
216 var signalSubscription; 124 var signalSubscription;
217 signalSubscription = _signals.listen((_) { 125 signalSubscription = _signals.listen((_) {
218 closed = true; 126 closed = true;
219 signalSubscription.cancel(); 127 signalSubscription.cancel();
220 loader.close(); 128 loader.close();
221 }); 129 });
222 130
223 try { 131 try {
224 var engine = new Engine(concurrency: concurrency); 132 var engine = new Engine(concurrency: _configuration.concurrency);
225 133
226 var watch = options["reporter"] == "compact" 134 var watch = _configuration.reporter == "compact"
227 ? CompactReporter.watch 135 ? CompactReporter.watch
228 : ExpandedReporter.watch; 136 : ExpandedReporter.watch;
229 137
230 watch( 138 watch(
231 engine, 139 engine,
232 color: color, 140 color: _configuration.color,
233 verboseTrace: options["verbose-trace"], 141 verboseTrace: _configuration.verboseTrace,
234 printPath: paths.length > 1 || 142 printPath: _configuration.paths.length > 1 ||
235 new Directory(paths.single).existsSync(), 143 new Directory(_configuration.paths.single).existsSync(),
236 printPlatform: platforms.length > 1); 144 printPlatform: _configuration.platforms.length > 1);
237 145
238 // Override the signal handler to close [reporter]. [loader] will still be 146 // Override the signal handler to close [reporter]. [loader] will still be
239 // closed in the [whenComplete] below. 147 // closed in the [whenComplete] below.
240 signalSubscription.onData((_) async { 148 signalSubscription.onData((_) async {
241 closed = true; 149 closed = true;
242 signalSubscription.cancel(); 150 signalSubscription.cancel();
243 151
244 // Wait a bit to print this message, since printing it eagerly looks weird 152 // Wait a bit to print this message, since printing it eagerly looks weird
245 // if the tests then finish immediately. 153 // if the tests then finish immediately.
246 var timer = new Timer(new Duration(seconds: 1), () { 154 var timer = new Timer(new Duration(seconds: 1), () {
247 // Print a blank line first to ensure that this doesn't interfere with 155 // Print a blank line first to ensure that this doesn't interfere with
248 // the compact reporter's unfinished line. 156 // the compact reporter's unfinished line.
249 print(""); 157 print("");
250 print("Waiting for current test(s) to finish."); 158 print("Waiting for current test(s) to finish.");
251 print("Press Control-C again to terminate immediately."); 159 print("Press Control-C again to terminate immediately.");
252 }); 160 });
253 161
254 // Make sure we close the engine *before* the loader. Otherwise, 162 // Make sure we close the engine *before* the loader. Otherwise,
255 // LoadSuites provided by the loader may get into bad states. 163 // LoadSuites provided by the loader may get into bad states.
256 await engine.close(); 164 await engine.close();
257 timer.cancel(); 165 timer.cancel();
258 await loader.close(); 166 await loader.close();
259 }); 167 });
260 168
261 try { 169 try {
262 var results = await Future.wait([ 170 var results = await Future.wait([
263 _loadSuites(paths, pattern, loader, engine), 171 _loadSuites(loader, engine),
264 engine.run() 172 engine.run()
265 ], eagerError: true); 173 ], eagerError: true);
266 174
267 if (closed) return; 175 if (closed) return;
268 176
269 // Explicitly check "== true" here because [engine.run] can return `null` 177 // Explicitly check "== true" here because [engine.run] can return `null`
270 // if the engine was closed prematurely. 178 // if the engine was closed prematurely.
271 exitCode = results.last == true ? 0 : 1; 179 exitCode = results.last == true ? 0 : 1;
272 } finally { 180 } finally {
273 signalSubscription.cancel(); 181 signalSubscription.cancel();
274 await engine.close(); 182 await engine.close();
275 } 183 }
276 184
277 if (engine.passed.length == 0 && engine.failed.length == 0 && 185 if (engine.passed.length == 0 && engine.failed.length == 0 &&
278 engine.skipped.length == 0 && pattern != null) { 186 engine.skipped.length == 0 && _configuration.pattern != null) {
279 stderr.write('No tests match '); 187 stderr.write('No tests match ');
280 188
281 if (pattern is RegExp) { 189 if (_configuration.pattern is RegExp) {
282 stderr.writeln('regular expression "${pattern.pattern}".'); 190 var pattern = (_configuration.pattern as RegExp).pattern;
191 stderr.writeln('regular expression "$pattern".');
283 } else { 192 } else {
284 stderr.writeln('"$pattern".'); 193 stderr.writeln('"${_configuration.pattern}".');
285 } 194 }
286 exitCode = exit_codes.data; 195 exitCode = exit_codes.data;
287 } 196 }
288 } on ApplicationException catch (error) { 197 } on ApplicationException catch (error) {
289 stderr.writeln(error.message); 198 stderr.writeln(error.message);
290 exitCode = exit_codes.data; 199 exitCode = exit_codes.data;
291 } catch (error, stackTrace) { 200 } catch (error, stackTrace) {
292 stderr.writeln(getErrorMessage(error)); 201 stderr.writeln(getErrorMessage(error));
293 stderr.writeln(new Trace.from(stackTrace).terse); 202 stderr.writeln(new Trace.from(stackTrace).terse);
294 stderr.writeln( 203 stderr.writeln(
295 "This is an unexpected error. Please file an issue at " 204 "This is an unexpected error. Please file an issue at "
296 "http://github.com/dart-lang/test\n" 205 "http://github.com/dart-lang/test\n"
297 "with the stack trace and instructions for reproducing the error."); 206 "with the stack trace and instructions for reproducing the error.");
298 exitCode = exit_codes.software; 207 exitCode = exit_codes.software;
299 } finally { 208 } finally {
300 signalSubscription.cancel(); 209 signalSubscription.cancel();
301 await loader.close(); 210 await loader.close();
302 } 211 }
303 } 212 }
304 213
305 /// Load the test suites in [paths] that match [pattern] and pass them to 214 /// Load the test suites in [_configuration.paths] that match
306 /// [engine]. 215 /// [_configuration.pattern] and pass them to [engine].
307 /// 216 ///
308 /// This completes once all the tests have been added to the engine. It does not 217 /// This completes once all the tests have been added to the engine. It does not
309 /// run the engine. 218 /// run the engine.
310 Future _loadSuites(List<String> paths, Pattern pattern, Loader loader, 219 Future _loadSuites(Loader loader, Engine engine) async {
311 Engine engine) async {
312 var group = new FutureGroup(); 220 var group = new FutureGroup();
313 221
314 mergeStreams(paths.map((path) { 222 mergeStreams(_configuration.paths.map((path) {
315 if (new Directory(path).existsSync()) return loader.loadDir(path); 223 if (new Directory(path).existsSync()) return loader.loadDir(path);
316 if (new File(path).existsSync()) return loader.loadFile(path); 224 if (new File(path).existsSync()) return loader.loadFile(path);
317 225
318 return new Stream.fromIterable([ 226 return new Stream.fromIterable([
319 new LoadSuite("loading $path", () => 227 new LoadSuite("loading $path", () =>
320 throw new LoadException(path, 'Does not exist.')) 228 throw new LoadException(path, 'Does not exist.'))
321 ]); 229 ]);
322 })).listen((loadSuite) { 230 })).listen((loadSuite) {
323 group.add(new Future.sync(() { 231 group.add(new Future.sync(() {
324 engine.suiteSink.add(loadSuite.changeSuite((suite) { 232 engine.suiteSink.add(loadSuite.changeSuite((suite) {
325 if (pattern == null) return suite; 233 if (_configuration.pattern == null) return suite;
326 return suite.change( 234 return suite.change(tests: suite.tests.where(
327 tests: suite.tests.where((test) => test.name.contains(pattern))); 235 (test) => test.name.contains(_configuration.pattern)));
328 })); 236 }));
329 })); 237 }));
330 }, onError: (error, stackTrace) { 238 }, onError: (error, stackTrace) {
331 group.add(new Future.error(error, stackTrace)); 239 group.add(new Future.error(error, stackTrace));
332 }, onDone: group.close); 240 }, onDone: group.close);
333 241
334 await group.future; 242 await group.future;
335 243
336 // Once we've loaded all the suites, notify the engine that no more will be 244 // Once we've loaded all the suites, notify the engine that no more will be
337 // coming. 245 // coming.
(...skipping 10 matching lines...) Expand all
348 var message = "Runs tests in this package."; 256 var message = "Runs tests in this package.";
349 if (error != null) { 257 if (error != null) {
350 message = error; 258 message = error;
351 output = stderr; 259 output = stderr;
352 } 260 }
353 261
354 output.write("""$message 262 output.write("""$message
355 263
356 Usage: pub run test:test [files or directories...] 264 Usage: pub run test:test [files or directories...]
357 265
358 ${_parser.usage} 266 ${Configuration.usage}
359 """); 267 """);
360 } 268 }
361 269
362 /// Prints the version number of the test package. 270 /// Prints the version number of the test package.
363 /// 271 ///
364 /// This loads the version number from the current package's lockfile. It 272 /// This loads the version number from the current package's lockfile. It
365 /// returns true if it successfully printed the version number and false if it 273 /// returns true if it successfully printed the version number and false if it
366 /// couldn't be loaded. 274 /// couldn't be loaded.
367 bool _printVersion() { 275 bool _printVersion() {
368 var lockfile; 276 var lockfile;
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
409 if (description is! Map) return false; 317 if (description is! Map) return false;
410 var path = description["path"]; 318 var path = description["path"];
411 if (path is! String) return false; 319 if (path is! String) return false;
412 320
413 print("$version (from $path)"); 321 print("$version (from $path)");
414 return true; 322 return true;
415 323
416 default: return false; 324 default: return false;
417 } 325 }
418 } 326 }
OLDNEW
« no previous file with comments | « no previous file | lib/src/runner/configuration.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698