| 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 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 111 class BrowserTestCase extends TestCase { | 111 class BrowserTestCase extends TestCase { |
| 112 /** | 112 /** |
| 113 * The executable that is run in a new process in the compilation phase. | 113 * The executable that is run in a new process in the compilation phase. |
| 114 */ | 114 */ |
| 115 String compilerPath; | 115 String compilerPath; |
| 116 /** | 116 /** |
| 117 * The arguments for the compilation command. | 117 * The arguments for the compilation command. |
| 118 */ | 118 */ |
| 119 List<String> compilerArguments; | 119 List<String> compilerArguments; |
| 120 /** | 120 /** |
| 121 * Indicates if this test is a rerun, to compensate for flaky browser tests. | 121 * Indicates the number of potential retries remaining, to compensate for |
| 122 * flaky browser tests. |
| 122 */ | 123 */ |
| 123 bool isRerun; | 124 bool numRetries; |
| 124 | 125 |
| 125 BrowserTestCase(displayName, | 126 BrowserTestCase(displayName, |
| 126 this.compilerPath, | 127 this.compilerPath, |
| 127 this.compilerArguments, | 128 this.compilerArguments, |
| 128 executablePath, | 129 executablePath, |
| 129 arguments, | 130 arguments, |
| 130 configuration, | 131 configuration, |
| 131 completedHandler, | 132 completedHandler, |
| 132 expectedOutcomes, | 133 expectedOutcomes, |
| 133 [isNegative = false]) : super(displayName, | 134 [isNegative = false]) : super(displayName, |
| 134 executablePath, | 135 executablePath, |
| 135 arguments, | 136 arguments, |
| 136 configuration, | 137 configuration, |
| 137 completedHandler, | 138 completedHandler, |
| 138 expectedOutcomes, | 139 expectedOutcomes, |
| 139 isNegative) { | 140 isNegative) { |
| 140 if (compilerPath != null) { | 141 if (compilerPath != null) { |
| 141 commandLine = 'execution command: $commandLine'; | 142 commandLine = 'execution command: $commandLine'; |
| 142 String compilationCommand = | 143 String compilationCommand = |
| 143 '$compilerPath ${Strings.join(compilerArguments, " ")}'; | 144 '$compilerPath ${Strings.join(compilerArguments, " ")}'; |
| 144 commandLine = 'compilation command: $compilationCommand\n$commandLine'; | 145 commandLine = 'compilation command: $compilationCommand\n$commandLine'; |
| 145 } | 146 } |
| 146 isRerun = false; | 147 numRetries = 2; // Allow two retries to compensate for flaky browser tests. |
| 147 } | 148 } |
| 148 } | 149 } |
| 149 | 150 |
| 150 | 151 |
| 151 /** | 152 /** |
| 152 * TestOutput records the output of a completed test: the process's exit code, | 153 * TestOutput records the output of a completed test: the process's exit code, |
| 153 * the standard output and standard error, whether the process timed out, and | 154 * the standard output and standard error, whether the process timed out, and |
| 154 * the time the process took to run. It also contains a pointer to the | 155 * the time the process took to run. It also contains a pointer to the |
| 155 * [TestCase] this is the output of. | 156 * [TestCase] this is the output of. |
| 156 */ | 157 */ |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 252 new TestOutput(testCase, exitCode, timedOut, stdout, | 253 new TestOutput(testCase, exitCode, timedOut, stdout, |
| 253 stderr, new Date.now().difference(startTime)); | 254 stderr, new Date.now().difference(startTime)); |
| 254 process.close(); | 255 process.close(); |
| 255 timeoutTimer.cancel(); | 256 timeoutTimer.cancel(); |
| 256 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { | 257 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { |
| 257 print(testCase.displayName); | 258 print(testCase.displayName); |
| 258 for (var line in testCase.output.stderr) print(line); | 259 for (var line in testCase.output.stderr) print(line); |
| 259 for (var line in testCase.output.stdout) print(line); | 260 for (var line in testCase.output.stdout) print(line); |
| 260 } | 261 } |
| 261 if (testCase is BrowserTestCase && testCase.output.unexpectedOutput && | 262 if (testCase is BrowserTestCase && testCase.output.unexpectedOutput && |
| 262 !testCase.isRerun) { | 263 testCase.numRetries > 0) { |
| 263 // Selenium tests can be flaky. Try rerunning. | 264 // Selenium tests can be flaky. Try rerunning. |
| 264 testCase.output.requestRetry = true; | 265 testCase.output.requestRetry = true; |
| 265 } | 266 } |
| 266 if (testCase.output.requestRetry) { | 267 if (testCase.output.requestRetry) { |
| 267 testCase.output.requestRetry = false; | 268 testCase.output.requestRetry = false; |
| 268 this.timedOut = false; | 269 this.timedOut = false; |
| 269 testCase.isRerun = true; | 270 testCase.dynamic.numRetries--; |
| 270 print("Potential flake. Re-running " + testCase.displayName); | 271 print("Potential flake. Re-running " + testCase.displayName); |
| 271 this.start(); | 272 this.start(); |
| 272 } else { | 273 } else { |
| 273 testCase.completed(); | 274 testCase.completed(); |
| 274 } | 275 } |
| 275 } | 276 } |
| 276 | 277 |
| 277 void compilerExitHandler(int exitCode) { | 278 void compilerExitHandler(int exitCode) { |
| 278 if (exitCode != 0) { | 279 if (exitCode != 0) { |
| 279 stderr.add('test.dart: Compilation step failed (exit code $exitCode)\n'); | 280 stderr.add('test.dart: Compilation step failed (exit code $exitCode)\n'); |
| (...skipping 223 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 503 Function _enqueueMoreWork; | 504 Function _enqueueMoreWork; |
| 504 Queue<TestCase> _tests; | 505 Queue<TestCase> _tests; |
| 505 ProgressIndicator _progress; | 506 ProgressIndicator _progress; |
| 506 String _temporaryDirectory; | 507 String _temporaryDirectory; |
| 507 // For dartc batch processing we keep a list of batch processes. | 508 // For dartc batch processing we keep a list of batch processes. |
| 508 List<DartcBatchRunnerProcess> _batchProcesses; | 509 List<DartcBatchRunnerProcess> _batchProcesses; |
| 509 // Cache information about test cases per test suite. For multiple | 510 // Cache information about test cases per test suite. For multiple |
| 510 // configurations there is no need to repeatedly search the file | 511 // configurations there is no need to repeatedly search the file |
| 511 // system, generate tests, and search test files for options. | 512 // system, generate tests, and search test files for options. |
| 512 Map<String, List<TestInformation>> _testCache; | 513 Map<String, List<TestInformation>> _testCache; |
| 514 /** |
| 515 * String indicating the browser used to run the tests. Empty if no browser |
| 516 * used. |
| 517 */ |
| 518 String browserUsed; |
| 513 | 519 |
| 514 ProcessQueue(int this._maxProcesses, | 520 ProcessQueue(int this._maxProcesses, |
| 515 String progress, | 521 String progress, |
| 516 Date startTime, | 522 Date startTime, |
| 517 bool printTiming, | 523 bool printTiming, |
| 518 Function this._enqueueMoreWork, | 524 Function this._enqueueMoreWork, |
| 519 [bool this._verbose = false, | 525 [bool this._verbose = false, |
| 520 bool this._listTests = false, | 526 bool this._listTests = false, |
| 521 bool this._keepGeneratedTests = false]) | 527 bool this._keepGeneratedTests = false]) |
| 522 : _tests = new Queue<TestCase>(), | 528 : _tests = new Queue<TestCase>(), |
| 523 _progress = new ProgressIndicator.fromName(progress, | 529 _progress = new ProgressIndicator.fromName(progress, |
| 524 startTime, | 530 startTime, |
| 525 printTiming), | 531 printTiming), |
| 526 _batchProcesses = new List<DartcBatchRunnerProcess>(), | 532 _batchProcesses = new List<DartcBatchRunnerProcess>(), |
| 527 _testCache = new Map<String, List<TestInformation>>() { | 533 _testCache = new Map<String, List<TestInformation>>() { |
| 528 if (!_enqueueMoreWork(this)) _progress.allDone(); | 534 if (!_enqueueMoreWork(this)) _progress.allDone(); |
| 535 browserUsed = ''; |
| 529 } | 536 } |
| 530 | 537 |
| 531 /** | 538 /** |
| 532 * Registers a TestSuite so that all of its tests will be run. | 539 * Registers a TestSuite so that all of its tests will be run. |
| 533 */ | 540 */ |
| 534 void addTestSuite(TestSuite testSuite) { | 541 void addTestSuite(TestSuite testSuite) { |
| 535 _activeTestListers++; | 542 _activeTestListers++; |
| 536 testSuite.forEachTest(_runTest, _testCache, globalTemporaryDirectory, | 543 testSuite.forEachTest(_runTest, _testCache, globalTemporaryDirectory, |
| 537 _testListerDone); | 544 _testListerDone); |
| 538 } | 545 } |
| 539 | 546 |
| 540 void _testListerDone() { | 547 void _testListerDone() { |
| 541 _activeTestListers--; | 548 _activeTestListers--; |
| 542 _checkDone(); | 549 _checkDone(); |
| 543 } | 550 } |
| 544 | 551 |
| 545 String globalTemporaryDirectory() { | 552 String globalTemporaryDirectory() { |
| 546 if (_temporaryDirectory != null) return _temporaryDirectory; | 553 if (_temporaryDirectory != null) return _temporaryDirectory; |
| 547 | 554 |
| 548 if (new Platform().operatingSystem() == 'windows') { | 555 if (new Platform().operatingSystem() == 'windows') { |
| 549 throw new Exception( | 556 throw new Exception( |
| 550 'Test suite requires temporary directory. Not supported on Windows.'); | 557 'Test suite requires temporary directory. Not supported on Windows.'); |
| 551 } | 558 } |
| 552 var tempDir = new Directory(''); | 559 var tempDir = new Directory(''); |
| 553 tempDir.createTempSync(); | 560 tempDir.createTempSync(); |
| 554 _temporaryDirectory = tempDir.path; | 561 _temporaryDirectory = tempDir.path; |
| 555 return _temporaryDirectory; | 562 return _temporaryDirectory; |
| 556 } | 563 } |
| 557 | 564 |
| 565 /** |
| 566 * Sometimes Webdriver doesn't close every browser window when it's done |
| 567 * with a test. At the end of all tests we clear out any neglected processes |
| 568 * that are still running. |
| 569 */ |
| 570 void killZombieBrowsers() { |
| 571 String chromeName = 'chrome'; |
| 572 if (new Platform().operatingSystem() == 'macos') { |
| 573 chromeName = 'Google\ Chrome'; |
| 574 } |
| 575 Map<String, List<String>> processNames = {'ie': ['iexplore'], 'safari': |
| 576 ['Safari'], 'ff': ['firefox'], 'chrome': ['chromedriver', chromeName]}; |
| 577 for (String name in processNames[browserUsed]) { |
| 578 Process process = null; |
| 579 if (new Platform().operatingSystem() == 'windows') { |
| 580 process = new Process.start( |
| 581 'C:\\Windows\\System32\\taskkill.exe', ['/F', '/IM', name + '.exe', |
| 582 '/T']); |
| 583 } else { |
| 584 process = new Process.start('killall', ['-9', name]); |
| 585 } |
| 586 |
| 587 if (name == processNames[browserUsed].last()) { |
| 588 process.exitHandler = (exitCode) { |
| 589 process.close(); |
| 590 _progress.allDone(); |
| 591 }; |
| 592 process.errorHandler = (error) { |
| 593 _progress.allDone(); |
| 594 }; |
| 595 } else { |
| 596 process.exitHandler = (exitCode) { |
| 597 process.close(); |
| 598 }; |
| 599 } |
| 600 } |
| 601 } |
| 602 |
| 603 /** |
| 604 * Perform any cleanup needed once all tests in a TestSuite have completed |
| 605 * and notify our progress indicator that we are done. |
| 606 */ |
| 607 void _cleanupAndMarkDone() { |
| 608 if (browserUsed != '') { |
| 609 killZombieBrowsers(); |
| 610 } else { |
| 611 _progress.allDone(); |
| 612 } |
| 613 } |
| 558 | 614 |
| 559 void _checkDone() { | 615 void _checkDone() { |
| 560 // When there are no more active test listers ask for more work | 616 // When there are no more active test listers ask for more work |
| 561 // from process queue users. | 617 // from process queue users. |
| 562 if (_activeTestListers == 0 && !_enqueueMoreWork(this)) { | 618 if (_activeTestListers == 0 && !_enqueueMoreWork(this)) { |
| 563 _progress.allTestsKnown(); | 619 _progress.allTestsKnown(); |
| 564 if (_tests.isEmpty() && _numProcesses == 0) { | 620 if (_tests.isEmpty() && _numProcesses == 0) { |
| 565 _terminateDartcBatchRunners(); | 621 _terminateDartcBatchRunners(); |
| 566 if (_keepGeneratedTests || _temporaryDirectory == null) { | 622 if (_keepGeneratedTests || _temporaryDirectory == null) { |
| 567 _progress.allDone(); | 623 _cleanupAndMarkDone(); |
| 568 } else if (!_temporaryDirectory.startsWith('/tmp/') || | 624 } else if (!_temporaryDirectory.startsWith('/tmp/') || |
| 569 _temporaryDirectory.contains('/../')) { | 625 _temporaryDirectory.contains('/../')) { |
| 570 // Let's be extra careful, since rm -rf is so dangerous. | 626 // Let's be extra careful, since rm -rf is so dangerous. |
| 571 print('Temporary directory $_temporaryDirectory unsafe to delete!'); | 627 print('Temporary directory $_temporaryDirectory unsafe to delete!'); |
| 572 _progress.allDone(); | 628 _cleanupAndMarkDone(); |
| 573 } else { | 629 } else { |
| 574 // TODO(dart:1211): Use delete(recursive=true) in Dart when it is | 630 // TODO(dart:1211): Use delete(recursive=true) in Dart when it is |
| 575 // implemented, and add Windows support. | 631 // implemented, and add Windows support. |
| 576 var deletion = | 632 var deletion = |
| 577 new Process.start('/bin/rm', ['-rf', _temporaryDirectory]); | 633 new Process.start('/bin/rm', ['-rf', _temporaryDirectory]); |
| 578 deletion.exitHandler = (int exitCode) { | 634 deletion.exitHandler = (int exitCode) { |
| 579 if (exitCode == 0) { | 635 if (exitCode == 0) { |
| 580 if (!_listTests) { // Output of --list option is used by scripts. | 636 if (!_listTests) { // Output of --list option is used by scripts. |
| 581 print('\nTemporary directory $_temporaryDirectory deleted.'); | 637 print('\nTemporary directory $_temporaryDirectory deleted.'); |
| 582 } | 638 } |
| 583 } else { | 639 } else { |
| 584 print('\nDeletion of temp dir $_temporaryDirectory failed.'); | 640 print('\nDeletion of temp dir $_temporaryDirectory failed.'); |
| 585 } | 641 } |
| 586 _progress.allDone(); | 642 _cleanupAndMarkDone(); |
| 587 }; | 643 }; |
| 588 } | 644 } |
| 589 } | 645 } |
| 590 } | 646 } |
| 591 } | 647 } |
| 592 | 648 |
| 593 void _runTest(TestCase test) { | 649 void _runTest(TestCase test) { |
| 650 if (test.configuration['component'] == 'webdriver') { |
| 651 browserUsed = test.configuration['browser']; |
| 652 } |
| 594 _progress.testAdded(); | 653 _progress.testAdded(); |
| 595 _tests.add(test); | 654 _tests.add(test); |
| 596 _tryRunTest(); | 655 _tryRunTest(); |
| 597 } | 656 } |
| 598 | 657 |
| 599 void _terminateDartcBatchRunners() { | 658 void _terminateDartcBatchRunners() { |
| 600 _batchProcesses.forEach((runner) => runner.terminate()); | 659 _batchProcesses.forEach((runner) => runner.terminate()); |
| 601 } | 660 } |
| 602 | 661 |
| 603 void _ensureDartcBatchRunnersStarted(String executable) { | 662 void _ensureDartcBatchRunnersStarted(String executable) { |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 642 test.displayName != 'dartc/junit_tests') { | 701 test.displayName != 'dartc/junit_tests') { |
| 643 _ensureDartcBatchRunnersStarted(test.executablePath); | 702 _ensureDartcBatchRunnersStarted(test.executablePath); |
| 644 _getDartcBatchRunnerProcess().startTest(test); | 703 _getDartcBatchRunnerProcess().startTest(test); |
| 645 } else { | 704 } else { |
| 646 new RunningProcess(test).start(); | 705 new RunningProcess(test).start(); |
| 647 } | 706 } |
| 648 _numProcesses++; | 707 _numProcesses++; |
| 649 } | 708 } |
| 650 } | 709 } |
| 651 } | 710 } |
| OLD | NEW |