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

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

Issue 841193003: cleanup to tools/testing/dart (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: one last bit Created 5 years, 11 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
« no previous file with comments | « tools/testing/dart/test_progress.dart ('k') | tools/testing/dart/test_suite.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 import "dart:convert" show LineSplitter, UTF8, JSON; 16 import 'dart:convert' show LineSplitter, UTF8, JSON;
17 // We need to use the 'io' prefix here, otherwise io.exitCode will shadow 17 // We need to use the 'io' prefix here, otherwise io.exitCode will shadow
18 // CommandOutput.exitCode in subclasses of CommandOutput. 18 // CommandOutput.exitCode in subclasses of CommandOutput.
19 import "dart:io" as io; 19 import 'dart:io' as io;
20 import "dart:math" as math; 20 import 'dart:math' as math;
21 import 'dependency_graph.dart' as dgraph;
22 import "browser_controller.dart";
23 import "path.dart";
24 import "status_file_parser.dart";
25 import "test_progress.dart";
26 import "test_suite.dart";
27 import "utils.dart";
28 import 'record_and_replay.dart';
29 21
30 const int CRASHING_BROWSER_EXITCODE = -10; 22 import 'lib/browser_controller.dart';
31 const int SLOW_TIMEOUT_MULTIPLIER = 4; 23 import 'lib/command.dart';
24 import 'lib/dependency_graph.dart' as dgraph;
25 import 'lib/path.dart';
26 import 'lib/record_and_replay.dart';
27 import 'lib/test_case.dart';
28 import 'lib/test_information.dart';
29 import 'lib/test_progress.dart';
30 import 'lib/utils.dart';
31 import 'test_suite.dart';
32 32
33 const MESSAGE_CANNOT_OPEN_DISPLAY = 'Gtk-WARNING **: cannot open display';
34 const MESSAGE_FAILED_TO_RUN_COMMAND = 'Failed to run command. return code=1';
35
36 typedef void TestCaseEvent(TestCase testCase);
37 typedef void ExitCodeEvent(int exitCode); 33 typedef void ExitCodeEvent(int exitCode);
38 typedef void EnqueueMoreWork(ProcessQueue queue); 34 typedef void EnqueueMoreWork(ProcessQueue queue);
39 35
40 // Some IO tests use these variables and get confused if the host environment 36 // Some IO tests use these variables and get confused if the host environment
41 // variables are inherited so they are excluded. 37 // variables are inherited so they are excluded.
42 const List<String> EXCLUDED_ENVIRONMENT_VARIABLES = 38 const List<String> EXCLUDED_ENVIRONMENT_VARIABLES =
43 const ['http_proxy', 'https_proxy', 'no_proxy', 39 const ['http_proxy', 'https_proxy', 'no_proxy',
44 'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']; 40 'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY'];
45 41
46
47 /** A command executed as a step in a test case. */
48 class Command {
49 /** A descriptive name for this command. */
50 String displayName;
51
52 /** Number of times this command *can* be retried */
53 int get maxNumRetries => 2;
54
55 /** Reproduction command */
56 String get reproductionCommand => null;
57
58 // We compute the Command.hashCode lazily and cache it here, since it might
59 // be expensive to compute (and hashCode is called often).
60 int _cachedHashCode;
61
62 Command._(this.displayName);
63
64 int get hashCode {
65 if (_cachedHashCode == null) {
66 var builder = new HashCodeBuilder();
67 _buildHashCode(builder);
68 _cachedHashCode = builder.value;
69 }
70 return _cachedHashCode;
71 }
72
73 operator ==(other) => identical(this, other) ||
74 (runtimeType == other.runtimeType && _equal(other));
75
76 void _buildHashCode(HashCodeBuilder builder) {
77 builder.addJson(displayName);
78 }
79
80 bool _equal(Command other) =>
81 hashCode == other.hashCode &&
82 displayName == other.displayName;
83
84 String toString() => reproductionCommand;
85
86 Future<bool> get outputIsUpToDate => new Future.value(false);
87 }
88
89 class ProcessCommand extends Command {
90 /** Path to the executable of this command. */
91 String executable;
92
93 /** Command line arguments to the executable. */
94 List<String> arguments;
95
96 /** Environment for the command */
97 Map<String, String> environmentOverrides;
98
99 /** Working directory for the command */
100 final String workingDirectory;
101
102 ProcessCommand._(String displayName, this.executable,
103 this.arguments,
104 [this.environmentOverrides = null,
105 this.workingDirectory = null])
106 : super._(displayName) {
107 if (io.Platform.operatingSystem == 'windows') {
108 // Windows can't handle the first command if it is a .bat file or the like
109 // with the slashes going the other direction.
110 // NOTE: Issue 1306
111 executable = executable.replaceAll('/', '\\');
112 }
113 }
114
115 void _buildHashCode(HashCodeBuilder builder) {
116 super._buildHashCode(builder);
117 builder.addJson(executable);
118 builder.addJson(workingDirectory);
119 builder.addJson(arguments);
120 builder.addJson(environmentOverrides);
121 }
122
123 bool _equal(ProcessCommand other) =>
124 super._equal(other) &&
125 executable == other.executable &&
126 deepJsonCompare(arguments, other.arguments) &&
127 workingDirectory == other.workingDirectory &&
128 deepJsonCompare(environmentOverrides, other.environmentOverrides);
129
130 String get reproductionCommand {
131 var command = ([executable]..addAll(arguments))
132 .map(escapeCommandLineArgument).join(' ');
133 if (workingDirectory != null) {
134 command = "$command (working directory: $workingDirectory)";
135 }
136 return command;
137 }
138
139 Future<bool> get outputIsUpToDate => new Future.value(false);
140 }
141
142 class CompilationCommand extends ProcessCommand {
143 final String _outputFile;
144 final bool _neverSkipCompilation;
145 final List<Uri> _bootstrapDependencies;
146
147 CompilationCommand._(String displayName,
148 this._outputFile,
149 this._neverSkipCompilation,
150 this._bootstrapDependencies,
151 String executable,
152 List<String> arguments,
153 Map<String, String> environmentOverrides)
154 : super._(displayName, executable, arguments, environmentOverrides);
155
156 Future<bool> get outputIsUpToDate {
157 if (_neverSkipCompilation) return new Future.value(false);
158
159 Future<List<Uri>> readDepsFile(String path) {
160 var file = new io.File(new Path(path).toNativePath());
161 if (!file.existsSync()) {
162 return new Future.value(null);
163 }
164 return file.readAsLines().then((List<String> lines) {
165 var dependencies = new List<Uri>();
166 for (var line in lines) {
167 line = line.trim();
168 if (line.length > 0) {
169 dependencies.add(Uri.parse(line));
170 }
171 }
172 return dependencies;
173 });
174 }
175
176 return readDepsFile("$_outputFile.deps").then((dependencies) {
177 if (dependencies != null) {
178 dependencies.addAll(_bootstrapDependencies);
179 var jsOutputLastModified = TestUtils.lastModifiedCache.getLastModified(
180 new Uri(scheme: 'file', path: _outputFile));
181 if (jsOutputLastModified != null) {
182 for (var dependency in dependencies) {
183 var dependencyLastModified =
184 TestUtils.lastModifiedCache.getLastModified(dependency);
185 if (dependencyLastModified == null ||
186 dependencyLastModified.isAfter(jsOutputLastModified)) {
187 return false;
188 }
189 }
190 return true;
191 }
192 }
193 return false;
194 });
195 }
196
197 void _buildHashCode(HashCodeBuilder builder) {
198 super._buildHashCode(builder);
199 builder.addJson(_outputFile);
200 builder.addJson(_neverSkipCompilation);
201 builder.addJson(_bootstrapDependencies);
202 }
203
204 bool _equal(CompilationCommand other) =>
205 super._equal(other) &&
206 _outputFile == other._outputFile &&
207 _neverSkipCompilation == other._neverSkipCompilation &&
208 deepJsonCompare(_bootstrapDependencies, other._bootstrapDependencies);
209 }
210
211 /// This is just a Pair(String, Map) class with hashCode and operator ==
212 class AddFlagsKey {
213 final String flags;
214 final Map env;
215 AddFlagsKey(this.flags, this.env);
216 // Just use object identity for environment map
217 bool operator ==(other) =>
218 other is AddFlagsKey && flags == other.flags && env == other.env;
219 int get hashCode => flags.hashCode ^ env.hashCode;
220 }
221
222 class ContentShellCommand extends ProcessCommand {
223 ContentShellCommand._(String executable,
224 String htmlFile,
225 List<String> options,
226 List<String> dartFlags,
227 Map<String, String> environmentOverrides)
228 : super._("content_shell",
229 executable,
230 _getArguments(options, htmlFile),
231 _getEnvironment(environmentOverrides, dartFlags));
232
233 // Cache the modified environments in a map from the old environment and
234 // the string of Dart flags to the new environment. Avoid creating new
235 // environment object for each command object.
236 static Map<AddFlagsKey, Map> environments =
237 new Map<AddFlagsKey, Map>();
238
239 static Map _getEnvironment(Map env, List<String> dartFlags) {
240 var needDartFlags = dartFlags != null && dartFlags.length > 0;
241 if (needDartFlags) {
242 if (env == null) {
243 env = const { };
244 }
245 var flags = dartFlags.join(' ');
246 return environments.putIfAbsent(new AddFlagsKey(flags, env),
247 () => new Map.from(env)
248 ..addAll({'DART_FLAGS': flags, 'DART_FORWARDING_PRINT': '1'}));
249 }
250 return env;
251 }
252
253 static List<String> _getArguments(List<String> options, String htmlFile) {
254 var arguments = new List.from(options);
255 arguments.add(htmlFile);
256 return arguments;
257 }
258
259 int get maxNumRetries => 3;
260 }
261
262 class BrowserTestCommand extends Command {
263 final String browser;
264 final String url;
265 final Map configuration;
266 final bool retry;
267
268 BrowserTestCommand._(String _browser,
269 this.url,
270 this.configuration,
271 this.retry)
272 : super._(_browser), browser = _browser;
273
274 void _buildHashCode(HashCodeBuilder builder) {
275 super._buildHashCode(builder);
276 builder.addJson(browser);
277 builder.addJson(url);
278 builder.add(configuration);
279 builder.add(retry);
280 }
281
282 bool _equal(BrowserTestCommand other) =>
283 super._equal(other) &&
284 browser == other.browser &&
285 url == other.url &&
286 identical(configuration, other.configuration) &&
287 retry == other.retry;
288
289 String get reproductionCommand {
290 var parts = [TestUtils.dartTestExecutable.toString(),
291 'tools/testing/dart/launch_browser.dart',
292 browser,
293 url];
294 return parts.map(escapeCommandLineArgument).join(' ');
295 }
296
297 int get maxNumRetries => 4;
298 }
299
300 class BrowserHtmlTestCommand extends BrowserTestCommand {
301 List<String> expectedMessages;
302 BrowserHtmlTestCommand._(String browser,
303 String url,
304 Map configuration,
305 this.expectedMessages,
306 bool retry)
307 : super._(browser, url, configuration, retry);
308
309 void _buildHashCode(HashCodeBuilder builder) {
310 super._buildHashCode(builder);
311 builder.addJson(expectedMessages);
312 }
313
314 bool _equal(BrowserHtmlTestCommand other) =>
315 super._equal(other) &&
316 identical(expectedMessages, other.expectedMessages);
317 }
318
319 class AnalysisCommand extends ProcessCommand {
320 final String flavor;
321
322 AnalysisCommand._(this.flavor,
323 String displayName,
324 String executable,
325 List<String> arguments,
326 Map<String, String> environmentOverrides)
327 : super._(displayName, executable, arguments, environmentOverrides);
328
329 void _buildHashCode(HashCodeBuilder builder) {
330 super._buildHashCode(builder);
331 builder.addJson(flavor);
332 }
333
334 bool _equal(AnalysisCommand other) =>
335 super._equal(other) &&
336 flavor == other.flavor;
337 }
338
339 class VmCommand extends ProcessCommand {
340 VmCommand._(String executable,
341 List<String> arguments,
342 Map<String,String> environmentOverrides)
343 : super._("vm", executable, arguments, environmentOverrides);
344 }
345
346 class JSCommandlineCommand extends ProcessCommand {
347 JSCommandlineCommand._(String displayName,
348 String executable,
349 List<String> arguments,
350 [Map<String, String> environmentOverrides = null])
351 : super._(displayName,
352 executable,
353 arguments,
354 environmentOverrides);
355 }
356
357 class PubCommand extends ProcessCommand {
358 final String command;
359
360 PubCommand._(String pubCommand,
361 String pubExecutable,
362 String pubspecYamlDirectory,
363 String pubCacheDirectory)
364 : super._('pub_$pubCommand',
365 new io.File(pubExecutable).absolute.path,
366 [pubCommand],
367 {'PUB_CACHE' : pubCacheDirectory},
368 pubspecYamlDirectory), command = pubCommand;
369
370 void _buildHashCode(HashCodeBuilder builder) {
371 super._buildHashCode(builder);
372 builder.addJson(command);
373 }
374
375 bool _equal(PubCommand other) =>
376 super._equal(other) &&
377 command == other.command;
378 }
379
380 /* [ScriptCommand]s are executed by dart code. */
381 abstract class ScriptCommand extends Command {
382 ScriptCommand._(String displayName) : super._(displayName);
383
384 Future<ScriptCommandOutputImpl> run();
385 }
386
387 class CleanDirectoryCopyCommand extends ScriptCommand {
388 final String _sourceDirectory;
389 final String _destinationDirectory;
390
391 CleanDirectoryCopyCommand._(this._sourceDirectory, this._destinationDirectory)
392 : super._('dir_copy');
393
394 String get reproductionCommand =>
395 "Copying '$_sourceDirectory' to '$_destinationDirectory'.";
396
397 Future<ScriptCommandOutputImpl> run() {
398 var watch = new Stopwatch()..start();
399
400 var destination = new io.Directory(_destinationDirectory);
401
402 return destination.exists().then((bool exists) {
403 var cleanDirectoryFuture;
404 if (exists) {
405 cleanDirectoryFuture = TestUtils.deleteDirectory(_destinationDirectory);
406 } else {
407 cleanDirectoryFuture = new Future.value(null);
408 }
409 return cleanDirectoryFuture.then((_) {
410 return TestUtils.copyDirectory(_sourceDirectory, _destinationDirectory);
411 });
412 }).then((_) {
413 return new ScriptCommandOutputImpl(
414 this, Expectation.PASS, "", watch.elapsed);
415 }).catchError((error) {
416 return new ScriptCommandOutputImpl(
417 this, Expectation.FAIL, "An error occured: $error.", watch.elapsed);
418 });
419 }
420
421 void _buildHashCode(HashCodeBuilder builder) {
422 super._buildHashCode(builder);
423 builder.addJson(_sourceDirectory);
424 builder.addJson(_destinationDirectory);
425 }
426
427 bool _equal(CleanDirectoryCopyCommand other) =>
428 super._equal(other) &&
429 _sourceDirectory == other._sourceDirectory &&
430 _destinationDirectory == other._destinationDirectory;
431 }
432
433 class ModifyPubspecYamlCommand extends ScriptCommand {
434 String _pubspecYamlFile;
435 String _destinationFile;
436 Map<String, Map> _dependencyOverrides;
437
438 ModifyPubspecYamlCommand._(this._pubspecYamlFile,
439 this._destinationFile,
440 this._dependencyOverrides)
441 : super._("modify_pubspec") {
442 assert(_pubspecYamlFile.endsWith("pubspec.yaml"));
443 assert(_destinationFile.endsWith("pubspec.yaml"));
444 }
445
446 String get reproductionCommand =>
447 "Adding necessary dependency overrides to '$_pubspecYamlFile' "
448 "(destination = $_destinationFile).";
449
450 Future<ScriptCommandOutputImpl> run() {
451 var watch = new Stopwatch()..start();
452
453 var pubspecLockFile =
454 _destinationFile.substring(0, _destinationFile.length - ".yaml".length)
455 + ".lock";
456
457 var file = new io.File(_pubspecYamlFile);
458 var destinationFile = new io.File(_destinationFile);
459 var lockfile = new io.File(pubspecLockFile);
460 return file.readAsString().then((String yamlString) {
461 var dependencyOverrideSection = new StringBuffer();
462 if (_dependencyOverrides.isNotEmpty) {
463 dependencyOverrideSection.write(
464 "\n"
465 "# This section was autogenerated by test.py!\n"
466 "dependency_overrides:\n");
467 _dependencyOverrides.forEach((String packageName, Map override) {
468 dependencyOverrideSection.write(" $packageName:\n");
469 override.forEach((overrideKey, overrideValue) {
470 dependencyOverrideSection.write(
471 " $overrideKey: $overrideValue\n");
472 });
473 });
474 }
475 var modifiedYamlString = "$yamlString\n$dependencyOverrideSection";
476 return destinationFile.writeAsString(modifiedYamlString).then((_) {
477 lockfile.exists().then((bool lockfileExists) {
478 if (lockfileExists) {
479 return lockfile.delete();
480 }
481 });
482 });
483 }).then((_) {
484 return new ScriptCommandOutputImpl(
485 this, Expectation.PASS, "", watch.elapsed);
486 }).catchError((error) {
487 return new ScriptCommandOutputImpl(
488 this, Expectation.FAIL, "An error occured: $error.", watch.elapsed);
489 });
490 }
491
492 void _buildHashCode(HashCodeBuilder builder) {
493 super._buildHashCode(builder);
494 builder.addJson(_pubspecYamlFile);
495 builder.addJson(_destinationFile);
496 builder.addJson(_dependencyOverrides);
497 }
498
499 bool _equal(ModifyPubspecYamlCommand other) =>
500 super._equal(other) &&
501 _pubspecYamlFile == other._pubspecYamlFile &&
502 _destinationFile == other._destinationFile &&
503 deepJsonCompare(_dependencyOverrides, other._dependencyOverrides);
504 }
505
506 /*
507 * [MakeSymlinkCommand] makes a symbolic link to another directory.
508 */
509 class MakeSymlinkCommand extends ScriptCommand {
510 String _link;
511 String _target;
512
513 MakeSymlinkCommand._(this._link, this._target) : super._('make_symlink');
514
515 String get reproductionCommand =>
516 "Make symbolic link '$_link' (target: $_target)'.";
517
518 Future<ScriptCommandOutputImpl> run() {
519 var watch = new Stopwatch()..start();
520 var targetFile = new io.Directory(_target);
521 return targetFile.exists().then((bool targetExists) {
522 if (!targetExists) {
523 throw new Exception("Target '$_target' does not exist");
524 }
525 var link = new io.Link(_link);
526
527 return link.exists()
528 .then((bool exists) { if (exists) return link.delete(); })
529 .then((_) => link.create(_target));
530 }).then((_) {
531 return new ScriptCommandOutputImpl(
532 this, Expectation.PASS, "", watch.elapsed);
533 }).catchError((error) {
534 return new ScriptCommandOutputImpl(
535 this, Expectation.FAIL, "An error occured: $error.", watch.elapsed);
536 });
537 }
538
539 void _buildHashCode(HashCodeBuilder builder) {
540 super._buildHashCode(builder);
541 builder.addJson(_link);
542 builder.addJson(_target);
543 }
544
545 bool _equal(MakeSymlinkCommand other) =>
546 super._equal(other) &&
547 _link == other._link &&
548 _target == other._target;
549 }
550
551 class CommandBuilder {
552 static final CommandBuilder instance = new CommandBuilder._();
553
554 bool _cleared = false;
555 final _cachedCommands = new Map<Command, Command>();
556
557 CommandBuilder._();
558
559 void clearCommandCache() {
560 _cachedCommands.clear();
561 _cleared = true;
562 }
563
564 ContentShellCommand getContentShellCommand(String executable,
565 String htmlFile,
566 List<String> options,
567 List<String> dartFlags,
568 Map<String, String> environment) {
569 ContentShellCommand command = new ContentShellCommand._(
570 executable, htmlFile, options, dartFlags, environment);
571 return _getUniqueCommand(command);
572 }
573
574 BrowserTestCommand getBrowserTestCommand(String browser,
575 String url,
576 Map configuration,
577 bool retry) {
578 var command = new BrowserTestCommand._(browser, url, configuration, retry);
579 return _getUniqueCommand(command);
580 }
581
582 BrowserHtmlTestCommand getBrowserHtmlTestCommand(String browser,
583 String url,
584 Map configuration,
585 List<String> expectedMessages,
586 bool retry) {
587 var command = new BrowserHtmlTestCommand._(
588 browser, url, configuration, expectedMessages, retry);
589 return _getUniqueCommand(command);
590 }
591
592 CompilationCommand getCompilationCommand(String displayName,
593 outputFile,
594 neverSkipCompilation,
595 List<Uri> bootstrapDependencies,
596 String executable,
597 List<String> arguments,
598 Map<String, String> environment) {
599 var command =
600 new CompilationCommand._(
601 displayName, outputFile, neverSkipCompilation,
602 bootstrapDependencies, executable, arguments, environment);
603 return _getUniqueCommand(command);
604 }
605
606 AnalysisCommand getAnalysisCommand(
607 String displayName, executable, arguments, environmentOverrides,
608 {String flavor: 'dartanalyzer'}) {
609 var command = new AnalysisCommand._(
610 flavor, displayName, executable, arguments, environmentOverrides);
611 return _getUniqueCommand(command);
612 }
613
614 VmCommand getVmCommand(String executable,
615 List<String> arguments,
616 Map<String, String> environmentOverrides) {
617 var command = new VmCommand._(executable, arguments, environmentOverrides);
618 return _getUniqueCommand(command);
619 }
620
621 Command getJSCommandlineCommand(String displayName, executable, arguments,
622 [environment = null]) {
623 var command = new JSCommandlineCommand._(displayName, executable, arguments,
624 environment);
625 return _getUniqueCommand(command);
626 }
627
628 Command getProcessCommand(String displayName, executable, arguments,
629 [environment = null, workingDirectory = null]) {
630 var command = new ProcessCommand._(displayName, executable, arguments,
631 environment, workingDirectory);
632 return _getUniqueCommand(command);
633 }
634
635 Command getCopyCommand(String sourceDirectory, String destinationDirectory) {
636 var command = new CleanDirectoryCopyCommand._(sourceDirectory,
637 destinationDirectory);
638 return _getUniqueCommand(command);
639 }
640
641 Command getPubCommand(String pubCommand,
642 String pubExecutable,
643 String pubspecYamlDirectory,
644 String pubCacheDirectory) {
645 var command = new PubCommand._(pubCommand,
646 pubExecutable,
647 pubspecYamlDirectory,
648 pubCacheDirectory);
649 return _getUniqueCommand(command);
650 }
651
652 Command getMakeSymlinkCommand(String link, String target) {
653 return _getUniqueCommand(new MakeSymlinkCommand._(link, target));
654 }
655
656 Command getModifyPubspecCommand(String pubspecYamlFile, Map depsOverrides,
657 {String destinationFile: null}) {
658 if (destinationFile == null) destinationFile = pubspecYamlFile;
659 return _getUniqueCommand(new ModifyPubspecYamlCommand._(
660 pubspecYamlFile, destinationFile, depsOverrides));
661 }
662
663 Command _getUniqueCommand(Command command) {
664 // All Command classes implement hashCode and operator==.
665 // We check if this command has already been built.
666 // If so, we return the cached one. Otherwise we
667 // store the one given as [command] argument.
668 if (_cleared) {
669 throw new Exception(
670 "CommandBuilder.get[type]Command called after cache cleared");
671 }
672 var cachedCommand = _cachedCommands[command];
673 if (cachedCommand != null) {
674 return cachedCommand;
675 }
676 _cachedCommands[command] = command;
677 return command;
678 }
679 }
680
681 /**
682 * TestCase contains all the information needed to run a test and evaluate
683 * its output. Running a test involves starting a separate process, with
684 * the executable and arguments given by the TestCase, and recording its
685 * stdout and stderr output streams, and its exit code. TestCase only
686 * contains static information about the test; actually running the test is
687 * performed by [ProcessQueue] using a [RunningProcess] object.
688 *
689 * The output information is stored in a [CommandOutput] instance contained
690 * in TestCase.commandOutputs. The last CommandOutput instance is responsible
691 * for evaluating if the test has passed, failed, crashed, or timed out, and the
692 * TestCase has information about what the expected result of the test should
693 * be.
694 *
695 * The TestCase has a callback function, [completedHandler], that is run when
696 * the test is completed.
697 */
698 class TestCase extends UniqueObject {
699 // Flags set in _expectations from the optional argument info.
700 static final int IS_NEGATIVE = 1 << 0;
701 static final int HAS_RUNTIME_ERROR = 1 << 1;
702 static final int HAS_STATIC_WARNING = 1 << 2;
703 static final int IS_NEGATIVE_IF_CHECKED = 1 << 3;
704 static final int HAS_COMPILE_ERROR = 1 << 4;
705 static final int HAS_COMPILE_ERROR_IF_CHECKED = 1 << 5;
706 static final int EXPECT_COMPILE_ERROR = 1 << 6;
707 /**
708 * A list of commands to execute. Most test cases have a single command.
709 * Dart2js tests have two commands, one to compile the source and another
710 * to execute it. Some isolate tests might even have three, if they require
711 * compiling multiple sources that are run in isolation.
712 */
713 List<Command> commands;
714 Map<Command, CommandOutput> commandOutputs = new Map<Command,CommandOutput>();
715
716 Map configuration;
717 String displayName;
718 int _expectations = 0;
719 int hash = 0;
720 Set<Expectation> expectedOutcomes;
721
722 TestCase(this.displayName,
723 this.commands,
724 this.configuration,
725 this.expectedOutcomes,
726 {isNegative: false,
727 TestInformation info: null}) {
728 if (isNegative || displayName.contains("negative_test")) {
729 _expectations |= IS_NEGATIVE;
730 }
731 if (info != null) {
732 _setExpectations(info);
733 hash = info.originTestPath.relativeTo(TestUtils.dartDir)
734 .toString().hashCode;
735 }
736 }
737
738 void _setExpectations(TestInformation info) {
739 // We don't want to keep the entire (large) TestInformation structure,
740 // so we copy the needed bools into flags set in a single integer.
741 if (info.hasRuntimeError) _expectations |= HAS_RUNTIME_ERROR;
742 if (info.hasStaticWarning) _expectations |= HAS_STATIC_WARNING;
743 if (info.isNegativeIfChecked) _expectations |= IS_NEGATIVE_IF_CHECKED;
744 if (info.hasCompileError) _expectations |= HAS_COMPILE_ERROR;
745 if (info.hasCompileErrorIfChecked) {
746 _expectations |= HAS_COMPILE_ERROR_IF_CHECKED;
747 }
748 if (info.hasCompileError ||
749 (configuration['checked'] && info.hasCompileErrorIfChecked)) {
750 _expectations |= EXPECT_COMPILE_ERROR;
751 }
752 }
753
754 bool get isNegative => _expectations & IS_NEGATIVE != 0;
755 bool get hasRuntimeError => _expectations & HAS_RUNTIME_ERROR != 0;
756 bool get hasStaticWarning => _expectations & HAS_STATIC_WARNING != 0;
757 bool get isNegativeIfChecked => _expectations & IS_NEGATIVE_IF_CHECKED != 0;
758 bool get hasCompileError => _expectations & HAS_COMPILE_ERROR != 0;
759 bool get hasCompileErrorIfChecked =>
760 _expectations & HAS_COMPILE_ERROR_IF_CHECKED != 0;
761 bool get expectCompileError => _expectations & EXPECT_COMPILE_ERROR != 0;
762
763 bool get unexpectedOutput {
764 var outcome = lastCommandOutput.result(this);
765 return !expectedOutcomes.any((expectation) {
766 return outcome.canBeOutcomeOf(expectation);
767 });
768 }
769
770 Expectation get result => lastCommandOutput.result(this);
771
772 CommandOutput get lastCommandOutput {
773 if (commandOutputs.length == 0) {
774 throw new Exception("CommandOutputs is empty, maybe no command was run? ("
775 "displayName: '$displayName', "
776 "configurationString: '$configurationString')");
777 }
778 return commandOutputs[commands[commandOutputs.length - 1]];
779 }
780
781 Command get lastCommandExecuted {
782 if (commandOutputs.length == 0) {
783 throw new Exception("CommandOutputs is empty, maybe no command was run? ("
784 "displayName: '$displayName', "
785 "configurationString: '$configurationString')");
786 }
787 return commands[commandOutputs.length - 1];
788 }
789
790 int get timeout {
791 if (expectedOutcomes.contains(Expectation.SLOW)) {
792 return configuration['timeout'] * SLOW_TIMEOUT_MULTIPLIER;
793 } else {
794 return configuration['timeout'];
795 }
796 }
797
798 String get configurationString {
799 final compiler = configuration['compiler'];
800 final runtime = configuration['runtime'];
801 final mode = configuration['mode'];
802 final arch = configuration['arch'];
803 final checked = configuration['checked'] ? '-checked' : '';
804 return "$compiler-$runtime$checked ${mode}_$arch";
805 }
806
807 List<String> get batchTestArguments {
808 assert(commands.last is ProcessCommand);
809 return (commands.last as ProcessCommand).arguments;
810 }
811
812 bool get isFlaky {
813 if (expectedOutcomes.contains(Expectation.SKIP) ||
814 expectedOutcomes.contains(Expectation.SKIP_BY_DESIGN)) {
815 return false;
816 }
817
818 return expectedOutcomes
819 .where((expectation) => !expectation.isMetaExpectation).length > 1;
820 }
821
822 bool get isFinished {
823 return commandOutputs.length > 0 &&
824 (!lastCommandOutput.successful ||
825 commands.length == commandOutputs.length);
826 }
827 }
828
829
830 /**
831 * BrowserTestCase has an extra compilation command that is run in a separate
832 * process, before the regular test is run as in the base class [TestCase].
833 * If the compilation command fails, then the rest of the test is not run.
834 */
835 class BrowserTestCase extends TestCase {
836
837 BrowserTestCase(displayName, commands, configuration,
838 expectedOutcomes, info, isNegative, this._testingUrl)
839 : super(displayName, commands, configuration,
840 expectedOutcomes, isNegative: isNegative, info: info);
841
842 String _testingUrl;
843
844 String get testingUrl => _testingUrl;
845 }
846
847 class UnittestSuiteMessagesMixin {
848 bool _isAsyncTest(String testOutput) {
849 return testOutput.contains("unittest-suite-wait-for-done");
850 }
851
852 bool _isAsyncTestSuccessful(String testOutput) {
853 return testOutput.contains("unittest-suite-success");
854 }
855
856 Expectation _negateOutcomeIfIncompleteAsyncTest(Expectation outcome,
857 String testOutput) {
858 // If this is an asynchronous test and the asynchronous operation didn't
859 // complete successfully, it's outcome is Expectation.FAIL.
860 // TODO: maybe we should introduce a AsyncIncomplete marker or so
861 if (outcome == Expectation.PASS) {
862 if (_isAsyncTest(testOutput) &&
863 !_isAsyncTestSuccessful(testOutput)) {
864 return Expectation.FAIL;
865 }
866 }
867 return outcome;
868 }
869 }
870
871 /**
872 * CommandOutput records the output of a completed command: the process's exit
873 * code, the standard output and standard error, whether the process timed out,
874 * and the time the process took to run. It also contains a pointer to the
875 * [TestCase] this is the output of.
876 */
877 abstract class CommandOutput {
878 Command get command;
879
880 Expectation result(TestCase testCase);
881
882 bool get hasCrashed;
883
884 bool get hasTimedOut;
885
886 bool didFail(testcase);
887
888 bool hasFailed(TestCase testCase);
889
890 bool get canRunDependendCommands;
891
892 bool get successful; // otherwise we might to retry running
893
894 Duration get time;
895
896 int get exitCode;
897
898 int get pid;
899
900 List<int> get stdout;
901
902 List<int> get stderr;
903
904 List<String> get diagnostics;
905
906 bool get compilationSkipped;
907 }
908
909 class CommandOutputImpl extends UniqueObject implements CommandOutput {
910 Command command;
911 int exitCode;
912
913 bool timedOut;
914 List<int> stdout;
915 List<int> stderr;
916 Duration time;
917 List<String> diagnostics;
918 bool compilationSkipped;
919 int pid;
920
921 /**
922 * A flag to indicate we have already printed a warning about ignoring the VM
923 * crash, to limit the amount of output produced per test.
924 */
925 bool alreadyPrintedWarning = false;
926
927 // TODO(kustermann): Remove testCase from this class.
928 CommandOutputImpl(Command this.command,
929 int this.exitCode,
930 bool this.timedOut,
931 List<int> this.stdout,
932 List<int> this.stderr,
933 Duration this.time,
934 bool this.compilationSkipped,
935 int this.pid) {
936 diagnostics = [];
937 }
938
939 Expectation result(TestCase testCase) {
940 if (hasCrashed) return Expectation.CRASH;
941 if (hasTimedOut) return Expectation.TIMEOUT;
942 return hasFailed(testCase) ? Expectation.FAIL : Expectation.PASS;
943 }
944
945 bool get hasCrashed {
946 // The Java dartc runner and dart2js exits with code 253 in case
947 // of unhandled exceptions.
948 if (exitCode == 253) return true;
949 if (io.Platform.operatingSystem == 'windows') {
950 // The VM uses std::abort to terminate on asserts.
951 // std::abort terminates with exit code 3 on Windows.
952 if (exitCode == 3 || exitCode == CRASHING_BROWSER_EXITCODE) {
953 return !timedOut;
954 }
955 // If a program receives an uncaught system exception, the program
956 // terminates with the exception code as exit code.
957 // The 0x3FFFFF00 mask here tries to determine if an exception indicates
958 // a crash of the program.
959 // System exception codes can be found in 'winnt.h', for example
960 // "#define STATUS_ACCESS_VIOLATION ((DWORD) 0xC0000005)"
961 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0));
962 }
963 return !timedOut && ((exitCode < 0));
964 }
965
966 bool get hasTimedOut => timedOut;
967
968 bool didFail(TestCase testCase) {
969 return (exitCode != 0 && !hasCrashed);
970 }
971
972 bool get canRunDependendCommands {
973 // FIXME(kustermann): We may need to change this
974 return !hasTimedOut && exitCode == 0;
975 }
976
977 bool get successful {
978 // FIXME(kustermann): We may need to change this
979 return !hasTimedOut && exitCode == 0;
980 }
981
982 // Reverse result of a negative test.
983 bool hasFailed(TestCase testCase) {
984 return testCase.isNegative ? !didFail(testCase) : didFail(testCase);
985 }
986
987 Expectation _negateOutcomeIfNegativeTest(Expectation outcome,
988 bool isNegative) {
989 if (!isNegative) return outcome;
990
991 if (outcome.canBeOutcomeOf(Expectation.FAIL)) {
992 return Expectation.PASS;
993 }
994 return Expectation.FAIL;
995 }
996 }
997
998 class BrowserCommandOutputImpl extends CommandOutputImpl {
999 // Although tests are reported as passing, content shell sometimes exits with
1000 // a nonzero exitcode which makes our dartium builders extremely falky.
1001 // See: http://dartbug.com/15139.
1002 static int WHITELISTED_CONTENTSHELL_EXITCODE = -1073740022;
1003 static bool isWindows = io.Platform.operatingSystem == 'windows';
1004
1005 bool _failedBecauseOfMissingXDisplay;
1006
1007 BrowserCommandOutputImpl(
1008 command,
1009 exitCode,
1010 timedOut,
1011 stdout,
1012 stderr,
1013 time,
1014 compilationSkipped) :
1015 super(command,
1016 exitCode,
1017 timedOut,
1018 stdout,
1019 stderr,
1020 time,
1021 compilationSkipped,
1022 0) {
1023 _failedBecauseOfMissingXDisplay = _didFailBecauseOfMissingXDisplay();
1024 if (_failedBecauseOfMissingXDisplay) {
1025 DebugLogger.warning("Warning: Test failure because of missing XDisplay");
1026 // If we get the X server error, or DRT crashes with a core dump, retry
1027 // the test.
1028 }
1029 }
1030
1031 Expectation result(TestCase testCase) {
1032 // Handle crashes and timeouts first
1033 if (hasCrashed) return Expectation.CRASH;
1034 if (hasTimedOut) return Expectation.TIMEOUT;
1035
1036 var outcome = _getOutcome();
1037
1038 if (testCase.hasRuntimeError) {
1039 if (!outcome.canBeOutcomeOf(Expectation.RUNTIME_ERROR)) {
1040 return Expectation.MISSING_RUNTIME_ERROR;
1041 }
1042 }
1043 if (testCase.isNegative) {
1044 if (outcome.canBeOutcomeOf(Expectation.FAIL)) return Expectation.PASS;
1045 return Expectation.FAIL;
1046 }
1047 return outcome;
1048 }
1049
1050 bool get successful => canRunDependendCommands;
1051
1052 bool get canRunDependendCommands {
1053 // We cannot rely on the exit code of content_shell as a method to determine
1054 // if we were successful or not.
1055 return super.canRunDependendCommands && !didFail(null);
1056 }
1057
1058 bool get hasCrashed {
1059 return super.hasCrashed || _rendererCrashed;
1060 }
1061
1062 Expectation _getOutcome() {
1063 if (_failedBecauseOfMissingXDisplay) {
1064 return Expectation.FAIL;
1065 }
1066
1067 if (_browserTestFailure) {
1068 return Expectation.RUNTIME_ERROR;
1069 }
1070 return Expectation.PASS;
1071 }
1072
1073 bool _didFailBecauseOfMissingXDisplay() {
1074 // Browser case:
1075 // If the browser test failed, it may have been because content shell
1076 // and the virtual framebuffer X server didn't hook up, or it crashed with
1077 // a core dump. Sometimes content shell crashes after it has set the stdout
1078 // to PASS, so we have to do this check first.
1079 var stderrLines = decodeUtf8(super.stderr).split("\n");
1080 for (String line in stderrLines) {
1081 // TODO(kustermann,ricow): Issue: 7564
1082 // This seems to happen quite frequently, we need to figure out why.
1083 if (line.contains(MESSAGE_CANNOT_OPEN_DISPLAY) ||
1084 line.contains(MESSAGE_FAILED_TO_RUN_COMMAND)) {
1085 return true;
1086 }
1087 }
1088 return false;
1089 }
1090
1091 bool get _rendererCrashed =>
1092 decodeUtf8(super.stdout).contains("#CRASHED - rendere");
1093
1094 bool get _browserTestFailure {
1095 // Browser tests fail unless stdout contains
1096 // 'Content-Type: text/plain' followed by 'PASS'.
1097 bool hasContentType = false;
1098 var stdoutLines = decodeUtf8(super.stdout).split("\n");
1099 var containsFail = false;
1100 var containsPass = false;
1101 for (String line in stdoutLines) {
1102 switch (line) {
1103 case 'Content-Type: text/plain':
1104 hasContentType = true;
1105 break;
1106 case 'FAIL':
1107 if (hasContentType) {
1108 containsFail = true;
1109 }
1110 break;
1111 case 'PASS':
1112 if (hasContentType) {
1113 containsPass = true;
1114 }
1115 break;
1116 }
1117 }
1118 if (hasContentType) {
1119 if (containsFail && containsPass) {
1120 DebugLogger.warning("Test had 'FAIL' and 'PASS' in stdout. ($command)");
1121 }
1122 if (!containsFail && !containsPass) {
1123 DebugLogger.warning("Test had neither 'FAIL' nor 'PASS' in stdout. "
1124 "($command)");
1125 return true;
1126 }
1127 if (containsFail) {
1128 return true;
1129 }
1130 assert(containsPass);
1131 if (exitCode != 0) {
1132 var message = "All tests passed, but exitCode != 0. "
1133 "Actual exitcode: $exitCode. "
1134 "($command)";
1135 DebugLogger.warning(message);
1136 diagnostics.add(message);
1137 }
1138 return (!hasCrashed &&
1139 exitCode != 0 &&
1140 (!isWindows || exitCode != WHITELISTED_CONTENTSHELL_EXITCODE));
1141 }
1142 DebugLogger.warning("Couldn't find 'Content-Type: text/plain' in output. "
1143 "($command).");
1144 return true;
1145 }
1146 }
1147
1148 class HTMLBrowserCommandOutputImpl extends BrowserCommandOutputImpl {
1149 HTMLBrowserCommandOutputImpl(
1150 command,
1151 exitCode,
1152 timedOut,
1153 stdout,
1154 stderr,
1155 time,
1156 compilationSkipped) :
1157 super(command,
1158 exitCode,
1159 timedOut,
1160 stdout,
1161 stderr,
1162 time,
1163 compilationSkipped);
1164
1165 bool didFail(TestCase testCase) {
1166 return _getOutcome() != Expectation.PASS;
1167 }
1168
1169
1170 bool get _browserTestFailure {
1171 // We should not need to convert back and forward.
1172 var output = decodeUtf8(super.stdout);
1173 if (output.contains("FAIL")) return true;
1174 return !output.contains("PASS");
1175 }
1176 }
1177
1178 class BrowserTestJsonResult {
1179 static const ALLOWED_TYPES =
1180 const ['sync_exception', 'window_onerror', 'script_onerror',
1181 'window_compilationerror', 'print', 'message_received', 'dom',
1182 'debug'];
1183
1184 final Expectation outcome;
1185 final String htmlDom;
1186 final List events;
1187
1188 BrowserTestJsonResult(this.outcome, this.htmlDom, this.events);
1189
1190 static BrowserTestJsonResult parseFromString(String content) {
1191 void validate(String assertion, bool value) {
1192 if (!value) {
1193 throw "InvalidFormat sent from browser driving page: $assertion:\n\n"
1194 "$content";
1195 }
1196 }
1197
1198 var events;
1199 try {
1200 events = JSON.decode(content);
1201 if (events != null) {
1202 validate("Message must be a List", events is List);
1203
1204 Map<String, List<String>> messagesByType = {};
1205 ALLOWED_TYPES.forEach((type) => messagesByType[type] = <String>[]);
1206
1207 for (var entry in events) {
1208 validate("An entry must be a Map", entry is Map);
1209
1210 var type = entry['type'];
1211 var value = entry['value'];
1212 var timestamp = entry['timestamp'];
1213
1214 validate("'type' of an entry must be a String",
1215 type is String);
1216 validate("'type' has to be in $ALLOWED_TYPES.",
1217 ALLOWED_TYPES.contains(type));
1218 validate("'timestamp' of an entry must be a number",
1219 timestamp is num);
1220
1221 messagesByType[type].add(value);
1222 }
1223 validate("The message must have exactly one 'dom' entry.",
1224 messagesByType['dom'].length == 1);
1225
1226 var dom = messagesByType['dom'][0];
1227 if (dom.endsWith('\n')) {
1228 dom = '$dom\n';
1229 }
1230
1231 return new BrowserTestJsonResult(
1232 _getOutcome(messagesByType), dom, events);
1233 }
1234 } catch(error) {
1235 // If something goes wrong, we know the content was not in the correct
1236 // JSON format. So we can't parse it.
1237 // The caller is responsible for falling back to the old way of
1238 // determining if a test failed.
1239 }
1240
1241 return null;
1242 }
1243
1244 static Expectation _getOutcome(Map<String, List<String>> messagesByType) {
1245 occured(type) => messagesByType[type].length > 0;
1246 searchForMsg(types, message) {
1247 return types.any((type) => messagesByType[type].contains(message));
1248 }
1249
1250 // FIXME(kustermann,ricow): I think this functionality doesn't work in
1251 // test_controller.js: So far I haven't seen anything being reported on
1252 // "window.compilationerror"
1253 if (occured('window_compilationerror')) {
1254 return Expectation.COMPILETIME_ERROR;
1255 }
1256
1257 if (occured('sync_exception') ||
1258 occured('window_onerror') ||
1259 occured('script_onerror')) {
1260 return Expectation.RUNTIME_ERROR;
1261 }
1262
1263 if (messagesByType['dom'][0].contains('FAIL')) {
1264 return Expectation.RUNTIME_ERROR;
1265 }
1266
1267 // We search for these messages in 'print' and 'message_received' because
1268 // the unittest implementation posts these messages using
1269 // "window.postMessage()" instead of the normal "print()" them.
1270
1271 var isAsyncTest = searchForMsg(['print', 'message_received'],
1272 'unittest-suite-wait-for-done');
1273 var isAsyncSuccess =
1274 searchForMsg(['print', 'message_received'], 'unittest-suite-success') ||
1275 searchForMsg(['print', 'message_received'], 'unittest-suite-done');
1276
1277 if (isAsyncTest) {
1278 if (isAsyncSuccess) {
1279 return Expectation.PASS;
1280 }
1281 return Expectation.RUNTIME_ERROR;
1282 }
1283
1284 var mainStarted =
1285 searchForMsg(['print', 'message_received'], 'dart-calling-main');
1286 var mainDone =
1287 searchForMsg(['print', 'message_received'], 'dart-main-done');
1288
1289 if (mainStarted && mainDone) {
1290 return Expectation.PASS;
1291 }
1292 return Expectation.FAIL;
1293 }
1294 }
1295
1296 class BrowserControllerTestOutcome extends CommandOutputImpl
1297 with UnittestSuiteMessagesMixin {
1298 BrowserTestOutput _result;
1299 Expectation _rawOutcome;
1300
1301 factory BrowserControllerTestOutcome(Command command,
1302 BrowserTestOutput result) {
1303 void validate(String assertion, bool value) {
1304 if (!value) {
1305 throw "InvalidFormat sent from browser driving page: $assertion:\n\n"
1306 "${result.lastKnownMessage}";
1307 }
1308 }
1309
1310 String indent(String string, int numSpaces) {
1311 var spaces = new List.filled(numSpaces, ' ').join('');
1312 return string.replaceAll('\r\n', '\n')
1313 .split('\n')
1314 .map((line) => "$spaces$line")
1315 .join('\n');
1316 }
1317
1318 String stdout = "";
1319 String stderr = "";
1320 Expectation outcome;
1321
1322 var parsedResult =
1323 BrowserTestJsonResult.parseFromString(result.lastKnownMessage);
1324 if (parsedResult != null) {
1325 outcome = parsedResult.outcome;
1326 } else {
1327 // Old way of determining whether a test failed or passed.
1328 if (result.lastKnownMessage.contains("FAIL")) {
1329 outcome = Expectation.RUNTIME_ERROR;
1330 } else if (result.lastKnownMessage.contains("PASS")) {
1331 outcome = Expectation.PASS;
1332 } else {
1333 outcome = Expectation.RUNTIME_ERROR;
1334 }
1335 }
1336
1337 if (result.didTimeout) {
1338 if (result.delayUntilTestStarted != null) {
1339 stderr = "This test timed out. The delay until the test actually "
1340 "started was: ${result.delayUntilTestStarted}.";
1341 } else {
1342 // TODO(ricow/kustermann) as soon as we record the state periodically,
1343 // we will have more information and can remove this warning.
1344 stderr = "This test has not notified test.py that it started running. "
1345 "This could be a bug in test.py! "
1346 "Please contact ricow/kustermann";
1347 }
1348 }
1349
1350 if (parsedResult != null) {
1351 stdout = "events:\n${indent(prettifyJson(parsedResult.events), 2)}\n\n";
1352 } else {
1353 stdout = "message:\n${indent(result.lastKnownMessage, 2)}\n\n";
1354 }
1355
1356 stderr =
1357 '$stderr\n\n'
1358 'BrowserOutput while running the test (* EXPERIMENTAL *):\n'
1359 'BrowserOutput.stdout:\n'
1360 '${indent(result.browserOutput.stdout.toString(), 2)}\n'
1361 'BrowserOutput.stderr:\n'
1362 '${indent(result.browserOutput.stderr.toString(), 2)}\n'
1363 '\n';
1364 return new BrowserControllerTestOutcome._internal(
1365 command, result, outcome, encodeUtf8(stdout), encodeUtf8(stderr));
1366 }
1367
1368 BrowserControllerTestOutcome._internal(
1369 Command command, BrowserTestOutput result, this._rawOutcome,
1370 List<int> stdout, List<int> stderr)
1371 : super(command, 0, result.didTimeout, stdout, stderr, result.duration,
1372 false, 0) {
1373 _result = result;
1374 }
1375
1376 Expectation result(TestCase testCase) {
1377 // Handle timeouts first
1378 if (_result.didTimeout) return Expectation.TIMEOUT;
1379
1380 // Multitests are handled specially
1381 if (testCase.hasRuntimeError) {
1382 if (_rawOutcome == Expectation.RUNTIME_ERROR) return Expectation.PASS;
1383 return Expectation.MISSING_RUNTIME_ERROR;
1384 }
1385
1386 return _negateOutcomeIfNegativeTest(_rawOutcome, testCase.isNegative);
1387 }
1388 }
1389
1390
1391 class AnalysisCommandOutputImpl extends CommandOutputImpl {
1392 // An error line has 8 fields that look like:
1393 // ERROR|COMPILER|MISSING_SOURCE|file:/tmp/t.dart|15|1|24|Missing source.
1394 final int ERROR_LEVEL = 0;
1395 final int ERROR_TYPE = 1;
1396 final int FILENAME = 3;
1397 final int FORMATTED_ERROR = 7;
1398
1399 AnalysisCommandOutputImpl(command,
1400 exitCode,
1401 timedOut,
1402 stdout,
1403 stderr,
1404 time,
1405 compilationSkipped) :
1406 super(command,
1407 exitCode,
1408 timedOut,
1409 stdout,
1410 stderr,
1411 time,
1412 compilationSkipped,
1413 0);
1414
1415 Expectation result(TestCase testCase) {
1416 // TODO(kustermann): If we run the analyzer not in batch mode, make sure
1417 // that command.exitCodes matches 2 (errors), 1 (warnings), 0 (no warnings,
1418 // no errors)
1419
1420 // Handle crashes and timeouts first
1421 if (hasCrashed) return Expectation.CRASH;
1422 if (hasTimedOut) return Expectation.TIMEOUT;
1423
1424 // Get the errors/warnings from the analyzer
1425 List<String> errors = [];
1426 List<String> warnings = [];
1427 parseAnalyzerOutput(errors, warnings);
1428
1429 // Handle errors / missing errors
1430 if (testCase.expectCompileError) {
1431 if (errors.length > 0) {
1432 return Expectation.PASS;
1433 }
1434 return Expectation.MISSING_COMPILETIME_ERROR;
1435 }
1436 if (errors.length > 0) {
1437 return Expectation.COMPILETIME_ERROR;
1438 }
1439
1440 // Handle static warnings / missing static warnings
1441 if (testCase.hasStaticWarning) {
1442 if (warnings.length > 0) {
1443 return Expectation.PASS;
1444 }
1445 return Expectation.MISSING_STATIC_WARNING;
1446 }
1447 if (warnings.length > 0) {
1448 return Expectation.STATIC_WARNING;
1449 }
1450
1451 assert (errors.length == 0 && warnings.length == 0);
1452 assert (!testCase.hasCompileError &&
1453 !testCase.hasStaticWarning);
1454 return Expectation.PASS;
1455 }
1456
1457 void parseAnalyzerOutput(List<String> outErrors, List<String> outWarnings) {
1458 // Parse a line delimited by the | character using \ as an escape charager
1459 // like: FOO|BAR|FOO\|BAR|FOO\\BAZ as 4 fields: FOO BAR FOO|BAR FOO\BAZ
1460 List<String> splitMachineError(String line) {
1461 StringBuffer field = new StringBuffer();
1462 List<String> result = [];
1463 bool escaped = false;
1464 for (var i = 0 ; i < line.length; i++) {
1465 var c = line[i];
1466 if (!escaped && c == '\\') {
1467 escaped = true;
1468 continue;
1469 }
1470 escaped = false;
1471 if (c == '|') {
1472 result.add(field.toString());
1473 field = new StringBuffer();
1474 continue;
1475 }
1476 field.write(c);
1477 }
1478 result.add(field.toString());
1479 return result;
1480 }
1481
1482 for (String line in decodeUtf8(super.stderr).split("\n")) {
1483 if (line.length == 0) continue;
1484 List<String> fields = splitMachineError(line);
1485 // We only consider errors/warnings for files of interest.
1486 if (fields.length > FORMATTED_ERROR) {
1487 if (fields[ERROR_LEVEL] == 'ERROR') {
1488 outErrors.add(fields[FORMATTED_ERROR]);
1489 } else if (fields[ERROR_LEVEL] == 'WARNING') {
1490 outWarnings.add(fields[FORMATTED_ERROR]);
1491 }
1492 // OK to Skip error output that doesn't match the machine format
1493 }
1494 }
1495 }
1496 }
1497
1498 class VmCommandOutputImpl extends CommandOutputImpl
1499 with UnittestSuiteMessagesMixin {
1500 static const DART_VM_EXITCODE_COMPILE_TIME_ERROR = 254;
1501 static const DART_VM_EXITCODE_UNCAUGHT_EXCEPTION = 255;
1502
1503 VmCommandOutputImpl(Command command, int exitCode, bool timedOut,
1504 List<int> stdout, List<int> stderr, Duration time,
1505 int pid)
1506 : super(command, exitCode, timedOut, stdout, stderr, time, false, pid);
1507
1508 Expectation result(TestCase testCase) {
1509 // Handle crashes and timeouts first
1510 if (hasCrashed) return Expectation.CRASH;
1511 if (hasTimedOut) return Expectation.TIMEOUT;
1512
1513 // Multitests are handled specially
1514 if (testCase.expectCompileError) {
1515 if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) {
1516 return Expectation.PASS;
1517 }
1518 return Expectation.MISSING_COMPILETIME_ERROR;
1519 }
1520 if (testCase.hasRuntimeError) {
1521 // TODO(kustermann): Do we consider a "runtimeError" only an uncaught
1522 // exception or does any nonzero exit code fullfil this requirement?
1523 if (exitCode != 0) {
1524 return Expectation.PASS;
1525 }
1526 return Expectation.MISSING_RUNTIME_ERROR;
1527 }
1528
1529 // The actual outcome depends on the exitCode
1530 Expectation outcome;
1531 if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) {
1532 outcome = Expectation.COMPILETIME_ERROR;
1533 } else if (exitCode == DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) {
1534 outcome = Expectation.RUNTIME_ERROR;
1535 } else if (exitCode != 0) {
1536 // This is a general fail, in case we get an unknown nonzero exitcode.
1537 outcome = Expectation.FAIL;
1538 } else {
1539 outcome = Expectation.PASS;
1540 }
1541 outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout));
1542 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
1543 }
1544 }
1545
1546 class CompilationCommandOutputImpl extends CommandOutputImpl {
1547 static const DART2JS_EXITCODE_CRASH = 253;
1548
1549 CompilationCommandOutputImpl(Command command, int exitCode, bool timedOut,
1550 List<int> stdout, List<int> stderr, Duration time,
1551 bool compilationSkipped)
1552 : super(command, exitCode, timedOut, stdout, stderr, time,
1553 compilationSkipped, 0);
1554
1555 Expectation result(TestCase testCase) {
1556 // Handle general crash/timeout detection.
1557 if (hasCrashed) return Expectation.CRASH;
1558 if (hasTimedOut) return Expectation.TIMEOUT;
1559
1560 // Handle dart2js/dart2dart specific crash detection
1561 if (exitCode == DART2JS_EXITCODE_CRASH ||
1562 exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_COMPILE_TIME_ERROR ||
1563 exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) {
1564 return Expectation.CRASH;
1565 }
1566
1567 // Multitests are handled specially
1568 if (testCase.expectCompileError) {
1569 // Nonzero exit code of the compiler means compilation failed
1570 // TODO(kustermann): Do we have a special exit code in that case???
1571 if (exitCode != 0) {
1572 return Expectation.PASS;
1573 }
1574 return Expectation.MISSING_COMPILETIME_ERROR;
1575 }
1576
1577 // TODO(kustermann): This is a hack, remove it
1578 if (testCase.hasRuntimeError && testCase.commands.length > 1) {
1579 // We expected to run the test, but we got an compile time error.
1580 // If the compilation succeeded, we wouldn't be in here!
1581 assert(exitCode != 0);
1582 return Expectation.COMPILETIME_ERROR;
1583 }
1584
1585 Expectation outcome =
1586 exitCode == 0 ? Expectation.PASS : Expectation.COMPILETIME_ERROR;
1587 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
1588 }
1589 }
1590
1591 class JsCommandlineOutputImpl extends CommandOutputImpl
1592 with UnittestSuiteMessagesMixin {
1593 JsCommandlineOutputImpl(Command command, int exitCode, bool timedOut,
1594 List<int> stdout, List<int> stderr, Duration time)
1595 : super(command, exitCode, timedOut, stdout, stderr, time, false, 0);
1596
1597 Expectation result(TestCase testCase) {
1598 // Handle crashes and timeouts first
1599 if (hasCrashed) return Expectation.CRASH;
1600 if (hasTimedOut) return Expectation.TIMEOUT;
1601
1602 if (testCase.hasRuntimeError) {
1603 if (exitCode != 0) return Expectation.PASS;
1604 return Expectation.MISSING_RUNTIME_ERROR;
1605 }
1606
1607 var outcome = exitCode == 0 ? Expectation.PASS : Expectation.RUNTIME_ERROR;
1608 outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout));
1609 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
1610 }
1611 }
1612
1613 class PubCommandOutputImpl extends CommandOutputImpl {
1614 PubCommandOutputImpl(PubCommand command, int exitCode, bool timedOut,
1615 List<int> stdout, List<int> stderr, Duration time)
1616 : super(command, exitCode, timedOut, stdout, stderr, time, false, 0);
1617
1618 Expectation result(TestCase testCase) {
1619 // Handle crashes and timeouts first
1620 if (hasCrashed) return Expectation.CRASH;
1621 if (hasTimedOut) return Expectation.TIMEOUT;
1622
1623 if (exitCode == 0) {
1624 return Expectation.PASS;
1625 } else if ((command as PubCommand).command == 'get') {
1626 return Expectation.PUB_GET_ERROR;
1627 } else {
1628 return Expectation.FAIL;
1629 }
1630 }
1631 }
1632
1633 class ScriptCommandOutputImpl extends CommandOutputImpl {
1634 final Expectation _result;
1635
1636 ScriptCommandOutputImpl(ScriptCommand command, this._result,
1637 String scriptExecutionInformation, Duration time)
1638 : super(command, 0, false, [], [], time, false, 0) {
1639 var lines = scriptExecutionInformation.split("\n");
1640 diagnostics.addAll(lines);
1641 }
1642
1643 Expectation result(TestCase testCase) => _result;
1644
1645 bool get canRunDependendCommands => _result == Expectation.PASS;
1646
1647 bool get successful => _result == Expectation.PASS;
1648
1649 }
1650
1651 CommandOutput createCommandOutput(Command command,
1652 int exitCode,
1653 bool timedOut,
1654 List<int> stdout,
1655 List<int> stderr,
1656 Duration time,
1657 bool compilationSkipped,
1658 [int pid = 0]) {
1659 if (command is ContentShellCommand) {
1660 return new BrowserCommandOutputImpl(
1661 command, exitCode, timedOut, stdout, stderr,
1662 time, compilationSkipped);
1663 } else if (command is BrowserTestCommand) {
1664 return new HTMLBrowserCommandOutputImpl(
1665 command, exitCode, timedOut, stdout, stderr,
1666 time, compilationSkipped);
1667 } else if (command is AnalysisCommand) {
1668 return new AnalysisCommandOutputImpl(
1669 command, exitCode, timedOut, stdout, stderr,
1670 time, compilationSkipped);
1671 } else if (command is VmCommand) {
1672 return new VmCommandOutputImpl(
1673 command, exitCode, timedOut, stdout, stderr, time, pid);
1674 } else if (command is CompilationCommand) {
1675 return new CompilationCommandOutputImpl(
1676 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1677 } else if (command is JSCommandlineCommand) {
1678 return new JsCommandlineOutputImpl(
1679 command, exitCode, timedOut, stdout, stderr, time);
1680 } else if (command is PubCommand) {
1681 return new PubCommandOutputImpl(
1682 command, exitCode, timedOut, stdout, stderr, time);
1683 }
1684
1685 return new CommandOutputImpl(
1686 command, exitCode, timedOut, stdout, stderr,
1687 time, compilationSkipped, pid);
1688 }
1689
1690
1691 /** 42 /**
1692 * An OutputLog records the output from a test, but truncates it if 43 * An OutputLog records the output from a test, but truncates it if
1693 * it is longer than MAX_HEAD characters, and just keeps the head and 44 * it is longer than MAX_HEAD characters, and just keeps the head and
1694 * the last TAIL_LENGTH characters of the output. 45 * the last TAIL_LENGTH characters of the output.
1695 */ 46 */
1696 class OutputLog { 47 class OutputLog {
1697 static const int MAX_HEAD = 100 * 1024; 48 static const int MAX_HEAD = 100 * 1024;
1698 static const int TAIL_LENGTH = 10 * 1024; 49 static const int TAIL_LENGTH = 10 * 1024;
1699 List<int> head = <int>[]; 50 List<int> head = <int>[];
1700 List<int> tail; 51 List<int> tail;
(...skipping 912 matching lines...) Expand 10 before | Expand all | Expand 10 after
2613 } 964 }
2614 965
2615 Future cleanup() => new Future.value(); 966 Future cleanup() => new Future.value();
2616 967
2617 Future<CommandOutput> runCommand(node, ProcessCommand command, int timeout) { 968 Future<CommandOutput> runCommand(node, ProcessCommand command, int timeout) {
2618 assert(node.dependencies.length == 0); 969 assert(node.dependencies.length == 0);
2619 return new Future.value(_archive.outputOf(command)); 970 return new Future.value(_archive.outputOf(command));
2620 } 971 }
2621 } 972 }
2622 973
2623 bool shouldRetryCommand(CommandOutput output) {
2624 var command = output.command;
2625 // We rerun tests on Safari because 6.2 and 7.1 are flaky. Issue 21434.
2626 if (command is BrowserTestCommand &&
2627 command.retry &&
2628 command.browser == 'safari' &&
2629 output is BrowserControllerTestOutcome &&
2630 output._rawOutcome != Expectation.PASS) {
2631 return true;
2632 }
2633
2634 if (!output.successful) {
2635 List<String> stdout, stderr;
2636
2637 decodeOutput() {
2638 if (stdout == null && stderr == null) {
2639 stdout = decodeUtf8(output.stderr).split("\n");
2640 stderr = decodeUtf8(output.stderr).split("\n");
2641 }
2642 }
2643
2644 if (io.Platform.operatingSystem == 'linux') {
2645 decodeOutput();
2646 // No matter which command we ran: If we get failures due to the
2647 // "xvfb-run" issue 7564, try re-running the test.
2648 bool containsFailureMsg(String line) {
2649 return line.contains(MESSAGE_CANNOT_OPEN_DISPLAY) ||
2650 line.contains(MESSAGE_FAILED_TO_RUN_COMMAND);
2651 }
2652 if (stdout.any(containsFailureMsg) || stderr.any(containsFailureMsg)) {
2653 return true;
2654 }
2655 }
2656
2657 // We currently rerun dartium tests, see issue 14074.
2658 if (command is BrowserTestCommand &&
2659 command.retry &&
2660 command.browser == 'dartium') {
2661 return true;
2662 }
2663 }
2664 return false;
2665 }
2666
2667 /* 974 /*
2668 * [TestCaseCompleter] will listen for 975 * [TestCaseCompleter] will listen for
2669 * NodeState.Processing -> NodeState.{Successful,Failed} state changes and 976 * NodeState.Processing -> NodeState.{Successful,Failed} state changes and
2670 * will complete a TestCase if it is finished. 977 * will complete a TestCase if it is finished.
2671 * 978 *
2672 * It provides a stream [finishedTestCases], which will stream all TestCases 979 * It provides a stream [finishedTestCases], which will stream all TestCases
2673 * once they're finished. After all TestCases are done, the stream will be 980 * once they're finished. After all TestCases are done, the stream will be
2674 * closed. 981 * closed.
2675 */ 982 */
2676 class TestCaseCompleter { 983 class TestCaseCompleter {
(...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after
2953 } 1260 }
2954 } 1261 }
2955 1262
2956 void eventAllTestsDone() { 1263 void eventAllTestsDone() {
2957 for (var listener in _eventListener) { 1264 for (var listener in _eventListener) {
2958 listener.allDone(); 1265 listener.allDone();
2959 } 1266 }
2960 _allDone(); 1267 _allDone();
2961 } 1268 }
2962 } 1269 }
OLDNEW
« no previous file with comments | « tools/testing/dart/test_progress.dart ('k') | tools/testing/dart/test_suite.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698