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