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

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

Issue 2933973002: Simplify Command classes. (Closed)
Patch Set: Rename class. Created 3 years, 6 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
« 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 import 'dart:async'; 12 import 'dart:async';
13 import 'dart:collection'; 13 import 'dart:collection';
14 import 'dart:convert'; 14 import 'dart:convert';
15 // We need to use the 'io' prefix here, otherwise io.exitCode will shadow 15 // We need to use the 'io' prefix here, otherwise io.exitCode will shadow
16 // CommandOutput.exitCode in subclasses of CommandOutput. 16 // CommandOutput.exitCode in subclasses of CommandOutput.
17 import 'dart:io' as io; 17 import 'dart:io' as io;
18 import 'dart:math' as math; 18 import 'dart:math' as math;
19 19
20 import 'android.dart'; 20 import 'android.dart';
21 import 'browser_controller.dart'; 21 import 'browser_controller.dart';
22 import 'command.dart';
23 import 'command_output.dart';
22 import 'configuration.dart'; 24 import 'configuration.dart';
23 import 'dependency_graph.dart' as dgraph; 25 import 'dependency_graph.dart' as dgraph;
24 import 'expectation.dart'; 26 import 'expectation.dart';
25 import 'path.dart';
26 import 'runtime_configuration.dart'; 27 import 'runtime_configuration.dart';
27 import 'test_progress.dart'; 28 import 'test_progress.dart';
28 import 'test_suite.dart'; 29 import 'test_suite.dart';
29 import 'utils.dart'; 30 import 'utils.dart';
30 31
31 const int CRASHING_BROWSER_EXITCODE = -10; 32 const int CRASHING_BROWSER_EXITCODE = -10;
32 const int SLOW_TIMEOUT_MULTIPLIER = 4; 33 const int SLOW_TIMEOUT_MULTIPLIER = 4;
33 const int NON_UTF_FAKE_EXITCODE = 0xFFFD; 34 const int NON_UTF_FAKE_EXITCODE = 0xFFFD;
34 35
35 const MESSAGE_CANNOT_OPEN_DISPLAY = 'Gtk-WARNING **: cannot open display'; 36 const MESSAGE_CANNOT_OPEN_DISPLAY = 'Gtk-WARNING **: cannot open display';
36 const MESSAGE_FAILED_TO_RUN_COMMAND = 'Failed to run command. return code=1'; 37 const MESSAGE_FAILED_TO_RUN_COMMAND = 'Failed to run command. return code=1';
37 38
38 typedef void TestCaseEvent(TestCase testCase); 39 typedef void TestCaseEvent(TestCase testCase);
39 typedef void ExitCodeEvent(int exitCode); 40 typedef void ExitCodeEvent(int exitCode);
40 typedef void EnqueueMoreWork(ProcessQueue queue); 41 typedef void EnqueueMoreWork(ProcessQueue queue);
41 typedef void Action(); 42 typedef void Action();
42 typedef Future<AdbCommandResult> StepFunction(); 43 typedef Future<AdbCommandResult> StepFunction();
43 44
44 // Some IO tests use these variables and get confused if the host environment 45 // Some IO tests use these variables and get confused if the host environment
45 // variables are inherited so they are excluded. 46 // variables are inherited so they are excluded.
46 const EXCLUDED_ENVIRONMENT_VARIABLES = const [ 47 const EXCLUDED_ENVIRONMENT_VARIABLES = const [
47 'http_proxy', 48 'http_proxy',
48 'https_proxy', 49 'https_proxy',
49 'no_proxy', 50 'no_proxy',
50 'HTTP_PROXY', 51 'HTTP_PROXY',
51 'HTTPS_PROXY', 52 'HTTPS_PROXY',
52 'NO_PROXY' 53 'NO_PROXY'
53 ]; 54 ];
54 55
55 /** A command executed as a step in a test case. */
56 class Command {
57 /** A descriptive name for this command. */
58 String displayName;
59
60 /** Number of times this command *can* be retried */
61 int get maxNumRetries => 2;
62
63 /** Reproduction command */
64 String get reproductionCommand => null;
65
66 // We compute the Command.hashCode lazily and cache it here, since it might
67 // be expensive to compute (and hashCode is called often).
68 int _cachedHashCode;
69
70 Command._(this.displayName);
71
72 int get hashCode {
73 if (_cachedHashCode == null) {
74 var builder = new HashCodeBuilder();
75 _buildHashCode(builder);
76 _cachedHashCode = builder.value;
77 }
78 return _cachedHashCode;
79 }
80
81 operator ==(Object other) =>
82 identical(this, other) ||
83 (runtimeType == other.runtimeType && _equal(other as Command));
84
85 void _buildHashCode(HashCodeBuilder builder) {
86 builder.addJson(displayName);
87 }
88
89 bool _equal(covariant Command other) =>
90 hashCode == other.hashCode && displayName == other.displayName;
91
92 String toString() => reproductionCommand;
93
94 Future<bool> get outputIsUpToDate => new Future.value(false);
95 }
96
97 class ProcessCommand extends Command {
98 /** Path to the executable of this command. */
99 String executable;
100
101 /** Command line arguments to the executable. */
102 List<String> arguments;
103
104 /** Environment for the command */
105 Map<String, String> environmentOverrides;
106
107 /** Working directory for the command */
108 final String workingDirectory;
109
110 ProcessCommand._(String displayName, this.executable, this.arguments,
111 [this.environmentOverrides = null, this.workingDirectory = null])
112 : super._(displayName) {
113 if (io.Platform.operatingSystem == 'windows') {
114 // Windows can't handle the first command if it is a .bat file or the like
115 // with the slashes going the other direction.
116 // NOTE: Issue 1306
117 executable = executable.replaceAll('/', '\\');
118 }
119 }
120
121 void _buildHashCode(HashCodeBuilder builder) {
122 super._buildHashCode(builder);
123 builder.addJson(executable);
124 builder.addJson(workingDirectory);
125 builder.addJson(arguments);
126 builder.addJson(environmentOverrides);
127 }
128
129 bool _equal(ProcessCommand other) =>
130 super._equal(other) &&
131 executable == other.executable &&
132 deepJsonCompare(arguments, other.arguments) &&
133 workingDirectory == other.workingDirectory &&
134 deepJsonCompare(environmentOverrides, other.environmentOverrides);
135
136 String get reproductionCommand {
137 var env = new StringBuffer();
138 environmentOverrides?.forEach((key, value) =>
139 (io.Platform.operatingSystem == 'windows')
140 ? env.write('set $key=${escapeCommandLineArgument(value)} & ')
141 : env.write('$key=${escapeCommandLineArgument(value)} '));
142 var command = ([executable]..addAll(batchArguments)..addAll(arguments))
143 .map(escapeCommandLineArgument)
144 .join(' ');
145 if (workingDirectory != null) {
146 command = "$command (working directory: $workingDirectory)";
147 }
148 return "$env$command";
149 }
150
151 Future<bool> get outputIsUpToDate => new Future.value(false);
152
153 /// Arguments that are passed to the process when starting batch mode.
154 ///
155 /// In non-batch mode, they should be passed before [arguments].
156 List<String> get batchArguments => const [];
157 }
158
159 class CompilationCommand extends ProcessCommand {
160 final String _outputFile;
161 final bool _neverSkipCompilation;
162 final List<Uri> _bootstrapDependencies;
163
164 CompilationCommand._(
165 String displayName,
166 this._outputFile,
167 this._neverSkipCompilation,
168 this._bootstrapDependencies,
169 String executable,
170 List<String> arguments,
171 Map<String, String> environmentOverrides)
172 : super._(displayName, executable, arguments, environmentOverrides);
173
174 Future<bool> get outputIsUpToDate {
175 if (_neverSkipCompilation) return new Future.value(false);
176
177 Future<List<Uri>> readDepsFile(String path) {
178 var file = new io.File(new Path(path).toNativePath());
179 if (!file.existsSync()) {
180 return new Future.value(null);
181 }
182 return file.readAsLines().then((List<String> lines) {
183 var dependencies = new List<Uri>();
184 for (var line in lines) {
185 line = line.trim();
186 if (line.length > 0) {
187 dependencies.add(Uri.parse(line));
188 }
189 }
190 return dependencies;
191 });
192 }
193
194 return readDepsFile("$_outputFile.deps").then((dependencies) {
195 if (dependencies != null) {
196 dependencies.addAll(_bootstrapDependencies);
197 var jsOutputLastModified = TestUtils.lastModifiedCache
198 .getLastModified(new Uri(scheme: 'file', path: _outputFile));
199 if (jsOutputLastModified != null) {
200 for (var dependency in dependencies) {
201 var dependencyLastModified =
202 TestUtils.lastModifiedCache.getLastModified(dependency);
203 if (dependencyLastModified == null ||
204 dependencyLastModified.isAfter(jsOutputLastModified)) {
205 return false;
206 }
207 }
208 return true;
209 }
210 }
211 return false;
212 });
213 }
214
215 void _buildHashCode(HashCodeBuilder builder) {
216 super._buildHashCode(builder);
217 builder.addJson(_outputFile);
218 builder.addJson(_neverSkipCompilation);
219 builder.addJson(_bootstrapDependencies);
220 }
221
222 bool _equal(CompilationCommand other) =>
223 super._equal(other) &&
224 _outputFile == other._outputFile &&
225 _neverSkipCompilation == other._neverSkipCompilation &&
226 deepJsonCompare(_bootstrapDependencies, other._bootstrapDependencies);
227 }
228
229 class KernelCompilationCommand extends CompilationCommand {
230 KernelCompilationCommand._(
231 String displayName,
232 String outputFile,
233 bool neverSkipCompilation,
234 List<Uri> bootstrapDependencies,
235 String executable,
236 List<String> arguments,
237 Map<String, String> environmentOverrides)
238 : super._(displayName, outputFile, neverSkipCompilation,
239 bootstrapDependencies, executable, arguments, environmentOverrides);
240
241 int get maxNumRetries => 1;
242 }
243
244 /// This is just a Pair(String, Map) class with hashCode and operator ==
245 class AddFlagsKey {
246 final String flags;
247 final Map env;
248 AddFlagsKey(this.flags, this.env);
249 // Just use object identity for environment map
250 bool operator ==(Object other) =>
251 other is AddFlagsKey && flags == other.flags && env == other.env;
252 int get hashCode => flags.hashCode ^ env.hashCode;
253 }
254
255 class ContentShellCommand extends ProcessCommand {
256 ContentShellCommand._(
257 String executable,
258 String htmlFile,
259 List<String> options,
260 List<String> dartFlags,
261 Map<String, String> environmentOverrides)
262 : super._("content_shell", executable, _getArguments(options, htmlFile),
263 _getEnvironment(environmentOverrides, dartFlags));
264
265 // Cache the modified environments in a map from the old environment and
266 // the string of Dart flags to the new environment. Avoid creating new
267 // environment object for each command object.
268 static Map<AddFlagsKey, Map<String, String>> environments = {};
269
270 static Map<String, String> _getEnvironment(
271 Map<String, String> env, List<String> dartFlags) {
272 var needDartFlags = dartFlags != null && dartFlags.isNotEmpty;
273 if (needDartFlags) {
274 if (env == null) {
275 env = const <String, String>{};
276 }
277 var flags = dartFlags.join(' ');
278 return environments.putIfAbsent(
279 new AddFlagsKey(flags, env),
280 () => new Map<String, String>.from(env)
281 ..addAll({'DART_FLAGS': flags, 'DART_FORWARDING_PRINT': '1'}));
282 }
283 return env;
284 }
285
286 static List<String> _getArguments(List<String> options, String htmlFile) {
287 var arguments = options.toList();
288 arguments.add(htmlFile);
289 return arguments;
290 }
291
292 int get maxNumRetries => 3;
293 }
294
295 class BrowserTestCommand extends Command {
296 Runtime get browser => configuration.runtime;
297 final String url;
298 final Configuration configuration;
299 final bool retry;
300
301 BrowserTestCommand._(this.url, this.configuration, this.retry)
302 : super._(configuration.runtime.name);
303
304 void _buildHashCode(HashCodeBuilder builder) {
305 super._buildHashCode(builder);
306 builder.addJson(browser.name);
307 builder.addJson(url);
308 builder.add(configuration);
309 builder.add(retry);
310 }
311
312 bool _equal(BrowserTestCommand other) =>
313 super._equal(other) &&
314 browser == other.browser &&
315 url == other.url &&
316 identical(configuration, other.configuration) &&
317 retry == other.retry;
318
319 String get reproductionCommand {
320 var parts = [
321 io.Platform.resolvedExecutable,
322 'tools/testing/dart/launch_browser.dart',
323 browser.name,
324 url
325 ];
326 return parts.map(escapeCommandLineArgument).join(' ');
327 }
328
329 int get maxNumRetries => 4;
330 }
331
332 class BrowserHtmlTestCommand extends BrowserTestCommand {
333 List<String> expectedMessages;
334 BrowserHtmlTestCommand._(String url, Configuration configuration,
335 this.expectedMessages, bool retry)
336 : super._(url, configuration, retry);
337
338 void _buildHashCode(HashCodeBuilder builder) {
339 super._buildHashCode(builder);
340 builder.addJson(expectedMessages);
341 }
342
343 bool _equal(BrowserHtmlTestCommand other) =>
344 super._equal(other) &&
345 identical(expectedMessages, other.expectedMessages);
346 }
347
348 class AnalysisCommand extends ProcessCommand {
349 final String flavor;
350
351 AnalysisCommand._(this.flavor, String displayName, String executable,
352 List<String> arguments, Map<String, String> environmentOverrides)
353 : super._(displayName, executable, arguments, environmentOverrides);
354
355 void _buildHashCode(HashCodeBuilder builder) {
356 super._buildHashCode(builder);
357 builder.addJson(flavor);
358 }
359
360 bool _equal(AnalysisCommand other) =>
361 super._equal(other) && flavor == other.flavor;
362 }
363
364 class VmCommand extends ProcessCommand {
365 VmCommand._(String executable, List<String> arguments,
366 Map<String, String> environmentOverrides)
367 : super._("vm", executable, arguments, environmentOverrides);
368 }
369
370 class VmBatchCommand extends ProcessCommand implements VmCommand {
371 final String dartFile;
372 final bool checked;
373
374 VmBatchCommand._(String executable, String dartFile, List<String> arguments,
375 Map<String, String> environmentOverrides,
376 {this.checked: true})
377 : this.dartFile = dartFile,
378 super._('vm-batch', executable, arguments, environmentOverrides);
379
380 @override
381 List<String> get batchArguments =>
382 checked ? ['--checked', dartFile] : [dartFile];
383
384 @override
385 bool _equal(VmBatchCommand other) {
386 return super._equal(other) &&
387 dartFile == other.dartFile &&
388 checked == other.checked;
389 }
390
391 @override
392 void _buildHashCode(HashCodeBuilder builder) {
393 super._buildHashCode(builder);
394 builder.addJson(dartFile);
395 builder.addJson(checked);
396 }
397 }
398
399 class AdbPrecompilationCommand extends Command {
400 final String precompiledRunnerFilename;
401 final String processTestFilename;
402 final String precompiledTestDirectory;
403 final List<String> arguments;
404 final bool useBlobs;
405
406 AdbPrecompilationCommand._(
407 this.precompiledRunnerFilename,
408 this.processTestFilename,
409 this.precompiledTestDirectory,
410 this.arguments,
411 this.useBlobs)
412 : super._("adb_precompilation");
413
414 void _buildHashCode(HashCodeBuilder builder) {
415 super._buildHashCode(builder);
416 builder.add(precompiledRunnerFilename);
417 builder.add(precompiledTestDirectory);
418 builder.add(arguments);
419 builder.add(useBlobs);
420 }
421
422 bool _equal(AdbPrecompilationCommand other) =>
423 super._equal(other) &&
424 precompiledRunnerFilename == other.precompiledRunnerFilename &&
425 useBlobs == other.useBlobs &&
426 arguments == other.arguments &&
427 precompiledTestDirectory == other.precompiledTestDirectory;
428
429 String toString() => 'Steps to push precompiled runner and precompiled code '
430 'to an attached device. Uses (and requires) adb.';
431 }
432
433 class JSCommandlineCommand extends ProcessCommand {
434 JSCommandlineCommand._(
435 String displayName, String executable, List<String> arguments,
436 [Map<String, String> environmentOverrides = null])
437 : super._(displayName, executable, arguments, environmentOverrides);
438 }
439
440 class PubCommand extends ProcessCommand {
441 final String command;
442
443 PubCommand._(String pubCommand, String pubExecutable,
444 String pubspecYamlDirectory, String pubCacheDirectory, List<String> args)
445 : command = pubCommand,
446 super._(
447 'pub_$pubCommand',
448 new io.File(pubExecutable).absolute.path,
449 [pubCommand]..addAll(args),
450 {'PUB_CACHE': pubCacheDirectory},
451 pubspecYamlDirectory);
452
453 void _buildHashCode(HashCodeBuilder builder) {
454 super._buildHashCode(builder);
455 builder.addJson(command);
456 }
457
458 bool _equal(PubCommand other) =>
459 super._equal(other) && command == other.command;
460 }
461
462 /* [ScriptCommand]s are executed by dart code. */
463 abstract class ScriptCommand extends Command {
464 ScriptCommand._(String displayName) : super._(displayName);
465
466 Future<ScriptCommandOutputImpl> run();
467 }
468
469 class CleanDirectoryCopyCommand extends ScriptCommand {
470 final String _sourceDirectory;
471 final String _destinationDirectory;
472
473 CleanDirectoryCopyCommand._(this._sourceDirectory, this._destinationDirectory)
474 : super._('dir_copy');
475
476 String get reproductionCommand =>
477 "Copying '$_sourceDirectory' to '$_destinationDirectory'.";
478
479 Future<ScriptCommandOutputImpl> run() {
480 var watch = new Stopwatch()..start();
481
482 var destination = new io.Directory(_destinationDirectory);
483
484 return destination.exists().then((bool exists) {
485 Future cleanDirectoryFuture;
486 if (exists) {
487 cleanDirectoryFuture = TestUtils.deleteDirectory(_destinationDirectory);
488 } else {
489 cleanDirectoryFuture = new Future.value(null);
490 }
491 return cleanDirectoryFuture.then((_) {
492 return TestUtils.copyDirectory(_sourceDirectory, _destinationDirectory);
493 });
494 }).then((_) {
495 return new ScriptCommandOutputImpl(
496 this, Expectation.pass, "", watch.elapsed);
497 }).catchError((error) {
498 return new ScriptCommandOutputImpl(
499 this, Expectation.fail, "An error occured: $error.", watch.elapsed);
500 });
501 }
502
503 void _buildHashCode(HashCodeBuilder builder) {
504 super._buildHashCode(builder);
505 builder.addJson(_sourceDirectory);
506 builder.addJson(_destinationDirectory);
507 }
508
509 bool _equal(CleanDirectoryCopyCommand other) =>
510 super._equal(other) &&
511 _sourceDirectory == other._sourceDirectory &&
512 _destinationDirectory == other._destinationDirectory;
513 }
514
515 /*
516 * [MakeSymlinkCommand] makes a symbolic link to another directory.
517 */
518 class MakeSymlinkCommand extends ScriptCommand {
519 String _link;
520 String _target;
521
522 MakeSymlinkCommand._(this._link, this._target) : super._('make_symlink');
523
524 String get reproductionCommand =>
525 "Make symbolic link '$_link' (target: $_target)'.";
526
527 Future<ScriptCommandOutputImpl> run() {
528 var watch = new Stopwatch()..start();
529 var targetFile = new io.Directory(_target);
530 return targetFile.exists().then((bool targetExists) {
531 if (!targetExists) {
532 throw new Exception("Target '$_target' does not exist");
533 }
534 var link = new io.Link(_link);
535
536 return link.exists().then((bool exists) {
537 if (exists) return link.delete();
538 }).then((_) => link.create(_target));
539 }).then((_) {
540 return new ScriptCommandOutputImpl(
541 this, Expectation.pass, "", watch.elapsed);
542 }).catchError((error) {
543 return new ScriptCommandOutputImpl(
544 this, Expectation.fail, "An error occured: $error.", watch.elapsed);
545 });
546 }
547
548 void _buildHashCode(HashCodeBuilder builder) {
549 super._buildHashCode(builder);
550 builder.addJson(_link);
551 builder.addJson(_target);
552 }
553
554 bool _equal(MakeSymlinkCommand other) =>
555 super._equal(other) && _link == other._link && _target == other._target;
556 }
557
558 class CommandBuilder {
559 static final CommandBuilder instance = new CommandBuilder._();
560
561 bool _cleared = false;
562 final _cachedCommands = new Map<Command, Command>();
563
564 CommandBuilder._();
565
566 void clearCommandCache() {
567 _cachedCommands.clear();
568 _cleared = true;
569 }
570
571 ContentShellCommand getContentShellCommand(
572 String executable,
573 String htmlFile,
574 List<String> options,
575 List<String> dartFlags,
576 Map<String, String> environment) {
577 ContentShellCommand command = new ContentShellCommand._(
578 executable, htmlFile, options, dartFlags, environment);
579 return _getUniqueCommand(command);
580 }
581
582 BrowserTestCommand getBrowserTestCommand(
583 String url, Configuration configuration, bool retry) {
584 var command = new BrowserTestCommand._(url, configuration, retry);
585 return _getUniqueCommand(command);
586 }
587
588 BrowserHtmlTestCommand getBrowserHtmlTestCommand(String url,
589 Configuration configuration, List<String> expectedMessages, bool retry) {
590 var command = new BrowserHtmlTestCommand._(
591 url, configuration, expectedMessages, retry);
592 return _getUniqueCommand(command);
593 }
594
595 CompilationCommand getCompilationCommand(
596 String displayName,
597 String outputFile,
598 bool neverSkipCompilation,
599 List<Uri> bootstrapDependencies,
600 String executable,
601 List<String> arguments,
602 Map<String, String> environment) {
603 var command = new CompilationCommand._(
604 displayName,
605 outputFile,
606 neverSkipCompilation,
607 bootstrapDependencies,
608 executable,
609 arguments,
610 environment);
611 return _getUniqueCommand(command);
612 }
613
614 CompilationCommand getKernelCompilationCommand(
615 String displayName,
616 String outputFile,
617 bool neverSkipCompilation,
618 List<Uri> bootstrapDependencies,
619 String executable,
620 List<String> arguments,
621 Map<String, String> environment) {
622 var command = new KernelCompilationCommand._(
623 displayName,
624 outputFile,
625 neverSkipCompilation,
626 bootstrapDependencies,
627 executable,
628 arguments,
629 environment);
630 return _getUniqueCommand(command);
631 }
632
633 AnalysisCommand getAnalysisCommand(String displayName, String executable,
634 List<String> arguments, Map<String, String> environmentOverrides,
635 {String flavor: 'dart2analyzer'}) {
636 var command = new AnalysisCommand._(
637 flavor, displayName, executable, arguments, environmentOverrides);
638 return _getUniqueCommand(command);
639 }
640
641 VmCommand getVmCommand(String executable, List<String> arguments,
642 Map<String, String> environmentOverrides) {
643 var command = new VmCommand._(executable, arguments, environmentOverrides);
644 return _getUniqueCommand(command);
645 }
646
647 VmBatchCommand getVmBatchCommand(String executable, String tester,
648 List<String> arguments, Map<String, String> environmentOverrides,
649 {bool checked: true}) {
650 var command = new VmBatchCommand._(
651 executable, tester, arguments, environmentOverrides,
652 checked: checked);
653 return _getUniqueCommand(command);
654 }
655
656 AdbPrecompilationCommand getAdbPrecompiledCommand(
657 String precompiledRunner,
658 String processTest,
659 String testDirectory,
660 List<String> arguments,
661 bool useBlobs) {
662 var command = new AdbPrecompilationCommand._(
663 precompiledRunner, processTest, testDirectory, arguments, useBlobs);
664 return _getUniqueCommand(command);
665 }
666
667 Command getJSCommandlineCommand(
668 String displayName, String executable, List<String> arguments,
669 [Map<String, String> environment]) {
670 var command = new JSCommandlineCommand._(
671 displayName, executable, arguments, environment);
672 return _getUniqueCommand(command);
673 }
674
675 Command getProcessCommand(
676 String displayName, String executable, List<String> arguments,
677 [Map<String, String> environment, String workingDirectory]) {
678 var command = new ProcessCommand._(
679 displayName, executable, arguments, environment, workingDirectory);
680 return _getUniqueCommand(command);
681 }
682
683 Command getCopyCommand(String sourceDirectory, String destinationDirectory) {
684 var command =
685 new CleanDirectoryCopyCommand._(sourceDirectory, destinationDirectory);
686 return _getUniqueCommand(command);
687 }
688
689 Command getPubCommand(String pubCommand, String pubExecutable,
690 String pubspecYamlDirectory, String pubCacheDirectory,
691 {List<String> arguments: const <String>[]}) {
692 var command = new PubCommand._(pubCommand, pubExecutable,
693 pubspecYamlDirectory, pubCacheDirectory, arguments);
694 return _getUniqueCommand(command);
695 }
696
697 Command getMakeSymlinkCommand(String link, String target) {
698 return _getUniqueCommand(new MakeSymlinkCommand._(link, target));
699 }
700
701 T _getUniqueCommand<T extends Command>(T command) {
702 // All Command classes implement hashCode and operator==.
703 // We check if this command has already been built.
704 // If so, we return the cached one. Otherwise we
705 // store the one given as [command] argument.
706 if (_cleared) {
707 throw new Exception(
708 "CommandBuilder.get[type]Command called after cache cleared");
709 }
710 var cachedCommand = _cachedCommands[command];
711 if (cachedCommand != null) {
712 return cachedCommand as T;
713 }
714 _cachedCommands[command] = command;
715 return command;
716 }
717 }
718
719 /** 56 /**
720 * TestCase contains all the information needed to run a test and evaluate 57 * TestCase contains all the information needed to run a test and evaluate
721 * its output. Running a test involves starting a separate process, with 58 * its output. Running a test involves starting a separate process, with
722 * the executable and arguments given by the TestCase, and recording its 59 * the executable and arguments given by the TestCase, and recording its
723 * stdout and stderr output streams, and its exit code. TestCase only 60 * stdout and stderr output streams, and its exit code. TestCase only
724 * contains static information about the test; actually running the test is 61 * contains static information about the test; actually running the test is
725 * performed by [ProcessQueue] using a [RunningProcess] object. 62 * performed by [ProcessQueue] using a [RunningProcess] object.
726 * 63 *
727 * The output information is stored in a [CommandOutput] instance contained 64 * The output information is stored in a [CommandOutput] instance contained
728 * in TestCase.commandOutputs. The last CommandOutput instance is responsible 65 * in TestCase.commandOutputs. The last CommandOutput instance is responsible
(...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after
882 bool isNegative, 219 bool isNegative,
883 this._testingUrl) 220 this._testingUrl)
884 : super(displayName, commands, configuration, expectedOutcomes, 221 : super(displayName, commands, configuration, expectedOutcomes,
885 isNegative: isNegative, info: info); 222 isNegative: isNegative, info: info);
886 223
887 String _testingUrl; 224 String _testingUrl;
888 225
889 String get testingUrl => _testingUrl; 226 String get testingUrl => _testingUrl;
890 } 227 }
891 228
892 class UnittestSuiteMessagesMixin {
893 bool _isAsyncTest(String testOutput) {
894 return testOutput.contains("unittest-suite-wait-for-done");
895 }
896
897 bool _isAsyncTestSuccessful(String testOutput) {
898 return testOutput.contains("unittest-suite-success");
899 }
900
901 Expectation _negateOutcomeIfIncompleteAsyncTest(
902 Expectation outcome, String testOutput) {
903 // If this is an asynchronous test and the asynchronous operation didn't
904 // complete successfully, it's outcome is Expectation.FAIL.
905 // TODO: maybe we should introduce a AsyncIncomplete marker or so
906 if (outcome == Expectation.pass) {
907 if (_isAsyncTest(testOutput) && !_isAsyncTestSuccessful(testOutput)) {
908 return Expectation.fail;
909 }
910 }
911 return outcome;
912 }
913 }
914
915 /**
916 * CommandOutput records the output of a completed command: the process's exit
917 * code, the standard output and standard error, whether the process timed out,
918 * and the time the process took to run. It does not contain a pointer to the
919 * [TestCase] this is the output of, so some functions require the test case
920 * to be passed as an argument.
921 */
922 abstract class CommandOutput {
923 Command get command;
924
925 Expectation result(TestCase testCase);
926
927 bool get hasCrashed;
928
929 bool get hasTimedOut;
930
931 bool didFail(TestCase testCase);
932
933 bool hasFailed(TestCase testCase);
934
935 bool get canRunDependendCommands;
936
937 bool get successful; // otherwise we might to retry running
938
939 Duration get time;
940
941 int get exitCode;
942
943 int get pid;
944
945 List<int> get stdout;
946
947 List<int> get stderr;
948
949 List<String> get diagnostics;
950
951 bool get compilationSkipped;
952 }
953
954 class CommandOutputImpl extends UniqueObject implements CommandOutput {
955 Command command;
956 int exitCode;
957
958 bool timedOut;
959 List<int> stdout;
960 List<int> stderr;
961 Duration time;
962 List<String> diagnostics;
963 bool compilationSkipped;
964 int pid;
965
966 /**
967 * A flag to indicate we have already printed a warning about ignoring the VM
968 * crash, to limit the amount of output produced per test.
969 */
970 bool alreadyPrintedWarning = false;
971
972 CommandOutputImpl(
973 Command this.command,
974 int this.exitCode,
975 bool this.timedOut,
976 List<int> this.stdout,
977 List<int> this.stderr,
978 Duration this.time,
979 bool this.compilationSkipped,
980 int this.pid) {
981 diagnostics = [];
982 }
983
984 Expectation result(TestCase testCase) {
985 if (hasCrashed) return Expectation.crash;
986 if (hasTimedOut) return Expectation.timeout;
987 if (hasFailed(testCase)) return Expectation.fail;
988 if (hasNonUtf8) return Expectation.nonUtf8Error;
989 return Expectation.pass;
990 }
991
992 bool get hasCrashed {
993 // dart2js exits with code 253 in case of unhandled exceptions.
994 // The dart binary exits with code 253 in case of an API error such
995 // as an invalid snapshot file.
996 // In either case an exit code of 253 is considered a crash.
997 if (exitCode == 253) return true;
998 if (io.Platform.operatingSystem == 'windows') {
999 // The VM uses std::abort to terminate on asserts.
1000 // std::abort terminates with exit code 3 on Windows.
1001 if (exitCode == 3 || exitCode == CRASHING_BROWSER_EXITCODE) {
1002 return !timedOut;
1003 }
1004 // If a program receives an uncaught system exception, the program
1005 // terminates with the exception code as exit code.
1006 // The 0x3FFFFF00 mask here tries to determine if an exception indicates
1007 // a crash of the program.
1008 // System exception codes can be found in 'winnt.h', for example
1009 // "#define STATUS_ACCESS_VIOLATION ((DWORD) 0xC0000005)"
1010 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0));
1011 }
1012 return !timedOut && ((exitCode < 0));
1013 }
1014
1015 bool get hasTimedOut => timedOut;
1016
1017 bool didFail(TestCase testCase) {
1018 return (exitCode != 0 && !hasCrashed);
1019 }
1020
1021 bool get canRunDependendCommands {
1022 // FIXME(kustermann): We may need to change this
1023 return !hasTimedOut && exitCode == 0;
1024 }
1025
1026 bool get successful {
1027 // FIXME(kustermann): We may need to change this
1028 return !hasTimedOut && exitCode == 0;
1029 }
1030
1031 // Reverse result of a negative test.
1032 bool hasFailed(TestCase testCase) {
1033 return testCase.isNegative ? !didFail(testCase) : didFail(testCase);
1034 }
1035
1036 bool get hasNonUtf8 => exitCode == NON_UTF_FAKE_EXITCODE;
1037
1038 Expectation _negateOutcomeIfNegativeTest(
1039 Expectation outcome, bool isNegative) {
1040 if (!isNegative) return outcome;
1041 if (outcome == Expectation.ignore) return outcome;
1042 if (outcome.canBeOutcomeOf(Expectation.fail)) {
1043 return Expectation.pass;
1044 }
1045 return Expectation.fail;
1046 }
1047 }
1048
1049 class ContentShellCommandOutputImpl extends CommandOutputImpl {
1050 // Although tests are reported as passing, content shell sometimes exits with
1051 // a nonzero exitcode which makes our dartium builders extremely falky.
1052 // See: http://dartbug.com/15139.
1053 // TODO(rnystrom): Is this still needed? The underlying bug is closed.
1054 static int WHITELISTED_CONTENTSHELL_EXITCODE = -1073740022;
1055 static bool isWindows = io.Platform.operatingSystem == 'windows';
1056 static bool _failedBecauseOfFlakyInfrastructure(
1057 Command command, bool timedOut, List<int> stderrBytes) {
1058 // If the browser test failed, it may have been because content shell
1059 // and the virtual framebuffer X server didn't hook up, or it crashed with
1060 // a core dump. Sometimes content shell crashes after it has set the stdout
1061 // to PASS, so we have to do this check first.
1062 // Content shell also fails with a broken pipe message: Issue 26739
1063 var zygoteCrash =
1064 new RegExp(r"ERROR:zygote_linux\.cc\(\d+\)] write: Broken pipe");
1065 var stderr = decodeUtf8(stderrBytes);
1066 // TODO(7564): See http://dartbug.com/7564
1067 // This may not be happening anymore. Test by removing this suppression.
1068 if (stderr.contains(MESSAGE_CANNOT_OPEN_DISPLAY) ||
1069 stderr.contains(MESSAGE_FAILED_TO_RUN_COMMAND)) {
1070 DebugLogger.warning(
1071 "Warning: Failure because of missing XDisplay. Test ignored");
1072 return true;
1073 }
1074 // TODO(26739): See http://dartbug.com/26739
1075 if (zygoteCrash.hasMatch(stderr)) {
1076 DebugLogger.warning("Warning: Failure because of content_shell "
1077 "zygote crash. Test ignored");
1078 return true;
1079 }
1080 return false;
1081 }
1082
1083 bool _infraFailure;
1084
1085 ContentShellCommandOutputImpl(
1086 Command command,
1087 int exitCode,
1088 bool timedOut,
1089 List<int> stdout,
1090 List<int> stderr,
1091 Duration time,
1092 bool compilationSkipped)
1093 : _infraFailure =
1094 _failedBecauseOfFlakyInfrastructure(command, timedOut, stderr),
1095 super(command, exitCode, timedOut, stdout, stderr, time,
1096 compilationSkipped, 0);
1097
1098 Expectation result(TestCase testCase) {
1099 if (_infraFailure) {
1100 return Expectation.ignore;
1101 }
1102
1103 // Handle crashes and timeouts first
1104 if (hasCrashed) return Expectation.crash;
1105 if (hasTimedOut) return Expectation.timeout;
1106 if (hasNonUtf8) return Expectation.nonUtf8Error;
1107
1108 var outcome = _getOutcome();
1109
1110 if (testCase.hasRuntimeError) {
1111 if (!outcome.canBeOutcomeOf(Expectation.runtimeError)) {
1112 return Expectation.missingRuntimeError;
1113 }
1114 }
1115 if (testCase.isNegative) {
1116 if (outcome.canBeOutcomeOf(Expectation.fail)) return Expectation.pass;
1117 return Expectation.fail;
1118 }
1119 return outcome;
1120 }
1121
1122 bool get successful => canRunDependendCommands;
1123
1124 bool get canRunDependendCommands {
1125 // We cannot rely on the exit code of content_shell as a method to
1126 // determine if we were successful or not.
1127 return super.canRunDependendCommands && !didFail(null);
1128 }
1129
1130 bool get hasCrashed {
1131 return super.hasCrashed || _rendererCrashed;
1132 }
1133
1134 Expectation _getOutcome() {
1135 if (_browserTestFailure) {
1136 return Expectation.runtimeError;
1137 }
1138 return Expectation.pass;
1139 }
1140
1141 bool get _rendererCrashed =>
1142 decodeUtf8(super.stdout).contains("#CRASHED - rendere");
1143
1144 bool get _browserTestFailure {
1145 // Browser tests fail unless stdout contains
1146 // 'Content-Type: text/plain' followed by 'PASS'.
1147 bool hasContentType = false;
1148 var stdoutLines = decodeUtf8(super.stdout).split("\n");
1149 var containsFail = false;
1150 var containsPass = false;
1151 for (String line in stdoutLines) {
1152 switch (line) {
1153 case 'Content-Type: text/plain':
1154 hasContentType = true;
1155 break;
1156 case 'FAIL':
1157 if (hasContentType) {
1158 containsFail = true;
1159 }
1160 break;
1161 case 'PASS':
1162 if (hasContentType) {
1163 containsPass = true;
1164 }
1165 break;
1166 }
1167 }
1168 if (hasContentType) {
1169 if (containsFail && containsPass) {
1170 DebugLogger.warning("Test had 'FAIL' and 'PASS' in stdout. ($command)");
1171 }
1172 if (!containsFail && !containsPass) {
1173 DebugLogger.warning("Test had neither 'FAIL' nor 'PASS' in stdout. "
1174 "($command)");
1175 return true;
1176 }
1177 if (containsFail) {
1178 return true;
1179 }
1180 assert(containsPass);
1181 if (exitCode != 0) {
1182 var message = "All tests passed, but exitCode != 0. "
1183 "Actual exitcode: $exitCode. "
1184 "($command)";
1185 DebugLogger.warning(message);
1186 diagnostics.add(message);
1187 }
1188 return (!hasCrashed &&
1189 exitCode != 0 &&
1190 (!isWindows || exitCode != WHITELISTED_CONTENTSHELL_EXITCODE));
1191 }
1192 DebugLogger.warning("Couldn't find 'Content-Type: text/plain' in output. "
1193 "($command).");
1194 return true;
1195 }
1196 }
1197
1198 // TODO(29869): Remove this class after verifying it isn't used.
1199 class HTMLBrowserCommandOutputImpl extends ContentShellCommandOutputImpl {
1200 HTMLBrowserCommandOutputImpl(
1201 Command command,
1202 int exitCode,
1203 bool timedOut,
1204 List<int> stdout,
1205 List<int> stderr,
1206 Duration time,
1207 bool compilationSkipped)
1208 : super(command, exitCode, timedOut, stdout, stderr, time,
1209 compilationSkipped);
1210
1211 bool didFail(TestCase testCase) {
1212 return _getOutcome() != Expectation.pass;
1213 }
1214
1215 bool get _browserTestFailure {
1216 // We should not need to convert back and forward.
1217 var output = decodeUtf8(super.stdout);
1218 if (output.contains("FAIL")) return true;
1219 return !output.contains("PASS");
1220 }
1221 }
1222
1223 class BrowserTestJsonResult {
1224 static const ALLOWED_TYPES = const [
1225 'sync_exception',
1226 'window_onerror',
1227 'script_onerror',
1228 'window_compilationerror',
1229 'print',
1230 'message_received',
1231 'dom',
1232 'debug'
1233 ];
1234
1235 final Expectation outcome;
1236 final String htmlDom;
1237 final List<dynamic> events;
1238
1239 BrowserTestJsonResult(this.outcome, this.htmlDom, this.events);
1240
1241 static BrowserTestJsonResult parseFromString(String content) {
1242 void validate(String assertion, bool value) {
1243 if (!value) {
1244 throw "InvalidFormat sent from browser driving page: $assertion:\n\n"
1245 "$content";
1246 }
1247 }
1248
1249 try {
1250 var events = JSON.decode(content);
1251 if (events != null) {
1252 validate("Message must be a List", events is List);
1253
1254 var messagesByType = <String, List<String>>{};
1255 ALLOWED_TYPES.forEach((type) => messagesByType[type] = <String>[]);
1256
1257 for (var entry in events) {
1258 validate("An entry must be a Map", entry is Map);
1259
1260 var type = entry['type'];
1261 var value = entry['value'] as String;
1262 var timestamp = entry['timestamp'];
1263
1264 validate("'type' of an entry must be a String", type is String);
1265 validate("'type' has to be in $ALLOWED_TYPES.",
1266 ALLOWED_TYPES.contains(type));
1267 validate(
1268 "'timestamp' of an entry must be a number", timestamp is num);
1269
1270 messagesByType[type].add(value);
1271 }
1272 validate("The message must have exactly one 'dom' entry.",
1273 messagesByType['dom'].length == 1);
1274
1275 var dom = messagesByType['dom'][0];
1276 if (dom.endsWith('\n')) {
1277 dom = '$dom\n';
1278 }
1279
1280 return new BrowserTestJsonResult(
1281 _getOutcome(messagesByType), dom, events as List<dynamic>);
1282 }
1283 } catch (error) {
1284 // If something goes wrong, we know the content was not in the correct
1285 // JSON format. So we can't parse it.
1286 // The caller is responsible for falling back to the old way of
1287 // determining if a test failed.
1288 }
1289
1290 return null;
1291 }
1292
1293 static Expectation _getOutcome(Map<String, List<String>> messagesByType) {
1294 occured(String type) => messagesByType[type].length > 0;
1295 searchForMsg(List<String> types, String message) {
1296 return types.any((type) => messagesByType[type].contains(message));
1297 }
1298
1299 // FIXME(kustermann,ricow): I think this functionality doesn't work in
1300 // test_controller.js: So far I haven't seen anything being reported on
1301 // "window.compilationerror"
1302 if (occured('window_compilationerror')) {
1303 return Expectation.compileTimeError;
1304 }
1305
1306 if (occured('sync_exception') ||
1307 occured('window_onerror') ||
1308 occured('script_onerror')) {
1309 return Expectation.runtimeError;
1310 }
1311
1312 if (messagesByType['dom'][0].contains('FAIL')) {
1313 return Expectation.runtimeError;
1314 }
1315
1316 // We search for these messages in 'print' and 'message_received' because
1317 // the unittest implementation posts these messages using
1318 // "window.postMessage()" instead of the normal "print()" them.
1319
1320 var isAsyncTest = searchForMsg(
1321 ['print', 'message_received'], 'unittest-suite-wait-for-done');
1322 var isAsyncSuccess =
1323 searchForMsg(['print', 'message_received'], 'unittest-suite-success') ||
1324 searchForMsg(['print', 'message_received'], 'unittest-suite-done');
1325
1326 if (isAsyncTest) {
1327 if (isAsyncSuccess) {
1328 return Expectation.pass;
1329 }
1330 return Expectation.runtimeError;
1331 }
1332
1333 var mainStarted =
1334 searchForMsg(['print', 'message_received'], 'dart-calling-main');
1335 var mainDone =
1336 searchForMsg(['print', 'message_received'], 'dart-main-done');
1337
1338 if (mainStarted && mainDone) {
1339 return Expectation.pass;
1340 }
1341 return Expectation.fail;
1342 }
1343 }
1344
1345 class BrowserControllerTestOutcome extends CommandOutputImpl
1346 with UnittestSuiteMessagesMixin {
1347 BrowserTestOutput _result;
1348 Expectation _rawOutcome;
1349
1350 factory BrowserControllerTestOutcome(
1351 Command command, BrowserTestOutput result) {
1352 String indent(String string, int numSpaces) {
1353 var spaces = new List.filled(numSpaces, ' ').join('');
1354 return string
1355 .replaceAll('\r\n', '\n')
1356 .split('\n')
1357 .map((line) => "$spaces$line")
1358 .join('\n');
1359 }
1360
1361 String stdout = "";
1362 String stderr = "";
1363 Expectation outcome;
1364
1365 var parsedResult =
1366 BrowserTestJsonResult.parseFromString(result.lastKnownMessage);
1367 if (parsedResult != null) {
1368 outcome = parsedResult.outcome;
1369 } else {
1370 // Old way of determining whether a test failed or passed.
1371 if (result.lastKnownMessage.contains("FAIL")) {
1372 outcome = Expectation.runtimeError;
1373 } else if (result.lastKnownMessage.contains("PASS")) {
1374 outcome = Expectation.pass;
1375 } else {
1376 outcome = Expectation.runtimeError;
1377 }
1378 }
1379
1380 if (result.didTimeout) {
1381 if (result.delayUntilTestStarted != null) {
1382 stderr = "This test timed out. The delay until the test actually "
1383 "started was: ${result.delayUntilTestStarted}.";
1384 } else {
1385 stderr = "This test has not notified test.py that it started running.";
1386 }
1387 }
1388
1389 if (parsedResult != null) {
1390 stdout = "events:\n${indent(prettifyJson(parsedResult.events), 2)}\n\n";
1391 } else {
1392 stdout = "message:\n${indent(result.lastKnownMessage, 2)}\n\n";
1393 }
1394
1395 stderr = '$stderr\n\n'
1396 'BrowserOutput while running the test (* EXPERIMENTAL *):\n'
1397 'BrowserOutput.stdout:\n'
1398 '${indent(result.browserOutput.stdout.toString(), 2)}\n'
1399 'BrowserOutput.stderr:\n'
1400 '${indent(result.browserOutput.stderr.toString(), 2)}\n'
1401 '\n';
1402 return new BrowserControllerTestOutcome._internal(
1403 command, result, outcome, encodeUtf8(stdout), encodeUtf8(stderr));
1404 }
1405
1406 BrowserControllerTestOutcome._internal(
1407 Command command,
1408 BrowserTestOutput result,
1409 this._rawOutcome,
1410 List<int> stdout,
1411 List<int> stderr)
1412 : super(command, 0, result.didTimeout, stdout, stderr, result.duration,
1413 false, 0) {
1414 _result = result;
1415 }
1416
1417 Expectation result(TestCase testCase) {
1418 // Handle timeouts first
1419 if (_result.didTimeout) {
1420 if (testCase.configuration.runtime == Runtime.ie11) {
1421 // TODO(28955): See http://dartbug.com/28955
1422 DebugLogger.warning("Timeout of ie11 on test ${testCase.displayName}");
1423 return Expectation.ignore;
1424 }
1425 return Expectation.timeout;
1426 }
1427
1428 if (hasNonUtf8) return Expectation.nonUtf8Error;
1429
1430 // Multitests are handled specially
1431 if (testCase.hasRuntimeError) {
1432 if (_rawOutcome == Expectation.runtimeError) return Expectation.pass;
1433 return Expectation.missingRuntimeError;
1434 }
1435
1436 return _negateOutcomeIfNegativeTest(_rawOutcome, testCase.isNegative);
1437 }
1438 }
1439
1440 class AnalysisCommandOutputImpl extends CommandOutputImpl {
1441 // An error line has 8 fields that look like:
1442 // ERROR|COMPILER|MISSING_SOURCE|file:/tmp/t.dart|15|1|24|Missing source.
1443 final int ERROR_LEVEL = 0;
1444 final int ERROR_TYPE = 1;
1445 final int FILENAME = 3;
1446 final int FORMATTED_ERROR = 7;
1447
1448 AnalysisCommandOutputImpl(
1449 Command command,
1450 int exitCode,
1451 bool timedOut,
1452 List<int> stdout,
1453 List<int> stderr,
1454 Duration time,
1455 bool compilationSkipped)
1456 : super(command, exitCode, timedOut, stdout, stderr, time,
1457 compilationSkipped, 0);
1458
1459 Expectation result(TestCase testCase) {
1460 // TODO(kustermann): If we run the analyzer not in batch mode, make sure
1461 // that command.exitCodes matches 2 (errors), 1 (warnings), 0 (no warnings,
1462 // no errors)
1463
1464 // Handle crashes and timeouts first
1465 if (hasCrashed) return Expectation.crash;
1466 if (hasTimedOut) return Expectation.timeout;
1467 if (hasNonUtf8) return Expectation.nonUtf8Error;
1468
1469 // Get the errors/warnings from the analyzer
1470 List<String> errors = [];
1471 List<String> warnings = [];
1472 parseAnalyzerOutput(errors, warnings);
1473
1474 // Handle errors / missing errors
1475 if (testCase.expectCompileError) {
1476 if (errors.length > 0) {
1477 return Expectation.pass;
1478 }
1479 return Expectation.missingCompileTimeError;
1480 }
1481 if (errors.length > 0) {
1482 return Expectation.compileTimeError;
1483 }
1484
1485 // Handle static warnings / missing static warnings
1486 if (testCase.hasStaticWarning) {
1487 if (warnings.length > 0) {
1488 return Expectation.pass;
1489 }
1490 return Expectation.missingStaticWarning;
1491 }
1492 if (warnings.length > 0) {
1493 return Expectation.staticWarning;
1494 }
1495
1496 assert(errors.length == 0 && warnings.length == 0);
1497 assert(!testCase.hasCompileError && !testCase.hasStaticWarning);
1498 return Expectation.pass;
1499 }
1500
1501 void parseAnalyzerOutput(List<String> outErrors, List<String> outWarnings) {
1502 // Parse a line delimited by the | character using \ as an escape character
1503 // like: FOO|BAR|FOO\|BAR|FOO\\BAZ as 4 fields: FOO BAR FOO|BAR FOO\BAZ
1504 List<String> splitMachineError(String line) {
1505 StringBuffer field = new StringBuffer();
1506 List<String> result = [];
1507 bool escaped = false;
1508 for (var i = 0; i < line.length; i++) {
1509 var c = line[i];
1510 if (!escaped && c == '\\') {
1511 escaped = true;
1512 continue;
1513 }
1514 escaped = false;
1515 if (c == '|') {
1516 result.add(field.toString());
1517 field = new StringBuffer();
1518 continue;
1519 }
1520 field.write(c);
1521 }
1522 result.add(field.toString());
1523 return result;
1524 }
1525
1526 for (String line in decodeUtf8(super.stderr).split("\n")) {
1527 if (line.length == 0) continue;
1528 List<String> fields = splitMachineError(line);
1529 // We only consider errors/warnings for files of interest.
1530 if (fields.length > FORMATTED_ERROR) {
1531 if (fields[ERROR_LEVEL] == 'ERROR') {
1532 outErrors.add(fields[FORMATTED_ERROR]);
1533 } else if (fields[ERROR_LEVEL] == 'WARNING') {
1534 outWarnings.add(fields[FORMATTED_ERROR]);
1535 }
1536 // OK to Skip error output that doesn't match the machine format
1537 }
1538 }
1539 }
1540 }
1541
1542 class VmCommandOutputImpl extends CommandOutputImpl
1543 with UnittestSuiteMessagesMixin {
1544 static const DART_VM_EXITCODE_DFE_ERROR = 252;
1545 static const DART_VM_EXITCODE_COMPILE_TIME_ERROR = 254;
1546 static const DART_VM_EXITCODE_UNCAUGHT_EXCEPTION = 255;
1547
1548 VmCommandOutputImpl(Command command, int exitCode, bool timedOut,
1549 List<int> stdout, List<int> stderr, Duration time, int pid)
1550 : super(command, exitCode, timedOut, stdout, stderr, time, false, pid);
1551
1552 Expectation result(TestCase testCase) {
1553 // Handle crashes and timeouts first
1554 if (exitCode == DART_VM_EXITCODE_DFE_ERROR) return Expectation.dartkCrash;
1555 if (hasCrashed) return Expectation.crash;
1556 if (hasTimedOut) return Expectation.timeout;
1557 if (hasNonUtf8) return Expectation.nonUtf8Error;
1558
1559 // Multitests are handled specially
1560 if (testCase.expectCompileError) {
1561 if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) {
1562 return Expectation.pass;
1563 }
1564 return Expectation.missingCompileTimeError;
1565 }
1566 if (testCase.hasRuntimeError) {
1567 // TODO(kustermann): Do we consider a "runtimeError" only an uncaught
1568 // exception or does any nonzero exit code fullfil this requirement?
1569 if (exitCode != 0) {
1570 return Expectation.pass;
1571 }
1572 return Expectation.missingRuntimeError;
1573 }
1574
1575 // The actual outcome depends on the exitCode
1576 Expectation outcome;
1577 if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) {
1578 outcome = Expectation.compileTimeError;
1579 } else if (exitCode == DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) {
1580 outcome = Expectation.runtimeError;
1581 } else if (exitCode != 0) {
1582 // This is a general fail, in case we get an unknown nonzero exitcode.
1583 outcome = Expectation.fail;
1584 } else {
1585 outcome = Expectation.pass;
1586 }
1587 outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout));
1588 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
1589 }
1590 }
1591
1592 class CompilationCommandOutputImpl extends CommandOutputImpl {
1593 static const DART2JS_EXITCODE_CRASH = 253;
1594
1595 CompilationCommandOutputImpl(
1596 Command command,
1597 int exitCode,
1598 bool timedOut,
1599 List<int> stdout,
1600 List<int> stderr,
1601 Duration time,
1602 bool compilationSkipped)
1603 : super(command, exitCode, timedOut, stdout, stderr, time,
1604 compilationSkipped, 0);
1605
1606 Expectation result(TestCase testCase) {
1607 // Handle general crash/timeout detection.
1608 if (hasCrashed) return Expectation.crash;
1609 if (hasTimedOut) {
1610 bool isWindows = io.Platform.operatingSystem == 'windows';
1611 bool isBrowserTestCase =
1612 testCase.commands.any((command) => command is BrowserTestCommand);
1613 // TODO(26060) Dart2js batch mode hangs on Windows under heavy load.
1614 return (isWindows && isBrowserTestCase)
1615 ? Expectation.ignore
1616 : Expectation.timeout;
1617 }
1618 if (hasNonUtf8) return Expectation.nonUtf8Error;
1619
1620 // Handle dart2js specific crash detection
1621 if (exitCode == DART2JS_EXITCODE_CRASH ||
1622 exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_COMPILE_TIME_ERROR ||
1623 exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) {
1624 return Expectation.crash;
1625 }
1626
1627 // Multitests are handled specially
1628 if (testCase.expectCompileError) {
1629 // Nonzero exit code of the compiler means compilation failed
1630 // TODO(kustermann): Do we have a special exit code in that case???
1631 if (exitCode != 0) {
1632 return Expectation.pass;
1633 }
1634 return Expectation.missingCompileTimeError;
1635 }
1636
1637 // TODO(kustermann): This is a hack, remove it
1638 if (testCase.hasRuntimeError && testCase.commands.length > 1) {
1639 // We expected to run the test, but we got an compile time error.
1640 // If the compilation succeeded, we wouldn't be in here!
1641 assert(exitCode != 0);
1642 return Expectation.compileTimeError;
1643 }
1644
1645 Expectation outcome =
1646 exitCode == 0 ? Expectation.pass : Expectation.compileTimeError;
1647 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
1648 }
1649 }
1650
1651 class KernelCompilationCommandOutputImpl extends CompilationCommandOutputImpl {
1652 KernelCompilationCommandOutputImpl(
1653 Command command,
1654 int exitCode,
1655 bool timedOut,
1656 List<int> stdout,
1657 List<int> stderr,
1658 Duration time,
1659 bool compilationSkipped)
1660 : super(command, exitCode, timedOut, stdout, stderr, time,
1661 compilationSkipped);
1662
1663 bool get canRunDependendCommands {
1664 // See [BatchRunnerProcess]: 0 means success, 1 means compile-time error.
1665 // TODO(asgerf): When the frontend supports it, continue running even if
1666 // there were compile-time errors. See kernel_sdk issue #18.
1667 return !hasCrashed && !timedOut && exitCode == 0;
1668 }
1669
1670 Expectation result(TestCase testCase) {
1671 Expectation result = super.result(testCase);
1672 if (result.canBeOutcomeOf(Expectation.crash)) {
1673 return Expectation.dartkCrash;
1674 } else if (result.canBeOutcomeOf(Expectation.timeout)) {
1675 return Expectation.dartkTimeout;
1676 } else if (result.canBeOutcomeOf(Expectation.compileTimeError)) {
1677 return Expectation.dartkCompileTimeError;
1678 }
1679 return result;
1680 }
1681
1682 // If the compiler was able to produce a Kernel IR file we want to run the
1683 // result on the Dart VM. We therefore mark the [KernelCompilationCommand] as
1684 // successful.
1685 // => This ensures we test that the DartVM produces correct CompileTime errors
1686 // as it is supposed to for our test suites.
1687 bool get successful => canRunDependendCommands;
1688 }
1689
1690 class JsCommandlineOutputImpl extends CommandOutputImpl
1691 with UnittestSuiteMessagesMixin {
1692 JsCommandlineOutputImpl(Command command, int exitCode, bool timedOut,
1693 List<int> stdout, List<int> stderr, Duration time)
1694 : super(command, exitCode, timedOut, stdout, stderr, time, false, 0);
1695
1696 Expectation result(TestCase testCase) {
1697 // Handle crashes and timeouts first
1698 if (hasCrashed) return Expectation.crash;
1699 if (hasTimedOut) return Expectation.timeout;
1700 if (hasNonUtf8) return Expectation.nonUtf8Error;
1701
1702 if (testCase.hasRuntimeError) {
1703 if (exitCode != 0) return Expectation.pass;
1704 return Expectation.missingRuntimeError;
1705 }
1706
1707 var outcome = exitCode == 0 ? Expectation.pass : Expectation.runtimeError;
1708 outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout));
1709 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
1710 }
1711 }
1712
1713 class PubCommandOutputImpl extends CommandOutputImpl {
1714 PubCommandOutputImpl(PubCommand command, int exitCode, bool timedOut,
1715 List<int> stdout, List<int> stderr, Duration time)
1716 : super(command, exitCode, timedOut, stdout, stderr, time, false, 0);
1717
1718 Expectation result(TestCase testCase) {
1719 // Handle crashes and timeouts first
1720 if (hasCrashed) return Expectation.crash;
1721 if (hasTimedOut) return Expectation.timeout;
1722 if (hasNonUtf8) return Expectation.nonUtf8Error;
1723
1724 if (exitCode == 0) {
1725 return Expectation.pass;
1726 } else if ((command as PubCommand).command == 'get') {
1727 return Expectation.pubGetError;
1728 } else {
1729 return Expectation.fail;
1730 }
1731 }
1732 }
1733
1734 class ScriptCommandOutputImpl extends CommandOutputImpl {
1735 final Expectation _result;
1736
1737 ScriptCommandOutputImpl(ScriptCommand command, this._result,
1738 String scriptExecutionInformation, Duration time)
1739 : super(command, 0, false, [], [], time, false, 0) {
1740 var lines = scriptExecutionInformation.split("\n");
1741 diagnostics.addAll(lines);
1742 }
1743
1744 Expectation result(TestCase testCase) => _result;
1745
1746 bool get canRunDependendCommands => _result == Expectation.pass;
1747
1748 bool get successful => _result == Expectation.pass;
1749 }
1750
1751 CommandOutput createCommandOutput(Command command, int exitCode, bool timedOut,
1752 List<int> stdout, List<int> stderr, Duration time, bool compilationSkipped,
1753 [int pid = 0]) {
1754 if (command is ContentShellCommand) {
1755 return new ContentShellCommandOutputImpl(
1756 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1757 } else if (command is BrowserTestCommand) {
1758 return new HTMLBrowserCommandOutputImpl(
1759 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1760 } else if (command is AnalysisCommand) {
1761 return new AnalysisCommandOutputImpl(
1762 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1763 } else if (command is VmCommand) {
1764 return new VmCommandOutputImpl(
1765 command, exitCode, timedOut, stdout, stderr, time, pid);
1766 } else if (command is KernelCompilationCommand) {
1767 return new KernelCompilationCommandOutputImpl(
1768 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1769 } else if (command is AdbPrecompilationCommand) {
1770 return new VmCommandOutputImpl(
1771 command, exitCode, timedOut, stdout, stderr, time, pid);
1772 } else if (command is CompilationCommand) {
1773 if (command.displayName == 'precompiler' ||
1774 command.displayName == 'app_jit') {
1775 return new VmCommandOutputImpl(
1776 command, exitCode, timedOut, stdout, stderr, time, pid);
1777 }
1778 return new CompilationCommandOutputImpl(
1779 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1780 } else if (command is JSCommandlineCommand) {
1781 return new JsCommandlineOutputImpl(
1782 command, exitCode, timedOut, stdout, stderr, time);
1783 } else if (command is PubCommand) {
1784 return new PubCommandOutputImpl(
1785 command, exitCode, timedOut, stdout, stderr, time);
1786 }
1787
1788 return new CommandOutputImpl(command, exitCode, timedOut, stdout, stderr,
1789 time, compilationSkipped, pid);
1790 }
1791
1792 /** 229 /**
1793 * An OutputLog records the output from a test, but truncates it if 230 * An OutputLog records the output from a test, but truncates it if
1794 * it is longer than MAX_HEAD characters, and just keeps the head and 231 * it is longer than MAX_HEAD characters, and just keeps the head and
1795 * the last TAIL_LENGTH characters of the output. 232 * the last TAIL_LENGTH characters of the output.
1796 */ 233 */
1797 class OutputLog { 234 class OutputLog {
1798 static const int MAX_HEAD = 100 * 1024; 235 static const int MAX_HEAD = 100 * 1024;
1799 static const int TAIL_LENGTH = 10 * 1024; 236 static const int TAIL_LENGTH = 10 * 1024;
1800 List<int> head = <int>[]; 237 List<int> head = <int>[];
1801 List<int> tail; 238 List<int> tail;
(...skipping 932 matching lines...) Expand 10 before | Expand all | Expand 10 after
2734 // For now, we always run dartk in batch mode. 1171 // For now, we always run dartk in batch mode.
2735 var name = command.displayName; 1172 var name = command.displayName;
2736 assert(name == 'dartk'); 1173 assert(name == 'dartk');
2737 return _getBatchRunner(name) 1174 return _getBatchRunner(name)
2738 .runCommand(name, command, timeout, command.arguments); 1175 .runCommand(name, command, timeout, command.arguments);
2739 } else if (command is CompilationCommand && 1176 } else if (command is CompilationCommand &&
2740 globalConfiguration.batchDart2JS) { 1177 globalConfiguration.batchDart2JS) {
2741 return _getBatchRunner("dart2js") 1178 return _getBatchRunner("dart2js")
2742 .runCommand("dart2js", command, timeout, command.arguments); 1179 .runCommand("dart2js", command, timeout, command.arguments);
2743 } else if (command is AnalysisCommand && globalConfiguration.batch) { 1180 } else if (command is AnalysisCommand && globalConfiguration.batch) {
2744 return _getBatchRunner(command.flavor) 1181 return _getBatchRunner(command.displayName)
2745 .runCommand(command.flavor, command, timeout, command.arguments); 1182 .runCommand(command.displayName, command, timeout, command.arguments);
2746 } else if (command is ScriptCommand) { 1183 } else if (command is ScriptCommand) {
2747 return command.run(); 1184 return command.run();
2748 } else if (command is AdbPrecompilationCommand) { 1185 } else if (command is AdbPrecompilationCommand) {
2749 assert(adbDevicePool != null); 1186 assert(adbDevicePool != null);
2750 return adbDevicePool.acquireDevice().then((AdbDevice device) { 1187 return adbDevicePool.acquireDevice().then((AdbDevice device) {
2751 return _runAdbPrecompilationCommand(device, command, timeout) 1188 return _runAdbPrecompilationCommand(device, command, timeout)
2752 .whenComplete(() { 1189 .whenComplete(() {
2753 adbDevicePool.releaseDevice(device); 1190 adbDevicePool.releaseDevice(device);
2754 }); 1191 });
2755 }); 1192 });
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after
2856 if (!runner._currentlyRunning) return runner; 1293 if (!runner._currentlyRunning) return runner;
2857 } 1294 }
2858 throw new Exception('Unable to find inactive batch runner.'); 1295 throw new Exception('Unable to find inactive batch runner.');
2859 } 1296 }
2860 1297
2861 Future<CommandOutput> _startBrowserControllerTest( 1298 Future<CommandOutput> _startBrowserControllerTest(
2862 BrowserTestCommand browserCommand, int timeout) { 1299 BrowserTestCommand browserCommand, int timeout) {
2863 var completer = new Completer<CommandOutput>(); 1300 var completer = new Completer<CommandOutput>();
2864 1301
2865 var callback = (BrowserTestOutput output) { 1302 var callback = (BrowserTestOutput output) {
2866 completer 1303 completer.complete(new BrowserCommandOutputImpl(browserCommand, output));
2867 .complete(new BrowserControllerTestOutcome(browserCommand, output));
2868 }; 1304 };
2869 1305
2870 BrowserTest browserTest; 1306 BrowserTest browserTest;
2871 if (browserCommand is BrowserHtmlTestCommand) { 1307 if (browserCommand is BrowserHtmlTestCommand) {
2872 browserTest = new HtmlTest(browserCommand.url, callback, timeout, 1308 browserTest = new HtmlTest(browserCommand.url, callback, timeout,
2873 browserCommand.expectedMessages); 1309 browserCommand.expectedMessages);
2874 } else { 1310 } else {
2875 browserTest = new BrowserTest(browserCommand.url, callback, timeout); 1311 browserTest = new BrowserTest(browserCommand.url, callback, timeout);
2876 } 1312 }
2877 _getBrowserTestRunner(browserCommand.configuration).then((testRunner) { 1313 _getBrowserTestRunner(browserCommand.configuration).then((testRunner) {
(...skipping 326 matching lines...) Expand 10 before | Expand all | Expand 10 after
3204 if (_globalConfiguration.listTests) { 1640 if (_globalConfiguration.listTests) {
3205 setupForListing(testCaseEnqueuer); 1641 setupForListing(testCaseEnqueuer);
3206 } else { 1642 } else {
3207 setupForRunning(testCaseEnqueuer); 1643 setupForRunning(testCaseEnqueuer);
3208 } 1644 }
3209 1645
3210 // Start enqueing all TestCases 1646 // Start enqueing all TestCases
3211 testCaseEnqueuer.enqueueTestSuites(testSuites); 1647 testCaseEnqueuer.enqueueTestSuites(testSuites);
3212 } 1648 }
3213 1649
3214 void freeEnqueueingStructures() {
3215 CommandBuilder.instance.clearCommandCache();
3216 }
3217
3218 void eventFinishedTestCase(TestCase testCase) { 1650 void eventFinishedTestCase(TestCase testCase) {
3219 for (var listener in _eventListener) { 1651 for (var listener in _eventListener) {
3220 listener.done(testCase); 1652 listener.done(testCase);
3221 } 1653 }
3222 } 1654 }
3223 1655
3224 void eventTestAdded(TestCase testCase) { 1656 void eventTestAdded(TestCase testCase) {
3225 for (var listener in _eventListener) { 1657 for (var listener in _eventListener) {
3226 listener.testAdded(); 1658 listener.testAdded();
3227 } 1659 }
3228 } 1660 }
3229 1661
3230 void eventAllTestsKnown() { 1662 void eventAllTestsKnown() {
3231 freeEnqueueingStructures();
3232 for (var listener in _eventListener) { 1663 for (var listener in _eventListener) {
3233 listener.allTestsKnown(); 1664 listener.allTestsKnown();
3234 } 1665 }
3235 } 1666 }
3236 1667
3237 void eventAllTestsDone() { 1668 void eventAllTestsDone() {
3238 for (var listener in _eventListener) { 1669 for (var listener in _eventListener) {
3239 listener.allDone(); 1670 listener.allDone();
3240 } 1671 }
3241 _allDone(); 1672 _allDone();
3242 } 1673 }
3243 } 1674 }
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