| 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(_) { |
| 961 timedOut = true; |
| 962 if (process != null) { |
| 963 process.kill(); |
| 964 } |
| 965 } |
| 966 process.onExit = _commandComplete; |
| 967 _drainStream(process.stdout, stdout); |
| 968 _drainStream(process.stderr, stderr); |
| 969 timeoutTimer = new Timer(1000 * testCase.timeout, timeoutHandler); |
| 970 }).catchError((e) { |
| 971 print("Process error:"); |
| 972 print(" Command: $command"); |
| 973 print(" Error: $e"); |
| 974 _commandComplete(-1); |
| 975 return true; |
| 976 }); |
| 977 } |
| 978 }); |
| 979 } |
| 980 |
| 981 void _commandComplete(int exitCode) { |
| 952 if (timeoutTimer != null) { | 982 if (timeoutTimer != null) { |
| 953 timeoutTimer.cancel(); | 983 timeoutTimer.cancel(); |
| 954 } | 984 } |
| 955 if (lastCommandOutput.unexpectedOutput | 985 var commandOutput = _createCommandOutput(command, exitCode); |
| 956 && testCase.configuration['verbose'] != null | 986 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 } | 987 } |
| 996 | 988 |
| 997 /** | 989 CommandOutput _createCommandOutput(Command command, int exitCode) { |
| 998 * Process exit handler called at the end of every command. It internally | 990 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( | 991 var commandOutput = new CommandOutput.fromCase( |
| 1047 testCase, | 992 testCase, |
| 1048 command, | 993 command, |
| 1049 exitCode, | 994 exitCode, |
| 1050 incomplete, | 995 incomplete, |
| 1051 timedOut, | 996 timedOut, |
| 1052 stdout, | 997 stdout, |
| 1053 stderr, | 998 stderr, |
| 1054 new Date.now().difference(startTime), | 999 new Date.now().difference(startTime), |
| 1055 compilationSkipped); | 1000 compilationSkipped); |
| 1056 resetLocalOutputInformation(); | |
| 1057 return commandOutput; | 1001 return commandOutput; |
| 1058 } | 1002 } |
| 1059 | 1003 |
| 1060 void resetLocalOutputInformation() { | 1004 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 () { | 1005 void onDataHandler () { |
| 1069 if (source.closed) { | 1006 if (source.closed) { |
| 1070 return; // TODO(whesse): Remove when bug is fixed. | 1007 return; // TODO(whesse): Remove when bug is fixed. |
| 1071 } | 1008 } |
| 1072 var data = source.read(); | 1009 var data = source.read(); |
| 1073 while (data != null) { | 1010 while (data != null) { |
| 1074 destination.addAll(data); | 1011 destination.addAll(data); |
| 1075 data = source.read(); | 1012 data = source.read(); |
| 1076 } | 1013 } |
| 1077 } | 1014 } |
| 1078 source.onData = onDataHandler; | 1015 source.onData = onDataHandler; |
| 1079 source.onClosed = onDataHandler; | 1016 source.onClosed = onDataHandler; |
| 1080 } | 1017 } |
| 1081 | 1018 |
| 1082 void start() { | 1019 io.ProcessOptions _createProcessOptions() { |
| 1083 Expect.isFalse(testCase.expectedOutcomes.contains(SKIP)); | 1020 var baseEnvironment = command.environment != null ? |
| 1084 resetLocalOutputInformation(); | 1021 command.environment : io.Platform.environment; |
| 1085 currentStep = 0; | 1022 io.ProcessOptions options = new io.ProcessOptions(); |
| 1086 startTime = new Date.now(); | 1023 options.environment = new Map<String, String>.from(baseEnvironment); |
| 1087 runCommand(testCase.commands[currentStep++], commandComplete); | 1024 options.environment['DART_CONFIGURATION'] = |
| 1088 } | 1025 TestUtils.configurationDir(testCase.configuration); |
| 1089 | 1026 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 } | 1027 } |
| 1154 } | 1028 } |
| 1155 | 1029 |
| 1156 /** | 1030 /** |
| 1157 * This class holds a value, that can be changed. It is used when | 1031 * 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. | 1032 * closures need a shared value, that they can all change and read. |
| 1159 */ | 1033 */ |
| 1160 class MutableValue<T> { | 1034 class MutableValue<T> { |
| 1161 MutableValue(T this.value); | 1035 MutableValue(T this.value); |
| 1162 T value; | 1036 T value; |
| (...skipping 264 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1427 * have completed. | 1301 * have completed. |
| 1428 * | 1302 * |
| 1429 * Because multiple configurations may be run on each test suite, the | 1303 * Because multiple configurations may be run on each test suite, the |
| 1430 * ProcessQueue contains a cache in which a test suite may record information | 1304 * 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 | 1305 * about its list of tests, and may retrieve that information when it is called |
| 1432 * upon to enqueue its tests again. | 1306 * upon to enqueue its tests again. |
| 1433 */ | 1307 */ |
| 1434 class ProcessQueue { | 1308 class ProcessQueue { |
| 1435 int _numProcesses = 0; | 1309 int _numProcesses = 0; |
| 1436 int _maxProcesses; | 1310 int _maxProcesses; |
| 1311 int _numBrowserProcesses = 0; |
| 1312 int _maxBrowserProcesses; |
| 1437 bool _allTestsWereEnqueued = false; | 1313 bool _allTestsWereEnqueued = false; |
| 1438 | 1314 |
| 1439 /** The number of tests we allow to actually fail before we stop retrying. */ | 1315 /** The number of tests we allow to actually fail before we stop retrying. */ |
| 1440 int _MAX_FAILED_NO_RETRY = 4; | 1316 int _MAX_FAILED_NO_RETRY = 4; |
| 1441 bool _verbose; | 1317 bool _verbose; |
| 1442 bool _listTests; | 1318 bool _listTests; |
| 1443 Function _allDone; | 1319 Function _allDone; |
| 1444 Queue<TestCase> _tests; | 1320 Queue<TestCase> _tests; |
| 1445 ProgressIndicator _progress; | 1321 ProgressIndicator _progress; |
| 1446 | 1322 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 1463 * tests.) | 1339 * tests.) |
| 1464 */ | 1340 */ |
| 1465 io.Process _seleniumServer = null; | 1341 io.Process _seleniumServer = null; |
| 1466 | 1342 |
| 1467 /** True if we are in the process of starting the server. */ | 1343 /** True if we are in the process of starting the server. */ |
| 1468 bool _startingServer = false; | 1344 bool _startingServer = false; |
| 1469 | 1345 |
| 1470 /** True if we find that there is already a selenium jar running. */ | 1346 /** True if we find that there is already a selenium jar running. */ |
| 1471 bool _seleniumAlreadyRunning = false; | 1347 bool _seleniumAlreadyRunning = false; |
| 1472 | 1348 |
| 1473 ProcessQueue(int this._maxProcesses, | 1349 ProcessQueue(this._maxProcesses, |
| 1350 this._maxBrowserProcesses, |
| 1474 String progress, | 1351 String progress, |
| 1475 Date startTime, | 1352 Date startTime, |
| 1476 bool printTiming, | 1353 bool printTiming, |
| 1477 testSuites, | 1354 testSuites, |
| 1478 this._allDone, | 1355 this._allDone, |
| 1479 [bool verbose = false, | 1356 [bool verbose = false, |
| 1480 bool listTests = false]) | 1357 bool listTests = false]) |
| 1481 : _verbose = verbose, | 1358 : _verbose = verbose, |
| 1482 _listTests = listTests, | 1359 _listTests = listTests, |
| 1483 _tests = new Queue<TestCase>(), | 1360 _tests = new Queue<TestCase>(), |
| (...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1700 test.isNegative.toString()]; | 1577 test.isNegative.toString()]; |
| 1701 fields.addAll(test.commands.last.arguments); | 1578 fields.addAll(test.commands.last.arguments); |
| 1702 print(Strings.join(fields, '\t')); | 1579 print(Strings.join(fields, '\t')); |
| 1703 return; | 1580 return; |
| 1704 } | 1581 } |
| 1705 if (test.usesWebDriver && _needsSelenium && !_isSeleniumAvailable || (test | 1582 if (test.usesWebDriver && _needsSelenium && !_isSeleniumAvailable || (test |
| 1706 is BrowserTestCase && test.waitingForOtherTest)) { | 1583 is BrowserTestCase && test.waitingForOtherTest)) { |
| 1707 // The test is not yet ready to run. Put the test back in | 1584 // The test is not yet ready to run. Put the test back in |
| 1708 // the queue. Avoid spin-polling by using a timeout. | 1585 // the queue. Avoid spin-polling by using a timeout. |
| 1709 _tests.add(test); | 1586 _tests.add(test); |
| 1710 new Timer(100, (timer) {_tryRunTest();}); // Don't lose a process. | 1587 new Timer(100, (_) => _tryRunTest()); // Don't lose a process. |
| 1711 return; | 1588 return; |
| 1712 } | 1589 } |
| 1713 if (_verbose) { | 1590 if (_verbose) { |
| 1714 int i = 1; | 1591 int i = 1; |
| 1715 if (test is BrowserTestCase) { | 1592 if (test is BrowserTestCase) { |
| 1716 // Additional command for rerunning the steps locally after the fact. | 1593 // Additional command for rerunning the steps locally after the fact. |
| 1717 print('$i. ${TestUtils.dartTestExecutable.toNativePath()} ' | 1594 print('$i. ${TestUtils.dartTestExecutable.toNativePath()} ' |
| 1718 '${TestUtils.dartDir().toNativePath()}/tools/testing/dart/' | 1595 '${TestUtils.dartDir().toNativePath()}/tools/testing/dart/' |
| 1719 'http_server.dart -m ${test.configuration["mode"]} ' | 1596 'http_server.dart -m ${test.configuration["mode"]} ' |
| 1720 '-a ${test.configuration["arch"]} ' | 1597 '-a ${test.configuration["arch"]} ' |
| 1721 '-p ${http_server.TestingServerRunner.serverList[0].port} ' | 1598 '-p ${http_server.TestingServerRunner.serverList[0].port} ' |
| 1722 '-c ${http_server.TestingServerRunner.serverList[1].port}'); | 1599 '-c ${http_server.TestingServerRunner.serverList[1].port}'); |
| 1723 i++; | 1600 i++; |
| 1724 } | 1601 } |
| 1725 for (Command command in test.commands) { | 1602 for (Command command in test.commands) { |
| 1726 print('$i. ${command.commandLine}'); | 1603 print('$i. ${command.commandLine}'); |
| 1727 i++; | 1604 i++; |
| 1728 } | 1605 } |
| 1729 } | 1606 } |
| 1607 |
| 1608 var isLastCommand = |
| 1609 ((test.commands.length-1) == test.commandOutputs.length); |
| 1610 var isBrowserCommand = isLastCommand && (test is BrowserTestCase); |
| 1611 if (isBrowserCommand && _numBrowserProcesses == _maxBrowserProcesses) { |
| 1612 // If there is no free browser runner, put it back into the queue. |
| 1613 _tests.add(test); |
| 1614 new Timer(100, (_) => _tryRunTest()); // Don't lose a process. |
| 1615 return; |
| 1616 } |
| 1617 |
| 1730 _progress.start(test); | 1618 _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 | 1619 |
| 1741 if ((test.configuration['compiler'] == 'dartc' && | 1620 // Dartc and browser test commands can be run by a [BatchRunnerProcess] |
| 1742 test.displayName != 'dartc/junit_tests') || | 1621 var nextCommandIndex = test.commandOutputs.keys.length; |
| 1743 (test.commands.length == 1 && test.usesWebDriver && | 1622 var numberOfCommands = test.commands.length; |
| 1744 !test.configuration['noBatch'])) { | 1623 var useBatchRunnerForDartc = test.configuration['compiler'] == 'dartc' && |
| 1745 // Dartc and browser test cases that do not require a precompilation | 1624 test.displayName != 'dartc/junit_tests'; |
| 1746 // step, start with the batch runner right away. | 1625 var isWebdriverCommand = nextCommandIndex == (numberOfCommands - 1) && |
| 1626 test.usesWebDriver && |
| 1627 !test.configuration['noBatch']; |
| 1628 if (useBatchRunnerForDartc || isWebdriverCommand) { |
| 1629 TestCaseEvent oldCallback = test.completedHandler; |
| 1630 void testCompleted(TestCase test_arg) { |
| 1631 _numProcesses--; |
| 1632 if (isBrowserCommand) { |
| 1633 _numBrowserProcesses--; |
| 1634 } |
| 1635 _progress.done(test_arg); |
| 1636 if (test_arg is BrowserTestCase) test_arg.notifyObservers(); |
| 1637 oldCallback(test_arg); |
| 1638 _tryRunTest(); |
| 1639 }; |
| 1640 test.completedHandler = testCompleted; |
| 1747 _getBatchRunner(test).startTest(test); | 1641 _getBatchRunner(test).startTest(test); |
| 1748 } else { | 1642 } else { |
| 1749 // Once we've actually failed a test, technically, we wouldn't need to | 1643 // 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. | 1644 // bother retrying any subsequent tests since the bot is already red. |
| 1751 // However, we continue to retry tests until we have actually failed | 1645 // However, we continue to retry tests until we have actually failed |
| 1752 // four tests (arbitrarily chosen) for more debugable output, so that | 1646 // 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 | 1647 // 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 | 1648 // tests that appear to be broken but were actually just flakes that |
| 1755 // didn't get retried because there had already been one failure. | 1649 // didn't get retried because there had already been one failure. |
| 1756 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; | 1650 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; |
| 1757 new RunningProcess(test, allowRetry, this).start(); | 1651 runNextCommandWithRetries(test, allowRetry).then((TestCase testCase) { |
| 1652 _numProcesses--; |
| 1653 if (isBrowserCommand) { |
| 1654 _numBrowserProcesses--; |
| 1655 } |
| 1656 if (isTestCaseFinished(testCase)) { |
| 1657 testCase.completed(); |
| 1658 _progress.done(testCase); |
| 1659 if (testCase is BrowserTestCase) testCase.notifyObservers(); |
| 1660 } else { |
| 1661 _tests.add(testCase); |
| 1662 } |
| 1663 _tryRunTest(); |
| 1664 }); |
| 1758 } | 1665 } |
| 1666 |
| 1759 _numProcesses++; | 1667 _numProcesses++; |
| 1668 if (isBrowserCommand) { |
| 1669 _numBrowserProcesses++; |
| 1670 } |
| 1760 } | 1671 } |
| 1761 } | 1672 } |
| 1673 |
| 1674 bool isTestCaseFinished(TestCase testCase) { |
| 1675 var numberOfCommandOutputs = testCase.commandOutputs.keys.length; |
| 1676 var numberOfCommands = testCase.commands.length; |
| 1677 |
| 1678 var lastCommandCompleted = (numberOfCommandOutputs == numberOfCommands); |
| 1679 var lastCommandOutput = testCase.lastCommandOutput; |
| 1680 var lastCommand = lastCommandOutput.command; |
| 1681 var timedOut = lastCommandOutput.hasTimedOut; |
| 1682 var nonZeroExitCode = lastCommandOutput.exitCode != 0; |
| 1683 // NOTE: If this was the last command or there was unexpected output |
| 1684 // we're done with the test. |
| 1685 // Otherwise we need to enqueue it again into the test queue. |
| 1686 if (lastCommandCompleted || timedOut || nonZeroExitCode) { |
| 1687 var verbose = testCase.configuration['verbose']; |
| 1688 if (nonZeroExitCode && verbose != null && verbose) { |
| 1689 print(testCase.displayName); |
| 1690 print("stderr:"); |
| 1691 print(decodeUtf8(lastCommandOutput.stderr)); |
| 1692 if (!lastCommand.isPixelTest) { |
| 1693 print("stdout:"); |
| 1694 print(decodeUtf8(lastCommandOutput.stdout)); |
| 1695 } else { |
| 1696 print(""); |
| 1697 print("DRT pixel test failed! stdout is not printed because it " |
| 1698 "contains binary data!"); |
| 1699 } |
| 1700 } |
| 1701 return true; |
| 1702 } else { |
| 1703 return false; |
| 1704 } |
| 1705 } |
| 1706 |
| 1707 Future runNextCommandWithRetries(TestCase testCase, bool allowRetry) { |
| 1708 var completer = new Completer(); |
| 1709 |
| 1710 var nextCommandIndex = testCase.commandOutputs.keys.length; |
| 1711 var numberOfCommands = testCase.commands.length; |
| 1712 Expect.isTrue(nextCommandIndex < numberOfCommands); |
| 1713 var command = testCase.commands[nextCommandIndex]; |
| 1714 var isLastCommand = nextCommandIndex == (numberOfCommands - 1); |
| 1715 |
| 1716 void runCommand() { |
| 1717 var runningProcess = new RunningProcess(testCase, command); |
| 1718 runningProcess.start().then((CommandOutput commandOutput) { |
| 1719 if (isLastCommand) { |
| 1720 if (allowRetry && testCase.usesWebDriver |
| 1721 && commandOutput.unexpectedOutput |
| 1722 && (testCase as BrowserTestCase).numRetries > 0) { |
| 1723 // Selenium tests can be flaky. Try rerunning. |
| 1724 commandOutput.requestRetry = true; |
| 1725 } |
| 1726 } |
| 1727 if (commandOutput.requestRetry) { |
| 1728 commandOutput.requestRetry = false; |
| 1729 (testCase as BrowserTestCase).numRetries--; |
| 1730 DebugLogger.warning("Rerunning Test: ${testCase.displayName} " |
| 1731 "(${(testCase as BrowserTestCase).numRetries} " |
| 1732 "attempt(s) remains) [cmd:$command]"); |
| 1733 runCommand(); |
| 1734 } else { |
| 1735 completer.complete(testCase); |
| 1736 } |
| 1737 }); |
| 1738 } |
| 1739 runCommand(); |
| 1740 |
| 1741 return completer.future; |
| 1742 } |
| 1762 } | 1743 } |
| 1744 |
| OLD | NEW |