Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(441)

Side by Side Diff: tools/testing/dart/test_runner.dart

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

Powered by Google App Engine
This is Rietveld 408576698