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:async"; | 14 import "dart:async"; |
| 15 import "dart:collection" show Queue; | 15 import "dart:collection" show Queue; |
| 16 // We need to use the 'io' prefix here, otherwise io.exitCode will shadow | 16 // We need to use the 'io' prefix here, otherwise io.exitCode will shadow |
| 17 // CommandOutput.exitCode in subclasses of CommandOutput. | 17 // CommandOutput.exitCode in subclasses of CommandOutput. |
| 18 import "dart:io" as io; | 18 import "dart:io" as io; |
| 19 import "dart:isolate"; | 19 import "dart:isolate"; |
| 20 import "dart:math" as math; | |
| 21 import 'dependency_graph.dart' as dgraph; | |
| 20 import "browser_controller.dart"; | 22 import "browser_controller.dart"; |
| 21 import "http_server.dart" as http_server; | 23 import "http_server.dart" as http_server; |
| 22 import "status_file_parser.dart"; | 24 import "status_file_parser.dart"; |
| 23 import "test_progress.dart"; | 25 import "test_progress.dart"; |
| 24 import "test_suite.dart"; | 26 import "test_suite.dart"; |
| 25 import "utils.dart"; | 27 import "utils.dart"; |
| 26 import 'record_and_replay.dart'; | 28 import 'record_and_replay.dart'; |
| 27 | 29 |
| 28 const int NO_TIMEOUT = 0; | 30 const int CRASHING_BROWSER_EXITCODE = -10; |
| 29 const int SLOW_TIMEOUT_MULTIPLIER = 4; | 31 const int SLOW_TIMEOUT_MULTIPLIER = 4; |
| 30 | 32 |
| 31 const int CRASHING_BROWSER_EXITCODE = -10; | |
| 32 | |
| 33 typedef void TestCaseEvent(TestCase testCase); | 33 typedef void TestCaseEvent(TestCase testCase); |
| 34 typedef void ExitCodeEvent(int exitCode); | 34 typedef void ExitCodeEvent(int exitCode); |
| 35 typedef void EnqueueMoreWork(ProcessQueue queue); | 35 typedef void EnqueueMoreWork(ProcessQueue queue); |
| 36 | 36 |
| 37 // Some IO tests use these variables and get confused if the host environment | 37 // Some IO tests use these variables and get confused if the host environment |
| 38 // variables are inherited so they are excluded. | 38 // variables are inherited so they are excluded. |
| 39 const List<String> EXCLUDED_ENVIRONMENT_VARIABLES = | 39 const List<String> EXCLUDED_ENVIRONMENT_VARIABLES = |
| 40 const ['http_proxy', 'https_proxy', 'no_proxy', | 40 const ['http_proxy', 'https_proxy', 'no_proxy', |
| 41 'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']; | 41 'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']; |
| 42 | 42 |
| 43 | 43 |
| 44 /** | |
| 45 * [areByteArraysEqual] compares a range of bytes from [buffer1] with a | |
| 46 * range of bytes from [buffer2]. | |
| 47 * | |
| 48 * Returns [true] if the [count] bytes in [buffer1] (starting at | |
| 49 * [offset1]) match the [count] bytes in [buffer2] (starting at | |
| 50 * [offset2]). | |
| 51 * Otherwise [false] is returned. | |
| 52 */ | |
| 53 bool areByteArraysEqual(List<int> buffer1, int offset1, | |
| 54 List<int> buffer2, int offset2, | |
| 55 int count) { | |
| 56 if ((offset1 + count) > buffer1.length || | |
| 57 (offset2 + count) > buffer2.length) { | |
| 58 return false; | |
| 59 } | |
| 60 | |
| 61 for (var i = 0; i < count; i++) { | |
| 62 if (buffer1[offset1 + i] != buffer2[offset2 + i]) { | |
| 63 return false; | |
| 64 } | |
| 65 } | |
| 66 return true; | |
| 67 } | |
| 68 | |
| 69 /** | |
| 70 * [findBytes] searches for [pattern] in [data] beginning at [startPos]. | |
| 71 * | |
| 72 * Returns [true] if [pattern] was found in [data]. | |
| 73 * Otherwise [false] is returned. | |
| 74 */ | |
| 75 int findBytes(List<int> data, List<int> pattern, [int startPos=0]) { | |
| 76 // TODO(kustermann): Use one of the fast string-matching algorithms! | |
| 77 for (int i=startPos; i < (data.length-pattern.length); i++) { | |
| 78 bool found = true; | |
| 79 for (int j=0; j<pattern.length; j++) { | |
| 80 if (data[i+j] != pattern[j]) { | |
| 81 found = false; | |
| 82 } | |
| 83 } | |
| 84 if (found) { | |
| 85 return i; | |
| 86 } | |
| 87 } | |
| 88 return -1; | |
| 89 } | |
| 90 | |
| 91 | |
| 92 /** A command executed as a step in a test case. */ | 44 /** A command executed as a step in a test case. */ |
| 93 class Command { | 45 class Command { |
| 94 static int nextHashCode = 0; | |
| 95 final int hashCode = nextHashCode++; | |
| 96 operator ==(other) => super == (other); | |
| 97 | |
| 98 /** Path to the executable of this command. */ | 46 /** Path to the executable of this command. */ |
| 99 String executable; | 47 String executable; |
| 100 | 48 |
| 101 /** Command line arguments to the executable. */ | |
| 102 List<String> arguments; | |
| 103 | |
| 104 /** Environment for the command */ | |
| 105 Map<String,String> environment; | |
| 106 | |
| 107 /** The actual command line that will be executed. */ | 49 /** The actual command line that will be executed. */ |
| 108 String commandLine; | 50 String commandLine; |
| 109 | 51 |
| 110 /** A descriptive name for this command. */ | 52 /** A descriptive name for this command. */ |
| 111 String displayName; | 53 String displayName; |
| 112 | 54 |
| 113 Command(this.displayName, this.executable, | 55 /** Command line arguments to the executable. */ |
| 114 this.arguments, [this.environment = null]) { | 56 List<String> arguments; |
| 57 | |
| 58 /** Environment for the command */ | |
| 59 Map<String, String> environmentOverrides; | |
| 60 | |
| 61 /** Number of times this command can be retried */ | |
| 62 int get numRetries => 0; | |
| 63 | |
| 64 // We compute the Command.hashCode lazily and cache it here, since it might | |
| 65 // be expensive to compute (and hashCode is called often). | |
| 66 int _cachedHashCode; | |
| 67 | |
| 68 Command._(this.displayName, this.executable, | |
| 69 this.arguments, String configurationDir, | |
| 70 [this.environmentOverrides = null]) { | |
| 115 if (io.Platform.operatingSystem == 'windows') { | 71 if (io.Platform.operatingSystem == 'windows') { |
| 116 // Windows can't handle the first command if it is a .bat file or the like | 72 // Windows can't handle the first command if it is a .bat file or the like |
| 117 // with the slashes going the other direction. | 73 // with the slashes going the other direction. |
| 118 // TODO(efortuna): Remove this when fixed (Issue 1306). | 74 // TODO(efortuna): Remove this when fixed (Issue 1306). |
| 119 executable = executable.replaceAll('/', '\\'); | 75 executable = executable.replaceAll('/', '\\'); |
| 120 } | 76 } |
| 121 var quotedArguments = []; | 77 var quotedArguments = []; |
| 122 quotedArguments.add(escapeCommandLineArgument(executable)); | 78 quotedArguments.add(escapeCommandLineArgument(executable)); |
| 123 quotedArguments.addAll(arguments.map(escapeCommandLineArgument)); | 79 quotedArguments.addAll(arguments.map(escapeCommandLineArgument)); |
| 124 commandLine = quotedArguments.join(' '); | 80 commandLine = quotedArguments.join(' '); |
| 81 | |
| 82 if (configurationDir != null) { | |
| 83 if (environmentOverrides == null) { | |
| 84 environmentOverrides = new Map<String, String>(); | |
| 85 } | |
| 86 environmentOverrides['DART_CONFIGURATION'] = configurationDir; | |
| 87 } | |
| 88 } | |
| 89 | |
| 90 int get hashCode { | |
| 91 if (_cachedHashCode == null) { | |
| 92 var builder = new HashCodeBuilder(); | |
| 93 _buildHashCode(builder); | |
| 94 _cachedHashCode = builder.value; | |
| 95 } | |
| 96 return _cachedHashCode; | |
| 97 } | |
| 98 | |
| 99 operator ==(other) { | |
| 100 if (other is Command) { | |
| 101 return identical(this, other) || _equal(other as Command); | |
| 102 } | |
| 103 return false; | |
| 104 } | |
| 105 | |
| 106 void _buildHashCode(HashCodeBuilder builder) { | |
| 107 builder.add(executable); | |
| 108 builder.add(commandLine); | |
| 109 builder.add(displayName); // FIXME(kustermann): Yes or No? | |
|
ricow1
2013/08/01 18:23:52
fix this non-informative fixme :-)
kustermann
2013/08/05 07:29:17
Done.
| |
| 110 for (var object in arguments) builder.add(object); | |
| 111 if (environmentOverrides != null) { | |
| 112 for (var key in environmentOverrides.keys) builder.add(environmentOverride s[key]); | |
|
ricow1
2013/08/01 18:23:52
Do we want to add the key to builder as well?
kustermann
2013/08/05 07:29:17
I don't think we're getting many collisions anyway
| |
| 113 } | |
| 114 } | |
| 115 | |
| 116 bool _equal(Command other) { | |
| 117 if (hashCode != other.hashCode || | |
| 118 executable != other.executable || | |
| 119 commandLine != other.commandLine || | |
| 120 displayName != other.displayName || // FIXME(kustermann): Yes or No? | |
|
ricow1
2013/08/01 18:23:52
same as above
kustermann
2013/08/05 07:29:17
Done.
| |
| 121 arguments.length != other.arguments.length) { | |
| 122 return false; | |
| 123 } | |
| 124 | |
| 125 if ((environmentOverrides != other.environmentOverrides) && | |
| 126 (environmentOverrides == null || other.environmentOverrides == null)) { | |
| 127 return false; | |
| 128 } | |
| 129 | |
|
ricow1
2013/08/01 18:23:52
add:
if (environmentOverrides == null) return true
kustermann
2013/08/05 07:29:17
Again, this is not possible, Just because
environ
| |
| 130 if (environmentOverrides != null && | |
| 131 environmentOverrides.length != other.environmentOverrides.length) { | |
| 132 return false; | |
| 133 } | |
| 134 | |
| 135 for (var i = 0; i < arguments.length; i++) { | |
| 136 if (arguments[i] != other.arguments[i]) return false; | |
| 137 } | |
| 138 | |
| 139 if (environmentOverrides != null) { | |
| 140 for (var key in environmentOverrides.keys) { | |
| 141 if (!other.environmentOverrides.containsKey(key) || | |
| 142 environmentOverrides[key] != other.environmentOverrides[key]) { | |
| 143 return false; | |
| 144 } | |
| 145 } | |
| 146 } | |
| 147 return true; | |
| 125 } | 148 } |
| 126 | 149 |
| 127 String toString() => commandLine; | 150 String toString() => commandLine; |
| 128 | 151 |
| 129 Future<bool> get outputIsUpToDate => new Future.value(false); | 152 Future<bool> get outputIsUpToDate => new Future.value(false); |
| 130 io.Path get expectedOutputFile => null; | 153 io.Path get expectedOutputFile => null; |
| 131 bool get isPixelTest => false; | 154 bool get isPixelTest => false; |
| 132 } | 155 } |
| 133 | 156 |
| 134 class CompilationCommand extends Command { | 157 class CompilationCommand extends Command { |
| 135 String _outputFile; | 158 String _outputFile; |
| 136 bool _neverSkipCompilation; | 159 bool _neverSkipCompilation; |
| 137 List<Uri> _bootstrapDependencies; | 160 List<Uri> _bootstrapDependencies; |
| 138 | 161 |
| 139 CompilationCommand(String displayName, | 162 CompilationCommand._(String displayName, |
| 140 this._outputFile, | 163 this._outputFile, |
| 141 this._neverSkipCompilation, | 164 this._neverSkipCompilation, |
| 142 this._bootstrapDependencies, | 165 List<String> bootstrapDependencies, |
| 143 String executable, | 166 String executable, |
| 144 List<String> arguments) | 167 List<String> arguments, |
| 145 : super(displayName, executable, arguments); | 168 String configurationDir) |
| 169 : super._(displayName, executable, arguments, configurationDir) { | |
| 170 // We sort here, so we can do a fast hashCode/operator== | |
| 171 _bootstrapDependencies = new List.from(bootstrapDependencies); | |
| 172 _bootstrapDependencies.sort(); | |
| 173 } | |
| 146 | 174 |
| 147 Future<bool> get outputIsUpToDate { | 175 Future<bool> get outputIsUpToDate { |
| 148 if (_neverSkipCompilation) return new Future.value(false); | 176 if (_neverSkipCompilation) return new Future.value(false); |
| 149 | 177 |
| 150 Future<List<Uri>> readDepsFile(String path) { | 178 Future<List<Uri>> readDepsFile(String path) { |
| 151 var file = new io.File(new io.Path(path).toNativePath()); | 179 var file = new io.File(new io.Path(path).toNativePath()); |
| 152 if (!file.existsSync()) { | 180 if (!file.existsSync()) { |
| 153 return new Future.value(null); | 181 return new Future.value(null); |
| 154 } | 182 } |
| 155 return file.readAsLines().then((List<String> lines) { | 183 return file.readAsLines().then((List<String> lines) { |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 177 dependencyLastModified.isAfter(jsOutputLastModified)) { | 205 dependencyLastModified.isAfter(jsOutputLastModified)) { |
| 178 return false; | 206 return false; |
| 179 } | 207 } |
| 180 } | 208 } |
| 181 return true; | 209 return true; |
| 182 } | 210 } |
| 183 } | 211 } |
| 184 return false; | 212 return false; |
| 185 }); | 213 }); |
| 186 } | 214 } |
| 215 | |
| 216 void _buildHashCode(HashCodeBuilder builder) { | |
| 217 super._buildHashCode(builder); | |
| 218 builder.add(_outputFile); | |
| 219 builder.add(_neverSkipCompilation); | |
| 220 for (var uri in _bootstrapDependencies) builder.add(uri); | |
| 221 } | |
| 222 | |
| 223 bool _equal(Command other) { | |
| 224 if (other is CompilationCommand && | |
| 225 super._equal(other) && | |
| 226 _outputFile == other._outputFile && | |
| 227 _neverSkipCompilation == other._neverSkipCompilation && | |
| 228 _bootstrapDependencies.length == other._bootstrapDependencies.length) { | |
| 229 for (var i = 0; i < _bootstrapDependencies.length; i++) { | |
| 230 if (_bootstrapDependencies[i] != other._bootstrapDependencies[i]) { | |
| 231 return false; | |
| 232 } | |
| 233 } | |
| 234 return true; | |
| 235 } | |
| 236 return false; | |
| 237 } | |
| 187 } | 238 } |
| 188 | 239 |
| 189 class ContentShellCommand extends Command { | 240 class ContentShellCommand extends Command { |
| 190 /** | 241 /** |
| 191 * If [expectedOutputPath] is set, the output of content shell is compared | 242 * If [expectedOutputPath] is set, the output of content shell is compared |
| 192 * with the content of [expectedOutputPath]. | 243 * with the content of [expectedOutputPath]. |
| 193 * This is used for example for pixel tests, where [expectedOutputPath] points | 244 * This is used for example for pixel tests, where [expectedOutputPath] points |
| 194 * to a *png file. | 245 * to a *png file. |
| 195 */ | 246 */ |
| 196 io.Path expectedOutputPath; | 247 io.Path expectedOutputPath; |
| 197 | 248 |
| 198 ContentShellCommand(String executable, | 249 ContentShellCommand._(String executable, |
| 199 String htmlFile, | 250 String htmlFile, |
| 200 List<String> options, | 251 List<String> options, |
| 201 List<String> dartFlags, | 252 List<String> dartFlags, |
| 202 io.Path this.expectedOutputPath) | 253 io.Path this.expectedOutputPath, |
| 203 : super("content_shell", | 254 String configurationDir) |
| 204 executable, | 255 : super._("content_shell", |
| 205 _getArguments(options, htmlFile), | 256 executable, |
| 206 _getEnvironment(dartFlags)); | 257 _getArguments(options, htmlFile), |
| 258 configurationDir, | |
| 259 _getEnvironment(dartFlags)); | |
| 207 | 260 |
| 208 static Map _getEnvironment(List<String> dartFlags) { | 261 static Map _getEnvironment(List<String> dartFlags) { |
| 209 var needDartFlags = dartFlags != null && dartFlags.length > 0; | 262 var needDartFlags = dartFlags != null && dartFlags.length > 0; |
| 210 | 263 |
| 211 var env = null; | 264 var env = null; |
| 212 if (needDartFlags) { | 265 if (needDartFlags) { |
| 213 env = new Map.from(io.Platform.environment); | 266 env = new Map<String, String>(); |
| 214 if (needDartFlags) { | 267 env['DART_FLAGS'] = dartFlags.join(" "); |
| 215 env['DART_FLAGS'] = dartFlags.join(" "); | |
| 216 } | |
| 217 } | 268 } |
| 218 | 269 |
| 219 return env; | 270 return env; |
| 220 } | 271 } |
| 221 | 272 |
| 222 static List<String> _getArguments(List<String> options, String htmlFile) { | 273 static List<String> _getArguments(List<String> options, String htmlFile) { |
| 223 var arguments = new List.from(options); | 274 var arguments = new List.from(options); |
| 224 arguments.add(htmlFile); | 275 arguments.add(htmlFile); |
| 225 return arguments; | 276 return arguments; |
| 226 } | 277 } |
| 227 | 278 |
| 228 io.Path get expectedOutputFile => expectedOutputPath; | 279 io.Path get expectedOutputFile => expectedOutputPath; |
| 229 bool get isPixelTest => (expectedOutputFile != null && | 280 bool get isPixelTest => (expectedOutputFile != null && |
| 230 expectedOutputFile.filename.endsWith(".png")); | 281 expectedOutputFile.filename.endsWith(".png")); |
| 282 | |
| 283 void _buildHashCode(HashCodeBuilder builder) { | |
| 284 super._buildHashCode(builder); | |
| 285 builder.add(expectedOutputPath.toString()); | |
| 286 } | |
| 287 | |
| 288 bool _equal(Command other) { | |
| 289 return | |
| 290 other is ContentShellCommand && | |
| 291 super._equal(other) && | |
| 292 expectedOutputPath.toString() == other.expectedOutputPath.toString(); | |
| 293 } | |
| 294 | |
| 295 // FIXME(kustermann): Remove this once we're stable | |
| 296 int get numRetries => 2; | |
| 231 } | 297 } |
| 232 | 298 |
| 299 class BrowserTestCommand extends Command { | |
| 300 final String browser; | |
| 301 final String url; | |
| 302 | |
| 303 BrowserTestCommand._(String _browser, | |
| 304 this.url, | |
| 305 String executable, | |
| 306 List<String> arguments, | |
| 307 String configurationDir) | |
| 308 : super._(_browser, executable, arguments, configurationDir), | |
| 309 browser = _browser; | |
| 310 | |
| 311 void _buildHashCode(HashCodeBuilder builder) { | |
| 312 super._buildHashCode(builder); | |
| 313 builder.add(browser); | |
| 314 builder.add(url); | |
| 315 } | |
| 316 | |
| 317 bool _equal(Command other) { | |
| 318 return | |
| 319 other is BrowserTestCommand && | |
| 320 super._equal(other) && | |
| 321 browser == other.browser && | |
| 322 url == other.url; | |
| 323 } | |
| 324 | |
| 325 // FIXME(kustermann): Remove this once we're stable | |
| 326 int get numRetries => 2; | |
| 327 } | |
| 328 | |
| 329 class SeleniumTestCommand extends Command { | |
| 330 final String browser; | |
| 331 final String url; | |
| 332 | |
| 333 SeleniumTestCommand._(String _browser, | |
| 334 this.url, | |
|
ricow1
2013/08/01 18:23:52
indentation
kustermann
2013/08/05 07:29:17
Done.
| |
| 335 String executable, | |
| 336 List<String> arguments, | |
| 337 String configurationDir) | |
| 338 : super._(_browser, executable, arguments, configurationDir), | |
| 339 browser = _browser; | |
| 340 | |
| 341 void _buildHashCode(HashCodeBuilder builder) { | |
| 342 super._buildHashCode(builder); | |
| 343 builder.add(browser); | |
| 344 builder.add(url); | |
| 345 } | |
| 346 | |
| 347 bool _equal(Command other) { | |
| 348 return | |
| 349 other is SeleniumTestCommand && | |
| 350 super._equal(other) && | |
| 351 browser == other.browser && | |
| 352 url == other.url; | |
| 353 } | |
| 354 | |
| 355 // FIXME(kustermann): Remove this once we're stable | |
| 356 int get numRetries => 2; | |
| 357 } | |
| 358 | |
| 359 class AnalysisCommand extends Command { | |
| 360 final String flavour; | |
| 361 | |
| 362 AnalysisCommand._(this.flavour, | |
| 363 String displayName, | |
| 364 String executable, | |
| 365 List<String> arguments, | |
| 366 String configurationDir) | |
| 367 : super._(displayName, executable, arguments, configurationDir); | |
| 368 | |
| 369 void _buildHashCode(HashCodeBuilder builder) { | |
| 370 super._buildHashCode(builder); | |
| 371 builder.add(flavour); | |
| 372 } | |
| 373 | |
| 374 bool _equal(Command other) { | |
| 375 return | |
| 376 other is AnalysisCommand && | |
| 377 super._equal(other) && | |
| 378 flavour == other.flavour; | |
| 379 } | |
| 380 } | |
| 381 | |
| 382 class CommandBuilder { | |
| 383 static final instance = new CommandBuilder._(); | |
| 384 | |
| 385 final _cachedCommands = new Map<Command, Command>(); | |
| 386 | |
| 387 CommandBuilder._(); | |
| 388 | |
| 389 ContentShellCommand getContentShellCommand(String executable, | |
| 390 String htmlFile, | |
| 391 List<String> options, | |
| 392 List<String> dartFlags, | |
| 393 io.Path expectedOutputPath, | |
| 394 String configurationDir) { | |
| 395 ContentShellCommand command = new ContentShellCommand._( | |
| 396 executable, htmlFile, options, dartFlags, expectedOutputPath, | |
| 397 configurationDir); | |
| 398 return _getUniqueCommand(command); | |
| 399 } | |
| 400 | |
| 401 BrowserTestCommand getBrowserTestCommand(String browser, | |
| 402 String url, | |
| 403 String executable, | |
| 404 List<String> arguments, | |
| 405 String configurationDir) { | |
| 406 var command = new BrowserTestCommand._( | |
| 407 browser, url, executable, arguments, configurationDir); | |
| 408 return _getUniqueCommand(command); | |
| 409 } | |
| 410 | |
| 411 SeleniumTestCommand getSeleniumTestCommand(String browser, | |
| 412 String url, | |
| 413 String executable, | |
| 414 List<String> arguments, | |
| 415 String configurationDir) { | |
| 416 var command = new SeleniumTestCommand._( | |
| 417 browser, url, executable, arguments, configurationDir); | |
| 418 return _getUniqueCommand(command); | |
| 419 } | |
| 420 | |
| 421 CompilationCommand getCompilationCommand(String displayName, | |
| 422 outputFile, | |
| 423 neverSkipCompilation, | |
| 424 List<String> bootstrapDependencies, | |
| 425 String executable, | |
| 426 List<String> arguments, | |
| 427 String configurationDir) { | |
| 428 var command = | |
| 429 new CompilationCommand._(displayName, outputFile, neverSkipCompilation, | |
| 430 bootstrapDependencies, executable, arguments, | |
| 431 configurationDir); | |
| 432 return _getUniqueCommand(command); | |
| 433 } | |
| 434 | |
| 435 AnalysisCommand getAnalysisCommand( | |
| 436 String displayName, executable, arguments, String configurationDir, | |
| 437 {String flavour: 'dartanalyzer'}) { | |
|
ricow1
2013/08/01 18:23:52
I think we normally use US spelling (i.e., flavour
kustermann
2013/08/05 07:29:17
Done.
| |
| 438 var command = new AnalysisCommand._( | |
| 439 flavour, displayName, executable, arguments, configurationDir); | |
| 440 return _getUniqueCommand(command); | |
| 441 } | |
| 442 | |
| 443 Command getCommand(String displayName, executable, arguments, | |
| 444 String configurationDir, [environment = null]) { | |
| 445 var command = new Command._(displayName, executable, arguments, | |
| 446 configurationDir, environment); | |
| 447 return _getUniqueCommand(command); | |
| 448 } | |
| 449 | |
| 450 Command _getUniqueCommand(Command command) { | |
| 451 // All Command classes have hashCode/operator==, so we check if this command | |
| 452 // has already been build, if so we return the cached one, otherwise we | |
| 453 // store the one given as [command] argument. | |
| 454 var cachedCommand = _cachedCommands[command]; | |
| 455 if (cachedCommand != null) { | |
| 456 return cachedCommand; | |
| 457 } | |
| 458 _cachedCommands[command] = command; | |
| 459 return command; | |
| 460 } | |
| 461 } | |
| 233 | 462 |
| 234 /** | 463 /** |
| 235 * TestCase contains all the information needed to run a test and evaluate | 464 * TestCase contains all the information needed to run a test and evaluate |
| 236 * its output. Running a test involves starting a separate process, with | 465 * its output. Running a test involves starting a separate process, with |
| 237 * the executable and arguments given by the TestCase, and recording its | 466 * the executable and arguments given by the TestCase, and recording its |
| 238 * stdout and stderr output streams, and its exit code. TestCase only | 467 * stdout and stderr output streams, and its exit code. TestCase only |
| 239 * contains static information about the test; actually running the test is | 468 * contains static information about the test; actually running the test is |
| 240 * performed by [ProcessQueue] using a [RunningProcess] object. | 469 * performed by [ProcessQueue] using a [RunningProcess] object. |
| 241 * | 470 * |
| 242 * The output information is stored in a [CommandOutput] instance contained | 471 * The output information is stored in a [CommandOutput] instance contained |
| 243 * in TestCase.commandOutputs. The last CommandOutput instance is responsible | 472 * in TestCase.commandOutputs. The last CommandOutput instance is responsible |
| 244 * for evaluating if the test has passed, failed, crashed, or timed out, and the | 473 * for evaluating if the test has passed, failed, crashed, or timed out, and the |
| 245 * TestCase has information about what the expected result of the test should | 474 * TestCase has information about what the expected result of the test should |
| 246 * be. | 475 * be. |
| 247 * | 476 * |
| 248 * The TestCase has a callback function, [completedHandler], that is run when | 477 * The TestCase has a callback function, [completedHandler], that is run when |
| 249 * the test is completed. | 478 * the test is completed. |
| 250 */ | 479 */ |
| 251 class TestCase { | 480 class TestCase extends UniqueObject { |
| 252 /** | 481 /** |
| 253 * A list of commands to execute. Most test cases have a single command. | 482 * A list of commands to execute. Most test cases have a single command. |
| 254 * Dart2js tests have two commands, one to compile the source and another | 483 * Dart2js tests have two commands, one to compile the source and another |
| 255 * to execute it. Some isolate tests might even have three, if they require | 484 * to execute it. Some isolate tests might even have three, if they require |
| 256 * compiling multiple sources that are run in isolation. | 485 * compiling multiple sources that are run in isolation. |
| 257 */ | 486 */ |
| 258 List<Command> commands; | 487 List<Command> commands; |
| 259 Map<Command, CommandOutput> commandOutputs = new Map<Command,CommandOutput>(); | 488 Map<Command, CommandOutput> commandOutputs = new Map<Command,CommandOutput>(); |
| 260 | 489 |
| 261 Map configuration; | 490 Map configuration; |
| 262 String displayName; | 491 String displayName; |
| 263 bool isNegative; | 492 bool isNegative; |
| 264 Set<String> expectedOutcomes; | 493 Set<String> expectedOutcomes; |
| 265 TestCaseEvent completedHandler; | |
| 266 TestInformation info; | 494 TestInformation info; |
| 267 | 495 |
| 268 TestCase(this.displayName, | 496 TestCase(this.displayName, |
| 269 this.commands, | 497 this.commands, |
| 270 this.configuration, | 498 this.configuration, |
| 271 this.completedHandler, | |
| 272 this.expectedOutcomes, | 499 this.expectedOutcomes, |
| 273 {this.isNegative: false, | 500 {this.isNegative: false, |
| 274 this.info: null}) { | 501 this.info: null}) { |
| 275 if (!isNegative) { | 502 if (!isNegative) { |
| 276 this.isNegative = displayName.contains("negative_test"); | 503 this.isNegative = displayName.contains("negative_test"); |
| 277 } | 504 } |
| 505 } | |
| 278 | 506 |
| 279 // Special command handling. If a special command is specified | 507 bool get unexpectedOutput { |
| 280 // we have to completely rewrite the command that we are using. | 508 return !expectedOutcomes.contains(lastCommandOutput.result(this)); |
| 281 // We generate a new command-line that is the special command where we | 509 } |
| 282 // replace '@' with the original command executable, and generate | |
| 283 // a command formed like the following | |
| 284 // Let PREFIX be what is before the @. | |
| 285 // Let SUFFIX be what is after the @. | |
| 286 // Let EXECUTABLE be the existing executable of the command. | |
| 287 // Let ARGUMENTS be the existing arguments to the existing executable. | |
| 288 // The new command will be: | |
| 289 // PREFIX EXECUTABLE SUFFIX ARGUMENTS | |
| 290 var specialCommand = configuration['special-command']; | |
| 291 if (!specialCommand.isEmpty) { | |
| 292 if (!specialCommand.contains('@')) { | |
| 293 throw new FormatException("special-command must contain a '@' char"); | |
| 294 } | |
| 295 var specialCommandSplit = specialCommand.split('@'); | |
| 296 var prefix = specialCommandSplit[0].trim(); | |
| 297 var suffix = specialCommandSplit[1].trim(); | |
| 298 List<Command> newCommands = []; | |
| 299 for (Command c in commands) { | |
| 300 // If we don't have a new prefix we will use the existing executable. | |
| 301 var newExecutablePath = c.executable;; | |
| 302 var newArguments = []; | |
| 303 | 510 |
| 304 if (prefix.length > 0) { | 511 String get result => lastCommandOutput.result(this); |
| 305 var prefixSplit = prefix.split(' '); | |
| 306 newExecutablePath = prefixSplit[0]; | |
| 307 for (int i = 1; i < prefixSplit.length; i++) { | |
| 308 var current = prefixSplit[i]; | |
| 309 if (!current.isEmpty) newArguments.add(current); | |
| 310 } | |
| 311 newArguments.add(c.executable); | |
| 312 } | |
| 313 | |
| 314 // Add any suffixes to the arguments of the original executable. | |
| 315 var suffixSplit = suffix.split(' '); | |
| 316 suffixSplit.forEach((e) { | |
| 317 if (!e.isEmpty) newArguments.add(e); | |
| 318 }); | |
| 319 | |
| 320 newArguments.addAll(c.arguments); | |
| 321 final newCommand = new Command(newExecutablePath, newArguments); | |
| 322 newCommands.add(newCommand); | |
| 323 } | |
| 324 commands = newCommands; | |
| 325 } | |
| 326 } | |
| 327 | 512 |
| 328 CommandOutput get lastCommandOutput { | 513 CommandOutput get lastCommandOutput { |
| 329 if (commandOutputs.length == 0) { | 514 if (commandOutputs.length == 0) { |
| 330 throw new Exception("CommandOutputs is empty, maybe no command was run? (" | 515 throw new Exception("CommandOutputs is empty, maybe no command was run? (" |
| 331 "displayName: '$displayName', " | 516 "displayName: '$displayName', " |
| 332 "configurationString: '$configurationString')"); | 517 "configurationString: '$configurationString')"); |
| 333 } | 518 } |
| 334 return commandOutputs[commands[commandOutputs.length - 1]]; | 519 return commandOutputs[commands[commandOutputs.length - 1]]; |
| 335 } | 520 } |
| 336 | 521 |
| 337 int get timeout { | 522 int get timeout { |
| 338 if (expectedOutcomes.contains(SLOW)) { | 523 if (expectedOutcomes.contains(SLOW)) { |
| 339 return configuration['timeout'] * SLOW_TIMEOUT_MULTIPLIER; | 524 return configuration['timeout'] * SLOW_TIMEOUT_MULTIPLIER; |
| 340 } else { | 525 } else { |
| 341 return configuration['timeout']; | 526 return configuration['timeout']; |
| 342 } | 527 } |
| 343 } | 528 } |
| 344 | 529 |
| 345 String get configurationString { | 530 String get configurationString { |
| 346 final compiler = configuration['compiler']; | 531 final compiler = configuration['compiler']; |
| 347 final runtime = configuration['runtime']; | 532 final runtime = configuration['runtime']; |
| 348 final mode = configuration['mode']; | 533 final mode = configuration['mode']; |
| 349 final arch = configuration['arch']; | 534 final arch = configuration['arch']; |
| 350 final checked = configuration['checked'] ? '-checked' : ''; | 535 final checked = configuration['checked'] ? '-checked' : ''; |
| 351 return "$compiler-$runtime$checked ${mode}_$arch"; | 536 return "$compiler-$runtime$checked ${mode}_$arch"; |
| 352 } | 537 } |
| 353 | 538 |
| 354 List<String> get batchRunnerArguments => ['-batch']; | |
| 355 List<String> get batchTestArguments => commands.last.arguments; | 539 List<String> get batchTestArguments => commands.last.arguments; |
| 356 | 540 |
| 357 bool get usesWebDriver => TestUtils.usesWebDriver(configuration['runtime']); | 541 bool get usesWebDriver => TestUtils.usesWebDriver(configuration['runtime']); |
| 358 | 542 |
| 359 bool get usesBrowserController => configuration['use_browser_controller']; | |
| 360 | |
| 361 void completed() { completedHandler(this); } | |
| 362 | |
| 363 bool get isFlaky { | 543 bool get isFlaky { |
| 364 if (expectedOutcomes.contains(SKIP)) { | 544 if (expectedOutcomes.contains(SKIP)) { |
| 365 return false; | 545 return false; |
| 366 } | 546 } |
| 367 | 547 |
| 368 var flags = new Set.from(expectedOutcomes); | 548 var flags = new Set.from(expectedOutcomes); |
| 369 flags..remove(TIMEOUT) | 549 flags..remove(TIMEOUT) |
| 370 ..remove(SLOW); | 550 ..remove(SLOW); |
| 371 return flags.contains(PASS) && flags.length > 1; | 551 return flags.contains(PASS) && flags.length > 1; |
| 372 } | 552 } |
| 553 | |
| 554 bool get isFinished { | |
| 555 return !lastCommandOutput.successfull || | |
| 556 commands.length == commandOutputs.length; | |
| 557 } | |
| 373 } | 558 } |
| 374 | 559 |
| 375 | 560 |
| 376 /** | 561 /** |
| 377 * BrowserTestCase has an extra compilation command that is run in a separate | 562 * BrowserTestCase has an extra compilation command that is run in a separate |
| 378 * process, before the regular test is run as in the base class [TestCase]. | 563 * process, before the regular test is run as in the base class [TestCase]. |
| 379 * If the compilation command fails, then the rest of the test is not run. | 564 * If the compilation command fails, then the rest of the test is not run. |
| 380 */ | 565 */ |
| 381 class BrowserTestCase extends TestCase { | 566 class BrowserTestCase extends TestCase { |
| 382 /** | |
| 383 * Indicates the number of potential retries remaining, to compensate for | |
| 384 * flaky browser tests. | |
| 385 */ | |
| 386 int numRetries; | |
| 387 | 567 |
| 388 /** | 568 BrowserTestCase(displayName, commands, configuration, |
| 389 * True if this test is dependent on another test completing before it can | 569 expectedOutcomes, info, isNegative, this._testingUrl) |
| 390 * star (for example, we might need to depend on some other test completing | 570 : super(displayName, commands, configuration, |
| 391 * first). | 571 expectedOutcomes, isNegative: isNegative, info: info); |
| 392 */ | |
| 393 bool waitingForOtherTest; | |
| 394 | |
| 395 /** | |
| 396 * The set of test cases that wish to be notified when this test has | |
| 397 * completed. | |
| 398 */ | |
| 399 List<BrowserTestCase> observers; | |
| 400 | |
| 401 BrowserTestCase(displayName, commands, configuration, completedHandler, | |
| 402 expectedOutcomes, info, isNegative, this._testingUrl, | |
| 403 [this.waitingForOtherTest = false]) | |
| 404 : super(displayName, commands, configuration, completedHandler, | |
| 405 expectedOutcomes, isNegative: isNegative, info: info) { | |
| 406 numRetries = 2; // Allow two retries to compensate for flaky browser tests. | |
| 407 observers = []; | |
| 408 } | |
| 409 | |
| 410 List<String> get _lastArguments => commands.last.arguments; | |
| 411 | |
| 412 List<String> get batchRunnerArguments => [_lastArguments[0], '--batch']; | |
| 413 | |
| 414 List<String> get batchTestArguments => _lastArguments.sublist(1); | |
| 415 | 572 |
| 416 String _testingUrl; | 573 String _testingUrl; |
| 417 | 574 |
| 418 /** Add a test case to listen for when this current test has completed. */ | |
| 419 void addObserver(BrowserTestCase testCase) { | |
| 420 observers.add(testCase); | |
| 421 } | |
| 422 | |
| 423 /** | |
| 424 * Notify all of the test cases that are dependent on this one that they can | |
| 425 * proceed. | |
| 426 */ | |
| 427 void notifyObservers() { | |
| 428 for (BrowserTestCase testCase in observers) { | |
| 429 testCase.waitingForOtherTest = false; | |
| 430 } | |
| 431 } | |
| 432 | |
| 433 String get testingUrl => _testingUrl; | 575 String get testingUrl => _testingUrl; |
| 434 } | 576 } |
| 435 | 577 |
| 436 | |
| 437 /** | 578 /** |
| 438 * CommandOutput records the output of a completed command: the process's exit | 579 * CommandOutput records the output of a completed command: the process's exit |
| 439 * code, the standard output and standard error, whether the process timed out, | 580 * code, the standard output and standard error, whether the process timed out, |
| 440 * and the time the process took to run. It also contains a pointer to the | 581 * and the time the process took to run. It also contains a pointer to the |
| 441 * [TestCase] this is the output of. | 582 * [TestCase] this is the output of. |
| 442 */ | 583 */ |
| 443 abstract class CommandOutput { | 584 abstract class CommandOutput { |
| 444 factory CommandOutput.fromCase(TestCase testCase, | |
| 445 Command command, | |
| 446 int exitCode, | |
| 447 bool incomplete, | |
| 448 bool timedOut, | |
| 449 List<int> stdout, | |
| 450 List<int> stderr, | |
| 451 Duration time, | |
| 452 bool compilationSkipped) { | |
| 453 return new CommandOutputImpl.fromCase(testCase, | |
| 454 command, | |
| 455 exitCode, | |
| 456 incomplete, | |
| 457 timedOut, | |
| 458 stdout, | |
| 459 stderr, | |
| 460 time, | |
| 461 compilationSkipped); | |
| 462 } | |
| 463 | |
| 464 Command get command; | 585 Command get command; |
| 465 | 586 |
| 466 TestCase testCase; | 587 String result(TestCase testCase); |
| 467 | |
| 468 bool get incomplete; | |
| 469 | |
| 470 String get result; | |
| 471 | |
| 472 bool get unexpectedOutput; | |
| 473 | 588 |
| 474 bool get hasCrashed; | 589 bool get hasCrashed; |
| 475 | 590 |
| 476 bool get hasTimedOut; | 591 bool get hasTimedOut; |
| 477 | 592 |
| 478 bool get didFail; | 593 bool didFail(testcase); |
| 479 | 594 |
| 480 bool requestRetry; | 595 bool hasFailed(TestCase testCase); |
| 596 | |
| 597 bool get canRunDependendCommands; | |
| 598 | |
| 599 bool get successfull; // otherwise we might to retry running | |
| 481 | 600 |
| 482 Duration get time; | 601 Duration get time; |
| 483 | 602 |
| 484 int get exitCode; | 603 int get exitCode; |
| 485 | 604 |
| 486 List<int> get stdout; | 605 List<int> get stdout; |
| 487 | 606 |
| 488 List<int> get stderr; | 607 List<int> get stderr; |
| 489 | 608 |
| 490 List<String> get diagnostics; | 609 List<String> get diagnostics; |
| 491 | 610 |
| 492 bool get compilationSkipped; | 611 bool get compilationSkipped; |
| 493 } | 612 } |
| 494 | 613 |
| 495 class CommandOutputImpl implements CommandOutput { | 614 class CommandOutputImpl extends UniqueObject implements CommandOutput { |
| 496 Command command; | 615 Command command; |
| 497 TestCase testCase; | |
| 498 int exitCode; | 616 int exitCode; |
| 499 | 617 |
| 500 /// Records if all commands were run, true if they weren't. | |
| 501 final bool incomplete; | |
| 502 | |
| 503 bool timedOut; | 618 bool timedOut; |
| 504 bool failed = false; | |
| 505 List<int> stdout; | 619 List<int> stdout; |
| 506 List<int> stderr; | 620 List<int> stderr; |
| 507 Duration time; | 621 Duration time; |
| 508 List<String> diagnostics; | 622 List<String> diagnostics; |
| 509 bool compilationSkipped; | 623 bool compilationSkipped; |
| 510 | 624 |
| 511 /** | 625 /** |
| 512 * A flag to indicate we have already printed a warning about ignoring the VM | 626 * A flag to indicate we have already printed a warning about ignoring the VM |
| 513 * crash, to limit the amount of output produced per test. | 627 * crash, to limit the amount of output produced per test. |
| 514 */ | 628 */ |
| 515 bool alreadyPrintedWarning = false; | 629 bool alreadyPrintedWarning = false; |
| 516 | 630 |
| 517 /** | 631 // TODO(kustermann): Remove testCase from this class. |
| 518 * Set to true if we encounter a condition in the output that indicates we | 632 CommandOutputImpl(Command this.command, |
| 519 * need to rerun this test. | |
| 520 */ | |
| 521 bool requestRetry = false; | |
| 522 | |
| 523 // Don't call this constructor, call CommandOutput.fromCase() to | |
| 524 // get a new TestOutput instance. | |
| 525 CommandOutputImpl(TestCase this.testCase, | |
| 526 Command this.command, | |
| 527 int this.exitCode, | 633 int this.exitCode, |
| 528 bool this.incomplete, | |
| 529 bool this.timedOut, | 634 bool this.timedOut, |
| 530 List<int> this.stdout, | 635 List<int> this.stdout, |
| 531 List<int> this.stderr, | 636 List<int> this.stderr, |
| 532 Duration this.time, | 637 Duration this.time, |
| 533 bool this.compilationSkipped) { | 638 bool this.compilationSkipped) { |
| 534 testCase.commandOutputs[command] = this; | |
| 535 diagnostics = []; | 639 diagnostics = []; |
| 536 } | 640 } |
| 537 factory CommandOutputImpl.fromCase(TestCase testCase, | |
| 538 Command command, | |
| 539 int exitCode, | |
| 540 bool incomplete, | |
| 541 bool timedOut, | |
| 542 List<int> stdout, | |
| 543 List<int> stderr, | |
| 544 Duration time, | |
| 545 bool compilationSkipped) { | |
| 546 if (testCase.usesBrowserController) { | |
| 547 return new HTMLBrowserCommandOutputImpl(testCase, | |
| 548 command, | |
| 549 exitCode, | |
| 550 incomplete, | |
| 551 timedOut, | |
| 552 stdout, | |
| 553 stderr, | |
| 554 time, | |
| 555 compilationSkipped); | |
| 556 } else if (testCase is BrowserTestCase) { | |
| 557 return new BrowserCommandOutputImpl(testCase, | |
| 558 command, | |
| 559 exitCode, | |
| 560 incomplete, | |
| 561 timedOut, | |
| 562 stdout, | |
| 563 stderr, | |
| 564 time, | |
| 565 compilationSkipped); | |
| 566 } else if (testCase.configuration['analyzer']) { | |
| 567 return new AnalysisCommandOutputImpl(testCase, | |
| 568 command, | |
| 569 exitCode, | |
| 570 timedOut, | |
| 571 stdout, | |
| 572 stderr, | |
| 573 time, | |
| 574 compilationSkipped); | |
| 575 } | |
| 576 return new CommandOutputImpl(testCase, | |
| 577 command, | |
| 578 exitCode, | |
| 579 incomplete, | |
| 580 timedOut, | |
| 581 stdout, | |
| 582 stderr, | |
| 583 time, | |
| 584 compilationSkipped); | |
| 585 } | |
| 586 | 641 |
| 587 String get result => | 642 String result(TestCase testCase) => hasCrashed ? CRASH : |
| 588 hasCrashed ? CRASH : (hasTimedOut ? TIMEOUT : (hasFailed ? FAIL : PASS)); | 643 (hasTimedOut ? TIMEOUT : (hasFailed(testCase) ? FAIL : PASS)); |
| 589 | |
| 590 bool get unexpectedOutput => !testCase.expectedOutcomes.contains(result); | |
| 591 | 644 |
| 592 bool get hasCrashed { | 645 bool get hasCrashed { |
| 593 // The Java dartc runner and dart2js exits with code 253 in case | 646 // The Java dartc runner and dart2js exits with code 253 in case |
| 594 // of unhandled exceptions. | 647 // of unhandled exceptions. |
| 595 if (exitCode == 253) return true; | 648 if (exitCode == 253) return true; |
| 596 if (io.Platform.operatingSystem == 'windows') { | 649 if (io.Platform.operatingSystem == 'windows') { |
| 597 // The VM uses std::abort to terminate on asserts. | 650 // The VM uses std::abort to terminate on asserts. |
| 598 // std::abort terminates with exit code 3 on Windows. | 651 // std::abort terminates with exit code 3 on Windows. |
| 599 if (exitCode == 3) { | 652 if (exitCode == 3) { |
| 600 return !timedOut; | 653 return !timedOut; |
| 601 } | 654 } |
| 602 // TODO(ricow): Remove this dirty hack ones we have a selenium | 655 // TODO(ricow): Remove this dirty hack ones we have a selenium |
| 603 // replacement. | 656 // replacement. |
| 604 if (exitCode == CRASHING_BROWSER_EXITCODE) { | 657 if (exitCode == CRASHING_BROWSER_EXITCODE) { |
| 605 return !timedOut; | 658 return !timedOut; |
| 606 } | 659 } |
| 607 // If a program receives an uncaught system exception, the program | 660 // If a program receives an uncaught system exception, the program |
| 608 // terminates with the exception code as exit code. | 661 // terminates with the exception code as exit code. |
| 609 // The 0x3FFFFF00 mask here tries to determine if an exception indicates | 662 // The 0x3FFFFF00 mask here tries to determine if an exception indicates |
| 610 // a crash of the program. | 663 // a crash of the program. |
| 611 // System exception codes can be found in 'winnt.h', for example | 664 // System exception codes can be found in 'winnt.h', for example |
| 612 // "#define STATUS_ACCESS_VIOLATION ((DWORD) 0xC0000005)" | 665 // "#define STATUS_ACCESS_VIOLATION ((DWORD) 0xC0000005)" |
| 613 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0)); | 666 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0)); |
| 614 } | 667 } |
| 615 return !timedOut && ((exitCode < 0)); | 668 return !timedOut && ((exitCode < 0)); |
| 616 } | 669 } |
| 617 | 670 |
| 618 bool get hasTimedOut => timedOut; | 671 bool get hasTimedOut => timedOut; |
| 619 | 672 |
| 620 bool get didFail { | 673 bool didFail(TestCase testCase) { |
| 621 return (exitCode != 0 && !hasCrashed); | 674 return (exitCode != 0 && !hasCrashed); |
| 622 } | 675 } |
| 623 | 676 |
| 677 bool get canRunDependendCommands { | |
| 678 // FIXME(kustermann): We may need to change this | |
| 679 return !hasTimedOut && exitCode == 0; | |
| 680 } | |
| 681 | |
| 682 bool get successfull { | |
| 683 // FIXME(kustermann): We may need to change this | |
| 684 return !hasTimedOut && exitCode == 0; | |
| 685 } | |
| 686 | |
| 624 // Reverse result of a negative test. | 687 // Reverse result of a negative test. |
| 625 bool get hasFailed { | 688 bool hasFailed(TestCase testCase) { |
| 626 // Always fail if a runtime-error is expected and compilation failed. | 689 // FIXME(kustermann): this is a hack, remove it |
| 627 if (testCase.info != null && testCase.info.hasRuntimeError && incomplete) { | 690 bool isCompilationCommand = testCase.commands.first == command |
| 628 return true; | 691 && testCase.commands.length > 1; |
| 692 if (isCompilationCommand && | |
| 693 testCase.info != null && testCase.info.hasRuntimeError) { | |
| 694 return exitCode != 0; | |
| 629 } | 695 } |
| 630 return testCase.isNegative ? !didFail : didFail; | 696 return testCase.isNegative ? !didFail(testCase) : didFail(testCase); |
| 631 } | 697 } |
| 632 } | 698 } |
| 633 | 699 |
| 634 class BrowserCommandOutputImpl extends CommandOutputImpl { | 700 class BrowserCommandOutputImpl extends CommandOutputImpl { |
| 701 bool _failedBecauseOfMissingXDisplay; | |
| 702 | |
| 635 BrowserCommandOutputImpl( | 703 BrowserCommandOutputImpl( |
| 636 testCase, | |
| 637 command, | 704 command, |
| 638 exitCode, | 705 exitCode, |
| 639 incomplete, | |
| 640 timedOut, | 706 timedOut, |
| 641 stdout, | 707 stdout, |
| 642 stderr, | 708 stderr, |
| 643 time, | 709 time, |
| 644 compilationSkipped) : | 710 compilationSkipped) : |
| 645 super(testCase, | 711 super(command, |
| 646 command, | |
| 647 exitCode, | 712 exitCode, |
| 648 incomplete, | |
| 649 timedOut, | 713 timedOut, |
| 650 stdout, | 714 stdout, |
| 651 stderr, | 715 stderr, |
| 652 time, | 716 time, |
| 653 compilationSkipped); | 717 compilationSkipped) { |
| 718 _failedBecauseOfMissingXDisplay = _didFailBecauseOfMissingXDisplay(); | |
| 719 if (_failedBecauseOfMissingXDisplay) { | |
| 720 DebugLogger.warning("Warning: Test failure because of missing XDisplay"); | |
| 721 // If we get the X server error, or DRT crashes with a core dump, retry | |
| 722 // the test. | |
| 723 } | |
| 724 } | |
| 654 | 725 |
| 655 bool get didFail { | 726 bool didFail(TestCase testCase) { |
| 656 if (_failedBecauseOfMissingXDisplay) { | 727 if (_failedBecauseOfMissingXDisplay) { |
| 657 return true; | 728 return true; |
| 658 } | 729 } |
| 659 | 730 |
| 660 if (command.expectedOutputFile != null) { | 731 if (command.expectedOutputFile != null) { |
| 661 // We are either doing a pixel test or a layout test with content shell | 732 // We are either doing a pixel test or a layout test with content shell |
| 662 return _failedBecauseOfUnexpectedDRTOutput; | 733 return _failedBecauseOfUnexpectedDRTOutput; |
| 663 } | 734 } |
| 664 return _browserTestFailure; | 735 return _browserTestFailure; |
| 665 } | 736 } |
| 666 | 737 |
| 667 bool get _failedBecauseOfMissingXDisplay { | 738 bool _didFailBecauseOfMissingXDisplay() { |
| 668 // Browser case: | 739 // Browser case: |
| 669 // If the browser test failed, it may have been because content shell | 740 // If the browser test failed, it may have been because content shell |
| 670 // and the virtual framebuffer X server didn't hook up, or it crashed with | 741 // and the virtual framebuffer X server didn't hook up, or it crashed with |
| 671 // a core dump. Sometimes content shell crashes after it has set the stdout | 742 // a core dump. Sometimes content shell crashes after it has set the stdout |
| 672 // to PASS, so we have to do this check first. | 743 // to PASS, so we have to do this check first. |
| 673 var stderrLines = decodeUtf8(super.stderr).split("\n"); | 744 var stderrLines = decodeUtf8(super.stderr).split("\n"); |
| 674 for (String line in stderrLines) { | 745 for (String line in stderrLines) { |
| 675 // TODO(kustermann,ricow): Issue: 7564 | 746 // TODO(kustermann,ricow): Issue: 7564 |
| 676 // This seems to happen quite frequently, we need to figure out why. | 747 // This seems to happen quite frequently, we need to figure out why. |
| 677 if (line.contains('Gtk-WARNING **: cannot open display') || | 748 if (line.contains('Gtk-WARNING **: cannot open display') || |
| 678 line.contains('Failed to run command. return code=1')) { | 749 line.contains('Failed to run command. return code=1')) { |
| 679 // If we get the X server error, or DRT crashes with a core dump, retry | |
| 680 // the test. | |
| 681 if ((testCase as BrowserTestCase).numRetries > 0) { | |
| 682 requestRetry = true; | |
| 683 } | |
| 684 print("Warning: Test failure because of missing XDisplay"); | |
| 685 return true; | 750 return true; |
| 686 } | 751 } |
| 687 } | 752 } |
| 688 return false; | 753 return false; |
| 689 } | 754 } |
| 690 | 755 |
| 691 bool get _failedBecauseOfUnexpectedDRTOutput { | 756 bool get _failedBecauseOfUnexpectedDRTOutput { |
| 692 /* | 757 /* |
| 693 * The output of content shell is different for pixel tests than for | 758 * The output of content shell is different for pixel tests than for |
| 694 * layout tests. | 759 * layout tests. |
| 695 * | 760 * |
| 696 * On a pixel test, the DRT output has the following format | 761 * On a pixel test, the DRT output has the following format |
| 697 * ...... | 762 * ...... |
| 698 * ...... | 763 * ...... |
| 699 * Content-Length: ...\n | 764 * Content-Length: ...\n |
| 700 * <*png data> | 765 * <*png data> |
| 701 * #EOF\n | 766 * #EOF\n |
| 702 * So we need to get the byte-range of the png data first, before | 767 * So we need to get the byte-range of the png data first, before |
| 703 * comparing it with the content of the expected output file. | 768 * comparing it with the content of the expected output file. |
| 704 * | 769 * |
| 705 * On a layout tests, the DRT output is directly compared with the | 770 * On a layout tests, the DRT output is directly compared with the |
| 706 * content of the expected output. | 771 * content of the expected output. |
| 707 */ | 772 */ |
| 708 var stdout = testCase.commandOutputs[command].stdout; | |
| 709 var file = new io.File.fromPath(command.expectedOutputFile); | 773 var file = new io.File.fromPath(command.expectedOutputFile); |
| 710 if (file.existsSync()) { | 774 if (file.existsSync()) { |
| 711 var bytesContentLength = "Content-Length:".codeUnits; | 775 var bytesContentLength = "Content-Length:".codeUnits; |
| 712 var bytesNewLine = "\n".codeUnits; | 776 var bytesNewLine = "\n".codeUnits; |
| 713 var bytesEOF = "#EOF\n".codeUnits; | 777 var bytesEOF = "#EOF\n".codeUnits; |
| 714 | 778 |
| 715 var expectedContent = file.readAsBytesSync(); | 779 var expectedContent = file.readAsBytesSync(); |
| 716 if (command.isPixelTest) { | 780 if (command.isPixelTest) { |
| 717 var startOfContentLength = findBytes(stdout, bytesContentLength); | 781 var startOfContentLength = findBytes(stdout, bytesContentLength); |
| 718 if (startOfContentLength >= 0) { | 782 if (startOfContentLength >= 0) { |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 747 bool has_content_type = false; | 811 bool has_content_type = false; |
| 748 var stdoutLines = decodeUtf8(super.stdout).split("\n"); | 812 var stdoutLines = decodeUtf8(super.stdout).split("\n"); |
| 749 for (String line in stdoutLines) { | 813 for (String line in stdoutLines) { |
| 750 switch (line) { | 814 switch (line) { |
| 751 case 'Content-Type: text/plain': | 815 case 'Content-Type: text/plain': |
| 752 has_content_type = true; | 816 has_content_type = true; |
| 753 break; | 817 break; |
| 754 case 'PASS': | 818 case 'PASS': |
| 755 if (has_content_type) { | 819 if (has_content_type) { |
| 756 if (exitCode != 0) { | 820 if (exitCode != 0) { |
| 757 print("Warning: All tests passed, but exitCode != 0 " | 821 print("Warning: All tests passed, but exitCode != 0 ($this)"); |
| 758 "(${testCase.displayName})"); | |
| 759 } | 822 } |
| 760 if (testCase.configuration['runtime'] == 'drt') { | 823 return (exitCode != 0 && !hasCrashed); |
| 761 // TODO(kustermann/ricow): Issue: 7563 | |
| 762 // We should eventually get rid of this hack. | |
| 763 return false; | |
| 764 } else { | |
| 765 return (exitCode != 0 && !hasCrashed); | |
| 766 } | |
| 767 } | 824 } |
| 768 break; | 825 break; |
| 769 } | 826 } |
| 770 } | 827 } |
| 771 return true; | 828 return true; |
| 772 } | 829 } |
| 773 } | 830 } |
| 774 | 831 |
| 775 class HTMLBrowserCommandOutputImpl extends BrowserCommandOutputImpl { | 832 class HTMLBrowserCommandOutputImpl extends BrowserCommandOutputImpl { |
| 776 HTMLBrowserCommandOutputImpl( | 833 HTMLBrowserCommandOutputImpl( |
| 777 testCase, | |
| 778 command, | 834 command, |
| 779 exitCode, | 835 exitCode, |
| 780 incomplete, | |
| 781 timedOut, | 836 timedOut, |
| 782 stdout, | 837 stdout, |
| 783 stderr, | 838 stderr, |
| 784 time, | 839 time, |
| 785 compilationSkipped) : | 840 compilationSkipped) : |
| 786 super(testCase, | 841 super(command, |
| 787 command, | |
| 788 exitCode, | 842 exitCode, |
| 789 incomplete, | |
| 790 timedOut, | 843 timedOut, |
| 791 stdout, | 844 stdout, |
| 792 stderr, | 845 stderr, |
| 793 time, | 846 time, |
| 794 compilationSkipped); | 847 compilationSkipped); |
| 795 | 848 |
| 796 bool get _browserTestFailure { | 849 bool get _browserTestFailure { |
| 797 // We should not need to convert back and forward. | 850 // We should not need to convert back and forward. |
| 798 var output = decodeUtf8(super.stdout); | 851 var output = decodeUtf8(super.stdout); |
| 799 if (output.contains("FAIL")) return true; | 852 if (output.contains("FAIL")) return true; |
| 800 return !output.contains("PASS"); | 853 return !output.contains("PASS"); |
| 801 } | 854 } |
| 802 } | 855 } |
| 803 | 856 |
| 804 | 857 |
| 805 // The static analyzer does not actually execute code, so | 858 // The static analyzer does not actually execute code, so |
| 806 // the criteria for success now depend on the text sent | 859 // the criteria for success now depend on the text sent |
| 807 // to stderr. | 860 // to stderr. |
| 808 class AnalysisCommandOutputImpl extends CommandOutputImpl { | 861 class AnalysisCommandOutputImpl extends CommandOutputImpl { |
| 809 // An error line has 8 fields that look like: | 862 // An error line has 8 fields that look like: |
| 810 // ERROR|COMPILER|MISSING_SOURCE|file:/tmp/t.dart|15|1|24|Missing source. | 863 // ERROR|COMPILER|MISSING_SOURCE|file:/tmp/t.dart|15|1|24|Missing source. |
| 811 final int ERROR_LEVEL = 0; | 864 final int ERROR_LEVEL = 0; |
| 812 final int ERROR_TYPE = 1; | 865 final int ERROR_TYPE = 1; |
| 813 final int FORMATTED_ERROR = 7; | 866 final int FORMATTED_ERROR = 7; |
| 814 | 867 |
| 815 bool alreadyComputed = false; | 868 bool alreadyComputed = false; |
| 816 bool failResult; | 869 bool failResult; |
| 817 | 870 |
| 818 AnalysisCommandOutputImpl(testCase, | 871 // TODO(kustermann): Remove testCase from this class |
| 819 command, | 872 AnalysisCommandOutputImpl(command, |
| 820 exitCode, | 873 exitCode, |
| 821 timedOut, | 874 timedOut, |
| 822 stdout, | 875 stdout, |
| 823 stderr, | 876 stderr, |
| 824 time, | 877 time, |
| 825 compilationSkipped) : | 878 compilationSkipped) : |
| 826 super(testCase, | 879 super(command, |
| 827 command, | |
| 828 exitCode, | 880 exitCode, |
| 829 false, | |
| 830 timedOut, | 881 timedOut, |
| 831 stdout, | 882 stdout, |
| 832 stderr, | 883 stderr, |
| 833 time, | 884 time, |
| 834 compilationSkipped); | 885 compilationSkipped); |
| 835 | 886 |
| 836 bool get didFail { | 887 bool didFail(TestCase testCase) { |
| 837 if (!alreadyComputed) { | 888 if (!alreadyComputed) { |
| 838 failResult = _didFail(); | 889 failResult = _didFail(testCase); |
| 839 alreadyComputed = true; | 890 alreadyComputed = true; |
| 840 } | 891 } |
| 841 return failResult; | 892 return failResult; |
| 842 } | 893 } |
| 843 | 894 |
| 844 bool _didFail() { | 895 bool _didFail(TestCase testCase) { |
| 845 if (hasCrashed) return false; | 896 if (hasCrashed) return false; |
| 846 | 897 |
| 847 List<String> errors = []; | 898 List<String> errors = []; |
| 848 List<String> staticWarnings = []; | 899 List<String> staticWarnings = []; |
| 849 | 900 |
| 850 // Read the returned list of errors and stuff them away. | 901 // Read the returned list of errors and stuff them away. |
| 851 var stderrLines = decodeUtf8(super.stderr).split("\n"); | 902 var stderrLines = decodeUtf8(super.stderr).split("\n"); |
| 852 for (String line in stderrLines) { | 903 for (String line in stderrLines) { |
| 853 if (line.length == 0) continue; | 904 if (line.length == 0) continue; |
| 854 List<String> fields = splitMachineError(line); | 905 List<String> fields = splitMachineError(line); |
| 855 if (fields[ERROR_LEVEL] == 'ERROR') { | 906 if (fields[ERROR_LEVEL] == 'ERROR') { |
| 856 errors.add(fields[FORMATTED_ERROR]); | 907 errors.add(fields[FORMATTED_ERROR]); |
| 857 } else if (fields[ERROR_LEVEL] == 'WARNING') { | 908 } else if (fields[ERROR_LEVEL] == 'WARNING') { |
| 858 staticWarnings.add(fields[FORMATTED_ERROR]); | 909 staticWarnings.add(fields[FORMATTED_ERROR]); |
| 859 } | 910 } |
| 860 // OK to Skip error output that doesn't match the machine format | 911 // OK to Skip error output that doesn't match the machine format |
| 861 } | 912 } |
| 913 // FIXME(kustermann): This is wrong, we should give the expectations in | |
|
ricow1
2013/08/01 18:23:52
in to command -> {in || to} command
kustermann
2013/08/05 07:29:17
Done.
| |
| 914 // to command | |
| 862 if (testCase.info != null | 915 if (testCase.info != null |
| 863 && testCase.info.optionsFromFile['isMultitest']) { | 916 && testCase.info.optionsFromFile['isMultitest']) { |
| 864 return _didMultitestFail(errors, staticWarnings); | 917 return _didMultitestFail(testCase, errors, staticWarnings); |
| 865 } | 918 } |
| 866 return _didStandardTestFail(errors, staticWarnings); | 919 return _didStandardTestFail(testCase, errors, staticWarnings); |
| 867 } | 920 } |
| 868 | 921 |
| 869 bool _didMultitestFail(List errors, List staticWarnings) { | 922 bool _didMultitestFail(TestCase testCase, List errors, List staticWarnings) { |
| 870 Set<String> outcome = testCase.info.multitestOutcome; | 923 Set<String> outcome = testCase.info.multitestOutcome; |
| 871 if (outcome == null) throw "outcome must not be null"; | 924 if (outcome == null) throw "outcome must not be null"; |
| 872 if (outcome.contains('compile-time error') && errors.length > 0) { | 925 if (outcome.contains('compile-time error') && errors.length > 0) { |
| 873 return true; | 926 return true; |
| 874 } else if (outcome.contains('static type warning') | 927 } else if (outcome.contains('static type warning') |
| 875 && staticWarnings.length > 0) { | 928 && staticWarnings.length > 0) { |
| 876 return true; | 929 return true; |
| 877 } else if (outcome.isEmpty | 930 } else if (outcome.isEmpty |
| 878 && (errors.length > 0 || staticWarnings.length > 0)) { | 931 && (errors.length > 0 || staticWarnings.length > 0)) { |
| 879 return true; | 932 return true; |
| 880 } | 933 } |
| 881 return false; | 934 return false; |
| 882 } | 935 } |
| 883 | 936 |
| 884 bool _didStandardTestFail(List errors, List staticWarnings) { | 937 bool _didStandardTestFail(TestCase testCase, List errors, List staticWarnings) { |
| 885 bool hasFatalTypeErrors = false; | 938 bool hasFatalTypeErrors = false; |
| 886 int numStaticTypeAnnotations = 0; | 939 int numStaticTypeAnnotations = 0; |
| 887 int numCompileTimeAnnotations = 0; | 940 int numCompileTimeAnnotations = 0; |
| 888 var isStaticClean = false; | 941 var isStaticClean = false; |
| 889 if (testCase.info != null) { | 942 if (testCase.info != null) { |
| 890 var optionsFromFile = testCase.info.optionsFromFile; | 943 var optionsFromFile = testCase.info.optionsFromFile; |
| 891 hasFatalTypeErrors = testCase.info.hasFatalTypeErrors; | 944 hasFatalTypeErrors = testCase.info.hasFatalTypeErrors; |
| 892 for (Command c in testCase.commands) { | 945 for (Command c in testCase.commands) { |
| 893 for (String arg in c.arguments) { | 946 for (String arg in c.arguments) { |
| 894 if (arg == '--fatal-type-errors') { | 947 if (arg == '--fatal-type-errors') { |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 971 continue; | 1024 continue; |
| 972 } | 1025 } |
| 973 field.write(c); | 1026 field.write(c); |
| 974 } | 1027 } |
| 975 result.add(field.toString()); | 1028 result.add(field.toString()); |
| 976 return result; | 1029 return result; |
| 977 } | 1030 } |
| 978 } | 1031 } |
| 979 | 1032 |
| 980 | 1033 |
| 1034 CommandOutput createCommandOutput(Command command, | |
| 1035 int exitCode, | |
| 1036 bool timedOut, | |
| 1037 List<int> stdout, | |
| 1038 List<int> stderr, | |
| 1039 Duration time, | |
| 1040 bool compilationSkipped) { | |
| 1041 if (command is ContentShellCommand) { | |
| 1042 return new BrowserCommandOutputImpl( | |
| 1043 command, exitCode, timedOut, stdout, stderr, | |
| 1044 time, compilationSkipped); | |
| 1045 } else if (command is BrowserTestCommand) { | |
| 1046 return new HTMLBrowserCommandOutputImpl( | |
| 1047 command, exitCode, timedOut, stdout, stderr, | |
| 1048 time, compilationSkipped); | |
| 1049 } else if (command is SeleniumTestCommand) { | |
| 1050 return new BrowserCommandOutputImpl( | |
| 1051 command, exitCode, timedOut, stdout, stderr, | |
| 1052 time, compilationSkipped); | |
| 1053 } else if (command is AnalysisCommand) { | |
| 1054 return new AnalysisCommandOutputImpl( | |
| 1055 command, exitCode, timedOut, stdout, stderr, | |
| 1056 time, compilationSkipped); | |
| 1057 } | |
| 1058 return new CommandOutputImpl( | |
| 1059 command, exitCode, timedOut, stdout, stderr, | |
| 1060 time, compilationSkipped); | |
| 1061 } | |
| 1062 | |
| 1063 | |
| 981 /** Modifies the --timeout=XX parameter passed to run_selenium.py */ | 1064 /** Modifies the --timeout=XX parameter passed to run_selenium.py */ |
| 982 List<String> _modifySeleniumTimeout(List<String> arguments, int timeout) { | 1065 List<String> _modifySeleniumTimeout(List<String> arguments, int timeout) { |
| 983 return arguments.map((argument) { | 1066 return arguments.map((argument) { |
| 984 if (argument.startsWith('--timeout=')) { | 1067 if (argument.startsWith('--timeout=')) { |
| 985 return "--timeout=$timeout"; | 1068 return "--timeout=$timeout"; |
| 986 } else { | 1069 } else { |
| 987 return argument; | 1070 return argument; |
| 988 } | 1071 } |
| 989 }).toList(); | 1072 }).toList(); |
| 990 } | 1073 } |
| 991 | 1074 |
| 992 | 1075 |
| 993 /** | 1076 /** |
| 994 * A RunningProcess actually runs a test, getting the command lines from | 1077 * A RunningProcess actually runs a test, getting the command lines from |
| 995 * its [TestCase], starting the test process (and first, a compilation | 1078 * its [TestCase], starting the test process (and first, a compilation |
| 996 * process if the TestCase is a [BrowserTestCase]), creating a timeout | 1079 * process if the TestCase is a [BrowserTestCase]), creating a timeout |
| 997 * timer, and recording the results in a new [CommandOutput] object, which it | 1080 * timer, and recording the results in a new [CommandOutput] object, which it |
| 998 * attaches to the TestCase. The lifetime of the RunningProcess is limited | 1081 * attaches to the TestCase. The lifetime of the RunningProcess is limited |
| 999 * to the time it takes to start the process, run the process, and record | 1082 * to the time it takes to start the process, run the process, and record |
| 1000 * the result; there are no pointers to it, so it should be available to | 1083 * the result; there are no pointers to it, so it should be available to |
| 1001 * be garbage collected as soon as it is done. | 1084 * be garbage collected as soon as it is done. |
| 1002 */ | 1085 */ |
| 1003 class RunningProcess { | 1086 class RunningProcess { |
| 1004 TestCase testCase; | |
| 1005 Command command; | 1087 Command command; |
| 1088 int timeout; | |
| 1006 bool timedOut = false; | 1089 bool timedOut = false; |
| 1007 DateTime startTime; | 1090 DateTime startTime; |
| 1008 Timer timeoutTimer; | 1091 Timer timeoutTimer; |
| 1009 List<int> stdout = <int>[]; | 1092 List<int> stdout = <int>[]; |
| 1010 List<int> stderr = <int>[]; | 1093 List<int> stderr = <int>[]; |
| 1011 bool compilationSkipped = false; | 1094 bool compilationSkipped = false; |
| 1012 Completer<CommandOutput> completer; | 1095 Completer<CommandOutput> completer; |
| 1013 | 1096 |
| 1014 RunningProcess(TestCase this.testCase, Command this.command); | 1097 RunningProcess(Command this.command, this.timeout); |
| 1015 | 1098 |
| 1016 Future<CommandOutput> start() { | 1099 Future<CommandOutput> run() { |
| 1017 if (testCase.expectedOutcomes.contains(SKIP)) { | |
| 1018 throw "testCase.expectedOutcomes must not contain 'SKIP'."; | |
| 1019 } | |
| 1020 | |
| 1021 completer = new Completer<CommandOutput>(); | 1100 completer = new Completer<CommandOutput>(); |
| 1022 startTime = new DateTime.now(); | 1101 startTime = new DateTime.now(); |
| 1023 _runCommand(); | 1102 _runCommand(); |
| 1024 return completer.future; | 1103 return completer.future; |
| 1025 } | 1104 } |
| 1026 | 1105 |
| 1027 void _runCommand() { | 1106 void _runCommand() { |
| 1028 command.outputIsUpToDate.then((bool isUpToDate) { | 1107 command.outputIsUpToDate.then((bool isUpToDate) { |
| 1029 if (isUpToDate) { | 1108 if (isUpToDate) { |
| 1030 compilationSkipped = true; | 1109 compilationSkipped = true; |
| 1031 _commandComplete(0); | 1110 _commandComplete(0); |
| 1032 } else { | 1111 } else { |
| 1033 var processEnvironment = _createProcessEnvironment(); | 1112 var processEnvironment = _createProcessEnvironment(); |
| 1034 var commandArguments = _modifySeleniumTimeout(command.arguments, | 1113 var commandArguments = _modifySeleniumTimeout(command.arguments, |
| 1035 testCase.timeout); | 1114 timeout); |
| 1036 Future processFuture = | 1115 Future processFuture = |
| 1037 io.Process.start(command.executable, | 1116 io.Process.start(command.executable, |
| 1038 commandArguments, | 1117 commandArguments, |
| 1039 environment: processEnvironment); | 1118 environment: processEnvironment); |
| 1040 processFuture.then((io.Process process) { | 1119 processFuture.then((io.Process process) { |
| 1041 // Close stdin so that tests that try to block on input will fail. | 1120 // Close stdin so that tests that try to block on input will fail. |
| 1042 process.stdin.close(); | 1121 process.stdin.close(); |
| 1043 void timeoutHandler() { | 1122 void timeoutHandler() { |
| 1044 timedOut = true; | 1123 timedOut = true; |
| 1045 if (process != null) { | 1124 if (process != null) { |
| 1046 process.kill(); | 1125 process.kill(); |
| 1047 } | 1126 } |
| 1048 } | 1127 } |
| 1049 process.exitCode.then(_commandComplete); | 1128 process.exitCode.then(_commandComplete); |
| 1050 _drainStream(process.stdout, stdout); | 1129 _drainStream(process.stdout, stdout); |
| 1051 _drainStream(process.stderr, stderr); | 1130 _drainStream(process.stderr, stderr); |
| 1052 timeoutTimer = new Timer(new Duration(seconds: testCase.timeout), | 1131 timeoutTimer = new Timer(new Duration(seconds: timeout), |
| 1053 timeoutHandler); | 1132 timeoutHandler); |
| 1054 }).catchError((e) { | 1133 }).catchError((e) { |
| 1055 // TODO(floitsch): should we try to report the stacktrace? | 1134 // TODO(floitsch): should we try to report the stacktrace? |
| 1056 print("Process error:"); | 1135 print("Process error:"); |
| 1057 print(" Command: $command"); | 1136 print(" Command: $command"); |
| 1058 print(" Error: $e"); | 1137 print(" Error: $e"); |
| 1059 _commandComplete(-1); | 1138 _commandComplete(-1); |
| 1060 return true; | 1139 return true; |
| 1061 }); | 1140 }); |
| 1062 } | 1141 } |
| 1063 }); | 1142 }); |
| 1064 } | 1143 } |
| 1065 | 1144 |
| 1066 void _commandComplete(int exitCode) { | 1145 void _commandComplete(int exitCode) { |
| 1067 if (timeoutTimer != null) { | 1146 if (timeoutTimer != null) { |
| 1068 timeoutTimer.cancel(); | 1147 timeoutTimer.cancel(); |
| 1069 } | 1148 } |
| 1070 var commandOutput = _createCommandOutput(command, exitCode); | 1149 var commandOutput = _createCommandOutput(command, exitCode); |
| 1071 completer.complete(commandOutput); | 1150 completer.complete(commandOutput); |
| 1072 } | 1151 } |
| 1073 | 1152 |
| 1074 CommandOutput _createCommandOutput(Command command, int exitCode) { | 1153 CommandOutput _createCommandOutput(Command command, int exitCode) { |
| 1075 var incomplete = command != testCase.commands.last; | 1154 var commandOutput = createCommandOutput( |
| 1076 var commandOutput = new CommandOutput.fromCase( | |
| 1077 testCase, | |
| 1078 command, | 1155 command, |
| 1079 exitCode, | 1156 exitCode, |
| 1080 incomplete, | |
| 1081 timedOut, | 1157 timedOut, |
| 1082 stdout, | 1158 stdout, |
| 1083 stderr, | 1159 stderr, |
| 1084 new DateTime.now().difference(startTime), | 1160 new DateTime.now().difference(startTime), |
| 1085 compilationSkipped); | 1161 compilationSkipped); |
| 1086 return commandOutput; | 1162 return commandOutput; |
| 1087 } | 1163 } |
| 1088 | 1164 |
| 1089 void _drainStream(Stream<List<int>> source, List<int> destination) { | 1165 void _drainStream(Stream<List<int>> source, List<int> destination) { |
| 1090 source.listen(destination.addAll); | 1166 source.listen(destination.addAll); |
| 1091 } | 1167 } |
| 1092 | 1168 |
| 1093 Map<String, String> _createProcessEnvironment() { | 1169 Map<String, String> _createProcessEnvironment() { |
| 1094 var baseEnvironment = command.environment != null ? | 1170 var environment = io.Platform.environment; |
| 1095 command.environment : io.Platform.environment; | |
| 1096 var environment = new Map<String, String>.from(baseEnvironment); | |
| 1097 environment['DART_CONFIGURATION'] = | |
| 1098 TestUtils.configurationDir(testCase.configuration); | |
| 1099 | 1171 |
| 1172 if (command.environmentOverrides != null) { | |
| 1173 for (var key in command.environmentOverrides.keys) { | |
| 1174 environment[key] = command.environmentOverrides[key]; | |
| 1175 } | |
| 1176 } | |
| 1100 for (var excludedEnvironmentVariable in EXCLUDED_ENVIRONMENT_VARIABLES) { | 1177 for (var excludedEnvironmentVariable in EXCLUDED_ENVIRONMENT_VARIABLES) { |
| 1101 environment.remove(excludedEnvironmentVariable); | 1178 environment.remove(excludedEnvironmentVariable); |
| 1102 } | 1179 } |
| 1103 | 1180 |
| 1104 return environment; | 1181 return environment; |
| 1105 } | 1182 } |
| 1106 } | 1183 } |
| 1107 | 1184 |
| 1108 class BatchRunnerProcess { | 1185 class BatchRunnerProcess { |
| 1186 final batchRunnerTypes = { | |
| 1187 'selenium' : { | |
| 1188 'run_executable' : 'python', | |
| 1189 'run_arguments' : ['tools/testing/run_selenium.py', '--batch'], | |
| 1190 'terminate_command' : ['--terminate'], | |
| 1191 }, | |
| 1192 'dartanalyzer' : { | |
| 1193 'run_executable' : 'sdk/bin/dartanalyzer_developer', // $suffix | |
| 1194 'run_arguments' : ['--batch'], | |
| 1195 'terminate_command' : null, | |
| 1196 }, | |
| 1197 'dart2analyzer' : { | |
| 1198 'run_executable' : 'editor/tools/analyzer_experimental', | |
| 1199 'run_arguments' : ['--batch'], | |
| 1200 'terminate_command' : null, | |
| 1201 }, | |
| 1202 }; | |
| 1203 | |
| 1204 Completer<CommandOutput> _completer; | |
| 1109 Command _command; | 1205 Command _command; |
| 1110 String _executable; | 1206 List<String> _arguments; |
| 1111 List<String> _batchArguments; | 1207 String _runnerType; |
| 1112 | 1208 |
| 1113 io.Process _process; | 1209 io.Process _process; |
| 1210 Map _processEnvironmentOverrides; | |
| 1114 Completer _stdoutCompleter; | 1211 Completer _stdoutCompleter; |
| 1115 Completer _stderrCompleter; | 1212 Completer _stderrCompleter; |
| 1116 StreamSubscription<String> _stdoutSubscription; | 1213 StreamSubscription<String> _stdoutSubscription; |
| 1117 StreamSubscription<String> _stderrSubscription; | 1214 StreamSubscription<String> _stderrSubscription; |
| 1118 Function _processExitHandler; | 1215 Function _processExitHandler; |
| 1119 | 1216 |
| 1120 TestCase _currentTest; | 1217 bool _currentlyRunning = false; |
| 1121 List<int> _testStdout; | 1218 List<int> _testStdout; |
| 1122 List<int> _testStderr; | 1219 List<int> _testStderr; |
| 1123 String _status; | 1220 String _status; |
| 1124 DateTime _startTime; | 1221 DateTime _startTime; |
| 1125 Timer _timer; | 1222 Timer _timer; |
| 1126 bool _isWebDriver; | |
| 1127 | 1223 |
| 1128 BatchRunnerProcess(TestCase testCase) { | 1224 BatchRunnerProcess(); |
| 1129 _command = testCase.commands.last; | |
| 1130 _executable = testCase.commands.last.executable; | |
| 1131 _batchArguments = testCase.batchRunnerArguments; | |
| 1132 _isWebDriver = testCase.usesWebDriver; | |
| 1133 } | |
| 1134 | 1225 |
| 1135 bool get active => _currentTest != null; | 1226 Future<CommandOutput> runCommand(String runnerType, Command command, |
| 1227 int timeout, List<String> arguments) { | |
| 1228 assert(_completer == null); | |
| 1229 assert(!_currentlyRunning); | |
| 1136 | 1230 |
| 1137 void startTest(TestCase testCase) { | 1231 _completer = new Completer<CommandOutput>(); |
| 1138 if (_currentTest != null) throw "_currentTest must be null."; | 1232 bool sameRunnerType = _runnerType == runnerType && |
| 1139 _currentTest = testCase; | 1233 _dictEquals(_processEnvironmentOverrides, command.environmentOverrides); |
| 1140 _command = testCase.commands.last; | 1234 _runnerType = runnerType; |
| 1235 _currentlyRunning = true; | |
| 1236 _command = command; | |
| 1237 _arguments = arguments; | |
| 1238 | |
| 1239 _processEnvironmentOverrides = command.environmentOverrides; | |
| 1240 | |
| 1141 if (_process == null) { | 1241 if (_process == null) { |
| 1142 // Start process if not yet started. | 1242 // Start process if not yet started. |
| 1143 _executable = testCase.commands.last.executable; | |
| 1144 _startProcess(() { | 1243 _startProcess(() { |
| 1145 doStartTest(testCase); | 1244 doStartTest(command, timeout); |
| 1146 }); | 1245 }); |
| 1147 } else if (testCase.commands.last.executable != _executable) { | 1246 } else if (!sameRunnerType) { |
| 1148 // Restart this runner with the right executable for this test | 1247 // Restart this runner with the right executable for this test if needed. |
| 1149 // if needed. | |
| 1150 _executable = testCase.commands.last.executable; | |
| 1151 _batchArguments = testCase.batchRunnerArguments; | |
| 1152 _processExitHandler = (_) { | 1248 _processExitHandler = (_) { |
| 1153 _startProcess(() { | 1249 _startProcess(() { |
| 1154 doStartTest(testCase); | 1250 doStartTest(command, timeout); |
| 1155 }); | 1251 }); |
| 1156 }; | 1252 }; |
| 1157 _process.kill(); | 1253 _process.kill(); |
| 1158 } else { | 1254 } else { |
| 1159 doStartTest(testCase); | 1255 doStartTest(command, timeout); |
| 1160 } | 1256 } |
| 1257 return _completer.future; | |
| 1161 } | 1258 } |
| 1162 | 1259 |
| 1163 Future terminate() { | 1260 Future terminate() { |
| 1164 if (_process == null) return new Future.value(true); | 1261 if (_process == null) return new Future.value(true); |
| 1165 Completer completer = new Completer(); | 1262 Completer completer = new Completer(); |
|
ricow1
2013/08/01 18:23:52
rename this completer to terminationCompleter - to
kustermann
2013/08/05 07:29:17
Done.
| |
| 1166 Timer killTimer; | 1263 Timer killTimer; |
| 1167 _processExitHandler = (_) { | 1264 _processExitHandler = (_) { |
| 1168 if (killTimer != null) killTimer.cancel(); | 1265 if (killTimer != null) killTimer.cancel(); |
| 1169 completer.complete(true); | 1266 completer.complete(true); |
| 1170 }; | 1267 }; |
| 1171 if (_isWebDriver) { | 1268 var shutdownCommand = batchRunnerTypes[_runnerType]['terminate_command']; |
| 1269 if (shutdownCommand != null && !shutdownCommand.isEmpty) { | |
| 1172 // Use a graceful shutdown so our Selenium script can close | 1270 // Use a graceful shutdown so our Selenium script can close |
| 1173 // the open browser processes. On Windows, signals do not exist | 1271 // the open browser processes. On Windows, signals do not exist |
| 1174 // and a kill is a hard kill. | 1272 // and a kill is a hard kill. |
| 1175 _process.stdin.writeln('--terminate'); | 1273 _process.stdin.writeln(shutdownCommand.join(' ')); |
| 1176 | 1274 |
| 1177 // In case the run_selenium process didn't close, kill it after 30s | 1275 // In case the run_selenium process didn't close, kill it after 30s |
| 1178 killTimer = new Timer(new Duration(seconds: 30), _process.kill); | 1276 killTimer = new Timer(new Duration(seconds: 30), _process.kill); |
| 1179 } else { | 1277 } else { |
| 1180 _process.kill(); | 1278 _process.kill(); |
| 1181 } | 1279 } |
| 1182 | 1280 |
| 1183 return completer.future; | 1281 return completer.future; |
| 1184 } | 1282 } |
| 1185 | 1283 |
| 1186 void doStartTest(TestCase testCase) { | 1284 void doStartTest(Command command, int timeout) { |
| 1187 _startTime = new DateTime.now(); | 1285 _startTime = new DateTime.now(); |
| 1188 _testStdout = []; | 1286 _testStdout = []; |
| 1189 _testStderr = []; | 1287 _testStderr = []; |
| 1190 _status = null; | 1288 _status = null; |
| 1191 _stdoutCompleter = new Completer(); | 1289 _stdoutCompleter = new Completer(); |
| 1192 _stderrCompleter = new Completer(); | 1290 _stderrCompleter = new Completer(); |
| 1193 _timer = new Timer(new Duration(seconds: testCase.timeout), | 1291 _timer = new Timer(new Duration(seconds: timeout), |
| 1194 _timeoutHandler); | 1292 _timeoutHandler); |
| 1195 | 1293 |
| 1196 if (testCase.commands.last.environment != null) { | 1294 var line = _createArgumentsLine(_arguments, timeout); |
| 1197 print("Warning: command.environment != null, but we don't support custom " | |
| 1198 "environments for batch runner tests!"); | |
| 1199 } | |
| 1200 | |
| 1201 var line = _createArgumentsLine(testCase.batchTestArguments, | |
| 1202 testCase.timeout); | |
| 1203 _process.stdin.write(line); | 1295 _process.stdin.write(line); |
| 1204 _stdoutSubscription.resume(); | 1296 _stdoutSubscription.resume(); |
| 1205 _stderrSubscription.resume(); | 1297 _stderrSubscription.resume(); |
| 1206 Future.wait([_stdoutCompleter.future, | 1298 Future.wait([_stdoutCompleter.future, |
| 1207 _stderrCompleter.future]).then((_) => _reportResult()); | 1299 _stderrCompleter.future]).then((_) => _reportResult()); |
| 1208 } | 1300 } |
| 1209 | 1301 |
| 1210 String _createArgumentsLine(List<String> arguments, int timeout) { | 1302 String _createArgumentsLine(List<String> arguments, int timeout) { |
| 1211 arguments = _modifySeleniumTimeout(arguments, timeout); | 1303 arguments = _modifySeleniumTimeout(arguments, timeout); |
| 1212 return arguments.join(' ') + '\n'; | 1304 return arguments.join(' ') + '\n'; |
| 1213 } | 1305 } |
| 1214 | 1306 |
| 1215 void _reportResult() { | 1307 void _reportResult() { |
| 1216 if (!active) return; | 1308 if (!_currentlyRunning) return; |
| 1217 // _status == '>>> TEST {PASS, FAIL, OK, CRASH, FAIL, TIMEOUT}' | 1309 // _status == '>>> TEST {PASS, FAIL, OK, CRASH, FAIL, TIMEOUT}' |
| 1218 | 1310 |
| 1219 var outcome = _status.split(" ")[2]; | 1311 var outcome = _status.split(" ")[2]; |
| 1220 var exitCode = 0; | 1312 var exitCode = 0; |
| 1221 if (outcome == "CRASH") exitCode = CRASHING_BROWSER_EXITCODE; | 1313 if (outcome == "CRASH") exitCode = CRASHING_BROWSER_EXITCODE; |
| 1222 if (outcome == "FAIL" || outcome == "TIMEOUT") exitCode = 1; | 1314 if (outcome == "FAIL" || outcome == "TIMEOUT") exitCode = 1; |
| 1223 new CommandOutput.fromCase(_currentTest, | 1315 var output = createCommandOutput(_command, |
| 1224 _command, | 1316 exitCode, |
| 1225 exitCode, | 1317 (outcome == "TIMEOUT"), |
| 1226 false, | 1318 _testStdout, |
| 1227 (outcome == "TIMEOUT"), | 1319 _testStderr, |
| 1228 _testStdout, | 1320 new DateTime.now().difference(_startTime), |
| 1229 _testStderr, | 1321 false); |
| 1230 new DateTime.now().difference(_startTime), | 1322 assert(_completer != null); |
| 1231 false); | 1323 _completer.complete(output); |
| 1232 var test = _currentTest; | 1324 _completer = null; |
| 1233 _currentTest = null; | 1325 _currentlyRunning = false; |
| 1234 test.completed(); | |
| 1235 } | 1326 } |
| 1236 | 1327 |
| 1237 ExitCodeEvent makeExitHandler(String status) { | 1328 ExitCodeEvent makeExitHandler(String status) { |
| 1238 void handler(int exitCode) { | 1329 void handler(int exitCode) { |
| 1239 if (active) { | 1330 if (_currentlyRunning) { |
| 1240 if (_timer != null) _timer.cancel(); | 1331 if (_timer != null) _timer.cancel(); |
| 1241 _status = status; | 1332 _status = status; |
| 1242 _stdoutSubscription.cancel(); | 1333 _stdoutSubscription.cancel(); |
| 1243 _stderrSubscription.cancel(); | 1334 _stderrSubscription.cancel(); |
| 1244 _startProcess(_reportResult); | 1335 _startProcess(_reportResult); |
| 1245 } else { // No active test case running. | 1336 } else { // No active test case running. |
| 1246 _process = null; | 1337 _process = null; |
| 1247 } | 1338 } |
| 1248 } | 1339 } |
| 1249 return handler; | 1340 return handler; |
| 1250 } | 1341 } |
| 1251 | 1342 |
| 1252 void _timeoutHandler() { | 1343 void _timeoutHandler() { |
| 1253 _processExitHandler = makeExitHandler(">>> TEST TIMEOUT"); | 1344 _processExitHandler = makeExitHandler(">>> TEST TIMEOUT"); |
| 1254 _process.kill(); | 1345 _process.kill(); |
| 1255 } | 1346 } |
| 1256 | 1347 |
| 1257 _startProcess(callback) { | 1348 _startProcess(callback) { |
| 1258 Future processFuture = io.Process.start(_executable, _batchArguments); | 1349 var executable = batchRunnerTypes[_runnerType]['run_executable']; |
| 1350 var arguments = batchRunnerTypes[_runnerType]['run_arguments']; | |
| 1351 var environment = new Map.from(io.Platform.environment); | |
| 1352 if (_processEnvironmentOverrides != null) { | |
| 1353 for (var key in _processEnvironmentOverrides.keys) { | |
| 1354 environment[key] = _processEnvironmentOverrides[key]; | |
| 1355 } | |
| 1356 } | |
| 1357 //print("STARTING $executable ${arguments.join(' ')} "); | |
|
ricow1
2013/08/01 13:26:21
commented out code
kustermann
2013/08/05 07:29:17
Done.
| |
| 1358 Future processFuture = io.Process.start(executable, | |
| 1359 arguments, | |
| 1360 environment: environment); | |
| 1259 processFuture.then((io.Process p) { | 1361 processFuture.then((io.Process p) { |
| 1260 _process = p; | 1362 _process = p; |
| 1261 | 1363 |
| 1262 var _stdoutStream = | 1364 var _stdoutStream = |
| 1263 _process.stdout | 1365 _process.stdout |
| 1264 .transform(new io.StringDecoder()) | 1366 .transform(new io.StringDecoder()) |
| 1265 .transform(new io.LineTransformer()); | 1367 .transform(new io.LineTransformer()); |
| 1266 _stdoutSubscription = _stdoutStream.listen((String line) { | 1368 _stdoutSubscription = _stdoutStream.listen((String line) { |
| 1369 //print("STDOUT $line"); | |
|
ricow1
2013/08/01 13:26:21
commented out line
kustermann
2013/08/05 07:29:17
Done.
| |
| 1267 if (line.startsWith('>>> TEST')) { | 1370 if (line.startsWith('>>> TEST')) { |
| 1268 _status = line; | 1371 _status = line; |
| 1269 } else if (line.startsWith('>>> BATCH')) { | 1372 } else if (line.startsWith('>>> BATCH')) { |
| 1270 // ignore | 1373 // ignore |
| 1271 } else if (line.startsWith('>>> ')) { | 1374 } else if (line.startsWith('>>> ')) { |
| 1272 throw new Exception("Unexpected command from batch runner: '$line'."); | 1375 throw new Exception("Unexpected command from batch runner: '$line'."); |
| 1273 } else { | 1376 } else { |
| 1274 _testStdout.addAll(encodeUtf8(line)); | 1377 _testStdout.addAll(encodeUtf8(line)); |
| 1275 _testStdout.addAll("\n".codeUnits); | 1378 _testStdout.addAll("\n".codeUnits); |
| 1276 } | 1379 } |
| 1277 if (_status != null) { | 1380 if (_status != null) { |
| 1278 _stdoutSubscription.pause(); | 1381 _stdoutSubscription.pause(); |
| 1279 _timer.cancel(); | 1382 _timer.cancel(); |
| 1280 _stdoutCompleter.complete(null); | 1383 _stdoutCompleter.complete(null); |
| 1281 } | 1384 } |
| 1282 }); | 1385 }); |
| 1283 _stdoutSubscription.pause(); | 1386 _stdoutSubscription.pause(); |
| 1284 | 1387 |
| 1285 var _stderrStream = | 1388 var _stderrStream = |
| 1286 _process.stderr | 1389 _process.stderr |
| 1287 .transform(new io.StringDecoder()) | 1390 .transform(new io.StringDecoder()) |
| 1288 .transform(new io.LineTransformer()); | 1391 .transform(new io.LineTransformer()); |
| 1289 _stderrSubscription = _stderrStream.listen((String line) { | 1392 _stderrSubscription = _stderrStream.listen((String line) { |
| 1393 //print("STDERR: $line"); | |
|
ricow1
2013/08/01 13:26:21
commented out line
kustermann
2013/08/05 07:29:17
Done.
| |
| 1290 if (line.startsWith('>>> EOF STDERR')) { | 1394 if (line.startsWith('>>> EOF STDERR')) { |
| 1291 _stderrSubscription.pause(); | 1395 _stderrSubscription.pause(); |
| 1292 _stderrCompleter.complete(null); | 1396 _stderrCompleter.complete(null); |
| 1293 } else { | 1397 } else { |
| 1294 _testStderr.addAll(encodeUtf8(line)); | 1398 _testStderr.addAll(encodeUtf8(line)); |
| 1295 _testStderr.addAll("\n".codeUnits); | 1399 _testStderr.addAll("\n".codeUnits); |
| 1296 } | 1400 } |
| 1297 }); | 1401 }); |
| 1298 _stderrSubscription.pause(); | 1402 _stderrSubscription.pause(); |
| 1299 | 1403 |
| 1300 _processExitHandler = makeExitHandler(">>> TEST CRASH"); | 1404 _processExitHandler = makeExitHandler(">>> TEST CRASH"); |
| 1301 _process.exitCode.then((exitCode) { | 1405 _process.exitCode.then((exitCode) { |
| 1302 _processExitHandler(exitCode); | 1406 _processExitHandler(exitCode); |
| 1303 }); | 1407 }); |
| 1304 | 1408 |
| 1305 _process.stdin.done.catchError((err) { | 1409 _process.stdin.done.catchError((err) { |
| 1306 print('Error on batch runner input stream stdin'); | 1410 print('Error on batch runner input stream stdin'); |
| 1307 print(' Previous test\'s status: $_status'); | 1411 print(' Previous test\'s status: $_status'); |
| 1308 print(' Error: $err'); | 1412 print(' Error: $err'); |
| 1309 throw err; | 1413 throw err; |
| 1310 }); | 1414 }); |
| 1311 callback(); | 1415 callback(); |
| 1312 }).catchError((e) { | 1416 }).catchError((e) { |
| 1313 // TODO(floitsch): should we try to report the stacktrace? | 1417 // TODO(floitsch): should we try to report the stacktrace? |
| 1314 print("Process error:"); | 1418 print("Process error:"); |
| 1315 print(" Command: $_executable ${_batchArguments.join(' ')}"); | 1419 print(" Command: $executable ${arguments.join(' ')} ($_arguments)"); |
| 1316 print(" Error: $e"); | 1420 print(" Error: $e"); |
| 1317 // If there is an error starting a batch process, chances are that | 1421 // If there is an error starting a batch process, chances are that |
| 1318 // it will always fail. So rather than re-trying a 1000+ times, we | 1422 // it will always fail. So rather than re-trying a 1000+ times, we |
| 1319 // exit. | 1423 // exit. |
| 1320 io.exit(1); | 1424 io.exit(1); |
| 1321 return true; | 1425 return true; |
| 1322 }); | 1426 }); |
| 1323 } | 1427 } |
| 1324 } | 1428 |
| 1429 bool _dictEquals(Map a, Map b) { | |
| 1430 if (a == null) return b == null; | |
| 1431 if (b == null) return false; | |
| 1432 for (var key in a.keys) { | |
| 1433 if (a[key] != b[key]) return false; | |
| 1434 } | |
| 1435 return true; | |
| 1436 } | |
| 1437 } | |
| 1438 | |
| 1325 | 1439 |
| 1326 /** | 1440 /** |
| 1327 * ProcessQueue is the master control class, responsible for running all | 1441 * [TestCaseEnqueuer] takes a list of TestSuites, generates TestCases and |
| 1328 * the tests in all the TestSuites that have been registered. It includes | 1442 * builds a dependency graph of all commands in every TestSuite. |
| 1329 * a rate-limited queue to run a limited number of tests in parallel, | 1443 * |
| 1330 * a ProgressIndicator which prints output when tests are started and | 1444 * It will maintain three helper data structures |
| 1331 * and completed, and a summary report when all tests are completed, | 1445 * - command2node: A mapping from a [Command] to a node in the dependency graph |
| 1332 * and counters to determine when all of the tests in all of the test suites | 1446 * - command2testCases: A mapping from [Command] to all TestCases that it is |
| 1333 * have completed. | 1447 * part of |
|
ricow1
2013/08/01 18:23:52
part of -> part of it?
kustermann
2013/08/05 07:29:17
I think "to all TestCases that it is part of" is o
| |
| 1334 * | 1448 * - remainingTestCases: A set of TestCases that were enqueued but are not |
| 1335 * Because multiple configurations may be run on each test suite, the | 1449 * finished |
| 1336 * ProcessQueue contains a cache in which a test suite may record information | 1450 * |
| 1337 * about its list of tests, and may retrieve that information when it is called | 1451 * [Command] and it's subclasses all have hashCode/operator== methods defined |
| 1338 * upon to enqueue its tests again. | 1452 * on them, so we can safely use them as keys in Map/Set objects. |
| 1339 */ | 1453 */ |
| 1340 class ProcessQueue { | 1454 class TestCaseEnqueuer { |
| 1455 final dgraph.Graph graph; | |
| 1456 final Function _onTestCaseAdded; | |
| 1457 | |
| 1458 final command2node = new Map<Command, dgraph.Node>(); | |
| 1459 final command2testCases = new Map<Command, List<TestCase>>(); | |
| 1460 final remainingTestCases = new Set<TestCase>(); | |
| 1461 | |
| 1462 TestCaseEnqueuer(this.graph, this._onTestCaseAdded); | |
| 1463 | |
| 1464 void enqueueTestSuites(List<TestSuite> testSuites) { | |
| 1465 void newTest(TestCase testCase) { | |
| 1466 remainingTestCases.add(testCase); | |
| 1467 | |
| 1468 var lastNode; | |
| 1469 for (var command in testCase.commands) { | |
| 1470 // Make exactly *one* node in the dependency graph for every command. | |
|
ricow1
2013/08/01 18:23:52
if this is read out of context it may be misunders
kustermann
2013/08/05 07:29:17
Added more comments.
| |
| 1471 var node = command2node[command]; | |
| 1472 if (node == null) { | |
| 1473 var requiredNodes = (lastNode != null) ? [lastNode] : []; | |
| 1474 node = graph.newNode(command, requiredNodes); | |
| 1475 command2node[command] = node; | |
| 1476 command2testCases[command] = <TestCase>[]; | |
| 1477 } | |
| 1478 // Keep mapping from command to all testCases that refer to it | |
| 1479 command2testCases[command].add(testCase); | |
| 1480 | |
| 1481 lastNode = node; | |
| 1482 } | |
| 1483 _onTestCaseAdded(testCase); | |
| 1484 } | |
| 1485 | |
| 1486 // Cache information about test cases per test suite. For multiple | |
| 1487 // configurations there is no need to repeatedly search the file | |
| 1488 // system, generate tests, and search test files for options. | |
| 1489 var testCache = new Map<String, List<TestInformation>>(); | |
| 1490 | |
| 1491 Iterator<TestSuite> iterator = testSuites.iterator; | |
| 1492 void enqueueNextSuite() { | |
| 1493 if (!iterator.moveNext()) { | |
| 1494 // We're finished with building the dependency graph. | |
| 1495 graph.sealGraph(); | |
| 1496 } else { | |
| 1497 iterator.current.forEachTest(newTest, testCache, enqueueNextSuite); | |
| 1498 } | |
| 1499 } | |
| 1500 enqueueNextSuite(); | |
| 1501 } | |
| 1502 } | |
| 1503 | |
| 1504 | |
| 1505 /* | |
| 1506 * [CommandEnqueuer] will | |
| 1507 * - change node.state to NodeState.Enqueuing as soon as all dependencies have | |
| 1508 * a state of NodeState.Successful | |
| 1509 * - change node.state to NodeState.UnableToRun if one or more dependencies | |
| 1510 * have a state of NodeState.Failed/NodeState.UnableToRun. | |
| 1511 */ | |
| 1512 class CommandEnqueuer { | |
| 1513 static final INIT_STATES = [dgraph.NodeState.Initialized, | |
| 1514 dgraph.NodeState.Waiting]; | |
| 1515 static final FINISHED_STATES = [dgraph.NodeState.Successful, | |
| 1516 dgraph.NodeState.Failed, | |
| 1517 dgraph.NodeState.UnableToRun]; | |
| 1518 final dgraph.Graph _graph; | |
| 1519 | |
| 1520 CommandEnqueuer(this._graph) { | |
| 1521 var eventCondition = _graph.events.where; | |
| 1522 | |
| 1523 eventCondition((e) => e is dgraph.NodeAddedEvent).listen((event) { | |
| 1524 dgraph.Node node = event.node; | |
| 1525 _changeNodeStateIfNecessary(node); | |
| 1526 }); | |
| 1527 | |
| 1528 eventCondition((e) => e is dgraph.StateChangedEvent).listen((event) { | |
| 1529 if (event.from == dgraph.NodeState.Processing) { | |
| 1530 assert(FINISHED_STATES.contains(event.to)); | |
| 1531 for (var dependendNode in event.node.neededFor) { | |
| 1532 _changeNodeStateIfNecessary(dependendNode); | |
| 1533 } | |
| 1534 } | |
| 1535 }); | |
| 1536 } | |
| 1537 | |
| 1538 // Called when either a new node was added or if one of it's dependencies | |
| 1539 // changed it's state. | |
| 1540 void _changeNodeStateIfNecessary(dgraph.Node node) { | |
| 1541 assert(INIT_STATES.contains(node.state)); | |
| 1542 bool allDependenciesFinished = | |
| 1543 node.dependencies.every((node) => FINISHED_STATES.contains(node.state)); | |
| 1544 | |
| 1545 var newState; | |
|
ricow1
2013/08/01 18:23:52
just set newState = dgraph.NodeState.Waiting here
kustermann
2013/08/05 07:29:17
Done.
| |
| 1546 if (allDependenciesFinished) { | |
| 1547 bool allDependenciesSuccessful = node.dependencies.every( | |
| 1548 (dep) => dep.state == dgraph.NodeState.Successful); | |
| 1549 | |
| 1550 if (allDependenciesSuccessful) { | |
| 1551 newState = dgraph.NodeState.Enqueuing; | |
| 1552 } else { | |
| 1553 newState = dgraph.NodeState.UnableToRun; | |
| 1554 } | |
| 1555 } else { | |
| 1556 newState = dgraph.NodeState.Waiting; | |
| 1557 } | |
| 1558 if (node.state != newState) { | |
| 1559 _graph.changeState(node, newState); | |
| 1560 } | |
| 1561 } | |
| 1562 } | |
| 1563 | |
| 1564 // TODO(kustermann): Add support for '--list' and '--verbose'! | |
| 1565 | |
| 1566 /* | |
| 1567 * [CommandQueue] will listen for nodes entering the NodeState.ENQUEUING state, | |
| 1568 * queue them up and run them. While nodes are processed they will be in the | |
| 1569 * NodeState.PROCESSING state. After running a command, the node will change | |
| 1570 * to a state of NodeState.Successfull or NodeState.Failed. | |
| 1571 * | |
| 1572 * It provides a synchronous stream [completedCommands] which provides the | |
| 1573 * [CommandOutputs] for the finished commands. | |
| 1574 * | |
| 1575 * It provides a [done] future, which will complete once there are no more | |
| 1576 * nodes left in the states Initialized/Waiting/Enqueing/Processing | |
| 1577 * and the [executor] has cleaned up it's resources. | |
| 1578 */ | |
| 1579 class CommandQueue { | |
| 1580 final dgraph.Graph graph; | |
| 1581 final CommandExecutor executor; | |
| 1582 final TestCaseEnqueuer enqueuer; | |
| 1583 | |
| 1584 final Queue<Command> _runQueue = new Queue<Command>(); | |
| 1585 final _commandOutputStream = new StreamController<CommandOutput>(sync: true); | |
| 1586 final _completer = new Completer(); | |
| 1587 | |
| 1341 int _numProcesses = 0; | 1588 int _numProcesses = 0; |
| 1342 int _maxProcesses; | 1589 int _maxProcesses; |
| 1343 int _numBrowserProcesses = 0; | 1590 int _numBrowserProcesses = 0; |
| 1344 int _maxBrowserProcesses; | 1591 int _maxBrowserProcesses; |
| 1345 int _numFailedTests = 0; | 1592 bool _finishing = false; |
| 1346 bool _allTestsWereEnqueued = false; | 1593 bool _verbose = false; |
| 1347 | 1594 |
| 1348 // Support for recording and replaying test commands. | 1595 CommandQueue(this.graph, this.enqueuer, this.executor, |
| 1349 TestCaseRecorder _testCaseRecorder; | 1596 this._maxProcesses, this._maxBrowserProcesses, this._verbose) { |
| 1350 TestCaseOutputArchive _testCaseOutputArchive; | 1597 var eventCondition = graph.events.where; |
| 1351 | 1598 eventCondition((event) => event is dgraph.StateChangedEvent) |
| 1352 /** The number of tests we allow to actually fail before we stop retrying. */ | 1599 .listen((event) { |
| 1353 int _MAX_FAILED_NO_RETRY = 4; | 1600 if (event.to == dgraph.NodeState.Enqueuing) { |
| 1354 bool _verbose; | 1601 assert(event.from == dgraph.NodeState.Initialized || |
| 1355 bool _listTests; | 1602 event.from == dgraph.NodeState.Waiting); |
| 1356 Function _allDone; | 1603 graph.changeState(event.node, dgraph.NodeState.Processing); |
| 1357 Queue<TestCase> _tests; | 1604 var command = event.node.userData; |
| 1358 List<EventListener> _eventListener; | 1605 _runQueue.add(command); |
| 1606 Timer.run(() => _tryRunNextCommand()); | |
| 1607 } | |
| 1608 }); | |
| 1609 } | |
| 1610 | |
| 1611 Stream<CommandOutput> get completedCommands => _commandOutputStream.stream; | |
| 1612 | |
| 1613 Future get done => _completer.future; | |
| 1614 | |
| 1615 void _tryRunNextCommand() { | |
| 1616 _checkDone(); | |
| 1617 | |
| 1618 if (_numProcesses < _maxProcesses && !_runQueue.isEmpty) { | |
| 1619 Command command = _runQueue.removeFirst(); | |
| 1620 var isBrowserCommand = | |
| 1621 command is SeleniumTestCommand || | |
| 1622 command is BrowserTestCase; | |
| 1623 | |
| 1624 if (isBrowserCommand && _numBrowserProcesses == _maxBrowserProcesses) { | |
| 1625 // If there is no free browser runner, put it back into the queue. | |
| 1626 _runQueue.add(command); | |
| 1627 // Don't lose a process. | |
| 1628 new Timer(new Duration(milliseconds: 100), _tryRunNextCommand); | |
| 1629 return; | |
| 1630 } | |
| 1631 | |
| 1632 _numProcesses++; | |
| 1633 if (isBrowserCommand) _numBrowserProcesses++; | |
| 1634 | |
| 1635 var node = enqueuer.command2node[command]; | |
| 1636 Iterable<TestCase> testCases = enqueuer.command2testCases[command]; | |
| 1637 int timeout = testCases.map((TestCase test) => test.timeout) | |
|
ricow1
2013/08/01 18:23:52
this is sort of changing current "semantics" but I
kustermann
2013/08/05 07:29:17
Just having TestCase.timeout is totally stupid, si
| |
| 1638 .fold(0, math.max); | |
| 1639 | |
| 1640 if (_verbose) { | |
| 1641 print('Running "${command.displayName}" command: $command'); | |
| 1642 } | |
| 1643 | |
| 1644 executor.runCommand(node, command, timeout).then((CommandOutput output) { | |
| 1645 assert(command == output.command); | |
| 1646 | |
| 1647 _commandOutputStream.add(output); | |
| 1648 if (output.canRunDependendCommands) { | |
| 1649 graph.changeState(node, dgraph.NodeState.Successful); | |
| 1650 } else { | |
| 1651 graph.changeState(node, dgraph.NodeState.Failed); | |
| 1652 } | |
| 1653 | |
| 1654 _numProcesses--; | |
| 1655 if (isBrowserCommand) _numBrowserProcesses--; | |
| 1656 | |
| 1657 // Don't loose a process | |
| 1658 Timer.run(() => _tryRunNextCommand()); | |
| 1659 }); | |
| 1660 } | |
| 1661 } | |
| 1662 | |
| 1663 void _checkDone() { | |
| 1664 if (!_finishing && | |
| 1665 _runQueue.isEmpty && | |
| 1666 _numProcesses == 0 && | |
| 1667 graph.isSealed && | |
| 1668 graph.stateCount(dgraph.NodeState.Initialized) == 0 && | |
| 1669 graph.stateCount(dgraph.NodeState.Waiting) == 0 && | |
| 1670 graph.stateCount(dgraph.NodeState.Enqueuing) == 0 && | |
| 1671 graph.stateCount(dgraph.NodeState.Processing) == 0) { | |
| 1672 _finishing = true; | |
| 1673 executor.cleanup().then((_) { | |
| 1674 _completer.complete(); | |
| 1675 _commandOutputStream.close(); | |
| 1676 }); | |
| 1677 } | |
| 1678 } | |
| 1679 } | |
| 1680 | |
| 1681 | |
| 1682 /* | |
| 1683 * [CommandExecutor] is responsible for executing commands. It will make sure | |
| 1684 * that the the following two constraints are satisfied | |
| 1685 * - [:maxProcesses < numberOfProcessesUsed:] | |
|
ricow1
2013/08/01 18:23:52
isn't it the other way around?
kustermann
2013/08/05 07:29:17
Yes, sorry.
| |
| 1686 * - [:maxBrowserProcesses < numberOfBrowserProcessesUsed:] | |
| 1687 * | |
| 1688 * It provides a [runCommand] method which will complete with a | |
| 1689 * [CommandOutput] object. | |
| 1690 * | |
| 1691 * It provides a [cleanup] method to free all the allocated resources. | |
| 1692 */ | |
| 1693 abstract class CommandExecutor { | |
| 1694 Future cleanup(); | |
| 1695 // TODO(kustermann): The [timeout] parameter should be a property of Command | |
|
ricow1
2013/08/01 18:23:52
agreed
| |
| 1696 Future<CommandOutput> runCommand(node, Command command, int timeout); | |
| 1697 } | |
| 1698 | |
| 1699 class CommandExecutorImpl implements CommandExecutor { | |
| 1700 final Map globalConfiguration; | |
| 1701 final int maxProcesses; | |
| 1702 final int maxBrowserProcesses; | |
| 1359 | 1703 |
| 1360 // For dartc/selenium batch processing we keep a list of batch processes. | 1704 // For dartc/selenium batch processing we keep a list of batch processes. |
| 1361 Map<String, List<BatchRunnerProcess>> _batchProcesses; | 1705 final _batchProcesses = new Map<String, List<BatchRunnerProcess>>(); |
| 1362 | 1706 // For browser tests we keepa [BrowserTestRunner] |
| 1363 // Cache information about test cases per test suite. For multiple | 1707 final _browserTestRunners = new Map<String, BrowserTestRunner>(); |
| 1364 // configurations there is no need to repeatedly search the file | 1708 |
| 1365 // system, generate tests, and search test files for options. | 1709 bool _finishing = false; |
| 1366 Map<String, List<TestInformation>> _testCache; | 1710 |
| 1367 | 1711 CommandExecutorImpl( |
| 1368 Map<String, BrowserTestRunner> _browserTestRunners; | 1712 this.globalConfiguration, this.maxProcesses, this.maxBrowserProcesses); |
| 1369 | 1713 |
| 1370 /** | 1714 Future cleanup() { |
| 1371 * String indicating the browser used to run the tests. Empty if no browser | 1715 assert(!_finishing); |
| 1372 * used. | 1716 _finishing = true; |
| 1373 */ | 1717 |
| 1374 String browserUsed = ''; | 1718 Future _terminateBatchRunners() { |
| 1375 | 1719 var futures = []; |
| 1376 /** | 1720 for (var runners in _batchProcesses.values) { |
| 1377 * Process running the selenium server .jar (only used for Safari and Opera | 1721 futures.addAll(runners.map((runner) => runner.terminate())); |
| 1378 * tests.) | 1722 } |
| 1379 */ | 1723 return Future.wait(futures); |
| 1380 io.Process _seleniumServer = null; | 1724 } |
| 1381 | 1725 |
| 1382 /** True if we are in the process of starting the server. */ | 1726 Future _terminateBrowserRunners() { |
| 1383 bool _startingServer = false; | 1727 var futures = |
| 1384 | 1728 _browserTestRunners.values.map((runner) => runner.terminate()); |
| 1385 /** True if we find that there is already a selenium jar running. */ | 1729 return Future.wait(futures); |
| 1386 bool _seleniumAlreadyRunning = false; | 1730 } |
| 1387 | 1731 |
| 1388 ProcessQueue(this._maxProcesses, | 1732 return Future.wait([_terminateBatchRunners(), _terminateBrowserRunners()]); |
| 1389 this._maxBrowserProcesses, | 1733 } |
| 1390 DateTime startTime, | 1734 |
| 1391 testSuites, | 1735 Future<CommandOutput> runCommand(node, Command command, int timeout) { |
|
ricow1
2013/08/02 07:40:32
you specify the type of the other variables, why n
ricow1
2013/08/02 07:40:32
I don't understand our need to pass around both th
kustermann
2013/08/05 07:29:17
This is a case where I would hit the 80 character
kustermann
2013/08/05 07:29:17
That is correct. Actually 'CommandeExecutor' shoul
| |
| 1392 this._eventListener, | 1736 assert(!_finishing); |
| 1393 this._allDone, | 1737 |
| 1394 [bool verbose = false, | 1738 Future<CommandOutput> runCommand(int retriesLeft) { |
| 1395 bool listTests = false, | 1739 return _runCommand(command, timeout).then((CommandOutput output) { |
| 1396 this._testCaseRecorder, | 1740 if (!output.canRunDependendCommands && retriesLeft > 0) { |
| 1397 this._testCaseOutputArchive]) | 1741 DebugLogger.warning("Rerunning Command: ($retriesLeft " |
| 1398 : _verbose = verbose, | 1742 "attempt(s) remains) [cmd: $command]"); |
| 1399 _listTests = listTests, | 1743 return runCommand(retriesLeft - 1); |
| 1400 _tests = new Queue<TestCase>(), | 1744 } else { |
| 1401 _batchProcesses = new Map<String, List<BatchRunnerProcess>>(), | 1745 return new Future.value(output); |
| 1402 _testCache = new Map<String, List<TestInformation>>(), | 1746 } |
| 1403 _browserTestRunners = new Map<String, BrowserTestRunner>() { | |
| 1404 _runTests(testSuites); | |
| 1405 } | |
| 1406 | |
| 1407 /** | |
| 1408 * Perform any cleanup needed once all tests in a TestSuite have completed | |
| 1409 * and notify our progress indicator that we are done. | |
| 1410 */ | |
| 1411 void _cleanupAndMarkDone() { | |
| 1412 _allDone(); | |
| 1413 if (browserUsed != '' && _seleniumServer != null) { | |
| 1414 _seleniumServer.kill(); | |
| 1415 } | |
| 1416 eventAllTestsDone(); | |
| 1417 } | |
| 1418 | |
| 1419 void _checkDone() { | |
| 1420 if (_allTestsWereEnqueued && _tests.isEmpty && _numProcesses == 0) { | |
| 1421 _terminateBatchRunners().then((_) { | |
| 1422 _terminateBrowserRunners().then((_) => _cleanupAndMarkDone()); | |
| 1423 }); | 1747 }); |
| 1424 } | 1748 } |
| 1425 } | 1749 return runCommand(command.numRetries); |
| 1426 | 1750 } |
| 1427 void _runTests(List<TestSuite> testSuites) { | 1751 |
| 1428 var newTest; | 1752 Future<CommandOutput> _runCommand(Command command, int timeout) { |
| 1429 var allTestsKnown; | 1753 var batchMode = !globalConfiguration['noBatch']; |
| 1430 | 1754 |
| 1431 if (_testCaseRecorder != null) { | 1755 if (command is BrowserTestCommand) { |
| 1432 // Mode: recording. | 1756 return _startBrowserControllerTest(command, timeout); |
| 1433 newTest = _testCaseRecorder.nextTestCase; | 1757 } else if (command is SeleniumTestCommand && batchMode) { |
| 1434 allTestsKnown = () { | 1758 var arguments = ['--force-refresh', '--browser=${command.browser}', |
| 1435 // We don't call any event*() methods, so test_progress.dart will not be | 1759 '--timeout=${timeout}', '--out', '${command.url}']; |
| 1436 // notified (that's fine, since we're not running any tests). | 1760 return _getBatchRunner(command.browser) |
| 1437 _testCaseRecorder.finish(); | 1761 .runCommand('selenium', command, timeout, arguments); |
| 1438 _allDone(); | 1762 } else if (command is AnalysisCommand && batchMode) { |
| 1439 }; | 1763 return _getBatchRunner(command.flavour) |
| 1764 .runCommand(command.flavour, command, timeout, command.arguments); | |
| 1440 } else { | 1765 } else { |
| 1441 if (_testCaseOutputArchive != null) { | 1766 return new RunningProcess(command, timeout).run(); |
| 1442 // Mode: replaying. | 1767 } |
| 1443 newTest = (TestCase testCase) { | 1768 } |
| 1444 // We're doing this asynchronously to emulate the normal behaviour. | 1769 |
| 1445 eventTestAdded(testCase); | 1770 BatchRunnerProcess _getBatchRunner(String identifier) { |
| 1446 Timer.run(() { | |
| 1447 var output = _testCaseOutputArchive.outputOf(testCase); | |
| 1448 testCase.completed(); | |
| 1449 eventFinishedTestCase(testCase); | |
| 1450 }); | |
| 1451 }; | |
| 1452 allTestsKnown = () { | |
| 1453 // If we're replaying commands, we need to call [_cleanupAndMarkDone] | |
| 1454 // manually. We're putting it at the end of the event queue to make | |
| 1455 // sure all the previous events were fired. | |
| 1456 Timer.run(() => _cleanupAndMarkDone()); | |
| 1457 }; | |
| 1458 } else { | |
| 1459 // Mode: none (we're not recording/replaying). | |
| 1460 newTest = (TestCase testCase) { | |
| 1461 _tests.add(testCase); | |
| 1462 eventTestAdded(testCase); | |
| 1463 _runTest(testCase); | |
| 1464 }; | |
| 1465 allTestsKnown = _checkDone; | |
| 1466 } | |
| 1467 } | |
| 1468 | |
| 1469 // FIXME: For some reason we cannot call this method on all test suites | |
| 1470 // in parallel. | |
| 1471 // If we do, not all tests get enqueued (if --arch=all was specified, | |
| 1472 // we don't get twice the number of tests [tested on -rvm -cnone]) | |
| 1473 // Issue: 7927 | |
| 1474 Iterator<TestSuite> iterator = testSuites.iterator; | |
| 1475 void enqueueNextSuite() { | |
| 1476 if (!iterator.moveNext()) { | |
| 1477 _allTestsWereEnqueued = true; | |
| 1478 allTestsKnown(); | |
| 1479 eventAllTestsKnown(); | |
| 1480 } else { | |
| 1481 iterator.current.forEachTest(newTest, _testCache, enqueueNextSuite); | |
| 1482 } | |
| 1483 } | |
| 1484 enqueueNextSuite(); | |
| 1485 } | |
| 1486 | |
| 1487 /** | |
| 1488 * True if we are using a browser + platform combination that needs the | |
| 1489 * Selenium server jar. | |
| 1490 */ | |
| 1491 bool get _needsSelenium => (io.Platform.operatingSystem == 'macos' && | |
| 1492 browserUsed == 'safari') || browserUsed == 'opera'; | |
| 1493 | |
| 1494 /** True if the Selenium Server is ready to be used. */ | |
| 1495 bool get _isSeleniumAvailable => _seleniumServer != null || | |
| 1496 _seleniumAlreadyRunning; | |
| 1497 | |
| 1498 /** | |
| 1499 * Restart all the processes that have been waiting/stopped for the server to | |
| 1500 * start up. If we just call this once we end up with a single-"threaded" run. | |
| 1501 */ | |
| 1502 void resumeTesting() { | |
| 1503 for (int i = 0; i < _maxProcesses; i++) _tryRunTest(); | |
| 1504 } | |
| 1505 | |
| 1506 /** Start the Selenium Server jar, if appropriate for this platform. */ | |
| 1507 void _ensureSeleniumServerRunning() { | |
| 1508 if (!_isSeleniumAvailable && !_startingServer) { | |
| 1509 _startingServer = true; | |
| 1510 | |
| 1511 // Check to see if the jar was already running before the program started. | |
| 1512 String cmd = 'ps'; | |
| 1513 var arg = ['aux']; | |
| 1514 if (io.Platform.operatingSystem == 'windows') { | |
| 1515 cmd = 'tasklist'; | |
| 1516 arg.add('/v'); | |
| 1517 } | |
| 1518 | |
| 1519 Future processFuture = io.Process.start(cmd, arg); | |
| 1520 processFuture.then((io.Process p) { | |
| 1521 // Drain stderr to not leak resources. | |
| 1522 p.stderr.listen((_) {}); | |
| 1523 final Stream<String> stdoutStringStream = | |
| 1524 p.stdout.transform(new io.StringDecoder()) | |
| 1525 .transform(new io.LineTransformer()); | |
| 1526 stdoutStringStream.listen((String line) { | |
| 1527 var regexp = new RegExp(r".*selenium-server-standalone.*"); | |
| 1528 if (regexp.hasMatch(line)) { | |
| 1529 _seleniumAlreadyRunning = true; | |
| 1530 resumeTesting(); | |
| 1531 } | |
| 1532 if (!_isSeleniumAvailable) { | |
| 1533 _startSeleniumServer(); | |
| 1534 } | |
| 1535 }); | |
| 1536 }).catchError((e) { | |
| 1537 // TODO(floitsch): should we try to report the stacktrace? | |
| 1538 print("Error starting process:"); | |
| 1539 print(" Command: $cmd ${arg.join(' ')}"); | |
| 1540 print(" Error: $e"); | |
| 1541 // TODO(ahe): How to report this as a test failure? | |
| 1542 io.exit(1); | |
| 1543 return true; | |
| 1544 }); | |
| 1545 } | |
| 1546 } | |
| 1547 | |
| 1548 void _runTest(TestCase test) { | |
| 1549 if (test.usesWebDriver) { | |
| 1550 browserUsed = test.configuration['runtime']; | |
| 1551 if (_needsSelenium) _ensureSeleniumServerRunning(); | |
| 1552 } | |
| 1553 _tryRunTest(); | |
| 1554 } | |
| 1555 | |
| 1556 /** | |
| 1557 * Monitor the output of the Selenium server, to know when we are ready to | |
| 1558 * begin running tests. | |
| 1559 * source: Output(Stream) from the Java server. | |
| 1560 */ | |
| 1561 void seleniumServerHandler(String line) { | |
| 1562 if (new RegExp(r".*Started.*Server.*").hasMatch(line) || | |
| 1563 new RegExp(r"Exception.*Selenium is already running.*").hasMatch( | |
| 1564 line)) { | |
| 1565 resumeTesting(); | |
| 1566 } | |
| 1567 } | |
| 1568 | |
| 1569 /** | |
| 1570 * For browser tests using Safari or Opera, we need to use the Selenium 1.0 | |
| 1571 * Java server. | |
| 1572 */ | |
| 1573 void _startSeleniumServer() { | |
| 1574 // Get the absolute path to the Selenium jar. | |
| 1575 String filePath = TestUtils.testScriptPath; | |
| 1576 String pathSep = io.Platform.pathSeparator; | |
| 1577 int index = filePath.lastIndexOf(pathSep); | |
| 1578 filePath = '${filePath.substring(0, index)}${pathSep}testing${pathSep}'; | |
| 1579 new io.Directory(filePath).list().listen((io.FileSystemEntity fse) { | |
| 1580 if (fse is io.File) { | |
| 1581 String file = fse.path; | |
| 1582 if (new RegExp(r"selenium-server-standalone-.*\.jar").hasMatch(file) | |
| 1583 && _seleniumServer == null) { | |
| 1584 Future processFuture = io.Process.start('java', ['-jar', file]); | |
| 1585 processFuture.then((io.Process server) { | |
| 1586 _seleniumServer = server; | |
| 1587 // Heads up: there seems to an obscure data race of some form in | |
| 1588 // the VM between launching the server process and launching the | |
| 1589 // test tasks that disappears when you read IO (which is | |
| 1590 // convenient, since that is our condition for knowing that the | |
| 1591 // server is ready). | |
| 1592 Stream<String> stdoutStringStream = | |
| 1593 _seleniumServer.stdout.transform(new io.StringDecoder()) | |
| 1594 .transform(new io.LineTransformer()); | |
| 1595 Stream<String> stderrStringStream = | |
| 1596 _seleniumServer.stderr.transform(new io.StringDecoder()) | |
| 1597 .transform(new io.LineTransformer()); | |
| 1598 stdoutStringStream.listen(seleniumServerHandler); | |
| 1599 stderrStringStream.listen(seleniumServerHandler); | |
| 1600 }).catchError((e) { | |
| 1601 // TODO(floitsch): should we try to report the stacktrace? | |
| 1602 print("Process error:"); | |
| 1603 print(" Command: java -jar $file"); | |
| 1604 print(" Error: $e"); | |
| 1605 // TODO(ahe): How to report this as a test failure? | |
| 1606 io.exit(1); | |
| 1607 return true; | |
| 1608 }); | |
| 1609 } | |
| 1610 } | |
| 1611 }); | |
| 1612 } | |
| 1613 | |
| 1614 Future _terminateBatchRunners() { | |
| 1615 var futures = new List(); | |
| 1616 for (var runners in _batchProcesses.values) { | |
| 1617 for (var runner in runners) { | |
| 1618 futures.add(runner.terminate()); | |
| 1619 } | |
| 1620 } | |
| 1621 // Change to Future.wait when updating binaries. | |
| 1622 return Future.wait(futures); | |
| 1623 } | |
| 1624 | |
| 1625 Future _terminateBrowserRunners() { | |
| 1626 var futures = []; | |
| 1627 for (BrowserTestRunner runner in _browserTestRunners.values) { | |
| 1628 futures.add(runner.terminate()); | |
| 1629 } | |
| 1630 return Future.wait(futures); | |
| 1631 } | |
| 1632 | |
| 1633 BatchRunnerProcess _getBatchRunner(TestCase test) { | |
| 1634 // Start batch processes if needed | 1771 // Start batch processes if needed |
| 1635 var compiler = test.configuration['compiler']; | 1772 var runners = _batchProcesses[identifier]; |
| 1636 var runners = _batchProcesses[compiler]; | |
| 1637 if (runners == null) { | 1773 if (runners == null) { |
| 1638 runners = new List<BatchRunnerProcess>(_maxProcesses); | 1774 runners = new List<BatchRunnerProcess>(maxProcesses); |
| 1639 for (int i = 0; i < _maxProcesses; i++) { | 1775 for (int i = 0; i < maxProcesses; i++) { |
| 1640 runners[i] = new BatchRunnerProcess(test); | 1776 runners[i] = new BatchRunnerProcess(); |
| 1641 } | 1777 } |
| 1642 _batchProcesses[compiler] = runners; | 1778 _batchProcesses[identifier] = runners; |
| 1643 } | 1779 } |
| 1644 | 1780 |
| 1645 for (var runner in runners) { | 1781 for (var runner in runners) { |
| 1646 if (!runner.active) return runner; | 1782 if (!runner._currentlyRunning) return runner; |
| 1647 } | 1783 } |
| 1648 throw new Exception('Unable to find inactive batch runner.'); | 1784 throw new Exception('Unable to find inactive batch runner.'); |
| 1649 } | 1785 } |
| 1650 | 1786 |
| 1651 Future<BrowserTestRunner> _getBrowserTestRunner(TestCase test) { | 1787 Future<CommandOutput> _startBrowserControllerTest( |
| 1652 var local_ip = test.configuration['local_ip']; | 1788 BrowserTestCommand browserCommand, int timeout) { |
| 1653 var runtime = test.configuration['runtime']; | 1789 var completer = new Completer<CommandOutput>(); |
| 1654 var num_browsers = _maxBrowserProcesses; | 1790 |
| 1655 if (_browserTestRunners[runtime] == null) { | 1791 var callback = (var output, var duration) { |
| 1792 var commandOutput = createCommandOutput(browserCommand, | |
| 1793 0, | |
| 1794 output == "TIMEOUT", | |
| 1795 encodeUtf8(output), | |
| 1796 [], | |
| 1797 duration, | |
| 1798 false); | |
| 1799 completer.complete(commandOutput); | |
| 1800 }; | |
| 1801 BrowserTest browserTest = new BrowserTest(browserCommand.url, | |
| 1802 callback, | |
| 1803 timeout); | |
| 1804 _getBrowserTestRunner(browserCommand.browser).then((testRunner) { | |
| 1805 testRunner.queueTest(browserTest); | |
| 1806 }); | |
| 1807 | |
| 1808 return completer.future; | |
| 1809 } | |
| 1810 | |
| 1811 Future<BrowserTestRunner> _getBrowserTestRunner(String browser) { | |
| 1812 var local_ip = globalConfiguration['local_ip']; | |
| 1813 var num_browsers = maxBrowserProcesses; | |
| 1814 if (_browserTestRunners[browser] == null) { | |
| 1656 var testRunner = | 1815 var testRunner = |
| 1657 new BrowserTestRunner(local_ip, runtime, num_browsers); | 1816 new BrowserTestRunner(local_ip, browser, num_browsers); |
| 1658 testRunner.logger = DebugLogger.info; | 1817 testRunner.logger = DebugLogger.info; |
| 1659 _browserTestRunners[runtime] = testRunner; | 1818 _browserTestRunners[browser] = testRunner; |
| 1660 return testRunner.start().then((started) { | 1819 return testRunner.start().then((started) { |
| 1661 if (started) { | 1820 if (started) { |
| 1662 return testRunner; | 1821 return testRunner; |
| 1663 } | 1822 } |
| 1664 print("Issue starting browser test runner"); | 1823 print("Issue starting browser test runner"); |
| 1665 io.exit(1); | 1824 io.exit(1); |
| 1666 }); | 1825 }); |
| 1667 } | 1826 } |
| 1668 return new Future.value(_browserTestRunners[runtime]); | 1827 return new Future.value(_browserTestRunners[browser]); |
| 1669 } | 1828 } |
| 1670 | 1829 } |
| 1671 void _startBrowserControllerTest(var test) { | 1830 |
| 1672 var callback = (var output, var duration) { | 1831 class RecordingCommandExecutor implements CommandExecutor { |
| 1673 var nextCommandIndex = test.commandOutputs.keys.length; | 1832 TestCaseRecorder _recorder; |
| 1674 new CommandOutput.fromCase(test, | 1833 |
| 1675 test.commands[nextCommandIndex], | 1834 RecordingCommandExecutor(io.Path path) |
| 1676 0, | 1835 : _recorder = new TestCaseRecorder(path); |
| 1677 false, | 1836 |
| 1678 output == "TIMEOUT", | 1837 Future<CommandOutput> runCommand(node, Command command, int timeout) { |
| 1679 encodeUtf8(output), | 1838 assert(node.dependencies.length == 0); |
| 1680 [], | 1839 assert(_cleanEnvironmentOverrides(command.environmentOverrides)); |
| 1681 duration, | 1840 _recorder.nextCommand(command, timeout); |
| 1682 false); | 1841 // Return dummy CommandOutput |
| 1683 test.completedHandler(test); | 1842 var output = |
| 1684 }; | 1843 createCommandOutput(command, 0, false, [], [], const Duration(), false); |
| 1685 BrowserTest browserTest = new BrowserTest(test.testingUrl, | 1844 return new Future.value(output); |
| 1686 callback, | 1845 } |
| 1687 test.timeout); | 1846 |
| 1688 _getBrowserTestRunner(test).then((testRunner) { | 1847 Future cleanup() { |
| 1689 testRunner.queueTest(browserTest); | 1848 _recorder.finish(); |
| 1690 }); | 1849 return new Future.value(); |
| 1691 } | 1850 } |
| 1692 | 1851 |
| 1693 void _tryRunTest() { | 1852 // Returns [:true:] if the environment contains only 'DART_CONFIGURATION' |
| 1694 _checkDone(); | 1853 bool _cleanEnvironmentOverrides(Map environment) { |
| 1695 // TODO(ricow): remove most of the hacked selenium code below when | 1854 if (environment == null) return true; |
| 1696 // we have eliminated the need. | 1855 return environment.length == 0 || |
| 1697 | 1856 (environment.length == 0 && |
| 1698 if (_numProcesses < _maxProcesses && !_tests.isEmpty) { | 1857 environment.containsKey("DART_CONFIGURATION")); |
| 1699 TestCase test = _tests.removeFirst(); | 1858 |
| 1700 if (_listTests) { | 1859 } |
| 1701 var fields = [test.displayName, | 1860 } |
| 1702 test.expectedOutcomes.join(','), | 1861 |
| 1703 test.isNegative.toString()]; | 1862 class ReplayingCommandExecutor implements CommandExecutor { |
| 1704 fields.addAll(test.commands.last.arguments); | 1863 TestCaseOutputArchive _archive = new TestCaseOutputArchive(); |
| 1705 print(fields.join('\t')); | 1864 |
| 1706 return; | 1865 ReplayingCommandExecutor(io.Path path) { |
| 1707 } | 1866 _archive.loadFromPath(path); |
| 1708 | 1867 } |
| 1709 if (test.usesWebDriver && _needsSelenium && !test.usesBrowserController | 1868 |
| 1710 && !_isSeleniumAvailable || | 1869 Future cleanup() => new Future.value(); |
| 1711 (test is BrowserTestCase && test.waitingForOtherTest)) { | 1870 |
| 1712 // The test is not yet ready to run. Put the test back in | 1871 Future<CommandOutput> runCommand(node, Command command, int timeout) { |
| 1713 // the queue. Avoid spin-polling by using a timeout. | 1872 assert(node.dependencies.length == 0); |
| 1714 _tests.add(test); | 1873 return new Future.value(_archive.outputOf(command)); |
| 1715 new Timer(new Duration(milliseconds: 100), | 1874 } |
| 1716 _tryRunTest); // Don't lose a process. | 1875 } |
| 1717 return; | 1876 |
| 1718 } | 1877 |
| 1719 // Before running any commands, we print out all commands if '--verbose' | 1878 /* |
| 1720 // was specified. | 1879 * [TestCaseCompleter] will listen for |
| 1721 if (_verbose && test.commandOutputs.length == 0) { | 1880 * NodeState.Processing -> NodeState.{Successfull,Failed} state changes and |
| 1722 int i = 1; | 1881 * will complete a TestCase if it is finished. |
| 1723 if (test is BrowserTestCase) { | 1882 * |
| 1724 // Additional command for rerunning the steps locally after the fact. | 1883 * It provides a stream [finishedTestCases], which will stream all TestCases |
| 1725 var command = | 1884 * once they're finished. After all TestCases are done, the stream will be |
| 1726 test.configuration["_servers_"].httpServerCommandline(); | 1885 * closed. |
| 1727 print('$i. $command'); | 1886 */ |
| 1728 i++; | 1887 class TestCaseCompleter { |
| 1729 } | 1888 static final COMPLETED_STATES = [dgraph.NodeState.Failed, |
| 1730 for (Command command in test.commands) { | 1889 dgraph.NodeState.Successful]; |
| 1731 print('$i. $command'); | 1890 final dgraph.Graph graph; |
| 1732 i++; | 1891 final TestCaseEnqueuer enqueuer; |
| 1892 final CommandQueue commandQueue; | |
| 1893 | |
| 1894 Map<Command, CommandOutput> _outputs = new Map<Command, CommandOutput>(); | |
| 1895 StreamController<TestCase> _controller = new StreamController<TestCase>(); | |
| 1896 | |
| 1897 TestCaseCompleter(this.graph, this.enqueuer, this.commandQueue) { | |
| 1898 var eventCondition = graph.events.where; | |
| 1899 | |
| 1900 // Store all the command outputs -- they will be delivered synchronously | |
| 1901 // (i.e. before state changes in the graph) | |
| 1902 commandQueue.completedCommands.listen((CommandOutput output) { | |
| 1903 _outputs[output.command] = output; | |
| 1904 }); | |
| 1905 | |
| 1906 // Listen for NodeState.Processing -> NodeState.{Successfull,Failed} | |
| 1907 // changes. | |
| 1908 eventCondition((event) => event is dgraph.StateChangedEvent) | |
| 1909 .listen((dgraph.StateChangedEvent event) { | |
| 1910 if (event.from == dgraph.NodeState.Processing) { | |
| 1911 assert(COMPLETED_STATES.contains(event.to)); | |
| 1912 _completeTestCasesIfPossible(event.node.userData); | |
| 1913 | |
| 1914 if (graph.isSealed && enqueuer.remainingTestCases.isEmpty) { | |
| 1915 _controller.close(); | |
| 1916 } | |
| 1917 } | |
| 1918 }); | |
| 1919 } | |
| 1920 | |
| 1921 Stream<TestCase> get finishedTestCases => _controller.stream; | |
| 1922 | |
| 1923 void _completeTestCasesIfPossible(Command command) { | |
| 1924 assert(_outputs[command] != null); | |
| 1925 | |
| 1926 var testCases = enqueuer.command2testCases[command]; | |
| 1927 | |
| 1928 // Update TestCases with command outputs | |
| 1929 for (TestCase test in testCases) { | |
| 1930 for (var icommand in test.commands) { | |
| 1931 var output = _outputs[icommand]; | |
| 1932 if (output != null) { | |
| 1933 test.commandOutputs[icommand] = output; | |
| 1733 } | 1934 } |
| 1734 } | 1935 } |
| 1735 | 1936 } |
| 1736 var isLastCommand = | 1937 |
| 1737 ((test.commands.length-1) == test.commandOutputs.length); | 1938 void completeTestCase(TestCase testCase) { |
| 1738 var isBrowserCommand = isLastCommand && (test is BrowserTestCase); | 1939 if (enqueuer.remainingTestCases.contains(testCase)) { |
| 1739 if (isBrowserCommand && _numBrowserProcesses == _maxBrowserProcesses) { | 1940 _controller.add(testCase); |
| 1740 // If there is no free browser runner, put it back into the queue. | 1941 enqueuer.remainingTestCases.remove(testCase); |
| 1741 _tests.add(test); | 1942 } else { |
| 1742 new Timer(new Duration(milliseconds: 100), | 1943 DebugLogger.error("${testCase.displayName} would be finished twice"); |
| 1743 _tryRunTest); // Don't lose a process. | |
| 1744 return; | |
| 1745 } | 1944 } |
| 1746 | 1945 } |
| 1747 eventStartTestCase(test); | 1946 |
| 1748 | 1947 for (var testCase in testCases) { |
| 1749 // Analyzer and browser test commands can be run by a [BatchRunnerProcess] | 1948 // Ask the [testCase] if it's done. Note that we assume, that |
| 1750 var nextCommandIndex = test.commandOutputs.keys.length; | 1949 // [TestCase.isFinished] will return true if all commands were executed |
| 1751 var numberOfCommands = test.commands.length; | 1950 // or if a previous one failed. |
| 1752 | 1951 if (testCase.isFinished) { |
| 1753 var useBatchRunnerForAnalyzer = | 1952 completeTestCase(testCase); |
| 1754 test.configuration['analyzer'] && | 1953 } |
| 1755 test.displayName != 'dartc/junit_tests'; | 1954 } |
| 1756 var isWebdriverCommand = nextCommandIndex == (numberOfCommands - 1) && | 1955 } |
| 1757 test.usesWebDriver && | 1956 } |
| 1758 !test.configuration['noBatch']; | 1957 |
| 1759 if (useBatchRunnerForAnalyzer || isWebdriverCommand) { | 1958 |
| 1760 TestCaseEvent oldCallback = test.completedHandler; | 1959 |
| 1761 void testCompleted(TestCase test_arg) { | 1960 class ProcessQueue { |
| 1762 _numProcesses--; | 1961 Map _globalConfiguration; |
| 1763 if (isBrowserCommand) { | 1962 |
| 1764 _numBrowserProcesses--; | 1963 bool _allTestsWereEnqueued = false; |
| 1765 } | 1964 |
| 1766 eventFinishedTestCase(test_arg); | 1965 bool _listTests; |
| 1767 if (test_arg is BrowserTestCase) { | 1966 Function _allDone; |
| 1768 (test_arg as BrowserTestCase).notifyObservers(); | 1967 final dgraph.Graph _graph = new dgraph.Graph(); |
| 1769 } | 1968 List<EventListener> _eventListener; |
| 1770 oldCallback(test_arg); | 1969 |
| 1771 _tryRunTest(); | 1970 ProcessQueue(this._globalConfiguration, |
| 1772 }; | 1971 maxProcesses, |
| 1773 test.completedHandler = testCompleted; | 1972 maxBrowserProcesses, |
| 1774 if (test.usesBrowserController) { | 1973 DateTime startTime, |
| 1775 _startBrowserControllerTest(test); | 1974 testSuites, |
| 1776 } else { | 1975 this._eventListener, |
| 1777 _getBatchRunner(test).startTest(test); | 1976 this._allDone, |
| 1977 [bool verbose = false, | |
| 1978 this._listTests = false, | |
| 1979 String recordingOutputFile, | |
| 1980 String recordedInputFile]) { | |
| 1981 bool recording = recordingOutputFile != null; | |
| 1982 bool replaying = recordedInputFile != null; | |
| 1983 | |
| 1984 // When the graph building is finished, notify event listeners. | |
| 1985 _graph.events | |
| 1986 .where((event) => event is dgraph.GraphSealedEvent).listen((event) { | |
| 1987 eventAllTestsKnown(); | |
| 1988 }); | |
| 1989 | |
| 1990 // Build up the dependency graph | |
| 1991 var testCaseEnqueuer = new TestCaseEnqueuer(_graph, (TestCase newTestCase) { | |
| 1992 eventTestAdded(newTestCase); | |
| 1993 }); | |
| 1994 | |
| 1995 // Queue commands as they become "runnable" | |
| 1996 var commandEnqueuer = new CommandEnqueuer(_graph); | |
| 1997 | |
| 1998 // CommandExecutor will execute commands | |
| 1999 var executor; | |
| 2000 if (recording) { | |
| 2001 executor = new RecordingCommandExecutor(new io.Path(recordingOutputFile)); | |
| 2002 } else if (replaying) { | |
| 2003 executor = new ReplayingCommandExecutor(new io.Path(recordedInputFile)); | |
| 2004 } else { | |
| 2005 executor = new CommandExecutorImpl( | |
| 2006 _globalConfiguration, maxProcesses, maxBrowserProcesses); | |
| 2007 } | |
| 2008 | |
| 2009 // Run "runnable commands" using [executor] subject to | |
| 2010 // maxProcesses/maxBrowserProcesses constraint | |
| 2011 var commandQueue = new CommandQueue( | |
| 2012 _graph, testCaseEnqueuer, executor, maxProcesses, maxBrowserProcesses, | |
| 2013 verbose); | |
| 2014 | |
| 2015 // Finish test cases when all commands were run (or some failed) | |
| 2016 var testCaseCompleter = | |
| 2017 new TestCaseCompleter(_graph, testCaseEnqueuer, commandQueue); | |
| 2018 testCaseCompleter.finishedTestCases.listen( | |
| 2019 (TestCase finishedTestCase) { | |
| 2020 // If we're recording, we don't report any TestCases to listeners. | |
| 2021 if (!recording) { | |
| 2022 eventFinishedTestCase(finishedTestCase); | |
| 1778 } | 2023 } |
| 1779 } else { | 2024 }, |
| 1780 // Once we've actually failed a test, technically, we wouldn't need to | 2025 onDone: () { |
| 1781 // bother retrying any subsequent tests since the bot is already red. | 2026 // Wait until the commandQueue/execturo is done (it may need to stop |
| 1782 // However, we continue to retry tests until we have actually failed | 2027 // batch runners, browser controllers, ....) |
| 1783 // four tests (arbitrarily chosen) for more debugable output, so that | 2028 commandQueue.done.then((_) => eventAllTestsDone()); |
| 1784 // the developer doesn't waste his or her time trying to fix a bunch of | |
| 1785 // tests that appear to be broken but were actually just flakes that | |
| 1786 // didn't get retried because there had already been one failure. | |
| 1787 bool allowRetry = _MAX_FAILED_NO_RETRY > _numFailedTests; | |
| 1788 runNextCommandWithRetries(test, allowRetry).then((TestCase testCase) { | |
| 1789 _numProcesses--; | |
| 1790 if (isBrowserCommand) { | |
| 1791 _numBrowserProcesses--; | |
| 1792 } | |
| 1793 if (isTestCaseFinished(testCase)) { | |
| 1794 testCase.completed(); | |
| 1795 eventFinishedTestCase(testCase); | |
| 1796 if (testCase is BrowserTestCase) { | |
| 1797 (testCase as BrowserTestCase).notifyObservers(); | |
| 1798 } | |
| 1799 } else { | |
| 1800 _tests.addFirst(testCase); | |
| 1801 } | |
| 1802 _tryRunTest(); | |
| 1803 }); | |
| 1804 } | |
| 1805 | |
| 1806 _numProcesses++; | |
| 1807 if (isBrowserCommand) { | |
| 1808 _numBrowserProcesses++; | |
| 1809 } | |
| 1810 } | |
| 1811 } | |
| 1812 | |
| 1813 bool isTestCaseFinished(TestCase testCase) { | |
| 1814 var numberOfCommandOutputs = testCase.commandOutputs.keys.length; | |
| 1815 var numberOfCommands = testCase.commands.length; | |
| 1816 | |
| 1817 var lastCommandCompleted = (numberOfCommandOutputs == numberOfCommands); | |
| 1818 var lastCommandOutput = testCase.lastCommandOutput; | |
| 1819 var lastCommand = lastCommandOutput.command; | |
| 1820 var timedOut = lastCommandOutput.hasTimedOut; | |
| 1821 var nonZeroExitCode = lastCommandOutput.exitCode != 0; | |
| 1822 // NOTE: If this was the last command or there was unexpected output | |
| 1823 // we're done with the test. | |
| 1824 // Otherwise we need to enqueue it again into the test queue. | |
| 1825 if (lastCommandCompleted || timedOut || nonZeroExitCode) { | |
| 1826 var verbose = testCase.configuration['verbose']; | |
| 1827 if (lastCommandOutput.unexpectedOutput && verbose != null && verbose) { | |
| 1828 print(testCase.displayName); | |
| 1829 print("stderr:"); | |
| 1830 print(decodeUtf8(lastCommandOutput.stderr)); | |
| 1831 if (!lastCommand.isPixelTest) { | |
| 1832 print("stdout:"); | |
| 1833 print(decodeUtf8(lastCommandOutput.stdout)); | |
| 1834 } else { | |
| 1835 print(""); | |
| 1836 print("DRT pixel test failed! stdout is not printed because it " | |
| 1837 "contains binary data!"); | |
| 1838 } | |
| 1839 } | |
| 1840 return true; | |
| 1841 } else { | |
| 1842 return false; | |
| 1843 } | |
| 1844 } | |
| 1845 | |
| 1846 Future runNextCommandWithRetries(TestCase testCase, bool allowRetry) { | |
| 1847 var completer = new Completer(); | |
| 1848 | |
| 1849 var nextCommandIndex = testCase.commandOutputs.keys.length; | |
| 1850 var numberOfCommands = testCase.commands.length; | |
| 1851 if (nextCommandIndex >= numberOfCommands) { | |
| 1852 throw "nextCommandIndex must be less than numberOfCommands"; | |
| 1853 } | |
| 1854 var command = testCase.commands[nextCommandIndex]; | |
| 1855 var isLastCommand = nextCommandIndex == (numberOfCommands - 1); | |
| 1856 | |
| 1857 void runCommand() { | |
| 1858 var runningProcess = new RunningProcess(testCase, command); | |
| 1859 runningProcess.start().then((CommandOutput commandOutput) { | |
| 1860 if (isLastCommand) { | |
| 1861 // NOTE: We need to call commandOutput.unexpectedOutput here. | |
| 1862 // Calling this getter may result in the side-effect, that | |
| 1863 // commandOutput.requestRetry is set to true. | |
| 1864 // (BrowserCommandOutputImpl._failedBecauseOfMissingXDisplay | |
| 1865 // does that for example) | |
| 1866 // TODO(ricow/kustermann): Issue 8206 | |
| 1867 var unexpectedOutput = commandOutput.unexpectedOutput; | |
| 1868 if (unexpectedOutput && allowRetry) { | |
| 1869 if (testCase.usesWebDriver | |
| 1870 && (testCase as BrowserTestCase).numRetries > 0) { | |
| 1871 // Selenium tests can be flaky. Try rerunning. | |
| 1872 commandOutput.requestRetry = true; | |
| 1873 } | |
| 1874 // FIXME(kustermann): Remove this condition once we figured out why | |
| 1875 // content_shell is sometimes not able to fetch resources from the | |
| 1876 // HttpServer. | |
| 1877 var configuration = testCase.configuration; | |
| 1878 if (configuration['runtime'] == 'drt' && | |
| 1879 configuration['system'] == 'windows' && | |
| 1880 (testCase as BrowserTestCase).numRetries > 0) { | |
| 1881 assert(TestUtils.isBrowserRuntime(configuration['runtime'])); | |
| 1882 commandOutput.requestRetry = true; | |
| 1883 } | |
| 1884 } | |
| 1885 } | |
| 1886 if (commandOutput.requestRetry) { | |
| 1887 commandOutput.requestRetry = false; | |
| 1888 (testCase as BrowserTestCase).numRetries--; | |
| 1889 DebugLogger.warning("Rerunning Test: ${testCase.displayName} " | |
| 1890 "(${(testCase as BrowserTestCase).numRetries} " | |
| 1891 "attempt(s) remains) [cmd:$command]"); | |
| 1892 runCommand(); | |
| 1893 } else { | |
| 1894 completer.complete(testCase); | |
| 1895 } | |
| 1896 }); | 2029 }); |
| 1897 } | 2030 |
| 1898 runCommand(); | 2031 // Start enqueing all TestCases |
| 1899 | 2032 testCaseEnqueuer.enqueueTestSuites(testSuites); |
| 1900 return completer.future; | |
| 1901 } | |
| 1902 | |
| 1903 void eventStartTestCase(TestCase testCase) { | |
| 1904 for (var listener in _eventListener) { | |
| 1905 listener.start(testCase); | |
| 1906 } | |
| 1907 } | 2033 } |
| 1908 | 2034 |
| 1909 void eventFinishedTestCase(TestCase testCase) { | 2035 void eventFinishedTestCase(TestCase testCase) { |
| 1910 if (testCase.lastCommandOutput.unexpectedOutput) { | |
| 1911 _numFailedTests++; | |
| 1912 } | |
| 1913 for (var listener in _eventListener) { | 2036 for (var listener in _eventListener) { |
| 1914 listener.done(testCase); | 2037 listener.done(testCase); |
| 1915 } | 2038 } |
| 1916 } | 2039 } |
| 1917 | 2040 |
| 1918 void eventTestAdded(TestCase testCase) { | 2041 void eventTestAdded(TestCase testCase) { |
| 1919 for (var listener in _eventListener) { | 2042 for (var listener in _eventListener) { |
| 1920 listener.testAdded(); | 2043 listener.testAdded(); |
| 1921 } | 2044 } |
| 1922 } | 2045 } |
| 1923 | 2046 |
| 1924 void eventAllTestsKnown() { | 2047 void eventAllTestsKnown() { |
| 1925 for (var listener in _eventListener) { | 2048 for (var listener in _eventListener) { |
| 1926 listener.allTestsKnown(); | 2049 listener.allTestsKnown(); |
| 1927 } | 2050 } |
| 1928 } | 2051 } |
| 1929 | 2052 |
| 1930 void eventAllTestsDone() { | 2053 void eventAllTestsDone() { |
| 1931 for (var listener in _eventListener) { | 2054 for (var listener in _eventListener) { |
| 1932 listener.allDone(); | 2055 listener.allDone(); |
| 1933 } | 2056 } |
| 2057 _allDone(); | |
| 1934 } | 2058 } |
| 1935 } | 2059 } |
| 1936 | |
| OLD | NEW |