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 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
121 final component = configuration['component']; | 121 final component = configuration['component']; |
122 final mode = configuration['mode']; | 122 final mode = configuration['mode']; |
123 final arch = configuration['arch']; | 123 final arch = configuration['arch']; |
124 return "$component ${mode}_$arch"; | 124 return "$component ${mode}_$arch"; |
125 } | 125 } |
126 | 126 |
127 List<String> get batchRunnerArguments() => ['-batch']; | 127 List<String> get batchRunnerArguments() => ['-batch']; |
128 List<String> get batchTestArguments() => commands.last().arguments; | 128 List<String> get batchTestArguments() => commands.last().arguments; |
129 | 129 |
130 void completed() { completedHandler(this); } | 130 void completed() { completedHandler(this); } |
131 | |
132 bool get usesWebDriver() => configuration['component'] == 'webdriver'; | |
131 } | 133 } |
132 | 134 |
133 | 135 |
134 /** | 136 /** |
135 * BrowserTestCase has an extra compilation command that is run in a separate | 137 * BrowserTestCase has an extra compilation command that is run in a separate |
136 * process, before the regular test is run as in the base class [TestCase]. | 138 * process, before the regular test is run as in the base class [TestCase]. |
137 * If the compilation command fails, then the rest of the test is not run. | 139 * If the compilation command fails, then the rest of the test is not run. |
138 */ | 140 */ |
139 class BrowserTestCase extends TestCase { | 141 class BrowserTestCase extends TestCase { |
140 /** | 142 /** |
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
279 void testComplete(int exitCode) { | 281 void testComplete(int exitCode) { |
280 new TestOutput(testCase, exitCode, timedOut, stdout, | 282 new TestOutput(testCase, exitCode, timedOut, stdout, |
281 stderr, new Date.now().difference(startTime)); | 283 stderr, new Date.now().difference(startTime)); |
282 timeoutTimer.cancel(); | 284 timeoutTimer.cancel(); |
283 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { | 285 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { |
284 print(testCase.displayName); | 286 print(testCase.displayName); |
285 for (var line in testCase.output.stderr) print(line); | 287 for (var line in testCase.output.stderr) print(line); |
286 for (var line in testCase.output.stdout) print(line); | 288 for (var line in testCase.output.stdout) print(line); |
287 } | 289 } |
288 if (allowRetries != null && allowRetries | 290 if (allowRetries != null && allowRetries |
289 && testCase.configuration['component'] == 'webdriver' && | 291 && testCase.usesWebDriver && testCase.output.unexpectedOutput |
290 testCase.output.unexpectedOutput && testCase.numRetries > 0) { | 292 && testCase.numRetries > 0) { |
291 // Selenium tests can be flaky. Try rerunning. | 293 // Selenium tests can be flaky. Try rerunning. |
292 testCase.output.requestRetry = true; | 294 testCase.output.requestRetry = true; |
293 } | 295 } |
294 if (testCase.output.requestRetry) { | 296 if (testCase.output.requestRetry) { |
295 testCase.output.requestRetry = false; | 297 testCase.output.requestRetry = false; |
296 this.timedOut = false; | 298 this.timedOut = false; |
297 testCase.dynamic.numRetries--; | 299 testCase.dynamic.numRetries--; |
298 print("Potential flake. " + | 300 print("Potential flake. " + |
299 "Re-running ${testCase.displayName} " + | 301 "Re-running ${testCase.displayName} " + |
300 "(${testCase.dynamic.numRetries} attempt(s) remains)"); | 302 "(${testCase.dynamic.numRetries} attempt(s) remains)"); |
(...skipping 13 matching lines...) Expand all Loading... | |
314 int totalSteps = testCase.commands.length; | 316 int totalSteps = testCase.commands.length; |
315 String suffix =' (step $currentStep of $totalSteps)'; | 317 String suffix =' (step $currentStep of $totalSteps)'; |
316 if (currentStep == totalSteps) { // done with test command | 318 if (currentStep == totalSteps) { // done with test command |
317 testComplete(exitCode); | 319 testComplete(exitCode); |
318 } else if (exitCode != 0) { | 320 } else if (exitCode != 0) { |
319 stderr.add('test.dart: Compilation failed$suffix, exit code $exitCode\n'); | 321 stderr.add('test.dart: Compilation failed$suffix, exit code $exitCode\n'); |
320 testComplete(exitCode); | 322 testComplete(exitCode); |
321 } else { | 323 } else { |
322 stderr.add('test.dart: Compilion finished $suffix\n'); | 324 stderr.add('test.dart: Compilion finished $suffix\n'); |
323 stdout.add('test.dart: Compilion finished $suffix\n'); | 325 stdout.add('test.dart: Compilion finished $suffix\n'); |
324 if (currentStep == totalSteps - 1 | 326 if (currentStep == totalSteps - 1 && testCase.usesWebDriver && |
325 && testCase.configuration['component'] == 'webdriver') { | 327 !testCase.configuration['noBatch']) { |
326 // Note: processQueue will always be non-null for component == webdriver | 328 // Note: processQueue will always be non-null for component == webdriver |
327 // (It is only null for component == vm) | 329 // (It is only null for component == vm) |
328 processQueue._getBatchRunner(testCase).startTest(testCase); | 330 processQueue._getBatchRunner(testCase).startTest(testCase); |
329 } else { | 331 } else { |
330 runCommand(testCase.commands[currentStep++], stepExitHandler); | 332 runCommand(testCase.commands[currentStep++], stepExitHandler); |
331 } | 333 } |
332 } | 334 } |
333 } | 335 } |
334 | 336 |
335 Function makeReadHandler(StringInputStream source, List<String> destination) { | 337 Function makeReadHandler(StringInputStream source, List<String> destination) { |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
393 bool _stdoutDrained = false; | 395 bool _stdoutDrained = false; |
394 bool _stderrDrained = false; | 396 bool _stderrDrained = false; |
395 Date _startTime; | 397 Date _startTime; |
396 Timer _timer; | 398 Timer _timer; |
397 | 399 |
398 bool _isWebDriver; | 400 bool _isWebDriver; |
399 | 401 |
400 BatchRunnerProcess(TestCase testCase) { | 402 BatchRunnerProcess(TestCase testCase) { |
401 _executable = testCase.commands.last().executable; | 403 _executable = testCase.commands.last().executable; |
402 _batchArguments = testCase.batchRunnerArguments; | 404 _batchArguments = testCase.batchRunnerArguments; |
403 _isWebDriver = testCase.configuration['component'] == 'webdriver'; | 405 _isWebDriver = testCase.usesWebDriver; |
404 } | 406 } |
405 | 407 |
406 bool get active() => _currentTest != null; | 408 bool get active() => _currentTest != null; |
407 | 409 |
408 void startTest(TestCase testCase) { | 410 void startTest(TestCase testCase) { |
409 _currentTest = testCase; | 411 _currentTest = testCase; |
410 if (_process === null) { | 412 if (_process === null) { |
411 // Start process if not yet started. | 413 // Start process if not yet started. |
412 _executable = testCase.commands.last().executable; | 414 _executable = testCase.commands.last().executable; |
413 _startProcess(() { | 415 _startProcess(() { |
(...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
598 Map<String, List<BatchRunnerProcess>> _batchProcesses; | 600 Map<String, List<BatchRunnerProcess>> _batchProcesses; |
599 | 601 |
600 // Cache information about test cases per test suite. For multiple | 602 // Cache information about test cases per test suite. For multiple |
601 // configurations there is no need to repeatedly search the file | 603 // configurations there is no need to repeatedly search the file |
602 // system, generate tests, and search test files for options. | 604 // system, generate tests, and search test files for options. |
603 Map<String, List<TestInformation>> _testCache; | 605 Map<String, List<TestInformation>> _testCache; |
604 /** | 606 /** |
605 * String indicating the browser used to run the tests. Empty if no browser | 607 * String indicating the browser used to run the tests. Empty if no browser |
606 * used. | 608 * used. |
607 */ | 609 */ |
608 String browserUsed; | 610 String browserUsed = ''; |
611 /** | |
612 * Process running the selenium server .jar (only used for Safari and Opera | |
613 * tests.) | |
614 */ | |
615 Process _seleniumServer = null; | |
616 /** True if we are in the process of starting the server. */ | |
617 bool _startingServer = false; | |
618 /** True if we find that there is already a selenium jar running. */ | |
619 bool _seleniumAlreadyRunning = false; | |
609 | 620 |
610 ProcessQueue(int this._maxProcesses, | 621 ProcessQueue(int this._maxProcesses, |
611 String progress, | 622 String progress, |
612 Date startTime, | 623 Date startTime, |
613 bool printTiming, | 624 bool printTiming, |
614 Function this._enqueueMoreWork, | 625 Function this._enqueueMoreWork, |
615 [bool this._verbose = false, | 626 [bool this._verbose = false, |
616 bool this._listTests = false, | 627 bool this._listTests = false, |
617 bool this._keepGeneratedTests = false]) | 628 bool this._keepGeneratedTests = false]) |
618 : _tests = new Queue<TestCase>(), | 629 : _tests = new Queue<TestCase>(), |
619 _progress = new ProgressIndicator.fromName(progress, | 630 _progress = new ProgressIndicator.fromName(progress, |
620 startTime, | 631 startTime, |
621 printTiming), | 632 printTiming), |
622 _batchProcesses = new Map<String, List<BatchRunnerProcess>>(), | 633 _batchProcesses = new Map<String, List<BatchRunnerProcess>>(), |
623 _testCache = new Map<String, List<TestInformation>>() { | 634 _testCache = new Map<String, List<TestInformation>>() { |
624 if (!_enqueueMoreWork(this)) _progress.allDone(); | 635 if (!_enqueueMoreWork(this)) _progress.allDone(); |
625 browserUsed = ''; | |
626 } | 636 } |
627 | 637 |
628 /** | 638 /** |
629 * Registers a TestSuite so that all of its tests will be run. | 639 * Registers a TestSuite so that all of its tests will be run. |
630 */ | 640 */ |
631 void addTestSuite(TestSuite testSuite) { | 641 void addTestSuite(TestSuite testSuite) { |
632 _activeTestListers++; | 642 _activeTestListers++; |
633 testSuite.forEachTest(_runTest, _testCache, globalTemporaryDirectory, | 643 testSuite.forEachTest(_runTest, _testCache, globalTemporaryDirectory, |
634 _testListerDone); | 644 _testListerDone); |
635 } | 645 } |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
691 } | 701 } |
692 } | 702 } |
693 | 703 |
694 /** | 704 /** |
695 * Perform any cleanup needed once all tests in a TestSuite have completed | 705 * Perform any cleanup needed once all tests in a TestSuite have completed |
696 * and notify our progress indicator that we are done. | 706 * and notify our progress indicator that we are done. |
697 */ | 707 */ |
698 void _cleanupAndMarkDone() { | 708 void _cleanupAndMarkDone() { |
699 if (browserUsed != '') { | 709 if (browserUsed != '') { |
700 killZombieBrowsers(); | 710 killZombieBrowsers(); |
711 if (_seleniumServer != null) { | |
712 _seleniumServer.kill(); | |
713 } | |
701 } else { | 714 } else { |
702 _progress.allDone(); | 715 _progress.allDone(); |
703 } | 716 } |
704 } | 717 } |
705 | 718 |
706 void _checkDone() { | 719 void _checkDone() { |
707 // When there are no more active test listers ask for more work | 720 // When there are no more active test listers ask for more work |
708 // from process queue users. | 721 // from process queue users. |
709 if (_activeTestListers == 0 && !_enqueueMoreWork(this)) { | 722 if (_activeTestListers == 0 && !_enqueueMoreWork(this)) { |
710 _progress.allTestsKnown(); | 723 _progress.allTestsKnown(); |
(...skipping 18 matching lines...) Expand all Loading... | |
729 } | 742 } |
730 } else { | 743 } else { |
731 print('\nDeletion of temp dir $_temporaryDirectory failed.'); | 744 print('\nDeletion of temp dir $_temporaryDirectory failed.'); |
732 } | 745 } |
733 _cleanupAndMarkDone(); | 746 _cleanupAndMarkDone(); |
734 }; | 747 }; |
735 } | 748 } |
736 } | 749 } |
737 } | 750 } |
738 } | 751 } |
752 | |
753 /** | |
754 * True if we are using a browser + platform combination that needs the | |
755 * Selenium server jar. | |
756 */ | |
757 bool get _needsSelenium() => new Platform().operatingSystem() == 'macos' && | |
758 browserUsed == 'safari'; | |
759 | |
760 /** True if the Selenium Server is ready to be used. */ | |
761 bool get _isSeleniumAvailable() => _seleniumServer != null || | |
762 _seleniumAlreadyRunning; | |
763 /** | |
764 * Restart all the processes that have been waiting/stopped for the server to | |
765 * start up. If we just call this once we end up with a single-"threaded" run. | |
766 */ | |
767 void resumeTesting() { | |
768 for (int i = 0; i < _maxProcesses; i++) _tryRunTest(); | |
769 } | |
770 | |
771 /** Start the Selenium Server jar, if appropriate for this platform. */ | |
772 void _ensureSeleniumServerRunning() { | |
773 if (!_isSeleniumAvailable && !_startingServer) { | |
774 _startingServer = true; | |
775 | |
776 // Check to see if the jar was already running before the program started. | |
Emily Fortuna
2012/03/02 22:45:35
New stuff here: check if selenium is already runni
| |
777 String cmd = 'ps'; | |
778 var arg = []; | |
Siggi Cherem (dart-lang)
2012/03/02 23:38:39
arg = ['axu']
| |
779 if (new Platform().operatingSystem() == 'windows') { | |
780 cmd = 'tasklist'; | |
781 arg.add('/v'); | |
782 } | |
783 Process p = new Process.start(cmd, arg); | |
784 final StringInputStream stdoutStringStream = | |
785 new StringInputStream(p.stdout); | |
786 stdoutStringStream.onLine = () { | |
787 var line = stdoutStringStream.readLine(); | |
788 while (null != line) { | |
789 if (const RegExp(@".*selenium-server-standalone.*").hasMatch(line)) { | |
790 _seleniumAlreadyRunning = true; | |
791 resumeTesting(); | |
792 } | |
793 line = stdoutStringStream.readLine(); | |
794 } | |
795 if (!_isSeleniumAvailable) { | |
796 _startSeleniumServer(); | |
797 } | |
798 }; | |
799 } | |
800 } | |
739 | 801 |
740 void _runTest(TestCase test) { | 802 void _runTest(TestCase test) { |
741 if (test.configuration['component'] == 'webdriver') { | 803 if (test.usesWebDriver) { |
742 browserUsed = test.configuration['browser']; | 804 browserUsed = test.configuration['browser']; |
805 if (_needsSelenium) _ensureSeleniumServerRunning(); | |
743 } | 806 } |
744 _progress.testAdded(); | 807 _progress.testAdded(); |
745 _tests.add(test); | 808 _tests.add(test); |
746 _tryRunTest(); | 809 _tryRunTest(); |
747 } | 810 } |
748 | 811 |
812 /** | |
813 * Monitor the output of the Selenium server, to know when we are ready to | |
814 * begin running tests. | |
815 * source: Output(Stream) from the Java server. | |
816 */ | |
817 Function makeSeleniumServerHandler(StringInputStream source) { | |
818 return () { | |
819 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. | |
820 var line = source.readLine(); | |
821 while (null != line) { | |
822 if (const RegExp(@".*Started.*Server.*").hasMatch(line) || | |
823 const RegExp(@"Exception.*Selenium is already running.*").hasMatch( | |
824 line)) { | |
825 resumeTesting(); | |
826 } | |
827 line = source.readLine(); | |
828 } | |
829 }; | |
830 } | |
831 | |
832 /** | |
833 * For browser tests using Safari or Opera, we need to use the Selenium 1.0 | |
834 * Java server. | |
835 */ | |
836 void _startSeleniumServer() { | |
837 // Get the absolute path to the Selenium jar. | |
838 String filePath = new Options().script; | |
839 String pathSep = new Platform().pathSeparator(); | |
840 int index = filePath.lastIndexOf(pathSep); | |
841 filePath = filePath.substring(0, index) + '${pathSep}testing${pathSep}'; | |
842 var dir = new Directory(filePath); | |
843 dir.onFile = (String file) { | |
844 if (const RegExp(@"selenium-server-standalone-.*\.jar").hasMatch(file) | |
845 && _seleniumServer == null) { | |
846 _seleniumServer = new Process.start('java', ['-jar', file]); | |
847 // Heads up: there seems to an obscure data race of some form in | |
848 // the VM between launching the server process and launching the test | |
849 // tasks that disappears when you read IO (which is convenient, since | |
850 // that is our condition for knowing that the server is ready). | |
851 StringInputStream stdoutStringStream = | |
852 new StringInputStream(_seleniumServer.stdout); | |
853 StringInputStream stderrStringStream = | |
854 new StringInputStream(_seleniumServer.stderr); | |
855 stdoutStringStream.onLine = | |
856 makeSeleniumServerHandler(stdoutStringStream); | |
857 stderrStringStream.onLine = | |
858 makeSeleniumServerHandler(stderrStringStream); | |
859 } | |
860 }; | |
861 dir.list(); | |
862 } | |
863 | |
749 void _terminateBatchRunners() { | 864 void _terminateBatchRunners() { |
750 for (var runners in _batchProcesses.getValues()) { | 865 for (var runners in _batchProcesses.getValues()) { |
751 for (var runner in runners) { | 866 for (var runner in runners) { |
752 runner.terminate(); | 867 runner.terminate(); |
753 } | 868 } |
754 } | 869 } |
755 } | 870 } |
756 | 871 |
757 BatchRunnerProcess _getBatchRunner(TestCase test) { | 872 BatchRunnerProcess _getBatchRunner(TestCase test) { |
758 // Start batch processes if needed | 873 // Start batch processes if needed |
(...skipping 10 matching lines...) Expand all Loading... | |
769 for (var runner in runners) { | 884 for (var runner in runners) { |
770 if (!runner.active) return runner; | 885 if (!runner.active) return runner; |
771 } | 886 } |
772 throw new Exception('Unable to find inactive batch runner.'); | 887 throw new Exception('Unable to find inactive batch runner.'); |
773 } | 888 } |
774 | 889 |
775 void _tryRunTest() { | 890 void _tryRunTest() { |
776 _checkDone(); | 891 _checkDone(); |
777 if (_numProcesses < _maxProcesses && !_tests.isEmpty()) { | 892 if (_numProcesses < _maxProcesses && !_tests.isEmpty()) { |
778 TestCase test = _tests.removeFirst(); | 893 TestCase test = _tests.removeFirst(); |
779 if (_verbose) print(test.commands.last().commandLine); | |
780 if (_listTests) { | 894 if (_listTests) { |
781 final String tab = '\t'; | 895 final String tab = '\t'; |
782 String outcomes = | 896 String outcomes = |
783 Strings.join(new List.from(test.expectedOutcomes), ','); | 897 Strings.join(new List.from(test.expectedOutcomes), ','); |
784 print(test.displayName + tab + outcomes + tab + test.isNegative + | 898 print(test.displayName + tab + outcomes + tab + test.isNegative + |
785 tab + Strings.join(test.commands.last().arguments, tab)); | 899 tab + Strings.join(test.commands.last().arguments, tab)); |
786 return; | 900 return; |
787 } | 901 } |
902 if (test.usesWebDriver && _needsSelenium && !_isSeleniumAvailable) { | |
903 // The server is not ready to run Selenium tests. Put the test back in | |
904 // the queue. | |
905 _tests.addFirst(test); | |
906 return; | |
907 } | |
908 if (_verbose) print(test.commands.last().commandLine); | |
788 _progress.start(test); | 909 _progress.start(test); |
789 Function oldCallback = test.completedHandler; | 910 Function oldCallback = test.completedHandler; |
790 Function wrapper = (TestCase test_arg) { | 911 Function wrapper = (TestCase test_arg) { |
791 _numProcesses--; | 912 _numProcesses--; |
792 _progress.done(test_arg); | 913 _progress.done(test_arg); |
793 _tryRunTest(); | 914 _tryRunTest(); |
794 oldCallback(test_arg); | 915 oldCallback(test_arg); |
795 }; | 916 }; |
796 test.completedHandler = wrapper; | 917 test.completedHandler = wrapper; |
797 if (test.configuration['component'] == 'dartc' && | 918 if (test.configuration['component'] == 'dartc' && |
798 test.displayName != 'dartc/junit_tests') { | 919 test.displayName != 'dartc/junit_tests') { |
799 _getBatchRunner(test).startTest(test); | 920 _getBatchRunner(test).startTest(test); |
800 } else { | 921 } else { |
801 // Once we've actually failed a test, technically, we wouldn't need to | 922 // Once we've actually failed a test, technically, we wouldn't need to |
802 // bother retrying any subsequent tests since the bot is already red. | 923 // bother retrying any subsequent tests since the bot is already red. |
803 // However, we continue to retry tests until we have actually failed | 924 // However, we continue to retry tests until we have actually failed |
804 // four tests (arbitrarily chosen) for more debugable output, so that | 925 // four tests (arbitrarily chosen) for more debugable output, so that |
805 // the developer doesn't waste his or her time trying to fix a bunch of | 926 // the developer doesn't waste his or her time trying to fix a bunch of |
806 // tests that appear to be broken but were actually just flakes that | 927 // tests that appear to be broken but were actually just flakes that |
807 // didn't get retried because there had already been one failure. | 928 // didn't get retried because there had already been one failure. |
808 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; | 929 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; |
809 new RunningProcess(test, allowRetry, this).start(); | 930 new RunningProcess(test, allowRetry, this).start(); |
810 } | 931 } |
811 _numProcesses++; | 932 _numProcesses++; |
812 } | 933 } |
813 } | 934 } |
814 } | 935 } |
OLD | NEW |