| 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. |
| 11 */ | 11 */ |
| 12 library test_runner; | 12 library test_runner; |
| 13 | 13 |
| 14 import "dart:io"; | 14 import "dart:async"; |
| 15 // We need to use the 'io' prefix here, otherwise io.exitCode will shadow |
| 16 // CommandOutput.exitCode in subclasses of CommandOutput. |
| 17 import "dart:io" as io; |
| 15 import "dart:isolate"; | 18 import "dart:isolate"; |
| 16 import "dart:uri"; | 19 import "dart:uri"; |
| 17 import "status_file_parser.dart"; | 20 import "status_file_parser.dart"; |
| 18 import "test_progress.dart"; | 21 import "test_progress.dart"; |
| 19 import "test_suite.dart"; | 22 import "test_suite.dart"; |
| 20 | 23 |
| 21 const int NO_TIMEOUT = 0; | 24 const int NO_TIMEOUT = 0; |
| 22 const int SLOW_TIMEOUT_MULTIPLIER = 4; | 25 const int SLOW_TIMEOUT_MULTIPLIER = 4; |
| 23 | 26 |
| 24 typedef void TestCaseEvent(TestCase testCase); | 27 typedef void TestCaseEvent(TestCase testCase); |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 82 /** Command line arguments to the executable. */ | 85 /** Command line arguments to the executable. */ |
| 83 List<String> arguments; | 86 List<String> arguments; |
| 84 | 87 |
| 85 /** Environment for the command */ | 88 /** Environment for the command */ |
| 86 Map<String,String> environment; | 89 Map<String,String> environment; |
| 87 | 90 |
| 88 /** The actual command line that will be executed. */ | 91 /** The actual command line that will be executed. */ |
| 89 String commandLine; | 92 String commandLine; |
| 90 | 93 |
| 91 Command(this.executable, this.arguments, [this.environment = null]) { | 94 Command(this.executable, this.arguments, [this.environment = null]) { |
| 92 if (Platform.operatingSystem == 'windows') { | 95 if (io.Platform.operatingSystem == 'windows') { |
| 93 // Windows can't handle the first command if it is a .bat file or the like | 96 // Windows can't handle the first command if it is a .bat file or the like |
| 94 // with the slashes going the other direction. | 97 // with the slashes going the other direction. |
| 95 // TODO(efortuna): Remove this when fixed (Issue 1306). | 98 // TODO(efortuna): Remove this when fixed (Issue 1306). |
| 96 executable = executable.replaceAll('/', '\\'); | 99 executable = executable.replaceAll('/', '\\'); |
| 97 } | 100 } |
| 98 commandLine = "$executable ${Strings.join(arguments, ' ')}"; | 101 commandLine = "$executable ${Strings.join(arguments, ' ')}"; |
| 99 } | 102 } |
| 100 | 103 |
| 101 String toString() => commandLine; | 104 String toString() => commandLine; |
| 102 | 105 |
| 103 Future<bool> get outputIsUpToDate => new Future.immediate(false); | 106 Future<bool> get outputIsUpToDate => new Future.immediate(false); |
| 104 Path get expectedOutputFile => null; | 107 io.Path get expectedOutputFile => null; |
| 105 bool get isPixelTest => false; | 108 bool get isPixelTest => false; |
| 106 } | 109 } |
| 107 | 110 |
| 108 class CompilationCommand extends Command { | 111 class CompilationCommand extends Command { |
| 109 String _outputFile; | 112 String _outputFile; |
| 110 bool _neverSkipCompilation; | 113 bool _neverSkipCompilation; |
| 111 List<Uri> _bootstrapDependencies; | 114 List<Uri> _bootstrapDependencies; |
| 112 | 115 |
| 113 CompilationCommand(this._outputFile, | 116 CompilationCommand(this._outputFile, |
| 114 this._neverSkipCompilation, | 117 this._neverSkipCompilation, |
| 115 this._bootstrapDependencies, | 118 this._bootstrapDependencies, |
| 116 String executable, | 119 String executable, |
| 117 List<String> arguments) | 120 List<String> arguments) |
| 118 : super(executable, arguments); | 121 : super(executable, arguments); |
| 119 | 122 |
| 120 Future<bool> get outputIsUpToDate { | 123 Future<bool> get outputIsUpToDate { |
| 121 if (_neverSkipCompilation) return new Future.immediate(false); | 124 if (_neverSkipCompilation) return new Future.immediate(false); |
| 122 | 125 |
| 123 Future<List<Uri>> readDepsFile(String path) { | 126 Future<List<Uri>> readDepsFile(String path) { |
| 124 var file = new File(new Path(path).toNativePath()); | 127 var file = new io.File(new io.Path(path).toNativePath()); |
| 125 if (!file.existsSync()) { | 128 if (!file.existsSync()) { |
| 126 return new Future.immediate(null); | 129 return new Future.immediate(null); |
| 127 } | 130 } |
| 128 return file.readAsLines().transform((List<String> lines) { | 131 return file.readAsLines().then((List<String> lines) { |
| 129 var dependencies = new List<Uri>(); | 132 var dependencies = new List<Uri>(); |
| 130 for (var line in lines) { | 133 for (var line in lines) { |
| 131 line = line.trim(); | 134 line = line.trim(); |
| 132 if (line.length > 0) { | 135 if (line.length > 0) { |
| 133 dependencies.add(new Uri(line)); | 136 dependencies.add(new Uri(line)); |
| 134 } | 137 } |
| 135 } | 138 } |
| 136 return dependencies; | 139 return dependencies; |
| 137 }); | 140 }); |
| 138 } | 141 } |
| 139 | 142 |
| 140 return readDepsFile("$_outputFile.deps").transform((dependencies) { | 143 return readDepsFile("$_outputFile.deps").then((dependencies) { |
| 141 if (dependencies != null) { | 144 if (dependencies != null) { |
| 142 dependencies.addAll(_bootstrapDependencies); | 145 dependencies.addAll(_bootstrapDependencies); |
| 143 var jsOutputLastModified = TestUtils.lastModifiedCache.getLastModified( | 146 var jsOutputLastModified = TestUtils.lastModifiedCache.getLastModified( |
| 144 new Uri.fromComponents(scheme: 'file', path: _outputFile)); | 147 new Uri.fromComponents(scheme: 'file', path: _outputFile)); |
| 145 if (jsOutputLastModified != null) { | 148 if (jsOutputLastModified != null) { |
| 146 for (var dependency in dependencies) { | 149 for (var dependency in dependencies) { |
| 147 var dependencyLastModified = | 150 var dependencyLastModified = |
| 148 TestUtils.lastModifiedCache.getLastModified(dependency); | 151 TestUtils.lastModifiedCache.getLastModified(dependency); |
| 149 if (dependencyLastModified == null || | 152 if (dependencyLastModified == null || |
| 150 dependencyLastModified > jsOutputLastModified) { | 153 dependencyLastModified > jsOutputLastModified) { |
| 151 return false; | 154 return false; |
| 152 } | 155 } |
| 153 } | 156 } |
| 154 return true; | 157 return true; |
| 155 } | 158 } |
| 156 } | 159 } |
| 157 return false; | 160 return false; |
| 158 }); | 161 }); |
| 159 } | 162 } |
| 160 } | 163 } |
| 161 | 164 |
| 162 class DumpRenderTreeCommand extends Command { | 165 class DumpRenderTreeCommand extends Command { |
| 163 /** | 166 /** |
| 164 * If [expectedOutputPath] is set, the output of DumpRenderTree is compared | 167 * If [expectedOutputPath] is set, the output of DumpRenderTree is compared |
| 165 * with the content of [expectedOutputPath]. | 168 * with the content of [expectedOutputPath]. |
| 166 * This is used for example for pixel tests, where [expectedOutputPath] points | 169 * This is used for example for pixel tests, where [expectedOutputPath] points |
| 167 * to a *png file. | 170 * to a *png file. |
| 168 */ | 171 */ |
| 169 Path expectedOutputPath; | 172 io.Path expectedOutputPath; |
| 170 | 173 |
| 171 DumpRenderTreeCommand(String executable, | 174 DumpRenderTreeCommand(String executable, |
| 172 String htmlFile, | 175 String htmlFile, |
| 173 List<String> options, | 176 List<String> options, |
| 174 List<String> dartFlags, | 177 List<String> dartFlags, |
| 175 Uri packageRootUri, | 178 Uri packageRootUri, |
| 176 Path this.expectedOutputPath) | 179 io.Path this.expectedOutputPath) |
| 177 : super(executable, | 180 : super(executable, |
| 178 _getArguments(options, htmlFile), | 181 _getArguments(options, htmlFile), |
| 179 _getEnvironment(dartFlags, packageRootUri)); | 182 _getEnvironment(dartFlags, packageRootUri)); |
| 180 | 183 |
| 181 static Map _getEnvironment(List<String> dartFlags, Uri packageRootUri) { | 184 static Map _getEnvironment(List<String> dartFlags, Uri packageRootUri) { |
| 182 var needDartFlags = dartFlags != null && dartFlags.length > 0; | 185 var needDartFlags = dartFlags != null && dartFlags.length > 0; |
| 183 var needDartPackageRoot = packageRootUri != null; | 186 var needDartPackageRoot = packageRootUri != null; |
| 184 | 187 |
| 185 var env = null; | 188 var env = null; |
| 186 if (needDartFlags || needDartPackageRoot) { | 189 if (needDartFlags || needDartPackageRoot) { |
| 187 env = new Map.from(Platform.environment); | 190 env = new Map.from(io.Platform.environment); |
| 188 if (needDartFlags) { | 191 if (needDartFlags) { |
| 189 env['DART_FLAGS'] = Strings.join(dartFlags, " "); | 192 env['DART_FLAGS'] = Strings.join(dartFlags, " "); |
| 190 } | 193 } |
| 191 if (needDartPackageRoot) { | 194 if (needDartPackageRoot) { |
| 192 env['DART_PACKAGE_ROOT'] = packageRootUri.toString(); | 195 env['DART_PACKAGE_ROOT'] = packageRootUri.toString(); |
| 193 } | 196 } |
| 194 } | 197 } |
| 195 | 198 |
| 196 return env; | 199 return env; |
| 197 } | 200 } |
| 198 | 201 |
| 199 static List<String> _getArguments(List<String> options, String htmlFile) { | 202 static List<String> _getArguments(List<String> options, String htmlFile) { |
| 200 var arguments = new List.from(options); | 203 var arguments = new List.from(options); |
| 201 arguments.add(htmlFile); | 204 arguments.add(htmlFile); |
| 202 return arguments; | 205 return arguments; |
| 203 } | 206 } |
| 204 | 207 |
| 205 Path get expectedOutputFile => expectedOutputPath; | 208 io.Path get expectedOutputFile => expectedOutputPath; |
| 206 bool get isPixelTest => (expectedOutputFile != null && | 209 bool get isPixelTest => (expectedOutputFile != null && |
| 207 expectedOutputFile.filename.endsWith(".png")); | 210 expectedOutputFile.filename.endsWith(".png")); |
| 208 } | 211 } |
| 209 | 212 |
| 210 | 213 |
| 211 /** | 214 /** |
| 212 * TestCase contains all the information needed to run a test and evaluate | 215 * TestCase contains all the information needed to run a test and evaluate |
| 213 * its output. Running a test involves starting a separate process, with | 216 * its output. Running a test involves starting a separate process, with |
| 214 * the executable and arguments given by the TestCase, and recording its | 217 * the executable and arguments given by the TestCase, and recording its |
| 215 * stdout and stderr output streams, and its exit code. TestCase only | 218 * stdout and stderr output streams, and its exit code. TestCase only |
| (...skipping 332 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 548 | 551 |
| 549 String get result => | 552 String get result => |
| 550 hasCrashed ? CRASH : (hasTimedOut ? TIMEOUT : (hasFailed ? FAIL : PASS)); | 553 hasCrashed ? CRASH : (hasTimedOut ? TIMEOUT : (hasFailed ? FAIL : PASS)); |
| 551 | 554 |
| 552 bool get unexpectedOutput => !testCase.expectedOutcomes.contains(result); | 555 bool get unexpectedOutput => !testCase.expectedOutcomes.contains(result); |
| 553 | 556 |
| 554 bool get hasCrashed { | 557 bool get hasCrashed { |
| 555 // The Java dartc runner and dart2js exits with code 253 in case | 558 // The Java dartc runner and dart2js exits with code 253 in case |
| 556 // of unhandled exceptions. | 559 // of unhandled exceptions. |
| 557 if (exitCode == 253) return true; | 560 if (exitCode == 253) return true; |
| 558 if (Platform.operatingSystem == 'windows') { | 561 if (io.Platform.operatingSystem == 'windows') { |
| 559 // The VM uses std::abort to terminate on asserts. | 562 // The VM uses std::abort to terminate on asserts. |
| 560 // std::abort terminates with exit code 3 on Windows. | 563 // std::abort terminates with exit code 3 on Windows. |
| 561 if (exitCode == 3) { | 564 if (exitCode == 3) { |
| 562 return !timedOut; | 565 return !timedOut; |
| 563 } | 566 } |
| 564 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0)); | 567 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0)); |
| 565 } | 568 } |
| 566 return !timedOut && ((exitCode < 0)); | 569 return !timedOut && ((exitCode < 0)); |
| 567 } | 570 } |
| 568 | 571 |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 651 * Content-Length: ...\n | 654 * Content-Length: ...\n |
| 652 * <*png data> | 655 * <*png data> |
| 653 * #EOF\n | 656 * #EOF\n |
| 654 * So we need to get the byte-range of the png data first, before | 657 * So we need to get the byte-range of the png data first, before |
| 655 * comparing it with the content of the expected output file. | 658 * comparing it with the content of the expected output file. |
| 656 * | 659 * |
| 657 * On a layout tests, the DRT output is directly compared with the | 660 * On a layout tests, the DRT output is directly compared with the |
| 658 * content of the expected output. | 661 * content of the expected output. |
| 659 */ | 662 */ |
| 660 var stdout = testCase.commandOutputs[command].stdout; | 663 var stdout = testCase.commandOutputs[command].stdout; |
| 661 var file = new File.fromPath(command.expectedOutputFile); | 664 var file = new io.File.fromPath(command.expectedOutputFile); |
| 662 if (file.existsSync()) { | 665 if (file.existsSync()) { |
| 663 var bytesContentLength = "Content-Length:".charCodes; | 666 var bytesContentLength = "Content-Length:".charCodes; |
| 664 var bytesNewLine = "\n".charCodes; | 667 var bytesNewLine = "\n".charCodes; |
| 665 var bytesEOF = "#EOF\n".charCodes; | 668 var bytesEOF = "#EOF\n".charCodes; |
| 666 | 669 |
| 667 var expectedContent = file.readAsBytesSync(); | 670 var expectedContent = file.readAsBytesSync(); |
| 668 if (command.isPixelTest) { | 671 if (command.isPixelTest) { |
| 669 var startOfContentLength = findBytes(stdout, bytesContentLength); | 672 var startOfContentLength = findBytes(stdout, bytesContentLength); |
| 670 if (startOfContentLength >= 0) { | 673 if (startOfContentLength >= 0) { |
| 671 var newLineAfterContentLength = findBytes(stdout, | 674 var newLineAfterContentLength = findBytes(stdout, |
| (...skipping 236 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 908 * its [TestCase], starting the test process (and first, a compilation | 911 * its [TestCase], starting the test process (and first, a compilation |
| 909 * process if the TestCase is a [BrowserTestCase]), creating a timeout | 912 * process if the TestCase is a [BrowserTestCase]), creating a timeout |
| 910 * timer, and recording the results in a new [CommandOutput] object, which it | 913 * timer, and recording the results in a new [CommandOutput] object, which it |
| 911 * attaches to the TestCase. The lifetime of the RunningProcess is limited | 914 * attaches to the TestCase. The lifetime of the RunningProcess is limited |
| 912 * to the time it takes to start the process, run the process, and record | 915 * to the time it takes to start the process, run the process, and record |
| 913 * the result; there are no pointers to it, so it should be available to | 916 * the result; there are no pointers to it, so it should be available to |
| 914 * be garbage collected as soon as it is done. | 917 * be garbage collected as soon as it is done. |
| 915 */ | 918 */ |
| 916 class RunningProcess { | 919 class RunningProcess { |
| 917 ProcessQueue processQueue; | 920 ProcessQueue processQueue; |
| 918 Process process; | 921 io.Process process; |
| 919 TestCase testCase; | 922 TestCase testCase; |
| 920 bool timedOut = false; | 923 bool timedOut = false; |
| 921 Date startTime; | 924 Date startTime; |
| 922 Timer timeoutTimer; | 925 Timer timeoutTimer; |
| 923 List<int> stdout; | 926 List<int> stdout; |
| 924 List<int> stderr; | 927 List<int> stderr; |
| 925 List<String> notifications; | 928 List<String> notifications; |
| 926 bool compilationSkipped; | 929 bool compilationSkipped; |
| 927 bool allowRetries; | 930 bool allowRetries; |
| 928 | 931 |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1046 return commandOutput; | 1049 return commandOutput; |
| 1047 } | 1050 } |
| 1048 | 1051 |
| 1049 void resetLocalOutputInformation() { | 1052 void resetLocalOutputInformation() { |
| 1050 stdout = new List<int>(); | 1053 stdout = new List<int>(); |
| 1051 stderr = new List<int>(); | 1054 stderr = new List<int>(); |
| 1052 notifications = new List<String>(); | 1055 notifications = new List<String>(); |
| 1053 compilationSkipped = false; | 1056 compilationSkipped = false; |
| 1054 } | 1057 } |
| 1055 | 1058 |
| 1056 void drainStream(InputStream source, List<int> destination) { | 1059 void drainStream(io.InputStream source, List<int> destination) { |
| 1057 void onDataHandler () { | 1060 void onDataHandler () { |
| 1058 if (source.closed) { | 1061 if (source.closed) { |
| 1059 return; // TODO(whesse): Remove when bug is fixed. | 1062 return; // TODO(whesse): Remove when bug is fixed. |
| 1060 } | 1063 } |
| 1061 var data = source.read(); | 1064 var data = source.read(); |
| 1062 while (data != null) { | 1065 while (data != null) { |
| 1063 destination.addAll(data); | 1066 destination.addAll(data); |
| 1064 data = source.read(); | 1067 data = source.read(); |
| 1065 } | 1068 } |
| 1066 } | 1069 } |
| (...skipping 14 matching lines...) Expand all Loading... |
| 1081 commandCompleteHandler(command, returnCode); | 1084 commandCompleteHandler(command, returnCode); |
| 1082 } | 1085 } |
| 1083 | 1086 |
| 1084 command.outputIsUpToDate.then((bool isUpToDate) { | 1087 command.outputIsUpToDate.then((bool isUpToDate) { |
| 1085 if (isUpToDate) { | 1088 if (isUpToDate) { |
| 1086 notifications.add("Skipped compilation because the old output is " | 1089 notifications.add("Skipped compilation because the old output is " |
| 1087 "still up to date!"); | 1090 "still up to date!"); |
| 1088 compilationSkipped = true; | 1091 compilationSkipped = true; |
| 1089 commandComplete(command, 0); | 1092 commandComplete(command, 0); |
| 1090 } else { | 1093 } else { |
| 1091 ProcessOptions options = new ProcessOptions(); | 1094 io.ProcessOptions options = new io.ProcessOptions(); |
| 1092 if (command.environment != null) { | 1095 if (command.environment != null) { |
| 1093 options.environment = | 1096 options.environment = |
| 1094 new Map<String, String>.from(command.environment); | 1097 new Map<String, String>.from(command.environment); |
| 1095 } else { | 1098 } else { |
| 1096 options.environment = | 1099 options.environment = |
| 1097 new Map<String, String>.from(Platform.environment); | 1100 new Map<String, String>.from(io.Platform.environment); |
| 1098 } | 1101 } |
| 1099 | 1102 |
| 1100 options.environment['DART_CONFIGURATION'] = | 1103 options.environment['DART_CONFIGURATION'] = |
| 1101 TestUtils.configurationDir(testCase.configuration); | 1104 TestUtils.configurationDir(testCase.configuration); |
| 1102 Future processFuture = Process.start(command.executable, | 1105 Future processFuture = io.Process.start(command.executable, |
| 1103 command.arguments, | 1106 command.arguments, |
| 1104 options); | 1107 options); |
| 1105 processFuture.then((Process p) { | 1108 processFuture.then((io.Process p) { |
| 1106 process = p; | 1109 process = p; |
| 1107 process.onExit = processExitHandler; | 1110 process.onExit = processExitHandler; |
| 1108 drainStream(process.stdout, stdout); | 1111 drainStream(process.stdout, stdout); |
| 1109 drainStream(process.stderr, stderr); | 1112 drainStream(process.stderr, stderr); |
| 1110 if (timeoutTimer == null) { | 1113 if (timeoutTimer == null) { |
| 1111 // Create one timeout timer when starting test case, remove it at | 1114 // Create one timeout timer when starting test case, remove it at |
| 1112 // the end. | 1115 // the end. |
| 1113 timeoutTimer = new Timer(1000 * testCase.timeout, timeoutHandler); | 1116 timeoutTimer = new Timer(1000 * testCase.timeout, timeoutHandler); |
| 1114 } | 1117 } |
| 1115 // If the timeout fired in between two commands, kill the just | 1118 // If the timeout fired in between two commands, kill the just |
| 1116 // started process immediately. | 1119 // started process immediately. |
| 1117 if (timedOut) safeKill(process); | 1120 if (timedOut) safeKill(process); |
| 1118 }); | 1121 }).catchError((e) { |
| 1119 processFuture.handleException((e) { | |
| 1120 print("Process error:"); | 1122 print("Process error:"); |
| 1121 print(" Command: $command"); | 1123 print(" Command: $command"); |
| 1122 print(" Error: $e"); | 1124 print(" Error: $e"); |
| 1123 testComplete(createCommandOutput(command, -1, false)); | 1125 testComplete(createCommandOutput(command, -1, false)); |
| 1124 return true; | 1126 return true; |
| 1125 }); | 1127 }); |
| 1126 } | 1128 } |
| 1127 }); | 1129 }); |
| 1128 } | 1130 } |
| 1129 | 1131 |
| 1130 void timeoutHandler(Timer unusedTimer) { | 1132 void timeoutHandler(Timer unusedTimer) { |
| 1131 timedOut = true; | 1133 timedOut = true; |
| 1132 safeKill(process); | 1134 safeKill(process); |
| 1133 } | 1135 } |
| 1134 | 1136 |
| 1135 void safeKill(Process p) { | 1137 void safeKill(io.Process p) { |
| 1136 if (p != null) { | 1138 if (p != null) { |
| 1137 try { | 1139 try { |
| 1138 p.kill(); | 1140 p.kill(); |
| 1139 } on ProcessException { | 1141 } on io.ProcessException { |
| 1140 // Hopefully, this means that the process died on its own. | 1142 // Hopefully, this means that the process died on its own. |
| 1141 } | 1143 } |
| 1142 } | 1144 } |
| 1143 } | 1145 } |
| 1144 } | 1146 } |
| 1145 | 1147 |
| 1146 /** | 1148 /** |
| 1147 * This class holds a value, that can be changed. It is used when | 1149 * This class holds a value, that can be changed. It is used when |
| 1148 * closures need a shared value, that they can all change and read. | 1150 * closures need a shared value, that they can all change and read. |
| 1149 */ | 1151 */ |
| 1150 class MutableValue<T> { | 1152 class MutableValue<T> { |
| 1151 MutableValue(T this.value); | 1153 MutableValue(T this.value); |
| 1152 T value; | 1154 T value; |
| 1153 } | 1155 } |
| 1154 | 1156 |
| 1155 class BatchRunnerProcess { | 1157 class BatchRunnerProcess { |
| 1156 Command _command; | 1158 Command _command; |
| 1157 String _executable; | 1159 String _executable; |
| 1158 List<String> _batchArguments; | 1160 List<String> _batchArguments; |
| 1159 | 1161 |
| 1160 Process _process; | 1162 io.Process _process; |
| 1161 StringInputStream _stdoutStream; | 1163 io.StringInputStream _stdoutStream; |
| 1162 StringInputStream _stderrStream; | 1164 io.StringInputStream _stderrStream; |
| 1163 | 1165 |
| 1164 TestCase _currentTest; | 1166 TestCase _currentTest; |
| 1165 List<int> _testStdout; | 1167 List<int> _testStdout; |
| 1166 List<int> _testStderr; | 1168 List<int> _testStderr; |
| 1167 String _status; | 1169 String _status; |
| 1168 bool _stdoutDrained = false; | 1170 bool _stdoutDrained = false; |
| 1169 bool _stderrDrained = false; | 1171 bool _stderrDrained = false; |
| 1170 MutableValue<bool> _ignoreStreams; | 1172 MutableValue<bool> _ignoreStreams; |
| 1171 Date _startTime; | 1173 Date _startTime; |
| 1172 Timer _timer; | 1174 Timer _timer; |
| (...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1291 // Move on when both stdout and stderr has been drained. | 1293 // Move on when both stdout and stderr has been drained. |
| 1292 if (_stdoutDrained) _reportResult(); | 1294 if (_stdoutDrained) _reportResult(); |
| 1293 } | 1295 } |
| 1294 | 1296 |
| 1295 void _stdoutDone() { | 1297 void _stdoutDone() { |
| 1296 _stdoutDrained = true; | 1298 _stdoutDrained = true; |
| 1297 // Move on when both stdout and stderr has been drained. | 1299 // Move on when both stdout and stderr has been drained. |
| 1298 if (_stderrDrained) _reportResult(); | 1300 if (_stderrDrained) _reportResult(); |
| 1299 } | 1301 } |
| 1300 | 1302 |
| 1301 void _readStdout(StringInputStream stream, List<int> buffer) { | 1303 void _readStdout(io.StringInputStream stream, List<int> buffer) { |
| 1302 var ignoreStreams = _ignoreStreams; // Capture this mutable object. | 1304 var ignoreStreams = _ignoreStreams; // Capture this mutable object. |
| 1303 void onLineHandler() { | 1305 void onLineHandler() { |
| 1304 if (ignoreStreams.value) { | 1306 if (ignoreStreams.value) { |
| 1305 while (stream.readLine() != null) { | 1307 while (stream.readLine() != null) { |
| 1306 // Do nothing. | 1308 // Do nothing. |
| 1307 } | 1309 } |
| 1308 return; | 1310 return; |
| 1309 } | 1311 } |
| 1310 // Otherwise, process output and call _reportResult() when done. | 1312 // Otherwise, process output and call _reportResult() when done. |
| 1311 var line = stream.readLine(); | 1313 var line = stream.readLine(); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 1322 line = stream.readLine(); | 1324 line = stream.readLine(); |
| 1323 } | 1325 } |
| 1324 if (_status != null) { | 1326 if (_status != null) { |
| 1325 _timer.cancel(); | 1327 _timer.cancel(); |
| 1326 _stdoutDone(); | 1328 _stdoutDone(); |
| 1327 } | 1329 } |
| 1328 } | 1330 } |
| 1329 stream.onLine = onLineHandler; | 1331 stream.onLine = onLineHandler; |
| 1330 } | 1332 } |
| 1331 | 1333 |
| 1332 void _readStderr(StringInputStream stream, List<int> buffer) { | 1334 void _readStderr(io.StringInputStream stream, List<int> buffer) { |
| 1333 var ignoreStreams = _ignoreStreams; // Capture this mutable object. | 1335 var ignoreStreams = _ignoreStreams; // Capture this mutable object. |
| 1334 void onLineHandler() { | 1336 void onLineHandler() { |
| 1335 if (ignoreStreams.value) { | 1337 if (ignoreStreams.value) { |
| 1336 while (stream.readLine() != null) { | 1338 while (stream.readLine() != null) { |
| 1337 // Do nothing. | 1339 // Do nothing. |
| 1338 } | 1340 } |
| 1339 return; | 1341 return; |
| 1340 } | 1342 } |
| 1341 // Otherwise, process output and call _reportResult() when done. | 1343 // Otherwise, process output and call _reportResult() when done. |
| 1342 var line = stream.readLine(); | 1344 var line = stream.readLine(); |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1378 } | 1380 } |
| 1379 return handler; | 1381 return handler; |
| 1380 } | 1382 } |
| 1381 | 1383 |
| 1382 void _timeoutHandler(ignore) { | 1384 void _timeoutHandler(ignore) { |
| 1383 _process.onExit = makeExitHandler(">>> TEST TIMEOUT"); | 1385 _process.onExit = makeExitHandler(">>> TEST TIMEOUT"); |
| 1384 _process.kill(); | 1386 _process.kill(); |
| 1385 } | 1387 } |
| 1386 | 1388 |
| 1387 _startProcess(callback) { | 1389 _startProcess(callback) { |
| 1388 Future processFuture = Process.start(_executable, _batchArguments); | 1390 Future processFuture = io.Process.start(_executable, _batchArguments); |
| 1389 processFuture.then((Process p) { | 1391 processFuture.then((io.Process p) { |
| 1390 _process = p; | 1392 _process = p; |
| 1391 _stdoutStream = new StringInputStream(_process.stdout); | 1393 _stdoutStream = new io.StringInputStream(_process.stdout); |
| 1392 _stderrStream = new StringInputStream(_process.stderr); | 1394 _stderrStream = new io.StringInputStream(_process.stderr); |
| 1393 _process.onExit = makeExitHandler(">>> TEST CRASH"); | 1395 _process.onExit = makeExitHandler(">>> TEST CRASH"); |
| 1394 callback(); | 1396 callback(); |
| 1395 }); | 1397 }).catchError((e) { |
| 1396 processFuture.handleException((e) { | |
| 1397 print("Process error:"); | 1398 print("Process error:"); |
| 1398 print(" Command: $_executable ${Strings.join(_batchArguments, ' ')}"); | 1399 print(" Command: $_executable ${Strings.join(_batchArguments, ' ')}"); |
| 1399 print(" Error: $e"); | 1400 print(" Error: $e"); |
| 1400 // If there is an error starting a batch process, chances are that | 1401 // If there is an error starting a batch process, chances are that |
| 1401 // it will always fail. So rather than re-trying a 1000+ times, we | 1402 // it will always fail. So rather than re-trying a 1000+ times, we |
| 1402 // exit. | 1403 // exit. |
| 1403 exit(1); | 1404 exit(1); |
| 1404 return true; | 1405 return true; |
| 1405 }); | 1406 }); |
| 1406 } | 1407 } |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1445 /** | 1446 /** |
| 1446 * String indicating the browser used to run the tests. Empty if no browser | 1447 * String indicating the browser used to run the tests. Empty if no browser |
| 1447 * used. | 1448 * used. |
| 1448 */ | 1449 */ |
| 1449 String browserUsed = ''; | 1450 String browserUsed = ''; |
| 1450 | 1451 |
| 1451 /** | 1452 /** |
| 1452 * Process running the selenium server .jar (only used for Safari and Opera | 1453 * Process running the selenium server .jar (only used for Safari and Opera |
| 1453 * tests.) | 1454 * tests.) |
| 1454 */ | 1455 */ |
| 1455 Process _seleniumServer = null; | 1456 io.Process _seleniumServer = null; |
| 1456 | 1457 |
| 1457 /** True if we are in the process of starting the server. */ | 1458 /** True if we are in the process of starting the server. */ |
| 1458 bool _startingServer = false; | 1459 bool _startingServer = false; |
| 1459 | 1460 |
| 1460 /** True if we find that there is already a selenium jar running. */ | 1461 /** True if we find that there is already a selenium jar running. */ |
| 1461 bool _seleniumAlreadyRunning = false; | 1462 bool _seleniumAlreadyRunning = false; |
| 1462 | 1463 |
| 1463 ProcessQueue(int this._maxProcesses, | 1464 ProcessQueue(int this._maxProcesses, |
| 1464 String progress, | 1465 String progress, |
| 1465 Date startTime, | 1466 Date startTime, |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1517 if (_tests.isEmpty && _numProcesses == 0) { | 1518 if (_tests.isEmpty && _numProcesses == 0) { |
| 1518 _terminateBatchRunners().then((_) => _cleanupAndMarkDone()); | 1519 _terminateBatchRunners().then((_) => _cleanupAndMarkDone()); |
| 1519 } | 1520 } |
| 1520 } | 1521 } |
| 1521 } | 1522 } |
| 1522 | 1523 |
| 1523 /** | 1524 /** |
| 1524 * True if we are using a browser + platform combination that needs the | 1525 * True if we are using a browser + platform combination that needs the |
| 1525 * Selenium server jar. | 1526 * Selenium server jar. |
| 1526 */ | 1527 */ |
| 1527 bool get _needsSelenium => (Platform.operatingSystem == 'macos' && | 1528 bool get _needsSelenium => (io.Platform.operatingSystem == 'macos' && |
| 1528 browserUsed == 'safari') || browserUsed == 'opera'; | 1529 browserUsed == 'safari') || browserUsed == 'opera'; |
| 1529 | 1530 |
| 1530 /** True if the Selenium Server is ready to be used. */ | 1531 /** True if the Selenium Server is ready to be used. */ |
| 1531 bool get _isSeleniumAvailable => _seleniumServer != null || | 1532 bool get _isSeleniumAvailable => _seleniumServer != null || |
| 1532 _seleniumAlreadyRunning; | 1533 _seleniumAlreadyRunning; |
| 1533 | 1534 |
| 1534 /** | 1535 /** |
| 1535 * Restart all the processes that have been waiting/stopped for the server to | 1536 * Restart all the processes that have been waiting/stopped for the server to |
| 1536 * start up. If we just call this once we end up with a single-"threaded" run. | 1537 * start up. If we just call this once we end up with a single-"threaded" run. |
| 1537 */ | 1538 */ |
| 1538 void resumeTesting() { | 1539 void resumeTesting() { |
| 1539 for (int i = 0; i < _maxProcesses; i++) _tryRunTest(); | 1540 for (int i = 0; i < _maxProcesses; i++) _tryRunTest(); |
| 1540 } | 1541 } |
| 1541 | 1542 |
| 1542 /** Start the Selenium Server jar, if appropriate for this platform. */ | 1543 /** Start the Selenium Server jar, if appropriate for this platform. */ |
| 1543 void _ensureSeleniumServerRunning() { | 1544 void _ensureSeleniumServerRunning() { |
| 1544 if (!_isSeleniumAvailable && !_startingServer) { | 1545 if (!_isSeleniumAvailable && !_startingServer) { |
| 1545 _startingServer = true; | 1546 _startingServer = true; |
| 1546 | 1547 |
| 1547 // Check to see if the jar was already running before the program started. | 1548 // Check to see if the jar was already running before the program started. |
| 1548 String cmd = 'ps'; | 1549 String cmd = 'ps'; |
| 1549 var arg = ['aux']; | 1550 var arg = ['aux']; |
| 1550 if (Platform.operatingSystem == 'windows') { | 1551 if (io.Platform.operatingSystem == 'windows') { |
| 1551 cmd = 'tasklist'; | 1552 cmd = 'tasklist'; |
| 1552 arg.add('/v'); | 1553 arg.add('/v'); |
| 1553 } | 1554 } |
| 1554 | 1555 |
| 1555 Future processFuture = Process.start(cmd, arg); | 1556 Future processFuture = io.Process.start(cmd, arg); |
| 1556 processFuture.then((Process p) { | 1557 processFuture.then((io.Process p) { |
| 1557 // Drain stderr to not leak resources. | 1558 // Drain stderr to not leak resources. |
| 1558 p.stderr.onData = p.stderr.read; | 1559 p.stderr.onData = p.stderr.read; |
| 1559 final StringInputStream stdoutStringStream = | 1560 final io.StringInputStream stdoutStringStream = |
| 1560 new StringInputStream(p.stdout); | 1561 new io.StringInputStream(p.stdout); |
| 1561 stdoutStringStream.onLine = () { | 1562 stdoutStringStream.onLine = () { |
| 1562 var line = stdoutStringStream.readLine(); | 1563 var line = stdoutStringStream.readLine(); |
| 1563 while (null != line) { | 1564 while (null != line) { |
| 1564 var regexp = new RegExp(r".*selenium-server-standalone.*"); | 1565 var regexp = new RegExp(r".*selenium-server-standalone.*"); |
| 1565 if (regexp.hasMatch(line)) { | 1566 if (regexp.hasMatch(line)) { |
| 1566 _seleniumAlreadyRunning = true; | 1567 _seleniumAlreadyRunning = true; |
| 1567 resumeTesting(); | 1568 resumeTesting(); |
| 1568 } | 1569 } |
| 1569 line = stdoutStringStream.readLine(); | 1570 line = stdoutStringStream.readLine(); |
| 1570 } | 1571 } |
| 1571 if (!_isSeleniumAvailable) { | 1572 if (!_isSeleniumAvailable) { |
| 1572 _startSeleniumServer(); | 1573 _startSeleniumServer(); |
| 1573 } | 1574 } |
| 1574 }; | 1575 }; |
| 1575 }); | 1576 }).catchError((e) { |
| 1576 processFuture.handleException((e) { | |
| 1577 print("Error starting process:"); | 1577 print("Error starting process:"); |
| 1578 print(" Command: $cmd ${Strings.join(arg, ' ')}"); | 1578 print(" Command: $cmd ${Strings.join(arg, ' ')}"); |
| 1579 print(" Error: $e"); | 1579 print(" Error: $e"); |
| 1580 // TODO(ahe): How to report this as a test failure? | 1580 // TODO(ahe): How to report this as a test failure? |
| 1581 exit(1); | 1581 exit(1); |
| 1582 return true; | 1582 return true; |
| 1583 }); | 1583 }); |
| 1584 } | 1584 } |
| 1585 } | 1585 } |
| 1586 | 1586 |
| 1587 void _runTest(TestCase test) { | 1587 void _runTest(TestCase test) { |
| 1588 if (test.usesWebDriver) { | 1588 if (test.usesWebDriver) { |
| 1589 browserUsed = test.configuration['browser']; | 1589 browserUsed = test.configuration['browser']; |
| 1590 if (_needsSelenium) _ensureSeleniumServerRunning(); | 1590 if (_needsSelenium) _ensureSeleniumServerRunning(); |
| 1591 } | 1591 } |
| 1592 _progress.testAdded(); | 1592 _progress.testAdded(); |
| 1593 _tests.add(test); | 1593 _tests.add(test); |
| 1594 _tryRunTest(); | 1594 _tryRunTest(); |
| 1595 } | 1595 } |
| 1596 | 1596 |
| 1597 /** | 1597 /** |
| 1598 * Monitor the output of the Selenium server, to know when we are ready to | 1598 * Monitor the output of the Selenium server, to know when we are ready to |
| 1599 * begin running tests. | 1599 * begin running tests. |
| 1600 * source: Output(Stream) from the Java server. | 1600 * source: Output(Stream) from the Java server. |
| 1601 */ | 1601 */ |
| 1602 VoidFunction makeSeleniumServerHandler(StringInputStream source) { | 1602 VoidFunction makeSeleniumServerHandler(io.StringInputStream source) { |
| 1603 void handler() { | 1603 void handler() { |
| 1604 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. | 1604 if (source.closed) return; // TODO(whesse): Remove when bug is fixed. |
| 1605 var line = source.readLine(); | 1605 var line = source.readLine(); |
| 1606 while (null != line) { | 1606 while (null != line) { |
| 1607 if (new RegExp(r".*Started.*Server.*").hasMatch(line) || | 1607 if (new RegExp(r".*Started.*Server.*").hasMatch(line) || |
| 1608 new RegExp(r"Exception.*Selenium is already running.*").hasMatch( | 1608 new RegExp(r"Exception.*Selenium is already running.*").hasMatch( |
| 1609 line)) { | 1609 line)) { |
| 1610 resumeTesting(); | 1610 resumeTesting(); |
| 1611 } | 1611 } |
| 1612 line = source.readLine(); | 1612 line = source.readLine(); |
| 1613 } | 1613 } |
| 1614 } | 1614 } |
| 1615 return handler; | 1615 return handler; |
| 1616 } | 1616 } |
| 1617 | 1617 |
| 1618 /** | 1618 /** |
| 1619 * For browser tests using Safari or Opera, we need to use the Selenium 1.0 | 1619 * For browser tests using Safari or Opera, we need to use the Selenium 1.0 |
| 1620 * Java server. | 1620 * Java server. |
| 1621 */ | 1621 */ |
| 1622 void _startSeleniumServer() { | 1622 void _startSeleniumServer() { |
| 1623 // Get the absolute path to the Selenium jar. | 1623 // Get the absolute path to the Selenium jar. |
| 1624 String filePath = TestUtils.testScriptPath; | 1624 String filePath = TestUtils.testScriptPath; |
| 1625 String pathSep = Platform.pathSeparator; | 1625 String pathSep = io.Platform.pathSeparator; |
| 1626 int index = filePath.lastIndexOf(pathSep); | 1626 int index = filePath.lastIndexOf(pathSep); |
| 1627 filePath = '${filePath.substring(0, index)}${pathSep}testing${pathSep}'; | 1627 filePath = '${filePath.substring(0, index)}${pathSep}testing${pathSep}'; |
| 1628 var lister = new Directory(filePath).list(); | 1628 var lister = new io.Directory(filePath).list(); |
| 1629 lister.onFile = (String file) { | 1629 lister.onFile = (String file) { |
| 1630 if (new RegExp(r"selenium-server-standalone-.*\.jar").hasMatch(file) | 1630 if (new RegExp(r"selenium-server-standalone-.*\.jar").hasMatch(file) |
| 1631 && _seleniumServer == null) { | 1631 && _seleniumServer == null) { |
| 1632 Future processFuture = Process.start('java', ['-jar', file]); | 1632 Future processFuture = io.Process.start('java', ['-jar', file]); |
| 1633 processFuture.then((Process server) { | 1633 processFuture.then((io.Process server) { |
| 1634 _seleniumServer = server; | 1634 _seleniumServer = server; |
| 1635 // Heads up: there seems to an obscure data race of some form in | 1635 // Heads up: there seems to an obscure data race of some form in |
| 1636 // the VM between launching the server process and launching the test | 1636 // the VM between launching the server process and launching the test |
| 1637 // tasks that disappears when you read IO (which is convenient, since | 1637 // tasks that disappears when you read IO (which is convenient, since |
| 1638 // that is our condition for knowing that the server is ready). | 1638 // that is our condition for knowing that the server is ready). |
| 1639 StringInputStream stdoutStringStream = | 1639 io.StringInputStream stdoutStringStream = |
| 1640 new StringInputStream(_seleniumServer.stdout); | 1640 new io.StringInputStream(_seleniumServer.stdout); |
| 1641 StringInputStream stderrStringStream = | 1641 io.StringInputStream stderrStringStream = |
| 1642 new StringInputStream(_seleniumServer.stderr); | 1642 new io.StringInputStream(_seleniumServer.stderr); |
| 1643 stdoutStringStream.onLine = | 1643 stdoutStringStream.onLine = |
| 1644 makeSeleniumServerHandler(stdoutStringStream); | 1644 makeSeleniumServerHandler(stdoutStringStream); |
| 1645 stderrStringStream.onLine = | 1645 stderrStringStream.onLine = |
| 1646 makeSeleniumServerHandler(stderrStringStream); | 1646 makeSeleniumServerHandler(stderrStringStream); |
| 1647 }); | 1647 }).catchError((e) { |
| 1648 processFuture.handleException((e) { | |
| 1649 print("Process error:"); | 1648 print("Process error:"); |
| 1650 print(" Command: java -jar $file"); | 1649 print(" Command: java -jar $file"); |
| 1651 print(" Error: $e"); | 1650 print(" Error: $e"); |
| 1652 // TODO(ahe): How to report this as a test failure? | 1651 // TODO(ahe): How to report this as a test failure? |
| 1653 exit(1); | 1652 exit(1); |
| 1654 return true; | 1653 return true; |
| 1655 }); | 1654 }); |
| 1656 } | 1655 } |
| 1657 }; | 1656 }; |
| 1658 } | 1657 } |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1738 // the developer doesn't waste his or her time trying to fix a bunch of | 1737 // the developer doesn't waste his or her time trying to fix a bunch of |
| 1739 // tests that appear to be broken but were actually just flakes that | 1738 // tests that appear to be broken but were actually just flakes that |
| 1740 // didn't get retried because there had already been one failure. | 1739 // didn't get retried because there had already been one failure. |
| 1741 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; | 1740 bool allowRetry = _MAX_FAILED_NO_RETRY > _progress.numFailedTests; |
| 1742 new RunningProcess(test, allowRetry, this).start(); | 1741 new RunningProcess(test, allowRetry, this).start(); |
| 1743 } | 1742 } |
| 1744 _numProcesses++; | 1743 _numProcesses++; |
| 1745 } | 1744 } |
| 1746 } | 1745 } |
| 1747 } | 1746 } |
| OLD | NEW |