Chromium Code Reviews| 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 907 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 918 * A RunningProcess actually runs a test, getting the command lines from | 918 * A RunningProcess actually runs a test, getting the command lines from |
| 919 * its [TestCase], starting the test process (and first, a compilation | 919 * its [TestCase], starting the test process (and first, a compilation |
| 920 * process if the TestCase is a [BrowserTestCase]), creating a timeout | 920 * process if the TestCase is a [BrowserTestCase]), creating a timeout |
| 921 * timer, and recording the results in a new [CommandOutput] object, which it | 921 * timer, and recording the results in a new [CommandOutput] object, which it |
| 922 * attaches to the TestCase. The lifetime of the RunningProcess is limited | 922 * attaches to the TestCase. The lifetime of the RunningProcess is limited |
| 923 * to the time it takes to start the process, run the process, and record | 923 * to the time it takes to start the process, run the process, and record |
| 924 * the result; there are no pointers to it, so it should be available to | 924 * the result; there are no pointers to it, so it should be available to |
| 925 * be garbage collected as soon as it is done. | 925 * be garbage collected as soon as it is done. |
| 926 */ | 926 */ |
| 927 class RunningProcess { | 927 class RunningProcess { |
| 928 ProcessQueue processQueue; | |
| 929 io.Process process; | |
| 930 TestCase testCase; | 928 TestCase testCase; |
| 929 Command command; | |
| 931 bool timedOut = false; | 930 bool timedOut = false; |
| 932 Date startTime; | 931 Date startTime; |
| 933 Timer timeoutTimer; | 932 Timer timeoutTimer; |
| 934 List<int> stdout; | 933 List<int> stdout = <int>[]; |
| 935 List<int> stderr; | 934 List<int> stderr = <int>[]; |
| 936 List<String> notifications; | 935 bool compilationSkipped = false; |
| 937 bool compilationSkipped; | 936 Completer<CommandOutput> completer; |
| 938 bool allowRetries; | |
| 939 | 937 |
| 940 /** Which command of [testCase.commands] is currently being executed. */ | 938 RunningProcess(TestCase this.testCase, Command this.command); |
| 941 int currentStep; | |
| 942 | 939 |
| 943 RunningProcess(TestCase this.testCase, | 940 Future<CommandOutput> start() { |
| 944 [this.allowRetries = false, this.processQueue]); | 941 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP)); |
| 945 | 942 |
| 946 /** | 943 completer = new Completer<CommandOutput>(); |
| 947 * Called when all commands are executed. | 944 startTime = new Date.now(); |
| 948 */ | 945 _runCommand(); |
| 949 void testComplete(CommandOutput lastCommandOutput) { | 946 return completer.future; |
| 950 var command = lastCommandOutput.command; | 947 } |
| 951 | 948 |
| 949 void _runCommand() { | |
| 950 command.outputIsUpToDate.then((bool isUpToDate) { | |
| 951 if (isUpToDate) { | |
| 952 compilationSkipped = true; | |
| 953 _commandComplete(0); | |
| 954 } else { | |
| 955 var processOptions = _createProcessOptions(); | |
| 956 Future processFuture = io.Process.start(command.executable, | |
| 957 command.arguments, | |
| 958 processOptions); | |
| 959 processFuture.then((io.Process process) { | |
| 960 void timeoutHandler(Timer unusedTimer) { | |
| 961 timedOut = true; | |
| 962 if (process != null) { | |
| 963 try { | |
| 964 process.kill(); | |
| 965 } on io.ProcessException { | |
| 966 // Hopefully, this means that the process died on its own. | |
| 967 } | |
| 968 } | |
| 969 } | |
| 970 process.onExit = _commandComplete; | |
| 971 _drainStream(process.stdout, stdout); | |
| 972 _drainStream(process.stderr, stderr); | |
| 973 timeoutTimer = new Timer(1000 * testCase.timeout, timeoutHandler); | |
| 974 }).catchError((e) { | |
| 975 print("Process error:"); | |
| 976 print(" Command: $command"); | |
| 977 print(" Error: $e"); | |
| 978 _commandComplete(-1); | |
| 979 return true; | |
| 980 }); | |
| 981 } | |
| 982 }); | |
| 983 } | |
| 984 | |
| 985 void _commandComplete(int exitCode) { | |
| 952 if (timeoutTimer != null) { | 986 if (timeoutTimer != null) { |
| 953 timeoutTimer.cancel(); | 987 timeoutTimer.cancel(); |
| 954 } | 988 } |
| 955 if (lastCommandOutput.unexpectedOutput | 989 var commandOutput = _createCommandOutput(command, exitCode); |
| 956 && testCase.configuration['verbose'] != null | 990 completer.complete(commandOutput); |
| 957 && testCase.configuration['verbose']) { | |
| 958 print(testCase.displayName); | |
| 959 | |
| 960 print(decodeUtf8(lastCommandOutput.stderr)); | |
| 961 if (!lastCommandOutput.command.isPixelTest) { | |
| 962 print(decodeUtf8(lastCommandOutput.stdout)); | |
| 963 } else { | |
| 964 print("DRT pixel test failed! stdout is not printed because it " | |
| 965 "contains binary data!"); | |
| 966 } | |
| 967 print(''); | |
| 968 if (notifications.length > 0) { | |
| 969 print("Notifications:"); | |
| 970 for (var line in notifications) { | |
| 971 print(notifications); | |
| 972 } | |
| 973 print(''); | |
| 974 } | |
| 975 } | |
| 976 if (allowRetries && testCase.usesWebDriver | |
| 977 && lastCommandOutput.unexpectedOutput | |
| 978 && (testCase as BrowserTestCase).numRetries > 0) { | |
| 979 // Selenium tests can be flaky. Try rerunning. | |
| 980 lastCommandOutput.requestRetry = true; | |
| 981 } | |
| 982 if (lastCommandOutput.requestRetry) { | |
| 983 lastCommandOutput.requestRetry = false; | |
| 984 this.timedOut = false; | |
| 985 (testCase as BrowserTestCase).numRetries--; | |
| 986 print("Potential flake. Re-running ${testCase.displayName} " | |
| 987 "(${(testCase as BrowserTestCase).numRetries} attempt(s) remains)"); | |
| 988 // When retrying we need to reset the timeout as well. | |
| 989 // Otherwise there will be no timeout handling for the retry. | |
| 990 timeoutTimer = null; | |
| 991 this.start(); | |
| 992 } else { | |
| 993 testCase.completed(); | |
| 994 } | |
| 995 } | 991 } |
| 996 | 992 |
| 997 /** | 993 CommandOutput _createCommandOutput(Command command, int exitCode) { |
| 998 * Process exit handler called at the end of every command. It internally | 994 var incomplete = command != testCase.commands.last; |
| 999 * treats all but the last command as compilation steps. The last command is | |
| 1000 * the actual test and its output is analyzed in [testComplete]. | |
| 1001 */ | |
| 1002 void commandComplete(Command command, int exitCode) { | |
| 1003 process = null; | |
| 1004 int totalSteps = testCase.commands.length; | |
| 1005 String suffix =' (step $currentStep of $totalSteps)'; | |
| 1006 if (timedOut) { | |
| 1007 // Non-webdriver test timed out before it could complete. Webdriver tests | |
| 1008 // run their own timeouts by timing from the launch of the browser (which | |
| 1009 // could be delayed). | |
| 1010 testComplete(createCommandOutput(command, 0, true)); | |
| 1011 } else if (currentStep == totalSteps) { | |
| 1012 // Done with all test commands. | |
| 1013 testComplete(createCommandOutput(command, exitCode, false)); | |
| 1014 } else if (exitCode != 0) { | |
| 1015 // One of the steps failed. | |
| 1016 notifications.add('test.dart: Compilation failed$suffix, ' | |
| 1017 'exit code $exitCode\n'); | |
| 1018 testComplete(createCommandOutput(command, exitCode, true)); | |
| 1019 } else { | |
| 1020 createCommandOutput(command, exitCode, true); | |
| 1021 // One compilation step successfully completed, move on to the | |
| 1022 // next step. | |
| 1023 notifications.add('test.dart: Compilation finished $suffix\n\n'); | |
| 1024 if (currentStep == totalSteps - 1 && testCase.usesWebDriver && | |
| 1025 !testCase.configuration['noBatch']) { | |
| 1026 // Note: processQueue will always be non-null for runtime == ie9, ie10, | |
| 1027 // ff, safari, chrome, opera. (It is only null for runtime == vm) | |
| 1028 // This RunningProcess object is done, and hands over control to | |
| 1029 // BatchRunner.startTest(), which handles reporting, etc. | |
| 1030 if (timeoutTimer != null) { | |
| 1031 timeoutTimer.cancel(); | |
| 1032 } | |
| 1033 processQueue._getBatchRunner(testCase).startTest(testCase); | |
| 1034 } else { | |
| 1035 runCommand(testCase.commands[currentStep++], commandComplete); | |
| 1036 } | |
| 1037 } | |
| 1038 } | |
| 1039 | |
| 1040 /** | |
| 1041 * Called for all executed commands. | |
| 1042 */ | |
| 1043 CommandOutput createCommandOutput(Command command, | |
| 1044 int exitCode, | |
| 1045 bool incomplete) { | |
| 1046 var commandOutput = new CommandOutput.fromCase( | 995 var commandOutput = new CommandOutput.fromCase( |
| 1047 testCase, | 996 testCase, |
| 1048 command, | 997 command, |
| 1049 exitCode, | 998 exitCode, |
| 1050 incomplete, | 999 incomplete, |
| 1051 timedOut, | 1000 timedOut, |
| 1052 stdout, | 1001 stdout, |
| 1053 stderr, | 1002 stderr, |
| 1054 new Date.now().difference(startTime), | 1003 new Date.now().difference(startTime), |
| 1055 compilationSkipped); | 1004 compilationSkipped); |
| 1056 resetLocalOutputInformation(); | |
| 1057 return commandOutput; | 1005 return commandOutput; |
| 1058 } | 1006 } |
| 1059 | 1007 |
| 1060 void resetLocalOutputInformation() { | 1008 void _drainStream(io.InputStream source, List<int> destination) { |
| 1061 stdout = new List<int>(); | |
| 1062 stderr = new List<int>(); | |
| 1063 notifications = new List<String>(); | |
| 1064 compilationSkipped = false; | |
| 1065 } | |
| 1066 | |
| 1067 void drainStream(io.InputStream source, List<int> destination) { | |
| 1068 void onDataHandler () { | 1009 void onDataHandler () { |
| 1069 if (source.closed) { | 1010 if (source.closed) { |
| 1070 return; // TODO(whesse): Remove when bug is fixed. | 1011 return; // TODO(whesse): Remove when bug is fixed. |
| 1071 } | 1012 } |
| 1072 var data = source.read(); | 1013 var data = source.read(); |
| 1073 while (data != null) { | 1014 while (data != null) { |
| 1074 destination.addAll(data); | 1015 destination.addAll(data); |
| 1075 data = source.read(); | 1016 data = source.read(); |
| 1076 } | 1017 } |
| 1077 } | 1018 } |
| 1078 source.onData = onDataHandler; | 1019 source.onData = onDataHandler; |
| 1079 source.onClosed = onDataHandler; | 1020 source.onClosed = onDataHandler; |
| 1080 } | 1021 } |
| 1081 | 1022 |
| 1082 void start() { | 1023 io.ProcessOptions _createProcessOptions() { |
| 1083 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP)); | 1024 var baseEnvironment = command.environment != null ? |
| 1084 resetLocalOutputInformation(); | 1025 command.environment : io.Platform.environment; |
| 1085 currentStep = 0; | 1026 io.ProcessOptions options = new io.ProcessOptions(); |
| 1086 startTime = new Date.now(); | 1027 options.environment = new Map<String, String>.from(baseEnvironment); |
| 1087 runCommand(testCase.commands[currentStep++], commandComplete); | 1028 options.environment['DART_CONFIGURATION'] = |
| 1088 } | 1029 TestUtils.configurationDir(testCase.configuration); |
| 1089 | 1030 return options; |
| 1090 void runCommand(Command command, void commandCompleteHandler(Command, int)) { | |
| 1091 void processExitHandler(int returnCode) { | |
| 1092 commandCompleteHandler(command, returnCode); | |
| 1093 } | |
| 1094 | |
| 1095 command.outputIsUpToDate.then((bool isUpToDate) { | |
| 1096 if (isUpToDate) { | |
| 1097 notifications.add("Skipped compilation because the old output is " | |
| 1098 "still up to date!"); | |
| 1099 compilationSkipped = true; | |
| 1100 commandComplete(command, 0); | |
| 1101 } else { | |
| 1102 io.ProcessOptions options = new io.ProcessOptions(); | |
| 1103 if (command.environment != null) { | |
| 1104 options.environment = | |
| 1105 new Map<String, String>.from(command.environment); | |
| 1106 } else { | |
| 1107 options.environment = | |
| 1108 new Map<String, String>.from(io.Platform.environment); | |
| 1109 } | |
| 1110 | |
| 1111 options.environment['DART_CONFIGURATION'] = | |
| 1112 TestUtils.configurationDir(testCase.configuration); | |
| 1113 Future processFuture = io.Process.start(command.executable, | |
| 1114 command.arguments, | |
| 1115 options); | |
| 1116 processFuture.then((io.Process p) { | |
| 1117 process = p; | |
| 1118 process.onExit = processExitHandler; | |
| 1119 drainStream(process.stdout, stdout); | |
| 1120 drainStream(process.stderr, stderr); | |
| 1121 if (timeoutTimer == null) { | |
| 1122 // Create one timeout timer when starting test case, remove it at | |
| 1123 // the end. | |
| 1124 timeoutTimer = new Timer(1000 * testCase.timeout, timeoutHandler); | |
| 1125 } | |
| 1126 // If the timeout fired in between two commands, kill the just | |
| 1127 // started process immediately. | |
| 1128 if (timedOut) safeKill(process); | |
| 1129 }).catchError((e) { | |
| 1130 print("Process error:"); | |
| 1131 print(" Command: $command"); | |
| 1132 print(" Error: $e"); | |
| 1133 testComplete(createCommandOutput(command, -1, false)); | |
| 1134 return true; | |
| 1135 }); | |
| 1136 } | |
| 1137 }); | |
| 1138 } | |
| 1139 | |
| 1140 void timeoutHandler(Timer unusedTimer) { | |
| 1141 timedOut = true; | |
| 1142 safeKill(process); | |
| 1143 } | |
| 1144 | |
| 1145 void safeKill(io.Process p) { | |
| 1146 if (p != null) { | |
| 1147 try { | |
| 1148 p.kill(); | |
| 1149 } on io.ProcessException { | |
| 1150 // Hopefully, this means that the process died on its own. | |
| 1151 } | |
| 1152 } | |
| 1153 } | 1031 } |
| 1154 } | 1032 } |
| 1155 | 1033 |
| 1156 /** | 1034 /** |
| 1157 * This class holds a value, that can be changed. It is used when | 1035 * This class holds a value, that can be changed. It is used when |
| 1158 * closures need a shared value, that they can all change and read. | 1036 * closures need a shared value, that they can all change and read. |
| 1159 */ | 1037 */ |
| 1160 class MutableValue<T> { | 1038 class MutableValue<T> { |
| 1161 MutableValue(T this.value); | 1039 MutableValue(T this.value); |
| 1162 T value; | 1040 T value; |
| (...skipping 264 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1427 * have completed. | 1305 * have completed. |
| 1428 * | 1306 * |
| 1429 * Because multiple configurations may be run on each test suite, the | 1307 * Because multiple configurations may be run on each test suite, the |
| 1430 * ProcessQueue contains a cache in which a test suite may record information | 1308 * ProcessQueue contains a cache in which a test suite may record information |
| 1431 * about its list of tests, and may retrieve that information when it is called | 1309 * about its list of tests, and may retrieve that information when it is called |
| 1432 * upon to enqueue its tests again. | 1310 * upon to enqueue its tests again. |
| 1433 */ | 1311 */ |
| 1434 class ProcessQueue { | 1312 class ProcessQueue { |
| 1435 int _numProcesses = 0; | 1313 int _numProcesses = 0; |
| 1436 int _maxProcesses; | 1314 int _maxProcesses; |
| 1315 int _numBrowserProcesses = 0; | |
|
Mads Ager (google)
2013/01/29 10:47:48
Maybe split this into numIEProcesses and a utility
kustermann
2013/01/29 12:10:49
I could make "_maxBrowserProcesses" a map from bro
| |
| 1316 int _maxBrowserProcesses; | |
| 1437 bool _allTestsWereEnqueued = false; | 1317 bool _allTestsWereEnqueued = false; |
| 1438 | 1318 |
| 1439 /** The number of tests we allow to actually fail before we stop retrying. */ | 1319 /** The number of tests we allow to actually fail before we stop retrying. */ |
| 1440 int _MAX_FAILED_NO_RETRY = 4; | 1320 int _MAX_FAILED_NO_RETRY = 4; |
| 1441 bool _verbose; | 1321 bool _verbose; |
| 1442 bool _listTests; | 1322 bool _listTests; |
| 1443 Function _allDone; | 1323 Function _allDone; |
| 1444 Queue<TestCase> _tests; | 1324 Queue<TestCase> _tests; |
| 1445 ProgressIndicator _progress; | 1325 ProgressIndicator _progress; |
| 1446 | 1326 |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 1463 * tests.) | 1343 * tests.) |
| 1464 */ | 1344 */ |
| 1465 io.Process _seleniumServer = null; | 1345 io.Process _seleniumServer = null; |
| 1466 | 1346 |
| 1467 /** True if we are in the process of starting the server. */ | 1347 /** True if we are in the process of starting the server. */ |
| 1468 bool _startingServer = false; | 1348 bool _startingServer = false; |
| 1469 | 1349 |
| 1470 /** True if we find that there is already a selenium jar running. */ | 1350 /** True if we find that there is already a selenium jar running. */ |
| 1471 bool _seleniumAlreadyRunning = false; | 1351 bool _seleniumAlreadyRunning = false; |
| 1472 | 1352 |
| 1473 ProcessQueue(int this._maxProcesses, | 1353 ProcessQueue(this._maxProcesses, |
| 1354 this._maxBrowserProcesses, | |
| 1474 String progress, | 1355 String progress, |
| 1475 Date startTime, | 1356 Date startTime, |
| 1476 bool printTiming, | 1357 bool printTiming, |
| 1477 testSuites, | 1358 testSuites, |
| 1478 this._allDone, | 1359 this._allDone, |
| 1479 [bool verbose = false, | 1360 [bool verbose = false, |
| 1480 bool listTests = false]) | 1361 bool listTests = false]) |
| 1481 : _verbose = verbose, | 1362 : _verbose = verbose, |
| 1482 _listTests = listTests, | 1363 _listTests = listTests, |
| 1483 _tests = new Queue<TestCase>(), | 1364 _tests = new Queue<TestCase>(), |
| (...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1708 // the queue. Avoid spin-polling by using a timeout. | 1589 // the queue. Avoid spin-polling by using a timeout. |
| 1709 _tests.add(test); | 1590 _tests.add(test); |
| 1710 new Timer(100, (timer) {_tryRunTest();}); // Don't lose a process. | 1591 new Timer(100, (timer) {_tryRunTest();}); // Don't lose a process. |
| 1711 return; | 1592 return; |
| 1712 } | 1593 } |
| 1713 if (_verbose) { | 1594 if (_verbose) { |
| 1714 int i = 1; | 1595 int i = 1; |
| 1715 if (test is BrowserTestCase) { | 1596 if (test is BrowserTestCase) { |
| 1716 // Additional command for rerunning the steps locally after the fact. | 1597 // Additional command for rerunning the steps locally after the fact. |
| 1717 print('$i. ${TestUtils.dartTestExecutable.toNativePath()} ' | 1598 print('$i. ${TestUtils.dartTestExecutable.toNativePath()} ' |
| 1718 '${TestUtils.dartDir().toNativePath()}/tools/testing/dart/' | 1599 '${TestUtils.dartDir().toNativePath()}/tools/testing/dart/' |
| 1719 'http_server.dart -m ${test.configuration["mode"]} ' | 1600 'http_server.dart -m ${test.configuration["mode"]} ' |
| 1720 '-a ${test.configuration["arch"]} ' | 1601 '-a ${test.configuration["arch"]} ' |
| 1721 '-p ${http_server.TestingServerRunner.serverList[0].port} ' | 1602 '-p ${http_server.TestingServerRunner.serverList[0].port} ' |
| 1722 '-c ${http_server.TestingServerRunner.serverList[1].port}'); | 1603 '-c ${http_server.TestingServerRunner.serverList[1].port}'); |
| 1723 i++; | 1604 i++; |
| 1724 } | 1605 } |
| 1725 for (Command command in test.commands) { | 1606 for (Command command in test.commands) { |
| 1726 print('$i. ${command.commandLine}'); | 1607 print('$i. ${command.commandLine}'); |
| 1727 i++; | 1608 i++; |
| 1728 } | 1609 } |
| 1729 } | 1610 } |
| 1611 | |
| 1612 var isLastCommand = | |
| 1613 ((test.commands.length-1) == test.commandOutputs.length); | |
| 1614 var isBrowserCommand = isLastCommand && (test is BrowserTestCase); | |
| 1615 if (isBrowserCommand && _numBrowserProcesses == _maxBrowserProcesses) { | |
| 1616 // If there is no free browser runner, put it back into the queue. | |
| 1617 _tests.add(test); | |
| 1618 new Timer(100, (timer) {_tryRunTest();}); // Don't lose a process. | |
|
Mads Ager (google)
2013/01/29 10:47:48
Is there a reason for waiting 100ms for this? Woul
kustermann
2013/01/29 12:10:49
Yes there is a reason for that: Let's assume that
| |
| 1619 | |
| 1620 return; | |
| 1621 } | |
| 1622 | |
| 1730 _progress.start(test); | 1623 _progress.start(test); |
| 1731 TestCaseEvent oldCallback = test.completedHandler; | |
| 1732 void wrapper(TestCase test_arg) { | |
| 1733 _numProcesses--; | |
| 1734 _progress.done(test_arg); | |
| 1735 if (test_arg is BrowserTestCase) test_arg.notifyObservers(); | |
| 1736 _tryRunTest(); | |
| 1737 oldCallback(test_arg); | |
| 1738 }; | |
| 1739 test.completedHandler = wrapper; | |
| 1740 | 1624 |
| 1741 if ((test.configuration['compiler'] == 'dartc' && | 1625 // Dartc and browser test commands can be run by a [BatchRunnerProcess] |
| 1742 test.displayName != 'dartc/junit_tests') || | 1626 var nextCommandIndex = test.commandOutputs.keys.length; |
| 1743 (test.commands.length == 1 && test.usesWebDriver && | 1627 var numberOfCommands = test.commands.length; |
| 1744 !test.configuration['noBatch'])) { | 1628 var useBatchRunnerForDartc = test.configuration['compiler'] == 'dartc' && |
| 1745 // Dartc and browser test cases that do not require a precompilation | 1629 test.displayName != 'dartc/junit_tests'; |
| 1746 // step, start with the batch runner right away. | 1630 var isWebdriverCommand = nextCommandIndex == (numberOfCommands - 1) && |
| 1631 test.usesWebDriver && | |
| 1632 !test.configuration['noBatch']; | |
| 1633 if (useBatchRunnerForDartc || isWebdriverCommand) { | |
| 1634 TestCaseEvent oldCallback = test.completedHandler; | |
| 1635 void wrapper(TestCase test_arg) { | |
| 1636 _numProcesses--; | |
| 1637 if (isBrowserCommand) { | |
| 1638 _numBrowserProcesses--; | |
| 1639 } | |
| 1640 _progress.done(test_arg); | |
| 1641 if (test_arg is BrowserTestCase) test_arg.notifyObservers(); | |
| 1642 oldCallback(test_arg); | |
| 1643 _tryRunTest(); | |
| 1644 }; | |
| 1645 test.completedHandler = wrapper; | |
| 1747 _getBatchRunner(test).startTest(test); | 1646 _getBatchRunner(test).startTest(test); |
| 1748 } else { | 1647 } else { |
| 1749 // Once we've actually failed a test, technically, we wouldn't need to | 1648 // Once we've actually failed a test, technically, we wouldn't need to |
| 1750 // bother retrying any subsequent tests since the bot is already red. | 1649 // bother retrying any subsequent tests since the bot is already red. |
| 1751 // However, we continue to retry tests until we have actually failed | 1650 // However, we continue to retry tests until we have actually failed |
| 1752 // four tests (arbitrarily chosen) for more debugable output, so that | 1651 // four tests (arbitrarily chosen) for more debugable output, so that |
| 1753 // the developer doesn't waste his or her time trying to fix a bunch of | 1652 // the developer doesn't waste his or her time trying to fix a bunch of |
| 1754 // tests that appear to be broken but were actually just flakes that | 1653 // tests that appear to be broken but were actually just flakes that |
| 1755 // didn't get retried because there had already been one failure. | 1654 // didn't get retried because there had already been one failure. |
| 1756 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; | 1655 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; |
| 1757 new RunningProcess(test, allowRetry, this).start(); | 1656 runNextCommandWithRetries(test, allowRetry).then((TestCase testCase) { |
| 1657 _numProcesses--; | |
| 1658 if (isBrowserCommand) { | |
| 1659 _numBrowserProcesses--; | |
| 1660 } | |
| 1661 if (isTestCaseFinished(testCase)) { | |
| 1662 testCase.completed(); | |
| 1663 _progress.done(testCase); | |
| 1664 if (testCase is BrowserTestCase) testCase.notifyObservers(); | |
| 1665 } else { | |
| 1666 _tests.add(testCase); | |
| 1667 } | |
| 1668 _tryRunTest(); | |
| 1669 }); | |
| 1758 } | 1670 } |
| 1671 | |
| 1759 _numProcesses++; | 1672 _numProcesses++; |
| 1673 if (isBrowserCommand) { | |
| 1674 _numBrowserProcesses++; | |
| 1675 } | |
| 1760 } | 1676 } |
| 1761 } | 1677 } |
| 1678 | |
| 1679 bool isTestCaseFinished(TestCase testCase) { | |
| 1680 var numberOfCommandOutputs = testCase.commandOutputs.keys.length; | |
| 1681 var numberOfCommands = testCase.commands.length; | |
| 1682 | |
| 1683 var lastCommandCompleted = (numberOfCommandOutputs == numberOfCommands); | |
| 1684 var lastCommandOutput = testCase.lastCommandOutput; | |
| 1685 var lastCommand = lastCommandOutput.command; | |
| 1686 var timedOut = lastCommandOutput.hasTimedOut; | |
| 1687 var nonZeroExitCode = lastCommandOutput.exitCode != 0; | |
| 1688 // NOTE: If this was the last command or there was unexpected output | |
| 1689 // we're done with the test. | |
| 1690 // Otherwise we need to enqueue it again into the test queue. | |
| 1691 if (lastCommandCompleted || timedOut || nonZeroExitCode) { | |
| 1692 var verbose = testCase.configuration['verbose']; | |
| 1693 if (nonZeroExitCode && verbose != null && verbose) { | |
| 1694 print(testCase.displayName); | |
| 1695 print("stderr:"); | |
| 1696 print(decodeUtf8(lastCommandOutput.stderr)); | |
| 1697 if (!lastCommand.isPixelTest) { | |
| 1698 print("stdout:"); | |
| 1699 print(decodeUtf8(lastCommandOutput.stdout)); | |
| 1700 } else { | |
| 1701 print(""); | |
| 1702 print("DRT pixel test failed! stdout is not printed because it " | |
| 1703 "contains binary data!"); | |
| 1704 } | |
| 1705 } | |
| 1706 return true; | |
| 1707 } else { | |
| 1708 return false; | |
| 1709 } | |
| 1710 } | |
| 1711 | |
| 1712 Future runNextCommandWithRetries(TestCase testCase, bool allowRetry) { | |
| 1713 var completer = new Completer(); | |
| 1714 | |
| 1715 var nextCommandIndex = testCase.commandOutputs.keys.length; | |
| 1716 var numberOfCommands = testCase.commands.length; | |
| 1717 Expect.isTrue(nextCommandIndex < numberOfCommands); | |
| 1718 var command = testCase.commands[nextCommandIndex]; | |
| 1719 var isLastCommand = nextCommandIndex == (numberOfCommands - 1); | |
| 1720 | |
| 1721 void runCommand() { | |
| 1722 var runningProcess = new RunningProcess(testCase, command); | |
| 1723 runningProcess.start().then((CommandOutput commandOutput) { | |
| 1724 if (isLastCommand) { | |
| 1725 if (allowRetry && testCase.usesWebDriver | |
| 1726 && commandOutput.unexpectedOutput | |
| 1727 && (testCase as BrowserTestCase).numRetries > 0) { | |
| 1728 // Selenium tests can be flaky. Try rerunning. | |
| 1729 commandOutput.requestRetry = true; | |
| 1730 } | |
| 1731 } | |
| 1732 if (commandOutput.requestRetry) { | |
| 1733 commandOutput.requestRetry = false; | |
| 1734 (testCase as BrowserTestCase).numRetries--; | |
| 1735 print("Potential flake. Re-running ${testCase.displayName} " | |
| 1736 "(${(testCase as BrowserTestCase).numRetries} " | |
| 1737 "attempt(s) remains)"); | |
| 1738 print("[cmd:$command]"); | |
| 1739 runCommand(); | |
| 1740 } else { | |
| 1741 completer.complete(testCase); | |
| 1742 } | |
| 1743 }); | |
| 1744 } | |
| 1745 runCommand(); | |
| 1746 | |
| 1747 return completer.future; | |
| 1748 } | |
| 1762 } | 1749 } |
| 1750 | |
| OLD | NEW |