| 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. |
| (...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 159 } | 159 } |
| 160 | 160 |
| 161 | 161 |
| 162 /** | 162 /** |
| 163 * 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, |
| 164 * 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 |
| 165 * 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 |
| 166 * [TestCase] this is the output of. | 166 * [TestCase] this is the output of. |
| 167 */ | 167 */ |
| 168 class TestOutput { | 168 class TestOutput { |
| 169 final TestCase testCase; | 169 TestCase testCase; |
| 170 final int exitCode; | 170 int exitCode; |
| 171 final bool timedOut; | 171 bool timedOut; |
| 172 final List<String> stdout; | |
| 173 final List<String> stderr; | |
| 174 final Duration time; | |
| 175 bool failed = false; | 172 bool failed = false; |
| 173 List<String> stdout; |
| 174 List<String> stderr; |
| 175 Duration time; |
| 176 /** | 176 /** |
| 177 * Set to true if we encounter a condition in the output that indicates we | 177 * Set to true if we encounter a condition in the output that indicates we |
| 178 * need to rerun this test. | 178 * need to rerun this test. |
| 179 */ | 179 */ |
| 180 bool requestRetry = false; | 180 bool requestRetry; |
| 181 | 181 |
| 182 TestOutput(this.testCase, this.exitCode, this.timedOut, this.stdout, | 182 TestOutput(this.testCase, this.exitCode, this.timedOut, this.stdout, |
| 183 this.stderr, this.time) { | 183 this.stderr, this.time) { |
| 184 testCase.output = this; | 184 testCase.output = this; |
| 185 requestRetry = false; |
| 185 } | 186 } |
| 186 | 187 |
| 187 String get result() => | 188 String get result() => |
| 188 hasCrashed ? CRASH : (hasTimedOut ? TIMEOUT : (hasFailed ? FAIL : PASS)); | 189 hasCrashed ? CRASH : (hasTimedOut ? TIMEOUT : (hasFailed ? FAIL : PASS)); |
| 189 | 190 |
| 190 bool get unexpectedOutput() => !testCase.expectedOutcomes.contains(result); | 191 bool get unexpectedOutput() => !testCase.expectedOutcomes.contains(result); |
| 191 | 192 |
| 192 bool get hasCrashed() { | 193 bool get hasCrashed() { |
| 193 if (new Platform().operatingSystem() == 'windows') { | 194 if (new Platform().operatingSystem() == 'windows') { |
| 194 // The VM uses std::abort to terminate on asserts. | 195 // The VM uses std::abort to terminate on asserts. |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 264 List<Function> handlers; | 265 List<Function> handlers; |
| 265 bool allowRetries = false; | 266 bool allowRetries = false; |
| 266 | 267 |
| 267 /** Which command of [testCase.commands] is currently being executed. */ | 268 /** Which command of [testCase.commands] is currently being executed. */ |
| 268 int currentStep; | 269 int currentStep; |
| 269 | 270 |
| 270 RunningProcess(TestCase this.testCase, | 271 RunningProcess(TestCase this.testCase, |
| 271 [this.allowRetries, this.processQueue]); | 272 [this.allowRetries, this.processQueue]); |
| 272 | 273 |
| 273 /** | 274 /** |
| 274 * Called when all commands are executed. [exitCode] is 0 if all commands | 275 * Called when all commands are executed. [exitCode] is 0 if all command |
| 275 * succeded, otherwise it will have the exit code of the first failing | 276 * succeded, otherwise it will have the exit code of the first failing |
| 276 * command. | 277 * command. |
| 277 */ | 278 */ |
| 278 void testComplete(int exitCode) { | 279 void testComplete(int exitCode) { |
| 279 new TestOutput(testCase, exitCode, timedOut, stdout, | 280 new TestOutput(testCase, exitCode, timedOut, stdout, |
| 280 stderr, new Date.now().difference(startTime)); | 281 stderr, new Date.now().difference(startTime)); |
| 281 timeoutTimer.cancel(); | 282 timeoutTimer.cancel(); |
| 282 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { | 283 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { |
| 283 print(testCase.displayName); | 284 print(testCase.displayName); |
| 284 for (var line in testCase.output.stderr) print(line); | 285 for (var line in testCase.output.stderr) print(line); |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 324 && testCase.configuration['component'] == 'webdriver') { | 325 && testCase.configuration['component'] == 'webdriver') { |
| 325 // Note: processQueue will always be non-null for component == webdriver | 326 // Note: processQueue will always be non-null for component == webdriver |
| 326 // (It is only null for component == vm) | 327 // (It is only null for component == vm) |
| 327 processQueue._getBatchRunner(testCase).startTest(testCase); | 328 processQueue._getBatchRunner(testCase).startTest(testCase); |
| 328 } else { | 329 } else { |
| 329 runCommand(testCase.commands[currentStep++], stepExitHandler); | 330 runCommand(testCase.commands[currentStep++], stepExitHandler); |
| 330 } | 331 } |
| 331 } | 332 } |
| 332 } | 333 } |
| 333 | 334 |
| 335 Function makeReadHandler(StringInputStream source, List<String> destination) { |
| 336 return () { |
| 337 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. |
| 338 var line = source.readLine(); |
| 339 while (null != line) { |
| 340 destination.add(line); |
| 341 line = source.readLine(); |
| 342 } |
| 343 }; |
| 344 } |
| 345 |
| 334 void start() { | 346 void start() { |
| 335 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP)); | 347 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP)); |
| 336 stdout = []; | 348 stdout = new List<String>(); |
| 337 stderr = []; | 349 stderr = new List<String>(); |
| 338 currentStep = 0; | 350 currentStep = 0; |
| 339 runCommand(testCase.commands[currentStep++], stepExitHandler); | 351 runCommand(testCase.commands[currentStep++], stepExitHandler); |
| 340 } | 352 } |
| 341 | 353 |
| 342 void runCommand(Command command, | 354 void runCommand(Command command, |
| 343 void exitHandler(int exitCode)) { | 355 void exitHandler(int exitCode)) { |
| 344 if (new Platform().operatingSystem() == 'windows') { | 356 if (new Platform().operatingSystem() == 'windows') { |
| 345 // 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 |
| 346 // with the slashes going the other direction. | 358 // with the slashes going the other direction. |
| 347 // TODO(efortuna): Remove this when fixed (Issue 1306). | 359 // TODO(efortuna): Remove this when fixed (Issue 1306). |
| 348 command.executable = command.executable.replaceAll('/', '\\'); | 360 command.executable = command.executable.replaceAll('/', '\\'); |
| 349 } | 361 } |
| 350 process = new Process.start(command.executable, command.arguments); | 362 process = new Process.start(command.executable, command.arguments); |
| 351 process.exitHandler = exitHandler; | 363 process.exitHandler = exitHandler; |
| 352 startTime = new Date.now(); | 364 startTime = new Date.now(); |
| 353 InputStream stdoutStream = process.stdout; | 365 InputStream stdoutStream = process.stdout; |
| 354 InputStream stderrStream = process.stderr; | 366 InputStream stderrStream = process.stderr; |
| 355 StringInputStream stdoutStringStream = new StringInputStream(stdoutStream); | 367 StringInputStream stdoutStringStream = new StringInputStream(stdoutStream); |
| 356 StringInputStream stderrStringStream = new StringInputStream(stderrStream); | 368 StringInputStream stderrStringStream = new StringInputStream(stderrStream); |
| 357 stdoutStringStream.lineHandler = | 369 stdoutStringStream.lineHandler = |
| 358 _makeReadHandler(stdoutStringStream, stdout); | 370 makeReadHandler(stdoutStringStream, stdout); |
| 359 stderrStringStream.lineHandler = | 371 stderrStringStream.lineHandler = |
| 360 _makeReadHandler(stderrStringStream, stderr); | 372 makeReadHandler(stderrStringStream, stderr); |
| 361 timeoutTimer = new Timer(timeoutHandler, 1000 * testCase.timeout); | 373 timeoutTimer = new Timer(timeoutHandler, 1000 * testCase.timeout); |
| 362 } | 374 } |
| 363 | 375 |
| 364 void timeoutHandler(Timer unusedTimer) { | 376 void timeoutHandler(Timer unusedTimer) { |
| 365 timedOut = true; | 377 timedOut = true; |
| 366 process.kill(); | 378 process.kill(); |
| 367 } | 379 } |
| 368 } | 380 } |
| 369 | 381 |
| 370 class BatchRunnerProcess { | 382 class BatchRunnerProcess { |
| 371 String _executable; | 383 String _executable; |
| 372 List<String> _batchArguments; | 384 List<String> _batchArguments; |
| 373 | 385 |
| 374 Process _process; | 386 Process _process; |
| 375 StringInputStream _stdoutStream; | 387 StringInputStream _stdoutStream; |
| 376 StringInputStream _stderrStream; | 388 StringInputStream _stderrStream; |
| 377 | 389 |
| 378 TestCase _currentTest; | 390 TestCase _currentTest; |
| 379 List<String> _testStdout; | 391 List<String> _testStdout; |
| 380 List<String> _testStderr; | 392 List<String> _testStderr; |
| 393 bool _stderrDrained = false; |
| 381 Date _startTime; | 394 Date _startTime; |
| 382 Timer _timer; | 395 Timer _timer; |
| 383 | 396 |
| 384 bool _isWebDriver; | 397 bool _isWebDriver; |
| 385 | 398 |
| 386 BatchRunnerProcess(TestCase testCase) { | 399 BatchRunnerProcess(TestCase testCase) { |
| 387 _executable = testCase.commands.last().executable; | 400 _executable = testCase.commands.last().executable; |
| 388 _batchArguments = testCase.batchRunnerArguments; | 401 _batchArguments = testCase.batchRunnerArguments; |
| 389 _isWebDriver = testCase.configuration['component'] == 'webdriver'; | 402 _isWebDriver = testCase.configuration['component'] == 'webdriver'; |
| 390 } | 403 } |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 433 bool shutdownMillisecs = 30000; | 446 bool shutdownMillisecs = 30000; |
| 434 new Timer((e) { if (!closed) _process.kill(); }, shutdownMillisecs); | 447 new Timer((e) { if (!closed) _process.kill(); }, shutdownMillisecs); |
| 435 } else { | 448 } else { |
| 436 _process.kill(); | 449 _process.kill(); |
| 437 } | 450 } |
| 438 } | 451 } |
| 439 } | 452 } |
| 440 | 453 |
| 441 void doStartTest(TestCase testCase) { | 454 void doStartTest(TestCase testCase) { |
| 442 _startTime = new Date.now(); | 455 _startTime = new Date.now(); |
| 443 _testStdout = []; | 456 _testStdout = new List<String>(); |
| 444 _testStderr = []; | 457 _testStderr = new List<String>(); |
| 458 _stderrDrained = false; |
| 445 _stdoutStream.lineHandler = _readStdout(_stdoutStream, _testStdout); | 459 _stdoutStream.lineHandler = _readStdout(_stdoutStream, _testStdout); |
| 446 _stderrStream.lineHandler = _makeReadHandler(_stderrStream, _testStderr); | 460 _stderrStream.lineHandler = _readStderr(_stderrStream, _testStderr); |
| 447 _timer = new Timer(_timeoutHandler, testCase.timeout * 1000); | 461 _timer = new Timer(_timeoutHandler, testCase.timeout * 1000); |
| 448 var line = _createArgumentsLine(testCase.batchTestArguments); | 462 var line = _createArgumentsLine(testCase.batchTestArguments); |
| 449 _process.stdin.write(line.charCodes()); | 463 _process.stdin.write(line.charCodes()); |
| 450 } | 464 } |
| 451 | 465 |
| 452 String _createArgumentsLine(List<String> arguments) { | 466 String _createArgumentsLine(List<String> arguments) { |
| 453 return Strings.join(arguments, ' ') + '\n'; | 467 return Strings.join(arguments, ' ') + '\n'; |
| 454 } | 468 } |
| 455 | 469 |
| 456 // This removes the line handler from stderr and reads all | 470 void _testCompleted() { |
| 457 // remaining bytes from it. | |
| 458 // TODO(zundel): dart:io stream apis need flush()? issue 1407 | |
| 459 _drainStderr() { | |
| 460 _stderrStream.lineHandler = null; | |
| 461 while(true) { | |
| 462 var available = 0; | |
| 463 try { | |
| 464 available = _process.stderr.available(); | |
| 465 } catch (SocketIOException ex) { | |
| 466 break; | |
| 467 } | |
| 468 if (available <= 0) break; | |
| 469 String result = _stderrStream.readLine(); | |
| 470 if (result == null) { | |
| 471 // This is intended to catch the last line, but might foul up | |
| 472 // if more bytes immediately come available after read() and before | |
| 473 // the test for available() | |
| 474 result = _stderrStream.read(); | |
| 475 if (result == null) { | |
| 476 var buf = new List<int>(available); | |
| 477 _process.stderr.readInto(buf, 0, available); | |
| 478 result = new String.fromCharCodes(buf); | |
| 479 _testStderr.add(result); | |
| 480 break; | |
| 481 } | |
| 482 } | |
| 483 _testStderr.add(result); | |
| 484 } | |
| 485 } | |
| 486 | |
| 487 int _reportResult(String output) { | |
| 488 _drainStderr(); | |
| 489 var test = _currentTest; | 471 var test = _currentTest; |
| 490 _currentTest = null; | 472 _currentTest = null; |
| 473 test.completed(); |
| 474 } |
| 491 | 475 |
| 476 int _reportResult(String output) { |
| 492 // output = '>>> TEST {PASS, FAIL, OK, CRASH, FAIL, TIMEOUT}' | 477 // output = '>>> TEST {PASS, FAIL, OK, CRASH, FAIL, TIMEOUT}' |
| 493 var outcome = output.split(" ")[2]; | 478 var outcome = output.split(" ")[2]; |
| 494 var exitCode = 0; | 479 var exitCode = 0; |
| 495 if (outcome == "CRASH") exitCode = -10; | 480 if (outcome == "CRASH") exitCode = -10; |
| 496 if (outcome == "FAIL" || outcome == "TIMEOUT") exitCode = 1; | 481 if (outcome == "FAIL" || outcome == "TIMEOUT") exitCode = 1; |
| 497 new TestOutput(test, exitCode, outcome == "TIMEOUT", _testStdout, | 482 new TestOutput(_currentTest, exitCode, outcome == "TIMEOUT", _testStdout, |
| 498 _testStderr, new Date.now().difference(_startTime)); | 483 _testStderr, new Date.now().difference(_startTime)); |
| 499 test.completed(); | 484 // Move on when both stdout and stderr has been drained. |
| 485 if (_stderrDrained) _testCompleted(); |
| 486 } |
| 487 |
| 488 void _stderrDone() { |
| 489 _stderrDrained = true; |
| 490 // Move on when both stdout and stderr has been drained. |
| 491 if (_currentTest.output != null) _testCompleted(); |
| 500 } | 492 } |
| 501 | 493 |
| 502 Function _readStdout(StringInputStream stream, List<String> buffer) { | 494 Function _readStdout(StringInputStream stream, List<String> buffer) { |
| 503 return () { | 495 return () { |
| 504 var status; | 496 var status; |
| 505 if (stream.closed) return; // TODO(whesse): Remove when bug is fixed. | |
| 506 var line = stream.readLine(); | 497 var line = stream.readLine(); |
| 507 // Drain the input stream to get the error output. | |
| 508 while (line != null) { | 498 while (line != null) { |
| 509 if (line.startsWith('>>> TEST')) { | 499 if (line.startsWith('>>> TEST')) { |
| 510 status = line; | 500 status = line; |
| 511 } else if (line.startsWith('>>> BATCH START')) { | 501 } else if (line.startsWith('>>> BATCH START')) { |
| 512 // ignore | 502 // ignore |
| 513 } else if (line.startsWith('>>> ')) { | 503 } else if (line.startsWith('>>> ')) { |
| 514 throw new Exception('Unexpected command from dartc batch runner.'); | 504 throw new Exception('Unexpected command from dartc batch runner.'); |
| 515 } else { | 505 } else { |
| 516 buffer.add(line); | 506 buffer.add(line); |
| 517 } | 507 } |
| 518 line = stream.readLine(); | 508 line = stream.readLine(); |
| 519 } | 509 } |
| 520 if (status != null) { | 510 if (status != null) { |
| 521 _timer.cancel(); | 511 _timer.cancel(); |
| 522 // For crashing processes, let the exit handler deal with it. | 512 // For crashing processes, let the exit handler deal with it. |
| 523 if (!status.contains("CRASH")) { | 513 if (!status.contains("CRASH")) { |
| 524 _reportResult(status); | 514 _reportResult(status); |
| 525 } | 515 } |
| 526 } | 516 } |
| 527 }; | 517 }; |
| 528 } | 518 } |
| 529 | 519 |
| 520 Function _readStderr(StringInputStream stream, List<String> buffer) { |
| 521 return () { |
| 522 var line = stream.readLine(); |
| 523 while (line != null) { |
| 524 if (line.startsWith('>>> EOF STDERR')) { |
| 525 _stderrDone(); |
| 526 } else { |
| 527 buffer.add(line); |
| 528 } |
| 529 line = stream.readLine(); |
| 530 } |
| 531 }; |
| 532 } |
| 533 |
| 530 void _exitHandler(exitCode) { | 534 void _exitHandler(exitCode) { |
| 531 if (_timer != null) _timer.cancel(); | 535 if (_timer != null) _timer.cancel(); |
| 532 _reportResult(">>> TEST CRASH"); | |
| 533 _process.close(); | 536 _process.close(); |
| 534 _startProcess(); | 537 _startProcess(() { |
| 538 _reportResult(">>> TEST CRASH"); |
| 539 }); |
| 535 } | 540 } |
| 536 | 541 |
| 537 void _timeoutHandler(ignore) { | 542 void _timeoutHandler(ignore) { |
| 538 _process.exitHandler = (exitCode) {_ | 543 _process.exitHandler = (exitCode) { |
| 539 reportResult(">>> TEST TIMEOUT"); | |
| 540 _process.close(); | 544 _process.close(); |
| 541 _startProcess(); | 545 _startProcess(() { |
| 546 _reportResult(">>> TEST TIMEOUT"); |
| 547 }); |
| 542 }; | 548 }; |
| 543 _process.kill(); | 549 _process.kill(); |
| 544 } | 550 } |
| 545 | 551 |
| 546 void _startProcess([Function then = null]) { | 552 void _startProcess(then) { |
| 547 _process = new Process.start(_executable, _batchArguments); | 553 _process = new Process.start(_executable, _batchArguments); |
| 548 _stdoutStream = new StringInputStream(_process.stdout); | 554 _stdoutStream = new StringInputStream(_process.stdout); |
| 549 _stderrStream = new StringInputStream(_process.stderr); | 555 _stderrStream = new StringInputStream(_process.stderr); |
| 550 _testStdout = []; | 556 _testStdout = new List<String>(); |
| 551 _testStderr = []; | 557 _testStderr = new List<String>(); |
| 558 _stderrDrained = false; |
| 552 _stdoutStream.lineHandler = _readStdout(_stdoutStream, _testStdout); | 559 _stdoutStream.lineHandler = _readStdout(_stdoutStream, _testStdout); |
| 553 _stderrStream.lineHandler =_makeReadHandler(_stderrStream, _testStderr); | 560 _stderrStream.lineHandler = _readStderr(_stderrStream, _testStderr); |
| 554 _process.exitHandler = _exitHandler; | 561 _process.exitHandler = _exitHandler; |
| 555 _process.startHandler = then; | 562 _process.startHandler = then; |
| 556 } | 563 } |
| 557 } | 564 } |
| 558 | 565 |
| 559 /** | 566 /** |
| 560 * ProcessQueue is the master control class, responsible for running all | 567 * ProcessQueue is the master control class, responsible for running all |
| 561 * the tests in all the TestSuites that have been registered. It includes | 568 * the tests in all the TestSuites that have been registered. It includes |
| 562 * a rate-limited queue to run a limited number of tests in parallel, | 569 * a rate-limited queue to run a limited number of tests in parallel, |
| 563 * a ProgressIndicator which prints output when tests are started and | 570 * a ProgressIndicator which prints output when tests are started and |
| (...skipping 230 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 794 // the developer doesn't waste his or her time trying to fix a bunch of | 801 // the developer doesn't waste his or her time trying to fix a bunch of |
| 795 // tests that appear to be broken but were actually just flakes that | 802 // tests that appear to be broken but were actually just flakes that |
| 796 // didn't get retried because there had already been one failure. | 803 // didn't get retried because there had already been one failure. |
| 797 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; | 804 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; |
| 798 new RunningProcess(test, allowRetry, this).start(); | 805 new RunningProcess(test, allowRetry, this).start(); |
| 799 } | 806 } |
| 800 _numProcesses++; | 807 _numProcesses++; |
| 801 } | 808 } |
| 802 } | 809 } |
| 803 } | 810 } |
| 804 | |
| 805 Function _makeReadHandler(StringInputStream source, List<String> destination) { | |
| 806 return () { | |
| 807 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. | |
| 808 var line = source.readLine(); | |
| 809 while (null != line) { | |
| 810 destination.add(line); | |
| 811 line = source.readLine(); | |
| 812 } | |
| 813 }; | |
| 814 } | |
| 815 | |
| 816 | |
| OLD | NEW |