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'; | 12 import 'dart:isolate'; |
| 13 import 'dart:math' as math; |
13 | 14 |
14 import 'package:args/args.dart'; | 15 import 'package:args/args.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/test_platform.dart'; | 19 import 'backend/test_platform.dart'; |
19 import 'runner/reporter/compact.dart'; | 20 import 'runner/reporter/compact.dart'; |
20 import 'runner/load_exception.dart'; | 21 import 'runner/load_exception.dart'; |
21 import 'runner/loader.dart'; | 22 import 'runner/loader.dart'; |
22 import 'util/exit_codes.dart' as exit_codes; | 23 import 'util/exit_codes.dart' as exit_codes; |
23 import 'util/io.dart'; | 24 import 'util/io.dart'; |
24 import 'utils.dart'; | 25 import 'utils.dart'; |
25 | 26 |
26 /// The argument parser used to parse the executable arguments. | 27 /// The argument parser used to parse the executable arguments. |
27 final _parser = new ArgParser(allowTrailingOptions: true); | 28 final _parser = new ArgParser(allowTrailingOptions: true); |
28 | 29 |
| 30 /// The default number of test suites to run at once. |
| 31 /// |
| 32 /// This defaults to half the available processors, since presumably some of |
| 33 /// them will be used for the OS and other processes. |
| 34 final _defaultConcurrency = math.max(1, Platform.numberOfProcessors ~/ 2); |
| 35 |
29 /// A merged stream of all signals that tell the test runner to shut down | 36 /// A merged stream of all signals that tell the test runner to shut down |
30 /// gracefully. | 37 /// gracefully. |
31 /// | 38 /// |
32 /// Signals will only be captured as long as this has an active subscription. | 39 /// Signals will only be captured as long as this has an active subscription. |
33 /// Otherwise, they'll be handled by Dart's default signal handler, which | 40 /// Otherwise, they'll be handled by Dart's default signal handler, which |
34 /// terminates the program immediately. | 41 /// terminates the program immediately. |
35 final _signals = mergeStreams([ | 42 final _signals = mergeStreams([ |
36 ProcessSignal.SIGTERM.watch(), ProcessSignal.SIGINT.watch() | 43 ProcessSignal.SIGTERM.watch(), ProcessSignal.SIGINT.watch() |
37 ]); | 44 ]); |
38 | 45 |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
73 'Regular expression syntax is supported.'); | 80 'Regular expression syntax is supported.'); |
74 _parser.addOption("plain-name", | 81 _parser.addOption("plain-name", |
75 abbr: 'N', | 82 abbr: 'N', |
76 help: 'A plain-text substring of the name of the test to run.'); | 83 help: 'A plain-text substring of the name of the test to run.'); |
77 _parser.addOption("platform", | 84 _parser.addOption("platform", |
78 abbr: 'p', | 85 abbr: 'p', |
79 help: 'The platform(s) on which to run the tests.', | 86 help: 'The platform(s) on which to run the tests.', |
80 allowed: TestPlatform.all.map((platform) => platform.identifier).toList(), | 87 allowed: TestPlatform.all.map((platform) => platform.identifier).toList(), |
81 defaultsTo: 'vm', | 88 defaultsTo: 'vm', |
82 allowMultiple: true); | 89 allowMultiple: true); |
| 90 _parser.addOption("concurrency", |
| 91 abbr: 'j', |
| 92 help: 'The number of concurrent test suites run.\n' |
| 93 '(defaults to $_defaultConcurrency)', |
| 94 valueHelp: 'threads'); |
83 _parser.addOption("pub-serve", | 95 _parser.addOption("pub-serve", |
84 help: 'The port of a pub serve instance serving "test/".', | 96 help: 'The port of a pub serve instance serving "test/".', |
85 hide: !supportsPubServe, | 97 hide: !supportsPubServe, |
86 valueHelp: 'port'); | 98 valueHelp: 'port'); |
87 _parser.addFlag("color", defaultsTo: null, | 99 _parser.addFlag("color", defaultsTo: null, |
88 help: 'Whether to use terminal colors.\n(auto-detected by default)'); | 100 help: 'Whether to use terminal colors.\n(auto-detected by default)'); |
89 | 101 |
90 var options; | 102 var options; |
91 try { | 103 try { |
92 options = _parser.parse(args); | 104 options = _parser.parse(args); |
(...skipping 27 matching lines...) Expand all Loading... |
120 return; | 132 return; |
121 } | 133 } |
122 } | 134 } |
123 | 135 |
124 var platforms = options["platform"].map(TestPlatform.find); | 136 var platforms = options["platform"].map(TestPlatform.find); |
125 var loader = new Loader(platforms, | 137 var loader = new Loader(platforms, |
126 pubServeUrl: pubServeUrl, | 138 pubServeUrl: pubServeUrl, |
127 packageRoot: options["package-root"], | 139 packageRoot: options["package-root"], |
128 color: color); | 140 color: color); |
129 | 141 |
| 142 var concurrency = _defaultConcurrency; |
| 143 if (options["concurrency"] != null) { |
| 144 try { |
| 145 concurrency = int.parse(options["concurrency"]); |
| 146 } catch (error) { |
| 147 _printUsage('Couldn\'t parse --concurrency "${options["concurrency"]}":' |
| 148 ' ${error.message}'); |
| 149 exitCode = exit_codes.usage; |
| 150 return; |
| 151 } |
| 152 } |
| 153 |
130 var signalSubscription; | 154 var signalSubscription; |
131 var closed = false; | 155 var closed = false; |
132 signalSubscription = _signals.listen((_) { | 156 signalSubscription = _signals.listen((_) { |
133 signalSubscription.cancel(); | 157 signalSubscription.cancel(); |
134 closed = true; | 158 closed = true; |
135 loader.close(); | 159 loader.close(); |
136 }); | 160 }); |
137 | 161 |
138 new Future.sync(() { | 162 new Future.sync(() { |
139 var paths = options.rest; | 163 var paths = options.rest; |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
179 if (pattern is RegExp) { | 203 if (pattern is RegExp) { |
180 stderr.write('regular expression "${pattern.pattern}".'); | 204 stderr.write('regular expression "${pattern.pattern}".'); |
181 } else { | 205 } else { |
182 stderr.writeln('"$pattern".'); | 206 stderr.writeln('"$pattern".'); |
183 } | 207 } |
184 exitCode = exit_codes.data; | 208 exitCode = exit_codes.data; |
185 return null; | 209 return null; |
186 } | 210 } |
187 } | 211 } |
188 | 212 |
189 var reporter = new CompactReporter(flatten(suites), color: color); | 213 var reporter = new CompactReporter(flatten(suites), |
| 214 concurrency: concurrency, color: color); |
190 | 215 |
191 // Override the signal handler to close [reporter]. [loader] will still be | 216 // Override the signal handler to close [reporter]. [loader] will still be |
192 // closed in the [whenComplete] below. | 217 // closed in the [whenComplete] below. |
193 signalSubscription.onData((_) { | 218 signalSubscription.onData((_) { |
194 signalSubscription.cancel(); | 219 signalSubscription.cancel(); |
195 closed = true; | 220 closed = true; |
196 | 221 |
197 // Wait a bit to print this message, since printing it eagerly looks weird | 222 // Wait a bit to print this message, since printing it eagerly looks weird |
198 // if the tests then finish immediately. | 223 // if the tests then finish immediately. |
199 var timer = new Timer(new Duration(seconds: 1), () { | 224 var timer = new Timer(new Duration(seconds: 1), () { |
200 print("Waiting for current test to finish."); | 225 // Print a blank line first to ensure that this doesn't interfere with |
| 226 // the compact reporter's unfinished line. |
| 227 print(""); |
| 228 print("Waiting for current test(s) to finish."); |
201 print("Press Control-C again to terminate immediately."); | 229 print("Press Control-C again to terminate immediately."); |
202 }); | 230 }); |
203 | 231 |
204 reporter.close().then((_) => timer.cancel()); | 232 reporter.close().then((_) => timer.cancel()); |
205 }); | 233 }); |
206 | 234 |
207 return reporter.run().then((success) { | 235 return reporter.run().then((success) { |
208 exitCode = success ? 0 : 1; | 236 exitCode = success ? 0 : 1; |
209 }).whenComplete(() { | 237 }).whenComplete(() { |
210 signalSubscription.cancel(); | 238 signalSubscription.cancel(); |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
256 output = stderr; | 284 output = stderr; |
257 } | 285 } |
258 | 286 |
259 output.write("""$message | 287 output.write("""$message |
260 | 288 |
261 Usage: pub run test:test [files or directories...] | 289 Usage: pub run test:test [files or directories...] |
262 | 290 |
263 ${_parser.usage} | 291 ${_parser.usage} |
264 """); | 292 """); |
265 } | 293 } |
OLD | NEW |