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

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

Issue 2933973002: Simplify Command classes. (Closed)
Patch Set: Move Command and CommandOutput classes to separate files. 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
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 150 matching lines...) Expand 10 before | Expand all | Expand 10 after
879 bool isNegative, 216 bool isNegative,
880 this._testingUrl) 217 this._testingUrl)
881 : super(displayName, commands, configuration, expectedOutcomes, 218 : super(displayName, commands, configuration, expectedOutcomes,
882 isNegative: isNegative, info: info); 219 isNegative: isNegative, info: info);
883 220
884 String _testingUrl; 221 String _testingUrl;
885 222
886 String get testingUrl => _testingUrl; 223 String get testingUrl => _testingUrl;
887 } 224 }
888 225
889 class UnittestSuiteMessagesMixin {
890 bool _isAsyncTest(String testOutput) {
891 return testOutput.contains("unittest-suite-wait-for-done");
892 }
893
894 bool _isAsyncTestSuccessful(String testOutput) {
895 return testOutput.contains("unittest-suite-success");
896 }
897
898 Expectation _negateOutcomeIfIncompleteAsyncTest(
899 Expectation outcome, String testOutput) {
900 // If this is an asynchronous test and the asynchronous operation didn't
901 // complete successfully, it's outcome is Expectation.FAIL.
902 // TODO: maybe we should introduce a AsyncIncomplete marker or so
903 if (outcome == Expectation.pass) {
904 if (_isAsyncTest(testOutput) && !_isAsyncTestSuccessful(testOutput)) {
905 return Expectation.fail;
906 }
907 }
908 return outcome;
909 }
910 }
911
912 /**
913 * CommandOutput records the output of a completed command: the process's exit
914 * code, the standard output and standard error, whether the process timed out,
915 * and the time the process took to run. It does not contain a pointer to the
916 * [TestCase] this is the output of, so some functions require the test case
917 * to be passed as an argument.
918 */
919 abstract class CommandOutput {
920 Command get command;
921
922 Expectation result(TestCase testCase);
923
924 bool get hasCrashed;
925
926 bool get hasTimedOut;
927
928 bool didFail(TestCase testCase);
929
930 bool hasFailed(TestCase testCase);
931
932 bool get canRunDependendCommands;
933
934 bool get successful; // otherwise we might to retry running
935
936 Duration get time;
937
938 int get exitCode;
939
940 int get pid;
941
942 List<int> get stdout;
943
944 List<int> get stderr;
945
946 List<String> get diagnostics;
947
948 bool get compilationSkipped;
949 }
950
951 class CommandOutputImpl extends UniqueObject implements CommandOutput {
952 Command command;
953 int exitCode;
954
955 bool timedOut;
956 List<int> stdout;
957 List<int> stderr;
958 Duration time;
959 List<String> diagnostics;
960 bool compilationSkipped;
961 int pid;
962
963 /**
964 * A flag to indicate we have already printed a warning about ignoring the VM
965 * crash, to limit the amount of output produced per test.
966 */
967 bool alreadyPrintedWarning = false;
968
969 CommandOutputImpl(
970 Command this.command,
971 int this.exitCode,
972 bool this.timedOut,
973 List<int> this.stdout,
974 List<int> this.stderr,
975 Duration this.time,
976 bool this.compilationSkipped,
977 int this.pid) {
978 diagnostics = [];
979 }
980
981 Expectation result(TestCase testCase) {
982 if (hasCrashed) return Expectation.crash;
983 if (hasTimedOut) return Expectation.timeout;
984 if (hasFailed(testCase)) return Expectation.fail;
985 if (hasNonUtf8) return Expectation.nonUtf8Error;
986 return Expectation.pass;
987 }
988
989 bool get hasCrashed {
990 // dart2js exits with code 253 in case of unhandled exceptions.
991 // The dart binary exits with code 253 in case of an API error such
992 // as an invalid snapshot file.
993 // In either case an exit code of 253 is considered a crash.
994 if (exitCode == 253) return true;
995 if (io.Platform.operatingSystem == 'windows') {
996 // The VM uses std::abort to terminate on asserts.
997 // std::abort terminates with exit code 3 on Windows.
998 if (exitCode == 3 || exitCode == CRASHING_BROWSER_EXITCODE) {
999 return !timedOut;
1000 }
1001 // If a program receives an uncaught system exception, the program
1002 // terminates with the exception code as exit code.
1003 // The 0x3FFFFF00 mask here tries to determine if an exception indicates
1004 // a crash of the program.
1005 // System exception codes can be found in 'winnt.h', for example
1006 // "#define STATUS_ACCESS_VIOLATION ((DWORD) 0xC0000005)"
1007 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0));
1008 }
1009 return !timedOut && ((exitCode < 0));
1010 }
1011
1012 bool get hasTimedOut => timedOut;
1013
1014 bool didFail(TestCase testCase) {
1015 return (exitCode != 0 && !hasCrashed);
1016 }
1017
1018 bool get canRunDependendCommands {
1019 // FIXME(kustermann): We may need to change this
1020 return !hasTimedOut && exitCode == 0;
1021 }
1022
1023 bool get successful {
1024 // FIXME(kustermann): We may need to change this
1025 return !hasTimedOut && exitCode == 0;
1026 }
1027
1028 // Reverse result of a negative test.
1029 bool hasFailed(TestCase testCase) {
1030 return testCase.isNegative ? !didFail(testCase) : didFail(testCase);
1031 }
1032
1033 bool get hasNonUtf8 => exitCode == NON_UTF_FAKE_EXITCODE;
1034
1035 Expectation _negateOutcomeIfNegativeTest(
1036 Expectation outcome, bool isNegative) {
1037 if (!isNegative) return outcome;
1038 if (outcome == Expectation.ignore) return outcome;
1039 if (outcome.canBeOutcomeOf(Expectation.fail)) {
1040 return Expectation.pass;
1041 }
1042 return Expectation.fail;
1043 }
1044 }
1045
1046 class BrowserCommandOutputImpl extends CommandOutputImpl {
1047 // Although tests are reported as passing, content shell sometimes exits with
1048 // a nonzero exitcode which makes our dartium builders extremely falky.
1049 // See: http://dartbug.com/15139.
1050 // TODO(rnystrom): Is this still needed? The underlying bug is closed.
1051 static int WHITELISTED_CONTENTSHELL_EXITCODE = -1073740022;
1052 static bool isWindows = io.Platform.operatingSystem == 'windows';
1053 static bool _failedBecauseOfFlakyInfrastructure(
1054 Command command, bool timedOut, List<int> stderrBytes) {
1055 // If the browser test failed, it may have been because content shell
1056 // and the virtual framebuffer X server didn't hook up, or it crashed with
1057 // a core dump. Sometimes content shell crashes after it has set the stdout
1058 // to PASS, so we have to do this check first.
1059 // Content shell also fails with a broken pipe message: Issue 26739
1060 var zygoteCrash =
1061 new RegExp(r"ERROR:zygote_linux\.cc\(\d+\)] write: Broken pipe");
1062 var stderr = decodeUtf8(stderrBytes);
1063 // TODO(7564): See http://dartbug.com/7564
1064 // This may not be happening anymore. Test by removing this suppression.
1065 if (stderr.contains(MESSAGE_CANNOT_OPEN_DISPLAY) ||
1066 stderr.contains(MESSAGE_FAILED_TO_RUN_COMMAND)) {
1067 DebugLogger.warning(
1068 "Warning: Failure because of missing XDisplay. Test ignored");
1069 return true;
1070 }
1071 // TODO(26739): See http://dartbug.com/26739
1072 if (zygoteCrash.hasMatch(stderr)) {
1073 DebugLogger.warning("Warning: Failure because of content_shell "
1074 "zygote crash. Test ignored");
1075 return true;
1076 }
1077 // TODO(28955): See http://dartbug.com/28955
1078 if (timedOut &&
1079 command is BrowserTestCommand &&
1080 command.browser == Runtime.ie11) {
1081 DebugLogger.warning("Timeout of ie11 on test page ${command.url}");
1082 return true;
1083 }
1084 return false;
1085 }
1086
1087 bool _infraFailure;
1088
1089 BrowserCommandOutputImpl(
1090 Command command,
1091 int exitCode,
1092 bool timedOut,
1093 List<int> stdout,
1094 List<int> stderr,
1095 Duration time,
1096 bool compilationSkipped)
1097 : _infraFailure =
1098 _failedBecauseOfFlakyInfrastructure(command, timedOut, stderr),
1099 super(command, exitCode, timedOut, stdout, stderr, time,
1100 compilationSkipped, 0);
1101
1102 Expectation result(TestCase testCase) {
1103 if (_infraFailure) {
1104 return Expectation.ignore;
1105 }
1106
1107 // Handle crashes and timeouts first
1108 if (hasCrashed) return Expectation.crash;
1109 if (hasTimedOut) return Expectation.timeout;
1110 if (hasNonUtf8) return Expectation.nonUtf8Error;
1111
1112 var outcome = _getOutcome();
1113
1114 if (testCase.hasRuntimeError) {
1115 if (!outcome.canBeOutcomeOf(Expectation.runtimeError)) {
1116 return Expectation.missingRuntimeError;
1117 }
1118 }
1119 if (testCase.isNegative) {
1120 if (outcome.canBeOutcomeOf(Expectation.fail)) return Expectation.pass;
1121 return Expectation.fail;
1122 }
1123 return outcome;
1124 }
1125
1126 bool get successful => canRunDependendCommands;
1127
1128 bool get canRunDependendCommands {
1129 // We cannot rely on the exit code of content_shell as a method to
1130 // determine if we were successful or not.
1131 return super.canRunDependendCommands && !didFail(null);
1132 }
1133
1134 bool get hasCrashed {
1135 return super.hasCrashed || _rendererCrashed;
1136 }
1137
1138 Expectation _getOutcome() {
1139 if (_browserTestFailure) {
1140 return Expectation.runtimeError;
1141 }
1142 return Expectation.pass;
1143 }
1144
1145 bool get _rendererCrashed =>
1146 decodeUtf8(super.stdout).contains("#CRASHED - rendere");
1147
1148 bool get _browserTestFailure {
1149 // Browser tests fail unless stdout contains
1150 // 'Content-Type: text/plain' followed by 'PASS'.
1151 bool hasContentType = false;
1152 var stdoutLines = decodeUtf8(super.stdout).split("\n");
1153 var containsFail = false;
1154 var containsPass = false;
1155 for (String line in stdoutLines) {
1156 switch (line) {
1157 case 'Content-Type: text/plain':
1158 hasContentType = true;
1159 break;
1160 case 'FAIL':
1161 if (hasContentType) {
1162 containsFail = true;
1163 }
1164 break;
1165 case 'PASS':
1166 if (hasContentType) {
1167 containsPass = true;
1168 }
1169 break;
1170 }
1171 }
1172 if (hasContentType) {
1173 if (containsFail && containsPass) {
1174 DebugLogger.warning("Test had 'FAIL' and 'PASS' in stdout. ($command)");
1175 }
1176 if (!containsFail && !containsPass) {
1177 DebugLogger.warning("Test had neither 'FAIL' nor 'PASS' in stdout. "
1178 "($command)");
1179 return true;
1180 }
1181 if (containsFail) {
1182 return true;
1183 }
1184 assert(containsPass);
1185 if (exitCode != 0) {
1186 var message = "All tests passed, but exitCode != 0. "
1187 "Actual exitcode: $exitCode. "
1188 "($command)";
1189 DebugLogger.warning(message);
1190 diagnostics.add(message);
1191 }
1192 return (!hasCrashed &&
1193 exitCode != 0 &&
1194 (!isWindows || exitCode != WHITELISTED_CONTENTSHELL_EXITCODE));
1195 }
1196 DebugLogger.warning("Couldn't find 'Content-Type: text/plain' in output. "
1197 "($command).");
1198 return true;
1199 }
1200 }
1201
1202 class HTMLBrowserCommandOutputImpl extends BrowserCommandOutputImpl {
1203 HTMLBrowserCommandOutputImpl(
1204 Command command,
1205 int exitCode,
1206 bool timedOut,
1207 List<int> stdout,
1208 List<int> stderr,
1209 Duration time,
1210 bool compilationSkipped)
1211 : super(command, exitCode, timedOut, stdout, stderr, time,
1212 compilationSkipped);
1213
1214 bool didFail(TestCase testCase) {
1215 return _getOutcome() != Expectation.pass;
1216 }
1217
1218 bool get _browserTestFailure {
1219 // We should not need to convert back and forward.
1220 var output = decodeUtf8(super.stdout);
1221 if (output.contains("FAIL")) return true;
1222 return !output.contains("PASS");
1223 }
1224 }
1225
1226 class BrowserTestJsonResult {
1227 static const ALLOWED_TYPES = const [
1228 'sync_exception',
1229 'window_onerror',
1230 'script_onerror',
1231 'window_compilationerror',
1232 'print',
1233 'message_received',
1234 'dom',
1235 'debug'
1236 ];
1237
1238 final Expectation outcome;
1239 final String htmlDom;
1240 final List<dynamic> events;
1241
1242 BrowserTestJsonResult(this.outcome, this.htmlDom, this.events);
1243
1244 static BrowserTestJsonResult parseFromString(String content) {
1245 void validate(String assertion, bool value) {
1246 if (!value) {
1247 throw "InvalidFormat sent from browser driving page: $assertion:\n\n"
1248 "$content";
1249 }
1250 }
1251
1252 try {
1253 var events = JSON.decode(content);
1254 if (events != null) {
1255 validate("Message must be a List", events is List);
1256
1257 var messagesByType = <String, List<String>>{};
1258 ALLOWED_TYPES.forEach((type) => messagesByType[type] = <String>[]);
1259
1260 for (var entry in events) {
1261 validate("An entry must be a Map", entry is Map);
1262
1263 var type = entry['type'];
1264 var value = entry['value'] as String;
1265 var timestamp = entry['timestamp'];
1266
1267 validate("'type' of an entry must be a String", type is String);
1268 validate("'type' has to be in $ALLOWED_TYPES.",
1269 ALLOWED_TYPES.contains(type));
1270 validate(
1271 "'timestamp' of an entry must be a number", timestamp is num);
1272
1273 messagesByType[type].add(value);
1274 }
1275 validate("The message must have exactly one 'dom' entry.",
1276 messagesByType['dom'].length == 1);
1277
1278 var dom = messagesByType['dom'][0];
1279 if (dom.endsWith('\n')) {
1280 dom = '$dom\n';
1281 }
1282
1283 return new BrowserTestJsonResult(
1284 _getOutcome(messagesByType), dom, events as List<dynamic>);
1285 }
1286 } catch (error) {
1287 // If something goes wrong, we know the content was not in the correct
1288 // JSON format. So we can't parse it.
1289 // The caller is responsible for falling back to the old way of
1290 // determining if a test failed.
1291 }
1292
1293 return null;
1294 }
1295
1296 static Expectation _getOutcome(Map<String, List<String>> messagesByType) {
1297 occured(String type) => messagesByType[type].length > 0;
1298 searchForMsg(List<String> types, String message) {
1299 return types.any((type) => messagesByType[type].contains(message));
1300 }
1301
1302 // FIXME(kustermann,ricow): I think this functionality doesn't work in
1303 // test_controller.js: So far I haven't seen anything being reported on
1304 // "window.compilationerror"
1305 if (occured('window_compilationerror')) {
1306 return Expectation.compileTimeError;
1307 }
1308
1309 if (occured('sync_exception') ||
1310 occured('window_onerror') ||
1311 occured('script_onerror')) {
1312 return Expectation.runtimeError;
1313 }
1314
1315 if (messagesByType['dom'][0].contains('FAIL')) {
1316 return Expectation.runtimeError;
1317 }
1318
1319 // We search for these messages in 'print' and 'message_received' because
1320 // the unittest implementation posts these messages using
1321 // "window.postMessage()" instead of the normal "print()" them.
1322
1323 var isAsyncTest = searchForMsg(
1324 ['print', 'message_received'], 'unittest-suite-wait-for-done');
1325 var isAsyncSuccess =
1326 searchForMsg(['print', 'message_received'], 'unittest-suite-success') ||
1327 searchForMsg(['print', 'message_received'], 'unittest-suite-done');
1328
1329 if (isAsyncTest) {
1330 if (isAsyncSuccess) {
1331 return Expectation.pass;
1332 }
1333 return Expectation.runtimeError;
1334 }
1335
1336 var mainStarted =
1337 searchForMsg(['print', 'message_received'], 'dart-calling-main');
1338 var mainDone =
1339 searchForMsg(['print', 'message_received'], 'dart-main-done');
1340
1341 if (mainStarted && mainDone) {
1342 return Expectation.pass;
1343 }
1344 return Expectation.fail;
1345 }
1346 }
1347
1348 class BrowserControllerTestOutcome extends CommandOutputImpl
1349 with UnittestSuiteMessagesMixin {
1350 BrowserTestOutput _result;
1351 Expectation _rawOutcome;
1352
1353 factory BrowserControllerTestOutcome(
1354 Command command, BrowserTestOutput result) {
1355 String indent(String string, int numSpaces) {
1356 var spaces = new List.filled(numSpaces, ' ').join('');
1357 return string
1358 .replaceAll('\r\n', '\n')
1359 .split('\n')
1360 .map((line) => "$spaces$line")
1361 .join('\n');
1362 }
1363
1364 String stdout = "";
1365 String stderr = "";
1366 Expectation outcome;
1367
1368 var parsedResult =
1369 BrowserTestJsonResult.parseFromString(result.lastKnownMessage);
1370 if (parsedResult != null) {
1371 outcome = parsedResult.outcome;
1372 } else {
1373 // Old way of determining whether a test failed or passed.
1374 if (result.lastKnownMessage.contains("FAIL")) {
1375 outcome = Expectation.runtimeError;
1376 } else if (result.lastKnownMessage.contains("PASS")) {
1377 outcome = Expectation.pass;
1378 } else {
1379 outcome = Expectation.runtimeError;
1380 }
1381 }
1382
1383 if (result.didTimeout) {
1384 if (result.delayUntilTestStarted != null) {
1385 stderr = "This test timed out. The delay until the test actually "
1386 "started was: ${result.delayUntilTestStarted}.";
1387 } else {
1388 stderr = "This test has not notified test.py that it started running.";
1389 }
1390 }
1391
1392 if (parsedResult != null) {
1393 stdout = "events:\n${indent(prettifyJson(parsedResult.events), 2)}\n\n";
1394 } else {
1395 stdout = "message:\n${indent(result.lastKnownMessage, 2)}\n\n";
1396 }
1397
1398 stderr = '$stderr\n\n'
1399 'BrowserOutput while running the test (* EXPERIMENTAL *):\n'
1400 'BrowserOutput.stdout:\n'
1401 '${indent(result.browserOutput.stdout.toString(), 2)}\n'
1402 'BrowserOutput.stderr:\n'
1403 '${indent(result.browserOutput.stderr.toString(), 2)}\n'
1404 '\n';
1405 return new BrowserControllerTestOutcome._internal(
1406 command, result, outcome, encodeUtf8(stdout), encodeUtf8(stderr));
1407 }
1408
1409 BrowserControllerTestOutcome._internal(
1410 Command command,
1411 BrowserTestOutput result,
1412 this._rawOutcome,
1413 List<int> stdout,
1414 List<int> stderr)
1415 : super(command, 0, result.didTimeout, stdout, stderr, result.duration,
1416 false, 0) {
1417 _result = result;
1418 }
1419
1420 Expectation result(TestCase testCase) {
1421 // Handle timeouts first
1422 if (_result.didTimeout) return Expectation.timeout;
1423 if (hasNonUtf8) return Expectation.nonUtf8Error;
1424
1425 // Multitests are handled specially
1426 if (testCase.hasRuntimeError) {
1427 if (_rawOutcome == Expectation.runtimeError) return Expectation.pass;
1428 return Expectation.missingRuntimeError;
1429 }
1430
1431 return _negateOutcomeIfNegativeTest(_rawOutcome, testCase.isNegative);
1432 }
1433 }
1434
1435 class AnalysisCommandOutputImpl extends CommandOutputImpl {
1436 // An error line has 8 fields that look like:
1437 // ERROR|COMPILER|MISSING_SOURCE|file:/tmp/t.dart|15|1|24|Missing source.
1438 final int ERROR_LEVEL = 0;
1439 final int ERROR_TYPE = 1;
1440 final int FILENAME = 3;
1441 final int FORMATTED_ERROR = 7;
1442
1443 AnalysisCommandOutputImpl(
1444 Command command,
1445 int exitCode,
1446 bool timedOut,
1447 List<int> stdout,
1448 List<int> stderr,
1449 Duration time,
1450 bool compilationSkipped)
1451 : super(command, exitCode, timedOut, stdout, stderr, time,
1452 compilationSkipped, 0);
1453
1454 Expectation result(TestCase testCase) {
1455 // TODO(kustermann): If we run the analyzer not in batch mode, make sure
1456 // that command.exitCodes matches 2 (errors), 1 (warnings), 0 (no warnings,
1457 // no errors)
1458
1459 // Handle crashes and timeouts first
1460 if (hasCrashed) return Expectation.crash;
1461 if (hasTimedOut) return Expectation.timeout;
1462 if (hasNonUtf8) return Expectation.nonUtf8Error;
1463
1464 // Get the errors/warnings from the analyzer
1465 List<String> errors = [];
1466 List<String> warnings = [];
1467 parseAnalyzerOutput(errors, warnings);
1468
1469 // Handle errors / missing errors
1470 if (testCase.expectCompileError) {
1471 if (errors.length > 0) {
1472 return Expectation.pass;
1473 }
1474 return Expectation.missingCompileTimeError;
1475 }
1476 if (errors.length > 0) {
1477 return Expectation.compileTimeError;
1478 }
1479
1480 // Handle static warnings / missing static warnings
1481 if (testCase.hasStaticWarning) {
1482 if (warnings.length > 0) {
1483 return Expectation.pass;
1484 }
1485 return Expectation.missingStaticWarning;
1486 }
1487 if (warnings.length > 0) {
1488 return Expectation.staticWarning;
1489 }
1490
1491 assert(errors.length == 0 && warnings.length == 0);
1492 assert(!testCase.hasCompileError && !testCase.hasStaticWarning);
1493 return Expectation.pass;
1494 }
1495
1496 void parseAnalyzerOutput(List<String> outErrors, List<String> outWarnings) {
1497 // Parse a line delimited by the | character using \ as an escape character
1498 // like: FOO|BAR|FOO\|BAR|FOO\\BAZ as 4 fields: FOO BAR FOO|BAR FOO\BAZ
1499 List<String> splitMachineError(String line) {
1500 StringBuffer field = new StringBuffer();
1501 List<String> result = [];
1502 bool escaped = false;
1503 for (var i = 0; i < line.length; i++) {
1504 var c = line[i];
1505 if (!escaped && c == '\\') {
1506 escaped = true;
1507 continue;
1508 }
1509 escaped = false;
1510 if (c == '|') {
1511 result.add(field.toString());
1512 field = new StringBuffer();
1513 continue;
1514 }
1515 field.write(c);
1516 }
1517 result.add(field.toString());
1518 return result;
1519 }
1520
1521 for (String line in decodeUtf8(super.stderr).split("\n")) {
1522 if (line.length == 0) continue;
1523 List<String> fields = splitMachineError(line);
1524 // We only consider errors/warnings for files of interest.
1525 if (fields.length > FORMATTED_ERROR) {
1526 if (fields[ERROR_LEVEL] == 'ERROR') {
1527 outErrors.add(fields[FORMATTED_ERROR]);
1528 } else if (fields[ERROR_LEVEL] == 'WARNING') {
1529 outWarnings.add(fields[FORMATTED_ERROR]);
1530 }
1531 // OK to Skip error output that doesn't match the machine format
1532 }
1533 }
1534 }
1535 }
1536
1537 class VmCommandOutputImpl extends CommandOutputImpl
1538 with UnittestSuiteMessagesMixin {
1539 static const DART_VM_EXITCODE_DFE_ERROR = 252;
1540 static const DART_VM_EXITCODE_COMPILE_TIME_ERROR = 254;
1541 static const DART_VM_EXITCODE_UNCAUGHT_EXCEPTION = 255;
1542
1543 VmCommandOutputImpl(Command command, int exitCode, bool timedOut,
1544 List<int> stdout, List<int> stderr, Duration time, int pid)
1545 : super(command, exitCode, timedOut, stdout, stderr, time, false, pid);
1546
1547 Expectation result(TestCase testCase) {
1548 // Handle crashes and timeouts first
1549 if (exitCode == DART_VM_EXITCODE_DFE_ERROR) return Expectation.dartkCrash;
1550 if (hasCrashed) return Expectation.crash;
1551 if (hasTimedOut) return Expectation.timeout;
1552 if (hasNonUtf8) return Expectation.nonUtf8Error;
1553
1554 // Multitests are handled specially
1555 if (testCase.expectCompileError) {
1556 if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) {
1557 return Expectation.pass;
1558 }
1559 return Expectation.missingCompileTimeError;
1560 }
1561 if (testCase.hasRuntimeError) {
1562 // TODO(kustermann): Do we consider a "runtimeError" only an uncaught
1563 // exception or does any nonzero exit code fullfil this requirement?
1564 if (exitCode != 0) {
1565 return Expectation.pass;
1566 }
1567 return Expectation.missingRuntimeError;
1568 }
1569
1570 // The actual outcome depends on the exitCode
1571 Expectation outcome;
1572 if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) {
1573 outcome = Expectation.compileTimeError;
1574 } else if (exitCode == DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) {
1575 outcome = Expectation.runtimeError;
1576 } else if (exitCode != 0) {
1577 // This is a general fail, in case we get an unknown nonzero exitcode.
1578 outcome = Expectation.fail;
1579 } else {
1580 outcome = Expectation.pass;
1581 }
1582 outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout));
1583 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
1584 }
1585 }
1586
1587 class CompilationCommandOutputImpl extends CommandOutputImpl {
1588 static const DART2JS_EXITCODE_CRASH = 253;
1589
1590 CompilationCommandOutputImpl(
1591 Command command,
1592 int exitCode,
1593 bool timedOut,
1594 List<int> stdout,
1595 List<int> stderr,
1596 Duration time,
1597 bool compilationSkipped)
1598 : super(command, exitCode, timedOut, stdout, stderr, time,
1599 compilationSkipped, 0);
1600
1601 Expectation result(TestCase testCase) {
1602 // Handle general crash/timeout detection.
1603 if (hasCrashed) return Expectation.crash;
1604 if (hasTimedOut) {
1605 bool isWindows = io.Platform.operatingSystem == 'windows';
1606 bool isBrowserTestCase =
1607 testCase.commands.any((command) => command is BrowserTestCommand);
1608 // TODO(26060) Dart2js batch mode hangs on Windows under heavy load.
1609 return (isWindows && isBrowserTestCase)
1610 ? Expectation.ignore
1611 : Expectation.timeout;
1612 }
1613 if (hasNonUtf8) return Expectation.nonUtf8Error;
1614
1615 // Handle dart2js specific crash detection
1616 if (exitCode == DART2JS_EXITCODE_CRASH ||
1617 exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_COMPILE_TIME_ERROR ||
1618 exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) {
1619 return Expectation.crash;
1620 }
1621
1622 // Multitests are handled specially
1623 if (testCase.expectCompileError) {
1624 // Nonzero exit code of the compiler means compilation failed
1625 // TODO(kustermann): Do we have a special exit code in that case???
1626 if (exitCode != 0) {
1627 return Expectation.pass;
1628 }
1629 return Expectation.missingCompileTimeError;
1630 }
1631
1632 // TODO(kustermann): This is a hack, remove it
1633 if (testCase.hasRuntimeError && testCase.commands.length > 1) {
1634 // We expected to run the test, but we got an compile time error.
1635 // If the compilation succeeded, we wouldn't be in here!
1636 assert(exitCode != 0);
1637 return Expectation.compileTimeError;
1638 }
1639
1640 Expectation outcome =
1641 exitCode == 0 ? Expectation.pass : Expectation.compileTimeError;
1642 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
1643 }
1644 }
1645
1646 class KernelCompilationCommandOutputImpl extends CompilationCommandOutputImpl {
1647 KernelCompilationCommandOutputImpl(
1648 Command command,
1649 int exitCode,
1650 bool timedOut,
1651 List<int> stdout,
1652 List<int> stderr,
1653 Duration time,
1654 bool compilationSkipped)
1655 : super(command, exitCode, timedOut, stdout, stderr, time,
1656 compilationSkipped);
1657
1658 bool get canRunDependendCommands {
1659 // See [BatchRunnerProcess]: 0 means success, 1 means compile-time error.
1660 // TODO(asgerf): When the frontend supports it, continue running even if
1661 // there were compile-time errors. See kernel_sdk issue #18.
1662 return !hasCrashed && !timedOut && exitCode == 0;
1663 }
1664
1665 Expectation result(TestCase testCase) {
1666 Expectation result = super.result(testCase);
1667 if (result.canBeOutcomeOf(Expectation.crash)) {
1668 return Expectation.dartkCrash;
1669 } else if (result.canBeOutcomeOf(Expectation.timeout)) {
1670 return Expectation.dartkTimeout;
1671 } else if (result.canBeOutcomeOf(Expectation.compileTimeError)) {
1672 return Expectation.dartkCompileTimeError;
1673 }
1674 return result;
1675 }
1676
1677 // If the compiler was able to produce a Kernel IR file we want to run the
1678 // result on the Dart VM. We therefore mark the [KernelCompilationCommand] as
1679 // successful.
1680 // => This ensures we test that the DartVM produces correct CompileTime errors
1681 // as it is supposed to for our test suites.
1682 bool get successful => canRunDependendCommands;
1683 }
1684
1685 class JsCommandlineOutputImpl extends CommandOutputImpl
1686 with UnittestSuiteMessagesMixin {
1687 JsCommandlineOutputImpl(Command command, int exitCode, bool timedOut,
1688 List<int> stdout, List<int> stderr, Duration time)
1689 : super(command, exitCode, timedOut, stdout, stderr, time, false, 0);
1690
1691 Expectation result(TestCase testCase) {
1692 // Handle crashes and timeouts first
1693 if (hasCrashed) return Expectation.crash;
1694 if (hasTimedOut) return Expectation.timeout;
1695 if (hasNonUtf8) return Expectation.nonUtf8Error;
1696
1697 if (testCase.hasRuntimeError) {
1698 if (exitCode != 0) return Expectation.pass;
1699 return Expectation.missingRuntimeError;
1700 }
1701
1702 var outcome = exitCode == 0 ? Expectation.pass : Expectation.runtimeError;
1703 outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout));
1704 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
1705 }
1706 }
1707
1708 class PubCommandOutputImpl extends CommandOutputImpl {
1709 PubCommandOutputImpl(PubCommand command, int exitCode, bool timedOut,
1710 List<int> stdout, List<int> stderr, Duration time)
1711 : super(command, exitCode, timedOut, stdout, stderr, time, false, 0);
1712
1713 Expectation result(TestCase testCase) {
1714 // Handle crashes and timeouts first
1715 if (hasCrashed) return Expectation.crash;
1716 if (hasTimedOut) return Expectation.timeout;
1717 if (hasNonUtf8) return Expectation.nonUtf8Error;
1718
1719 if (exitCode == 0) {
1720 return Expectation.pass;
1721 } else if ((command as PubCommand).command == 'get') {
1722 return Expectation.pubGetError;
1723 } else {
1724 return Expectation.fail;
1725 }
1726 }
1727 }
1728
1729 class ScriptCommandOutputImpl extends CommandOutputImpl {
1730 final Expectation _result;
1731
1732 ScriptCommandOutputImpl(ScriptCommand command, this._result,
1733 String scriptExecutionInformation, Duration time)
1734 : super(command, 0, false, [], [], time, false, 0) {
1735 var lines = scriptExecutionInformation.split("\n");
1736 diagnostics.addAll(lines);
1737 }
1738
1739 Expectation result(TestCase testCase) => _result;
1740
1741 bool get canRunDependendCommands => _result == Expectation.pass;
1742
1743 bool get successful => _result == Expectation.pass;
1744 }
1745
1746 CommandOutput createCommandOutput(Command command, int exitCode, bool timedOut,
1747 List<int> stdout, List<int> stderr, Duration time, bool compilationSkipped,
1748 [int pid = 0]) {
1749 if (command is ContentShellCommand) {
1750 return new BrowserCommandOutputImpl(
1751 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1752 } else if (command is BrowserTestCommand) {
1753 return new HTMLBrowserCommandOutputImpl(
1754 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1755 } else if (command is AnalysisCommand) {
1756 return new AnalysisCommandOutputImpl(
1757 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1758 } else if (command is VmCommand) {
1759 return new VmCommandOutputImpl(
1760 command, exitCode, timedOut, stdout, stderr, time, pid);
1761 } else if (command is KernelCompilationCommand) {
1762 return new KernelCompilationCommandOutputImpl(
1763 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1764 } else if (command is AdbPrecompilationCommand) {
1765 return new VmCommandOutputImpl(
1766 command, exitCode, timedOut, stdout, stderr, time, pid);
1767 } else if (command is CompilationCommand) {
1768 if (command.displayName == 'precompiler' ||
1769 command.displayName == 'app_jit') {
1770 return new VmCommandOutputImpl(
1771 command, exitCode, timedOut, stdout, stderr, time, pid);
1772 }
1773 return new CompilationCommandOutputImpl(
1774 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
1775 } else if (command is JSCommandlineCommand) {
1776 return new JsCommandlineOutputImpl(
1777 command, exitCode, timedOut, stdout, stderr, time);
1778 } else if (command is PubCommand) {
1779 return new PubCommandOutputImpl(
1780 command, exitCode, timedOut, stdout, stderr, time);
1781 }
1782
1783 return new CommandOutputImpl(command, exitCode, timedOut, stdout, stderr,
1784 time, compilationSkipped, pid);
1785 }
1786
1787 /** 226 /**
1788 * An OutputLog records the output from a test, but truncates it if 227 * An OutputLog records the output from a test, but truncates it if
1789 * it is longer than MAX_HEAD characters, and just keeps the head and 228 * it is longer than MAX_HEAD characters, and just keeps the head and
1790 * the last TAIL_LENGTH characters of the output. 229 * the last TAIL_LENGTH characters of the output.
1791 */ 230 */
1792 class OutputLog { 231 class OutputLog {
1793 static const int MAX_HEAD = 100 * 1024; 232 static const int MAX_HEAD = 100 * 1024;
1794 static const int TAIL_LENGTH = 10 * 1024; 233 static const int TAIL_LENGTH = 10 * 1024;
1795 List<int> head = <int>[]; 234 List<int> head = <int>[];
1796 List<int> tail; 235 List<int> tail;
(...skipping 932 matching lines...) Expand 10 before | Expand all | Expand 10 after
2729 // For now, we always run dartk in batch mode. 1168 // For now, we always run dartk in batch mode.
2730 var name = command.displayName; 1169 var name = command.displayName;
2731 assert(name == 'dartk'); 1170 assert(name == 'dartk');
2732 return _getBatchRunner(name) 1171 return _getBatchRunner(name)
2733 .runCommand(name, command, timeout, command.arguments); 1172 .runCommand(name, command, timeout, command.arguments);
2734 } else if (command is CompilationCommand && 1173 } else if (command is CompilationCommand &&
2735 globalConfiguration.batchDart2JS) { 1174 globalConfiguration.batchDart2JS) {
2736 return _getBatchRunner("dart2js") 1175 return _getBatchRunner("dart2js")
2737 .runCommand("dart2js", command, timeout, command.arguments); 1176 .runCommand("dart2js", command, timeout, command.arguments);
2738 } else if (command is AnalysisCommand && globalConfiguration.batch) { 1177 } else if (command is AnalysisCommand && globalConfiguration.batch) {
2739 return _getBatchRunner(command.flavor) 1178 return _getBatchRunner(command.displayName)
2740 .runCommand(command.flavor, command, timeout, command.arguments); 1179 .runCommand(command.displayName, command, timeout, command.arguments);
2741 } else if (command is ScriptCommand) { 1180 } else if (command is ScriptCommand) {
2742 return command.run(); 1181 return command.run();
2743 } else if (command is AdbPrecompilationCommand) { 1182 } else if (command is AdbPrecompilationCommand) {
2744 assert(adbDevicePool != null); 1183 assert(adbDevicePool != null);
2745 return adbDevicePool.acquireDevice().then((AdbDevice device) { 1184 return adbDevicePool.acquireDevice().then((AdbDevice device) {
2746 return _runAdbPrecompilationCommand(device, command, timeout) 1185 return _runAdbPrecompilationCommand(device, command, timeout)
2747 .whenComplete(() { 1186 .whenComplete(() {
2748 adbDevicePool.releaseDevice(device); 1187 adbDevicePool.releaseDevice(device);
2749 }); 1188 });
2750 }); 1189 });
(...skipping 448 matching lines...) Expand 10 before | Expand all | Expand 10 after
3199 if (_globalConfiguration.listTests) { 1638 if (_globalConfiguration.listTests) {
3200 setupForListing(testCaseEnqueuer); 1639 setupForListing(testCaseEnqueuer);
3201 } else { 1640 } else {
3202 setupForRunning(testCaseEnqueuer); 1641 setupForRunning(testCaseEnqueuer);
3203 } 1642 }
3204 1643
3205 // Start enqueing all TestCases 1644 // Start enqueing all TestCases
3206 testCaseEnqueuer.enqueueTestSuites(testSuites); 1645 testCaseEnqueuer.enqueueTestSuites(testSuites);
3207 } 1646 }
3208 1647
3209 void freeEnqueueingStructures() {
3210 CommandBuilder.instance.clearCommandCache();
3211 }
3212
3213 void eventFinishedTestCase(TestCase testCase) { 1648 void eventFinishedTestCase(TestCase testCase) {
3214 for (var listener in _eventListener) { 1649 for (var listener in _eventListener) {
3215 listener.done(testCase); 1650 listener.done(testCase);
3216 } 1651 }
3217 } 1652 }
3218 1653
3219 void eventTestAdded(TestCase testCase) { 1654 void eventTestAdded(TestCase testCase) {
3220 for (var listener in _eventListener) { 1655 for (var listener in _eventListener) {
3221 listener.testAdded(); 1656 listener.testAdded();
3222 } 1657 }
3223 } 1658 }
3224 1659
3225 void eventAllTestsKnown() { 1660 void eventAllTestsKnown() {
3226 freeEnqueueingStructures();
3227 for (var listener in _eventListener) { 1661 for (var listener in _eventListener) {
3228 listener.allTestsKnown(); 1662 listener.allTestsKnown();
3229 } 1663 }
3230 } 1664 }
3231 1665
3232 void eventAllTestsDone() { 1666 void eventAllTestsDone() {
3233 for (var listener in _eventListener) { 1667 for (var listener in _eventListener) {
3234 listener.allDone(); 1668 listener.allDone();
3235 } 1669 }
3236 _allDone(); 1670 _allDone();
3237 } 1671 }
3238 } 1672 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698