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

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

Powered by Google App Engine
This is Rietveld 408576698