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

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

Powered by Google App Engine
This is Rietveld 408576698