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