| OLD | NEW |
| 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 int numRetries; | 144 int 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 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 255 Process process; | 258 Process process; |
| 256 TestCase testCase; | 259 TestCase testCase; |
| 257 bool timedOut = false; | 260 bool timedOut = false; |
| 258 Date startTime; | 261 Date startTime; |
| 259 Timer timeoutTimer; | 262 Timer timeoutTimer; |
| 260 List<String> stdout; | 263 List<String> stdout; |
| 261 List<String> stderr; | 264 List<String> stderr; |
| 262 List<Function> handlers; | 265 List<Function> handlers; |
| 263 bool allowRetries = false; | 266 bool allowRetries = false; |
| 264 | 267 |
| 268 /** Which command of [testCase.commands] is currently being executed. */ |
| 269 int currentStep; |
| 270 |
| 265 RunningProcess(TestCase this.testCase, | 271 RunningProcess(TestCase this.testCase, |
| 266 [this.allowRetries, this.processQueue]); | 272 [this.allowRetries, this.processQueue]); |
| 267 | 273 |
| 268 void exitHandler(int exitCode) { | 274 /** |
| 275 * Called when all commands are executed. [exitCode] is 0 if all command |
| 276 * succeded, otherwise it will have the exit code of the first failing |
| 277 * command. |
| 278 */ |
| 279 void testComplete(int exitCode) { |
| 269 new TestOutput(testCase, exitCode, timedOut, stdout, | 280 new TestOutput(testCase, exitCode, timedOut, stdout, |
| 270 stderr, new Date.now().difference(startTime)); | 281 stderr, new Date.now().difference(startTime)); |
| 271 process.close(); | |
| 272 timeoutTimer.cancel(); | 282 timeoutTimer.cancel(); |
| 273 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { | 283 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { |
| 274 print(testCase.displayName); | 284 print(testCase.displayName); |
| 275 for (var line in testCase.output.stderr) print(line); | 285 for (var line in testCase.output.stderr) print(line); |
| 276 for (var line in testCase.output.stdout) print(line); | 286 for (var line in testCase.output.stdout) print(line); |
| 277 } | 287 } |
| 278 if (allowRetries != null && allowRetries | 288 if (allowRetries != null && allowRetries |
| 279 && testCase.configuration['component'] == 'webdriver' && | 289 && testCase.configuration['component'] == 'webdriver' && |
| 280 testCase.output.unexpectedOutput && testCase.numRetries > 0) { | 290 testCase.output.unexpectedOutput && testCase.numRetries > 0) { |
| 281 // Selenium tests can be flaky. Try rerunning. | 291 // Selenium tests can be flaky. Try rerunning. |
| 282 testCase.output.requestRetry = true; | 292 testCase.output.requestRetry = true; |
| 283 } | 293 } |
| 284 if (testCase.output.requestRetry) { | 294 if (testCase.output.requestRetry) { |
| 285 testCase.output.requestRetry = false; | 295 testCase.output.requestRetry = false; |
| 286 this.timedOut = false; | 296 this.timedOut = false; |
| 287 testCase.dynamic.numRetries--; | 297 testCase.dynamic.numRetries--; |
| 288 print("Potential flake. " + | 298 print("Potential flake. " + |
| 289 "Re-running ${testCase.displayName} " + | 299 "Re-running ${testCase.displayName} " + |
| 290 "(${testCase.dynamic.numRetries} attempt(s) remains)"); | 300 "(${testCase.dynamic.numRetries} attempt(s) remains)"); |
| 291 this.start(); | 301 this.start(); |
| 292 } else { | 302 } else { |
| 293 testCase.completed(); | 303 testCase.completed(); |
| 294 } | 304 } |
| 295 } | 305 } |
| 296 | 306 |
| 297 void compilerExitHandler(int exitCode) { | 307 /** |
| 298 if (exitCode != 0) { | 308 * Process exit handler called at the end of every command. It internally |
| 299 stderr.add('test.dart: Compilation step failed (exit code $exitCode)\n'); | 309 * treats all but the last command as compilation steps. The last command is |
| 300 exitHandler(exitCode); | 310 * the actual test and its output is analyzed in [testComplete]. |
| 311 */ |
| 312 void stepExitHandler(int exitCode) { |
| 313 process.close(); |
| 314 int totalSteps = testCase.commands.length; |
| 315 String suffix =' (step $currentStep of $totalSteps)'; |
| 316 if (currentStep == totalSteps) { // done with test command |
| 317 testComplete(exitCode); |
| 318 } else if (exitCode != 0) { |
| 319 stderr.add('test.dart: Compilation failed$suffix, exit code $exitCode\n'); |
| 320 testComplete(exitCode); |
| 301 } else { | 321 } else { |
| 302 process.close(); | 322 stderr.add('test.dart: Compilion finished $suffix\n'); |
| 303 stderr.add('test.dart: Compilation finished, starting execution\n'); | 323 stdout.add('test.dart: Compilion finished $suffix\n'); |
| 304 stdout.add('test.dart: Compilation finished, starting execution\n'); | 324 if (currentStep == totalSteps - 1 |
| 305 if (testCase.configuration['component'] == 'webdriver') { | 325 && testCase.configuration['component'] == 'webdriver') { |
| 306 // Note: processQueue will always be non-null for component == webdriver | 326 // Note: processQueue will always be non-null for component == webdriver |
| 307 // (It is only null for component == vm) | 327 // (It is only null for component == vm) |
| 308 processQueue._getBatchRunner(testCase).startTest(testCase); | 328 processQueue._getBatchRunner(testCase).startTest(testCase); |
| 309 } else { | 329 } else { |
| 310 runCommand(testCase.executablePath, testCase.arguments, exitHandler); | 330 runCommand(testCase.commands[currentStep++], stepExitHandler); |
| 311 } | 331 } |
| 312 } | 332 } |
| 313 } | 333 } |
| 314 | 334 |
| 315 Function makeReadHandler(StringInputStream source, List<String> destination) { | 335 Function makeReadHandler(StringInputStream source, List<String> destination) { |
| 316 return () { | 336 return () { |
| 317 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. | 337 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. |
| 318 var line = source.readLine(); | 338 var line = source.readLine(); |
| 319 while (null != line) { | 339 while (null != line) { |
| 320 destination.add(line); | 340 destination.add(line); |
| 321 line = source.readLine(); | 341 line = source.readLine(); |
| 322 } | 342 } |
| 323 }; | 343 }; |
| 324 } | 344 } |
| 325 | 345 |
| 326 void start() { | 346 void start() { |
| 327 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP)); | 347 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP)); |
| 328 stdout = new List<String>(); | 348 stdout = new List<String>(); |
| 329 stderr = new List<String>(); | 349 stderr = new List<String>(); |
| 330 if (testCase is BrowserTestCase && testCase.dynamic.compilerPath != null) { | 350 currentStep = 0; |
| 331 runCommand(testCase.dynamic.compilerPath, | 351 runCommand(testCase.commands[currentStep++], stepExitHandler); |
| 332 testCase.dynamic.compilerArguments, | |
| 333 compilerExitHandler); | |
| 334 } else { | |
| 335 runCommand(testCase.executablePath, testCase.arguments, exitHandler); | |
| 336 } | |
| 337 } | 352 } |
| 338 | 353 |
| 339 void runCommand(String executable, | 354 void runCommand(Command command, |
| 340 List<String> arguments, | |
| 341 void exitHandler(int exitCode)) { | 355 void exitHandler(int exitCode)) { |
| 342 if (new Platform().operatingSystem() == 'windows') { | 356 if (new Platform().operatingSystem() == 'windows') { |
| 343 // Windows can't handle the first command if it is a .bat file or the like | 357 // Windows can't handle the first command if it is a .bat file or the like |
| 344 // with the slashes going the other direction. | 358 // with the slashes going the other direction. |
| 345 // TODO(efortuna): Remove this when fixed (Issue 1306). | 359 // TODO(efortuna): Remove this when fixed (Issue 1306). |
| 346 executable = executable.replaceAll('/', '\\'); | 360 command.executable = command.executable.replaceAll('/', '\\'); |
| 347 } | 361 } |
| 348 process = new Process.start(executable, arguments); | 362 process = new Process.start(command.executable, command.arguments); |
| 349 process.exitHandler = exitHandler; | 363 process.exitHandler = exitHandler; |
| 350 startTime = new Date.now(); | 364 startTime = new Date.now(); |
| 351 InputStream stdoutStream = process.stdout; | 365 InputStream stdoutStream = process.stdout; |
| 352 InputStream stderrStream = process.stderr; | 366 InputStream stderrStream = process.stderr; |
| 353 StringInputStream stdoutStringStream = new StringInputStream(stdoutStream); | 367 StringInputStream stdoutStringStream = new StringInputStream(stdoutStream); |
| 354 StringInputStream stderrStringStream = new StringInputStream(stderrStream); | 368 StringInputStream stderrStringStream = new StringInputStream(stderrStream); |
| 355 stdoutStringStream.lineHandler = | 369 stdoutStringStream.lineHandler = |
| 356 makeReadHandler(stdoutStringStream, stdout); | 370 makeReadHandler(stdoutStringStream, stdout); |
| 357 stderrStringStream.lineHandler = | 371 stderrStringStream.lineHandler = |
| 358 makeReadHandler(stderrStringStream, stderr); | 372 makeReadHandler(stderrStringStream, stderr); |
| (...skipping 16 matching lines...) Expand all Loading... |
| 375 | 389 |
| 376 TestCase _currentTest; | 390 TestCase _currentTest; |
| 377 List<String> _testStdout; | 391 List<String> _testStdout; |
| 378 List<String> _testStderr; | 392 List<String> _testStderr; |
| 379 Date _startTime; | 393 Date _startTime; |
| 380 Timer _timer; | 394 Timer _timer; |
| 381 | 395 |
| 382 bool _isWebDriver; | 396 bool _isWebDriver; |
| 383 | 397 |
| 384 BatchRunnerProcess(TestCase testCase) { | 398 BatchRunnerProcess(TestCase testCase) { |
| 385 _executable = testCase.executablePath; | 399 _executable = testCase.commands.last().executable; |
| 386 _batchArguments = testCase.batchRunnerArguments; | 400 _batchArguments = testCase.batchRunnerArguments; |
| 387 _isWebDriver = testCase.configuration['component'] == 'webdriver'; | 401 _isWebDriver = testCase.configuration['component'] == 'webdriver'; |
| 388 } | 402 } |
| 389 | 403 |
| 390 bool get active() => _currentTest != null; | 404 bool get active() => _currentTest != null; |
| 391 | 405 |
| 392 void startTest(TestCase testCase) { | 406 void startTest(TestCase testCase) { |
| 393 _currentTest = testCase; | 407 _currentTest = testCase; |
| 394 if (_process === null) { | 408 if (_process === null) { |
| 395 // Start process if not yet started. | 409 // Start process if not yet started. |
| 396 _executable = testCase.executablePath; | 410 _executable = testCase.commands.last().executable; |
| 397 _startProcess(() { | 411 _startProcess(() { |
| 398 doStartTest(testCase); | 412 doStartTest(testCase); |
| 399 }); | 413 }); |
| 400 } else if (testCase.executablePath != _executable) { | 414 } else if (testCase.commands.last().executable != _executable) { |
| 401 // Restart this runner with the right executable for this test | 415 // Restart this runner with the right executable for this test |
| 402 // if needed. | 416 // if needed. |
| 403 _executable = testCase.executablePath; | 417 _executable = testCase.commands.last().executable; |
| 404 _batchArguments = testCase.batchRunnerArguments; | 418 _batchArguments = testCase.batchRunnerArguments; |
| 405 _process.exitHandler = (exitCode) { | 419 _process.exitHandler = (exitCode) { |
| 406 _process.close(); | 420 _process.close(); |
| 407 _startProcess(() { | 421 _startProcess(() { |
| 408 doStartTest(testCase); | 422 doStartTest(testCase); |
| 409 }); | 423 }); |
| 410 }; | 424 }; |
| 411 _process.kill(); | 425 _process.kill(); |
| 412 } else { | 426 } else { |
| 413 doStartTest(testCase); | 427 doStartTest(testCase); |
| (...skipping 311 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 725 for (var runner in runners) { | 739 for (var runner in runners) { |
| 726 if (!runner.active) return runner; | 740 if (!runner.active) return runner; |
| 727 } | 741 } |
| 728 throw new Exception('Unable to find inactive batch runner.'); | 742 throw new Exception('Unable to find inactive batch runner.'); |
| 729 } | 743 } |
| 730 | 744 |
| 731 void _tryRunTest() { | 745 void _tryRunTest() { |
| 732 _checkDone(); | 746 _checkDone(); |
| 733 if (_numProcesses < _maxProcesses && !_tests.isEmpty()) { | 747 if (_numProcesses < _maxProcesses && !_tests.isEmpty()) { |
| 734 TestCase test = _tests.removeFirst(); | 748 TestCase test = _tests.removeFirst(); |
| 735 if (_verbose) print(test.commandLine); | 749 if (_verbose) print(test.commands.last().commandLine); |
| 736 if (_listTests) { | 750 if (_listTests) { |
| 737 final String tab = '\t'; | 751 final String tab = '\t'; |
| 738 String outcomes = | 752 String outcomes = |
| 739 Strings.join(new List.from(test.expectedOutcomes), ','); | 753 Strings.join(new List.from(test.expectedOutcomes), ','); |
| 740 print(test.displayName + tab + outcomes + tab + test.isNegative + | 754 print(test.displayName + tab + outcomes + tab + test.isNegative + |
| 741 tab + Strings.join(test.arguments, tab)); | 755 tab + Strings.join(test.commands.last().arguments, tab)); |
| 742 return; | 756 return; |
| 743 } | 757 } |
| 744 _progress.start(test); | 758 _progress.start(test); |
| 745 Function oldCallback = test.completedHandler; | 759 Function oldCallback = test.completedHandler; |
| 746 Function wrapper = (TestCase test_arg) { | 760 Function wrapper = (TestCase test_arg) { |
| 747 _numProcesses--; | 761 _numProcesses--; |
| 748 _progress.done(test_arg); | 762 _progress.done(test_arg); |
| 749 _tryRunTest(); | 763 _tryRunTest(); |
| 750 oldCallback(test_arg); | 764 oldCallback(test_arg); |
| 751 }; | 765 }; |
| 752 test.completedHandler = wrapper; | 766 test.completedHandler = wrapper; |
| 753 if (test.configuration['component'] == 'dartc' && | 767 if (test.configuration['component'] == 'dartc' && |
| 754 test.displayName != 'dartc/junit_tests') { | 768 test.displayName != 'dartc/junit_tests') { |
| 755 _getBatchRunner(test).startTest(test); | 769 _getBatchRunner(test).startTest(test); |
| 756 } else { | 770 } else { |
| 757 // Once we've actually failed a test, technically, we wouldn't need to | 771 // Once we've actually failed a test, technically, we wouldn't need to |
| 758 // bother retrying any subsequent tests since the bot is already red. | 772 // bother retrying any subsequent tests since the bot is already red. |
| 759 // However, we continue to retry tests until we have actually failed | 773 // However, we continue to retry tests until we have actually failed |
| 760 // four tests (arbitrarily chosen) for more debugable output, so that | 774 // four tests (arbitrarily chosen) for more debugable output, so that |
| 761 // the developer doesn't waste his or her time trying to fix a bunch of | 775 // the developer doesn't waste his or her time trying to fix a bunch of |
| 762 // tests that appear to be broken but were actually just flakes that | 776 // tests that appear to be broken but were actually just flakes that |
| 763 // didn't get retried because there had already been one failure. | 777 // didn't get retried because there had already been one failure. |
| 764 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; | 778 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; |
| 765 new RunningProcess(test, allowRetry, this).start(); | 779 new RunningProcess(test, allowRetry, this).start(); |
| 766 } | 780 } |
| 767 _numProcesses++; | 781 _numProcesses++; |
| 768 } | 782 } |
| 769 } | 783 } |
| 770 } | 784 } |
| OLD | NEW |