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

Side by Side Diff: tools/testing/dart/test_runner.dart

Issue 9475038: test.dart: add support for compiling multiple scripts for a single test. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 9 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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 /** 5 /**
6 * Classes and methods for executing tests. 6 * Classes and methods for executing tests.
7 * 7 *
8 * This module includes: 8 * This module includes:
9 * - Managing parallel execution of tests, including timeout checks. 9 * - Managing parallel execution of tests, including timeout checks.
10 * - Evaluating the output of each test as pass/fail/crash/timeout. 10 * - Evaluating the output of each test as pass/fail/crash/timeout.
11 */ 11 */
12 #library("test_runner"); 12 #library("test_runner");
13 13
14 #import("dart:io"); 14 #import("dart:io");
15 #import("status_file_parser.dart"); 15 #import("status_file_parser.dart");
16 #import("test_progress.dart"); 16 #import("test_progress.dart");
17 #import("test_suite.dart"); 17 #import("test_suite.dart");
18 18
19 final int NO_TIMEOUT = 0; 19 final int NO_TIMEOUT = 0;
20 20
21 /** A command executed as a step in a test case. */
22 class Command {
23 /** Path to the executable of this command. */
24 String executable;
25
26 /** Command line arguments to the executable. */
27 List<String> arguments;
28
29 /** The actual command line that will be executed. */
30 String commandLine;
31
32 Command(this.executable, this.arguments) {
33 commandLine = "$executable ${Strings.join(arguments, ' ')}";
34 }
35 }
21 36
22 /** 37 /**
23 * TestCase contains all the information needed to run a test and evaluate 38 * TestCase contains all the information needed to run a test and evaluate
24 * its output. Running a test involves starting a separate process, with 39 * its output. Running a test involves starting a separate process, with
25 * the executable and arguments given by the TestCase, and recording its 40 * the executable and arguments given by the TestCase, and recording its
26 * stdout and stderr output streams, and its exit code. TestCase only 41 * stdout and stderr output streams, and its exit code. TestCase only
27 * contains static information about the test; actually running the test is 42 * contains static information about the test; actually running the test is
28 * performed by [ProcessQueue] using a [RunningProcess] object. 43 * performed by [ProcessQueue] using a [RunningProcess] object.
29 * 44 *
30 * The output information is stored in a [TestOutput] instance contained 45 * The output information is stored in a [TestOutput] instance contained
31 * in the TestCase. The TestOutput instance is responsible for evaluating 46 * in the TestCase. The TestOutput instance is responsible for evaluating
32 * if the test has passed, failed, crashed, or timed out, and the TestCase 47 * if the test has passed, failed, crashed, or timed out, and the TestCase
33 * has information about what the expected result of the test should be. 48 * has information about what the expected result of the test should be.
34 * 49 *
35 * The TestCase has a callback function, [completedHandler], that is run when 50 * The TestCase has a callback function, [completedHandler], that is run when
36 * the test is completed. 51 * the test is completed.
37 */ 52 */
38 class TestCase { 53 class TestCase {
39 String executablePath; 54 /**
40 List<String> arguments; 55 * A list of commands to execute. Most test cases have a single command. Frog
56 * tests have two commands, one to compilate the source and another to execute
57 * it. Some isolate tests might even have three, if they require compiling
58 * multiple sources that are run in isolation.
59 */
60 final List<Command> commands;
61
41 Map configuration; 62 Map configuration;
42 String commandLine;
43 String displayName; 63 String displayName;
44 TestOutput output; 64 TestOutput output;
45 bool isNegative; 65 bool isNegative;
46 Set<String> expectedOutcomes; 66 Set<String> expectedOutcomes;
47 Function completedHandler; 67 Function completedHandler;
48 68
49 TestCase(this.displayName, 69 TestCase(this.displayName,
50 this.executablePath, 70 this.commands,
51 this.arguments,
52 this.configuration, 71 this.configuration,
53 this.completedHandler, 72 this.completedHandler,
54 this.expectedOutcomes, 73 this.expectedOutcomes,
55 [this.isNegative = false]) { 74 [this.isNegative = false]) {
56 if (!isNegative) { 75 if (!isNegative) {
57 this.isNegative = displayName.contains("NegativeTest"); 76 this.isNegative = displayName.contains("NegativeTest");
58 } 77 }
59 commandLine = "$executablePath ${Strings.join(arguments, ' ')}";
60 78
61 // Special command handling. If a special command is specified 79 // Special command handling. If a special command is specified
62 // we have to completely rewrite the command that we are using. 80 // we have to completely rewrite the command that we are using.
63 // We generate a new command-line that is the special command 81 // We generate a new command-line that is the special command
64 // where we replace '@' with the original command. 82 // where we replace '@' with the original command.
65 var specialCommand = configuration['special-command']; 83 var specialCommand = configuration['special-command'];
66 if (!specialCommand.isEmpty()) { 84 if (!specialCommand.isEmpty()) {
67 Expect.isTrue(specialCommand.contains('@'), 85 Expect.isTrue(specialCommand.contains('@'),
68 "special-command must contain a '@' char"); 86 "special-command must contain a '@' char");
69 var specialCommandSplit = specialCommand.split('@'); 87 var specialCommandSplit = specialCommand.split('@');
70 var prefix = specialCommandSplit[0]; 88 var prefix = specialCommandSplit[0];
71 var suffix = specialCommandSplit[1]; 89 var suffix = specialCommandSplit[1];
72 commandLine = '$prefix $commandLine $suffix'; 90 List<Command> newCommands = [];
73 var newArguments = []; 91 for (Command c in commands) {
74 if (prefix.length > 0) { 92 var newExecutablePath;
75 var prefixSplit = prefix.split(' '); 93 var newArguments = [];
76 var newExecutablePath = prefixSplit[0]; 94
77 for (int i = 1; i < prefixSplit.length; i++) { 95 if (prefix.length > 0) {
78 var current = prefixSplit[i]; 96 var prefixSplit = prefix.split(' ');
79 if (!current.isEmpty()) newArguments.add(current); 97 newExecutablePath = prefixSplit[0];
98 for (int i = 1; i < prefixSplit.length; i++) {
99 var current = prefixSplit[i];
100 if (!current.isEmpty()) newArguments.add(current);
101 }
102 newArguments.add(c.executable);
80 } 103 }
81 newArguments.add(executablePath); 104 newArguments.addAll(arguments);
82 executablePath = newExecutablePath; 105 var suffixSplit = suffix.split(' ');
106 suffixSplit.forEach((e) {
107 if (!e.isEmpty()) newArguments.add(e);
108 });
109 final newCommand = new Command(newExecutablePath, newArguments);
110 newCommands.add(newCommand);
111 Expect.stringEquals('$prefix ${c.commandLine} $suffix',
112 newCommand.commandLine);
83 } 113 }
84 newArguments.addAll(arguments); 114 commands = newCommand;
85 var suffixSplit = suffix.split(' ');
86 suffixSplit.forEach((e) {
87 if (!e.isEmpty()) newArguments.add(e);
88 });
89 arguments = newArguments;
90 } 115 }
91 } 116 }
92 117
93 int get timeout() => configuration['timeout']; 118 int get timeout() => configuration['timeout'];
94 119
95 String get configurationString() { 120 String get configurationString() {
96 final component = configuration['component']; 121 final component = configuration['component'];
97 final mode = configuration['mode']; 122 final mode = configuration['mode'];
98 final arch = configuration['arch']; 123 final arch = configuration['arch'];
99 return "$component ${mode}_$arch"; 124 return "$component ${mode}_$arch";
100 } 125 }
101 126
102 List<String> get batchRunnerArguments() => ['-batch']; 127 List<String> get batchRunnerArguments() => ['-batch'];
103 List<String> get batchTestArguments() => arguments; 128 List<String> get batchTestArguments() => commands.last().arguments;
104 129
105 void completed() { completedHandler(this); } 130 void completed() { completedHandler(this); }
106 } 131 }
107 132
108 133
109 /** 134 /**
110 * BrowserTestCase has an extra compilation command that is run in a separate 135 * BrowserTestCase has an extra compilation command that is run in a separate
111 * process, before the regular test is run as in the base class [TestCase]. 136 * process, before the regular test is run as in the base class [TestCase].
112 * If the compilation command fails, then the rest of the test is not run. 137 * If the compilation command fails, then the rest of the test is not run.
113 */ 138 */
114 class BrowserTestCase extends TestCase { 139 class BrowserTestCase extends TestCase {
115 /** 140 /**
116 * The executable that is run in a new process in the compilation phase.
117 */
118 String compilerPath;
119 /**
120 * The arguments for the compilation command.
121 */
122 List<String> compilerArguments;
123 /**
124 * Indicates the number of potential retries remaining, to compensate for 141 * Indicates the number of potential retries remaining, to compensate for
125 * flaky browser tests. 142 * flaky browser tests.
126 */ 143 */
127 bool numRetries; 144 bool numRetries;
128 145
129 BrowserTestCase(displayName, 146 BrowserTestCase(displayName, commands, configuration, completedHandler,
130 this.compilerPath, 147 expectedOutcomes, [isNegative = false])
131 this.compilerArguments, 148 : super(displayName, commands, configuration, completedHandler,
132 executablePath, 149 expectedOutcomes, isNegative) {
133 arguments,
134 configuration,
135 completedHandler,
136 expectedOutcomes,
137 [isNegative = false]) : super(displayName,
138 executablePath,
139 arguments,
140 configuration,
141 completedHandler,
142 expectedOutcomes,
143 isNegative) {
144 if (compilerPath != null) {
145 commandLine = 'execution command: $commandLine';
146 String compilationCommand =
147 '$compilerPath ${Strings.join(compilerArguments, " ")}';
148 commandLine = 'compilation command: $compilationCommand\n$commandLine';
149 }
150 numRetries = 2; // Allow two retries to compensate for flaky browser tests. 150 numRetries = 2; // Allow two retries to compensate for flaky browser tests.
151 } 151 }
152 152
153 List<String> get batchRunnerArguments() => [arguments[0], '--batch']; 153 List<String> get _lastArguments() => command.last().arguments;
154
155 List<String> get batchRunnerArguments() => [_lastArguments[0], '--batch'];
156
154 List<String> get batchTestArguments() => 157 List<String> get batchTestArguments() =>
155 arguments.getRange(1, arguments.length - 1); 158 _lastArguments.getRange(1, _lastArguments.length - 1);
156 } 159 }
157 160
158 161
159 /** 162 /**
160 * TestOutput records the output of a completed test: the process's exit code, 163 * TestOutput records the output of a completed test: the process's exit code,
161 * the standard output and standard error, whether the process timed out, and 164 * the standard output and standard error, whether the process timed out, and
162 * the time the process took to run. It also contains a pointer to the 165 * the time the process took to run. It also contains a pointer to the
163 * [TestCase] this is the output of. 166 * [TestCase] this is the output of.
164 */ 167 */
165 class TestOutput { 168 class TestOutput {
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
253 Process process; 256 Process process;
254 TestCase testCase; 257 TestCase testCase;
255 bool timedOut = false; 258 bool timedOut = false;
256 Date startTime; 259 Date startTime;
257 Timer timeoutTimer; 260 Timer timeoutTimer;
258 List<String> stdout; 261 List<String> stdout;
259 List<String> stderr; 262 List<String> stderr;
260 List<Function> handlers; 263 List<Function> handlers;
261 bool allowRetries = false; 264 bool allowRetries = false;
262 265
266 /** Which command of [testCase.commands] is currently being executed. */
267 int currentStep;
268
263 RunningProcess(TestCase this.testCase, 269 RunningProcess(TestCase this.testCase,
264 [this.allowRetries, this.processQueue]); 270 [this.allowRetries, this.processQueue]);
265 271
266 void exitHandler(int exitCode) { 272 /**
273 * Called when all commands are executed. [exitCode] is 0 if all command
274 * succeded, otherwise it will have the exit code of the first failing
275 * command.
276 */
277 void testComplete(int exitCode) {
267 new TestOutput(testCase, exitCode, timedOut, stdout, 278 new TestOutput(testCase, exitCode, timedOut, stdout,
268 stderr, new Date.now().difference(startTime)); 279 stderr, new Date.now().difference(startTime));
269 process.close();
270 timeoutTimer.cancel(); 280 timeoutTimer.cancel();
271 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { 281 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) {
272 print(testCase.displayName); 282 print(testCase.displayName);
273 for (var line in testCase.output.stderr) print(line); 283 for (var line in testCase.output.stderr) print(line);
274 for (var line in testCase.output.stdout) print(line); 284 for (var line in testCase.output.stdout) print(line);
275 } 285 }
276 if (allowRetries != null && allowRetries 286 if (allowRetries != null && allowRetries
277 && testCase.configuration['component'] == 'webdriver' && 287 && testCase.configuration['component'] == 'webdriver' &&
278 testCase.output.unexpectedOutput && testCase.numRetries > 0) { 288 testCase.output.unexpectedOutput && testCase.numRetries > 0) {
279 // Selenium tests can be flaky. Try rerunning. 289 // Selenium tests can be flaky. Try rerunning.
280 testCase.output.requestRetry = true; 290 testCase.output.requestRetry = true;
281 } 291 }
282 if (testCase.output.requestRetry) { 292 if (testCase.output.requestRetry) {
283 testCase.output.requestRetry = false; 293 testCase.output.requestRetry = false;
284 this.timedOut = false; 294 this.timedOut = false;
285 testCase.dynamic.numRetries--; 295 testCase.dynamic.numRetries--;
286 print("Potential flake. Re-running " + testCase.displayName); 296 print("Potential flake. Re-running ${testCase.displayName}");
287 this.start(); 297 this.start();
288 } else { 298 } else {
289 testCase.completed(); 299 testCase.completed();
290 } 300 }
291 } 301 }
292 302
293 void compilerExitHandler(int exitCode) { 303 /**
294 if (exitCode != 0) { 304 * Process exit handler called at the end of every command. It internally
305 * decides what is the most appropriate handler. In particular, all but the
306 * last command are compilation steps analyzed in [compilerExitHandler], the
307 * last command actually runs the test, which is analyzed in
308 * [testExitHandler].
309 */
310 void stepExitHandler(int exitCode) {
311 process.close();
312 if (currentStep == testCase.commands.length) { // done with test command
313 testComplete(exitCode);
314 } else if (exitCode != 0) {
295 stderr.add('test.dart: Compilation step failed (exit code $exitCode)\n'); 315 stderr.add('test.dart: Compilation step failed (exit code $exitCode)\n');
296 exitHandler(exitCode); 316 testComplete(exitCode);
297 } else { 317 } else {
298 process.close(); 318 stderr.add('test.dart: Compilation step finished\n');
Bill Hesse 2012/02/28 16:41:56 Could we add currentStep to the messages in stderr
Siggi Cherem (dart-lang) 2012/02/28 17:42:37 Done
299 stderr.add('test.dart: Compilation finished, starting execution\n'); 319 stdout.add('test.dart: Compilation step finished\n');
300 stdout.add('test.dart: Compilation finished, starting execution\n'); 320 if (currentStep == testCase.commands.length - 1
301 if (testCase.configuration['component'] == 'webdriver') { 321 && testCase.configuration['component'] == 'webdriver') {
302 // Note: processQueue will always be non-null for component == webdriver 322 // Note: processQueue will always be non-null for component == webdriver
303 // (It is only null for component == vm) 323 // (It is only null for component == vm)
304 processQueue._getBatchRunner(testCase).startTest(testCase); 324 processQueue._getBatchRunner(testCase).startTest(testCase);
305 } else { 325 } else {
306 runCommand(testCase.executablePath, testCase.arguments, exitHandler); 326 runCommand(testCase.commands[currentStep++], stepExitHandler);
307 } 327 }
308 } 328 }
309 } 329 }
310 330
311 Function makeReadHandler(StringInputStream source, List<String> destination) { 331 Function makeReadHandler(StringInputStream source, List<String> destination) {
312 return () { 332 return () {
313 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. 333 if (source.closed) return; // TODO(whesse): Remove when bug is fixed.
314 var line = source.readLine(); 334 var line = source.readLine();
315 while (null != line) { 335 while (null != line) {
316 destination.add(line); 336 destination.add(line);
317 line = source.readLine(); 337 line = source.readLine();
318 } 338 }
319 }; 339 };
320 } 340 }
321 341
322 void start() { 342 void start() {
323 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP)); 343 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP));
324 stdout = new List<String>(); 344 stdout = new List<String>();
325 stderr = new List<String>(); 345 stderr = new List<String>();
326 if (testCase is BrowserTestCase && testCase.dynamic.compilerPath != null) { 346 currentStep = 0;
327 runCommand(testCase.dynamic.compilerPath, 347 runCommand(testCase.commands[currentStep++], stepExitHandler);
328 testCase.dynamic.compilerArguments,
329 compilerExitHandler);
330 } else {
331 runCommand(testCase.executablePath, testCase.arguments, exitHandler);
332 }
333 } 348 }
334 349
335 void runCommand(String executable, 350 void runCommand(Command command,
336 List<String> arguments,
337 void exitHandler(int exitCode)) { 351 void exitHandler(int exitCode)) {
338 if (new Platform().operatingSystem() == 'windows') { 352 if (new Platform().operatingSystem() == 'windows') {
339 // Windows can't handle the first command if it is a .bat file or the like 353 // Windows can't handle the first command if it is a .bat file or the like
340 // with the slashes going the other direction. 354 // with the slashes going the other direction.
341 // TODO(efortuna): Remove this when fixed (Issue 1306). 355 // TODO(efortuna): Remove this when fixed (Issue 1306).
342 executable = executable.replaceAll('/', '\\'); 356 command.executable = command.executable.replaceAll('/', '\\');
343 } 357 }
344 process = new Process.start(executable, arguments); 358 process = new Process.start(command.executable, command.arguments);
345 process.exitHandler = exitHandler; 359 process.exitHandler = exitHandler;
346 startTime = new Date.now(); 360 startTime = new Date.now();
347 InputStream stdoutStream = process.stdout; 361 InputStream stdoutStream = process.stdout;
348 InputStream stderrStream = process.stderr; 362 InputStream stderrStream = process.stderr;
349 StringInputStream stdoutStringStream = new StringInputStream(stdoutStream); 363 StringInputStream stdoutStringStream = new StringInputStream(stdoutStream);
350 StringInputStream stderrStringStream = new StringInputStream(stderrStream); 364 StringInputStream stderrStringStream = new StringInputStream(stderrStream);
351 stdoutStringStream.lineHandler = 365 stdoutStringStream.lineHandler =
352 makeReadHandler(stdoutStringStream, stdout); 366 makeReadHandler(stdoutStringStream, stdout);
353 stderrStringStream.lineHandler = 367 stderrStringStream.lineHandler =
354 makeReadHandler(stderrStringStream, stderr); 368 makeReadHandler(stderrStringStream, stderr);
(...skipping 16 matching lines...) Expand all
371 385
372 TestCase _currentTest; 386 TestCase _currentTest;
373 List<String> _testStdout; 387 List<String> _testStdout;
374 List<String> _testStderr; 388 List<String> _testStderr;
375 Date _startTime; 389 Date _startTime;
376 Timer _timer; 390 Timer _timer;
377 391
378 bool _isWebDriver; 392 bool _isWebDriver;
379 393
380 BatchRunnerProcess(TestCase testCase) { 394 BatchRunnerProcess(TestCase testCase) {
381 _executable = testCase.executablePath; 395 _executable = testCase.commands.last().executable;
382 _batchArguments = testCase.batchRunnerArguments; 396 _batchArguments = testCase.batchRunnerArguments;
383 _isWebDriver = testCase.configuration['component'] == 'webdriver'; 397 _isWebDriver = testCase.configuration['component'] == 'webdriver';
384 } 398 }
385 399
386 bool get active() => _currentTest != null; 400 bool get active() => _currentTest != null;
387 401
388 void startTest(TestCase testCase) { 402 void startTest(TestCase testCase) {
389 _currentTest = testCase; 403 _currentTest = testCase;
390 if (_process === null) { 404 if (_process === null) {
391 // Start process if not yet started. 405 // Start process if not yet started.
392 _executable = testCase.executablePath; 406 _executable = testCase.commands.last().executable;
393 _startProcess(() { 407 _startProcess(() {
394 doStartTest(testCase); 408 doStartTest(testCase);
395 }); 409 });
396 } else if (testCase.executablePath != _executable) { 410 } else if (testCase.commands.last().executable != _executable) {
397 // Restart this runner with the right executable for this test 411 // Restart this runner with the right executable for this test
398 // if needed. 412 // if needed.
399 _executable = testCase.executablePath; 413 _executable = testCase.commands.last().executable;
400 _batchArguments = testCase.batchRunnerArguments; 414 _batchArguments = testCase.batchRunnerArguments;
401 _process.exitHandler = (exitCode) { 415 _process.exitHandler = (exitCode) {
402 _process.close(); 416 _process.close();
403 _startProcess(() { 417 _startProcess(() {
404 doStartTest(testCase); 418 doStartTest(testCase);
405 }); 419 });
406 }; 420 };
407 _process.kill(); 421 _process.kill();
408 } else { 422 } else {
409 doStartTest(testCase); 423 doStartTest(testCase);
(...skipping 311 matching lines...) Expand 10 before | Expand all | Expand 10 after
721 for (var runner in runners) { 735 for (var runner in runners) {
722 if (!runner.active) return runner; 736 if (!runner.active) return runner;
723 } 737 }
724 throw new Exception('Unable to find inactive batch runner.'); 738 throw new Exception('Unable to find inactive batch runner.');
725 } 739 }
726 740
727 void _tryRunTest() { 741 void _tryRunTest() {
728 _checkDone(); 742 _checkDone();
729 if (_numProcesses < _maxProcesses && !_tests.isEmpty()) { 743 if (_numProcesses < _maxProcesses && !_tests.isEmpty()) {
730 TestCase test = _tests.removeFirst(); 744 TestCase test = _tests.removeFirst();
731 if (_verbose) print(test.commandLine); 745 if (_verbose) print(test.commands.last().commandLine);
732 if (_listTests) { 746 if (_listTests) {
733 final String tab = '\t'; 747 final String tab = '\t';
734 String outcomes = 748 String outcomes =
735 Strings.join(new List.from(test.expectedOutcomes), ','); 749 Strings.join(new List.from(test.expectedOutcomes), ',');
736 print(test.displayName + tab + outcomes + tab + test.isNegative + 750 print(test.displayName + tab + outcomes + tab + test.isNegative +
737 tab + Strings.join(test.arguments, tab)); 751 tab + Strings.join(test.commands.last().arguments, tab));
738 return; 752 return;
739 } 753 }
740 _progress.start(test); 754 _progress.start(test);
741 Function oldCallback = test.completedHandler; 755 Function oldCallback = test.completedHandler;
742 Function wrapper = (TestCase test_arg) { 756 Function wrapper = (TestCase test_arg) {
743 _numProcesses--; 757 _numProcesses--;
744 _progress.done(test_arg); 758 _progress.done(test_arg);
745 _tryRunTest(); 759 _tryRunTest();
746 oldCallback(test_arg); 760 oldCallback(test_arg);
747 }; 761 };
748 test.completedHandler = wrapper; 762 test.completedHandler = wrapper;
749 if (test.configuration['component'] == 'dartc' && 763 if (test.configuration['component'] == 'dartc' &&
750 test.displayName != 'dartc/junit_tests') { 764 test.displayName != 'dartc/junit_tests') {
751 _getBatchRunner(test).startTest(test); 765 _getBatchRunner(test).startTest(test);
752 } else { 766 } else {
753 // Once we've actually failed a test, technically, we wouldn't need to 767 // Once we've actually failed a test, technically, we wouldn't need to
754 // bother retrying any subsequent tests since the bot is already red. 768 // bother retrying any subsequent tests since the bot is already red.
755 // However, we continue to retry tests until we have actually failed 769 // However, we continue to retry tests until we have actually failed
756 // four tests (arbitrarily chosen) for more debugable output, so that 770 // four tests (arbitrarily chosen) for more debugable output, so that
757 // the developer doesn't waste his or her time trying to fix a bunch of 771 // the developer doesn't waste his or her time trying to fix a bunch of
758 // tests that appear to be broken but were actually just flakes that 772 // tests that appear to be broken but were actually just flakes that
759 // didn't get retried because there had already been one failure. 773 // didn't get retried because there had already been one failure.
760 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; 774 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests;
761 new RunningProcess(test, allowRetry, this).start(); 775 new RunningProcess(test, allowRetry, this).start();
762 } 776 }
763 _numProcesses++; 777 _numProcesses++;
764 } 778 }
765 } 779 }
766 } 780 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698