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 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
125 final component = configuration['component']; | 125 final component = configuration['component']; |
126 final mode = configuration['mode']; | 126 final mode = configuration['mode']; |
127 final arch = configuration['arch']; | 127 final arch = configuration['arch']; |
128 return "$component ${mode}_$arch"; | 128 return "$component ${mode}_$arch"; |
129 } | 129 } |
130 | 130 |
131 List<String> get batchRunnerArguments() => ['-batch']; | 131 List<String> get batchRunnerArguments() => ['-batch']; |
132 List<String> get batchTestArguments() => commands.last().arguments; | 132 List<String> get batchTestArguments() => commands.last().arguments; |
133 | 133 |
134 void completed() { completedHandler(this); } | 134 void completed() { completedHandler(this); } |
| 135 |
| 136 bool get usesWebDriver() => configuration['component'] == 'webdriver'; |
135 } | 137 } |
136 | 138 |
137 | 139 |
138 /** | 140 /** |
139 * BrowserTestCase has an extra compilation command that is run in a separate | 141 * BrowserTestCase has an extra compilation command that is run in a separate |
140 * process, before the regular test is run as in the base class [TestCase]. | 142 * process, before the regular test is run as in the base class [TestCase]. |
141 * If the compilation command fails, then the rest of the test is not run. | 143 * If the compilation command fails, then the rest of the test is not run. |
142 */ | 144 */ |
143 class BrowserTestCase extends TestCase { | 145 class BrowserTestCase extends TestCase { |
144 /** | 146 /** |
(...skipping 328 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
473 void testComplete(int exitCode) { | 475 void testComplete(int exitCode) { |
474 new TestOutput.fromCase(testCase, exitCode, timedOut, stdout, | 476 new TestOutput.fromCase(testCase, exitCode, timedOut, stdout, |
475 stderr, new Date.now().difference(startTime)); | 477 stderr, new Date.now().difference(startTime)); |
476 timeoutTimer.cancel(); | 478 timeoutTimer.cancel(); |
477 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { | 479 if (testCase.output.unexpectedOutput && testCase.configuration['verbose']) { |
478 print(testCase.displayName); | 480 print(testCase.displayName); |
479 for (var line in testCase.output.stderr) print(line); | 481 for (var line in testCase.output.stderr) print(line); |
480 for (var line in testCase.output.stdout) print(line); | 482 for (var line in testCase.output.stdout) print(line); |
481 } | 483 } |
482 if (allowRetries != null && allowRetries | 484 if (allowRetries != null && allowRetries |
483 && testCase.configuration['component'] == 'webdriver' && | 485 && testCase.usesWebDriver && testCase.output.unexpectedOutput |
484 testCase.output.unexpectedOutput && testCase.numRetries > 0) { | 486 && testCase.numRetries > 0) { |
485 // Selenium tests can be flaky. Try rerunning. | 487 // Selenium tests can be flaky. Try rerunning. |
486 testCase.output.requestRetry = true; | 488 testCase.output.requestRetry = true; |
487 } | 489 } |
488 if (testCase.output.requestRetry) { | 490 if (testCase.output.requestRetry) { |
489 testCase.output.requestRetry = false; | 491 testCase.output.requestRetry = false; |
490 this.timedOut = false; | 492 this.timedOut = false; |
491 testCase.dynamic.numRetries--; | 493 testCase.dynamic.numRetries--; |
492 print("Potential flake. Re-running ${testCase.displayName} " + | 494 print("Potential flake. Re-running ${testCase.displayName} " + |
493 "(${testCase.dynamic.numRetries} attempt(s) remains)"); | 495 "(${testCase.dynamic.numRetries} attempt(s) remains)"); |
494 this.start(); | 496 this.start(); |
(...skipping 12 matching lines...) Expand all Loading... |
507 int totalSteps = testCase.commands.length; | 509 int totalSteps = testCase.commands.length; |
508 String suffix =' (step $currentStep of $totalSteps)'; | 510 String suffix =' (step $currentStep of $totalSteps)'; |
509 if (currentStep == totalSteps) { // done with test command | 511 if (currentStep == totalSteps) { // done with test command |
510 testComplete(exitCode); | 512 testComplete(exitCode); |
511 } else if (exitCode != 0) { | 513 } else if (exitCode != 0) { |
512 stderr.add('test.dart: Compilation failed$suffix, exit code $exitCode\n'); | 514 stderr.add('test.dart: Compilation failed$suffix, exit code $exitCode\n'); |
513 testComplete(exitCode); | 515 testComplete(exitCode); |
514 } else { | 516 } else { |
515 stderr.add('test.dart: Compilion finished $suffix\n'); | 517 stderr.add('test.dart: Compilion finished $suffix\n'); |
516 stdout.add('test.dart: Compilion finished $suffix\n'); | 518 stdout.add('test.dart: Compilion finished $suffix\n'); |
517 if (currentStep == totalSteps - 1 | 519 if (currentStep == totalSteps - 1 && testCase.usesWebDriver && |
518 && testCase.configuration['component'] == 'webdriver') { | 520 !testCase.configuration['noBatch']) { |
519 // Note: processQueue will always be non-null for component == webdriver | 521 // Note: processQueue will always be non-null for component == webdriver |
520 // (It is only null for component == vm) | 522 // (It is only null for component == vm) |
521 processQueue._getBatchRunner(testCase).startTest(testCase); | 523 processQueue._getBatchRunner(testCase).startTest(testCase); |
522 } else { | 524 } else { |
523 runCommand(testCase.commands[currentStep++], stepExitHandler); | 525 runCommand(testCase.commands[currentStep++], stepExitHandler); |
524 } | 526 } |
525 } | 527 } |
526 } | 528 } |
527 | 529 |
528 Function makeReadHandler(StringInputStream source, List<String> destination) { | 530 Function makeReadHandler(StringInputStream source, List<String> destination) { |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
586 bool _stdoutDrained = false; | 588 bool _stdoutDrained = false; |
587 bool _stderrDrained = false; | 589 bool _stderrDrained = false; |
588 Date _startTime; | 590 Date _startTime; |
589 Timer _timer; | 591 Timer _timer; |
590 | 592 |
591 bool _isWebDriver; | 593 bool _isWebDriver; |
592 | 594 |
593 BatchRunnerProcess(TestCase testCase) { | 595 BatchRunnerProcess(TestCase testCase) { |
594 _executable = testCase.commands.last().executable; | 596 _executable = testCase.commands.last().executable; |
595 _batchArguments = testCase.batchRunnerArguments; | 597 _batchArguments = testCase.batchRunnerArguments; |
596 _isWebDriver = testCase.configuration['component'] == 'webdriver'; | 598 _isWebDriver = testCase.usesWebDriver; |
597 } | 599 } |
598 | 600 |
599 bool get active() => _currentTest != null; | 601 bool get active() => _currentTest != null; |
600 | 602 |
601 void startTest(TestCase testCase) { | 603 void startTest(TestCase testCase) { |
602 _currentTest = testCase; | 604 _currentTest = testCase; |
603 if (_process === null) { | 605 if (_process === null) { |
604 // Start process if not yet started. | 606 // Start process if not yet started. |
605 _executable = testCase.commands.last().executable; | 607 _executable = testCase.commands.last().executable; |
606 _startProcess(() { | 608 _startProcess(() { |
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
771 * | 773 * |
772 * Because multiple configurations may be run on each test suite, the | 774 * Because multiple configurations may be run on each test suite, the |
773 * ProcessQueue contains a cache in which a test suite may record information | 775 * ProcessQueue contains a cache in which a test suite may record information |
774 * about its list of tests, and may retrieve that information when it is called | 776 * about its list of tests, and may retrieve that information when it is called |
775 * upon to enqueue its tests again. | 777 * upon to enqueue its tests again. |
776 */ | 778 */ |
777 class ProcessQueue { | 779 class ProcessQueue { |
778 int _numProcesses = 0; | 780 int _numProcesses = 0; |
779 int _activeTestListers = 0; | 781 int _activeTestListers = 0; |
780 int _maxProcesses; | 782 int _maxProcesses; |
| 783 |
781 /** The number of tests we allow to actually fail before we stop retrying. */ | 784 /** The number of tests we allow to actually fail before we stop retrying. */ |
782 int _MAX_FAILED_NO_RETRY = 4; | 785 int _MAX_FAILED_NO_RETRY = 4; |
783 bool _verbose; | 786 bool _verbose; |
784 bool _listTests; | 787 bool _listTests; |
785 bool _keepGeneratedTests; | 788 bool _keepGeneratedTests; |
786 Function _enqueueMoreWork; | 789 Function _enqueueMoreWork; |
787 Queue<TestCase> _tests; | 790 Queue<TestCase> _tests; |
788 ProgressIndicator _progress; | 791 ProgressIndicator _progress; |
789 String _temporaryDirectory; | 792 String _temporaryDirectory; |
| 793 |
790 // For dartc/selenium batch processing we keep a list of batch processes. | 794 // For dartc/selenium batch processing we keep a list of batch processes. |
791 Map<String, List<BatchRunnerProcess>> _batchProcesses; | 795 Map<String, List<BatchRunnerProcess>> _batchProcesses; |
792 | 796 |
793 // Cache information about test cases per test suite. For multiple | 797 // Cache information about test cases per test suite. For multiple |
794 // configurations there is no need to repeatedly search the file | 798 // configurations there is no need to repeatedly search the file |
795 // system, generate tests, and search test files for options. | 799 // system, generate tests, and search test files for options. |
796 Map<String, List<TestInformation>> _testCache; | 800 Map<String, List<TestInformation>> _testCache; |
| 801 |
797 /** | 802 /** |
798 * String indicating the browser used to run the tests. Empty if no browser | 803 * String indicating the browser used to run the tests. Empty if no browser |
799 * used. | 804 * used. |
800 */ | 805 */ |
801 String browserUsed; | 806 String browserUsed = ''; |
| 807 |
| 808 /** |
| 809 * Process running the selenium server .jar (only used for Safari and Opera |
| 810 * tests.) |
| 811 */ |
| 812 Process _seleniumServer = null; |
| 813 |
| 814 /** True if we are in the process of starting the server. */ |
| 815 bool _startingServer = false; |
| 816 |
| 817 /** True if we find that there is already a selenium jar running. */ |
| 818 bool _seleniumAlreadyRunning = false; |
802 | 819 |
803 ProcessQueue(int this._maxProcesses, | 820 ProcessQueue(int this._maxProcesses, |
804 String progress, | 821 String progress, |
805 Date startTime, | 822 Date startTime, |
806 bool printTiming, | 823 bool printTiming, |
807 Function this._enqueueMoreWork, | 824 Function this._enqueueMoreWork, |
808 [bool this._verbose = false, | 825 [bool this._verbose = false, |
809 bool this._listTests = false, | 826 bool this._listTests = false, |
810 bool this._keepGeneratedTests = false]) | 827 bool this._keepGeneratedTests = false]) |
811 : _tests = new Queue<TestCase>(), | 828 : _tests = new Queue<TestCase>(), |
812 _progress = new ProgressIndicator.fromName(progress, | 829 _progress = new ProgressIndicator.fromName(progress, |
813 startTime, | 830 startTime, |
814 printTiming), | 831 printTiming), |
815 _batchProcesses = new Map<String, List<BatchRunnerProcess>>(), | 832 _batchProcesses = new Map<String, List<BatchRunnerProcess>>(), |
816 _testCache = new Map<String, List<TestInformation>>() { | 833 _testCache = new Map<String, List<TestInformation>>() { |
817 if (!_enqueueMoreWork(this)) _progress.allDone(); | 834 if (!_enqueueMoreWork(this)) _progress.allDone(); |
818 browserUsed = ''; | |
819 } | 835 } |
820 | 836 |
821 /** | 837 /** |
822 * Registers a TestSuite so that all of its tests will be run. | 838 * Registers a TestSuite so that all of its tests will be run. |
823 */ | 839 */ |
824 void addTestSuite(TestSuite testSuite) { | 840 void addTestSuite(TestSuite testSuite) { |
825 _activeTestListers++; | 841 _activeTestListers++; |
826 testSuite.forEachTest(_runTest, _testCache, globalTemporaryDirectory, | 842 testSuite.forEachTest(_runTest, _testCache, globalTemporaryDirectory, |
827 _testListerDone); | 843 _testListerDone); |
828 } | 844 } |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
884 } | 900 } |
885 } | 901 } |
886 | 902 |
887 /** | 903 /** |
888 * Perform any cleanup needed once all tests in a TestSuite have completed | 904 * Perform any cleanup needed once all tests in a TestSuite have completed |
889 * and notify our progress indicator that we are done. | 905 * and notify our progress indicator that we are done. |
890 */ | 906 */ |
891 void _cleanupAndMarkDone() { | 907 void _cleanupAndMarkDone() { |
892 if (browserUsed != '') { | 908 if (browserUsed != '') { |
893 killZombieBrowsers(); | 909 killZombieBrowsers(); |
| 910 if (_seleniumServer != null) { |
| 911 _seleniumServer.kill(); |
| 912 } |
894 } else { | 913 } else { |
895 _progress.allDone(); | 914 _progress.allDone(); |
896 } | 915 } |
897 } | 916 } |
898 | 917 |
899 void _checkDone() { | 918 void _checkDone() { |
900 // When there are no more active test listers ask for more work | 919 // When there are no more active test listers ask for more work |
901 // from process queue users. | 920 // from process queue users. |
902 if (_activeTestListers == 0 && !_enqueueMoreWork(this)) { | 921 if (_activeTestListers == 0 && !_enqueueMoreWork(this)) { |
903 _progress.allTestsKnown(); | 922 _progress.allTestsKnown(); |
(...skipping 18 matching lines...) Expand all Loading... |
922 } | 941 } |
923 } else { | 942 } else { |
924 print('\nDeletion of temp dir $_temporaryDirectory failed.'); | 943 print('\nDeletion of temp dir $_temporaryDirectory failed.'); |
925 } | 944 } |
926 _cleanupAndMarkDone(); | 945 _cleanupAndMarkDone(); |
927 }; | 946 }; |
928 } | 947 } |
929 } | 948 } |
930 } | 949 } |
931 } | 950 } |
| 951 |
| 952 /** |
| 953 * True if we are using a browser + platform combination that needs the |
| 954 * Selenium server jar. |
| 955 */ |
| 956 bool get _needsSelenium() => new Platform().operatingSystem() == 'macos' && |
| 957 browserUsed == 'safari'; |
| 958 |
| 959 /** True if the Selenium Server is ready to be used. */ |
| 960 bool get _isSeleniumAvailable() => _seleniumServer != null || |
| 961 _seleniumAlreadyRunning; |
| 962 |
| 963 /** |
| 964 * Restart all the processes that have been waiting/stopped for the server to |
| 965 * start up. If we just call this once we end up with a single-"threaded" run. |
| 966 */ |
| 967 void resumeTesting() { |
| 968 for (int i = 0; i < _maxProcesses; i++) _tryRunTest(); |
| 969 } |
| 970 |
| 971 /** Start the Selenium Server jar, if appropriate for this platform. */ |
| 972 void _ensureSeleniumServerRunning() { |
| 973 if (!_isSeleniumAvailable && !_startingServer) { |
| 974 _startingServer = true; |
| 975 |
| 976 // Check to see if the jar was already running before the program started. |
| 977 String cmd = 'ps'; |
| 978 var arg = ['aux']; |
| 979 if (new Platform().operatingSystem() == 'windows') { |
| 980 cmd = 'tasklist'; |
| 981 arg.add('/v'); |
| 982 } |
| 983 Process p = new Process.start(cmd, arg); |
| 984 final StringInputStream stdoutStringStream = |
| 985 new StringInputStream(p.stdout); |
| 986 stdoutStringStream.onLine = () { |
| 987 var line = stdoutStringStream.readLine(); |
| 988 while (null != line) { |
| 989 if (const RegExp(@".*selenium-server-standalone.*").hasMatch(line)) { |
| 990 _seleniumAlreadyRunning = true; |
| 991 resumeTesting(); |
| 992 } |
| 993 line = stdoutStringStream.readLine(); |
| 994 } |
| 995 if (!_isSeleniumAvailable) { |
| 996 _startSeleniumServer(); |
| 997 } |
| 998 }; |
| 999 } |
| 1000 } |
932 | 1001 |
933 void _runTest(TestCase test) { | 1002 void _runTest(TestCase test) { |
934 if (test.configuration['component'] == 'webdriver') { | 1003 if (test.usesWebDriver) { |
935 browserUsed = test.configuration['browser']; | 1004 browserUsed = test.configuration['browser']; |
| 1005 if (_needsSelenium) _ensureSeleniumServerRunning(); |
936 } | 1006 } |
937 _progress.testAdded(); | 1007 _progress.testAdded(); |
938 _tests.add(test); | 1008 _tests.add(test); |
939 _tryRunTest(); | 1009 _tryRunTest(); |
940 } | 1010 } |
941 | 1011 |
| 1012 /** |
| 1013 * Monitor the output of the Selenium server, to know when we are ready to |
| 1014 * begin running tests. |
| 1015 * source: Output(Stream) from the Java server. |
| 1016 */ |
| 1017 Function makeSeleniumServerHandler(StringInputStream source) { |
| 1018 return () { |
| 1019 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. |
| 1020 var line = source.readLine(); |
| 1021 while (null != line) { |
| 1022 if (const RegExp(@".*Started.*Server.*").hasMatch(line) || |
| 1023 const RegExp(@"Exception.*Selenium is already running.*").hasMatch( |
| 1024 line)) { |
| 1025 resumeTesting(); |
| 1026 } |
| 1027 line = source.readLine(); |
| 1028 } |
| 1029 }; |
| 1030 } |
| 1031 |
| 1032 /** |
| 1033 * For browser tests using Safari or Opera, we need to use the Selenium 1.0 |
| 1034 * Java server. |
| 1035 */ |
| 1036 void _startSeleniumServer() { |
| 1037 // Get the absolute path to the Selenium jar. |
| 1038 String filePath = new Options().script; |
| 1039 String pathSep = new Platform().pathSeparator(); |
| 1040 int index = filePath.lastIndexOf(pathSep); |
| 1041 filePath = filePath.substring(0, index) + '${pathSep}testing${pathSep}'; |
| 1042 var dir = new Directory(filePath); |
| 1043 dir.onFile = (String file) { |
| 1044 if (const RegExp(@"selenium-server-standalone-.*\.jar").hasMatch(file) |
| 1045 && _seleniumServer == null) { |
| 1046 _seleniumServer = new Process.start('java', ['-jar', file]); |
| 1047 // Heads up: there seems to an obscure data race of some form in |
| 1048 // the VM between launching the server process and launching the test |
| 1049 // tasks that disappears when you read IO (which is convenient, since |
| 1050 // that is our condition for knowing that the server is ready). |
| 1051 StringInputStream stdoutStringStream = |
| 1052 new StringInputStream(_seleniumServer.stdout); |
| 1053 StringInputStream stderrStringStream = |
| 1054 new StringInputStream(_seleniumServer.stderr); |
| 1055 stdoutStringStream.onLine = |
| 1056 makeSeleniumServerHandler(stdoutStringStream); |
| 1057 stderrStringStream.onLine = |
| 1058 makeSeleniumServerHandler(stderrStringStream); |
| 1059 } |
| 1060 }; |
| 1061 dir.list(); |
| 1062 } |
| 1063 |
942 void _terminateBatchRunners() { | 1064 void _terminateBatchRunners() { |
943 for (var runners in _batchProcesses.getValues()) { | 1065 for (var runners in _batchProcesses.getValues()) { |
944 for (var runner in runners) { | 1066 for (var runner in runners) { |
945 runner.terminate(); | 1067 runner.terminate(); |
946 } | 1068 } |
947 } | 1069 } |
948 } | 1070 } |
949 | 1071 |
950 BatchRunnerProcess _getBatchRunner(TestCase test) { | 1072 BatchRunnerProcess _getBatchRunner(TestCase test) { |
951 // Start batch processes if needed | 1073 // Start batch processes if needed |
(...skipping 10 matching lines...) Expand all Loading... |
962 for (var runner in runners) { | 1084 for (var runner in runners) { |
963 if (!runner.active) return runner; | 1085 if (!runner.active) return runner; |
964 } | 1086 } |
965 throw new Exception('Unable to find inactive batch runner.'); | 1087 throw new Exception('Unable to find inactive batch runner.'); |
966 } | 1088 } |
967 | 1089 |
968 void _tryRunTest() { | 1090 void _tryRunTest() { |
969 _checkDone(); | 1091 _checkDone(); |
970 if (_numProcesses < _maxProcesses && !_tests.isEmpty()) { | 1092 if (_numProcesses < _maxProcesses && !_tests.isEmpty()) { |
971 TestCase test = _tests.removeFirst(); | 1093 TestCase test = _tests.removeFirst(); |
972 if (_verbose) print(test.commands.last().commandLine); | |
973 if (_listTests) { | 1094 if (_listTests) { |
974 final String tab = '\t'; | 1095 final String tab = '\t'; |
975 String outcomes = | 1096 String outcomes = |
976 Strings.join(new List.from(test.expectedOutcomes), ','); | 1097 Strings.join(new List.from(test.expectedOutcomes), ','); |
977 print(test.displayName + tab + outcomes + tab + test.isNegative + | 1098 print(test.displayName + tab + outcomes + tab + test.isNegative + |
978 tab + Strings.join(test.commands.last().arguments, tab)); | 1099 tab + Strings.join(test.commands.last().arguments, tab)); |
979 return; | 1100 return; |
980 } | 1101 } |
| 1102 if (test.usesWebDriver && _needsSelenium && !_isSeleniumAvailable) { |
| 1103 // The server is not ready to run Selenium tests. Put the test back in |
| 1104 // the queue. |
| 1105 _tests.addFirst(test); |
| 1106 return; |
| 1107 } |
| 1108 if (_verbose) print(test.commands.last().commandLine); |
981 _progress.start(test); | 1109 _progress.start(test); |
982 Function oldCallback = test.completedHandler; | 1110 Function oldCallback = test.completedHandler; |
983 Function wrapper = (TestCase test_arg) { | 1111 Function wrapper = (TestCase test_arg) { |
984 _numProcesses--; | 1112 _numProcesses--; |
985 _progress.done(test_arg); | 1113 _progress.done(test_arg); |
986 _tryRunTest(); | 1114 _tryRunTest(); |
987 oldCallback(test_arg); | 1115 oldCallback(test_arg); |
988 }; | 1116 }; |
989 test.completedHandler = wrapper; | 1117 test.completedHandler = wrapper; |
990 if (test.configuration['component'] == 'dartc' && | 1118 if (test.configuration['component'] == 'dartc' && |
991 test.displayName != 'dartc/junit_tests') { | 1119 test.displayName != 'dartc/junit_tests') { |
992 _getBatchRunner(test).startTest(test); | 1120 _getBatchRunner(test).startTest(test); |
993 } else { | 1121 } else { |
994 // Once we've actually failed a test, technically, we wouldn't need to | 1122 // Once we've actually failed a test, technically, we wouldn't need to |
995 // bother retrying any subsequent tests since the bot is already red. | 1123 // bother retrying any subsequent tests since the bot is already red. |
996 // However, we continue to retry tests until we have actually failed | 1124 // However, we continue to retry tests until we have actually failed |
997 // four tests (arbitrarily chosen) for more debugable output, so that | 1125 // four tests (arbitrarily chosen) for more debugable output, so that |
998 // the developer doesn't waste his or her time trying to fix a bunch of | 1126 // the developer doesn't waste his or her time trying to fix a bunch of |
999 // tests that appear to be broken but were actually just flakes that | 1127 // tests that appear to be broken but were actually just flakes that |
1000 // didn't get retried because there had already been one failure. | 1128 // didn't get retried because there had already been one failure. |
1001 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; | 1129 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; |
1002 new RunningProcess(test, allowRetry, this).start(); | 1130 new RunningProcess(test, allowRetry, this).start(); |
1003 } | 1131 } |
1004 _numProcesses++; | 1132 _numProcesses++; |
1005 } | 1133 } |
1006 } | 1134 } |
1007 } | 1135 } |
OLD | NEW |