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

Unified Diff: tools/testing/dart/command_output.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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/testing/dart/command.dart ('k') | tools/testing/dart/compiler_configuration.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/testing/dart/command_output.dart
diff --git a/tools/testing/dart/command_output.dart b/tools/testing/dart/command_output.dart
new file mode 100644
index 0000000000000000000000000000000000000000..d9f9cf9f39fa946cdd351f1de9087c9cdf7db36f
--- /dev/null
+++ b/tools/testing/dart/command_output.dart
@@ -0,0 +1,910 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+// We need to use the 'io' prefix here, otherwise io.exitCode will shadow
+// CommandOutput.exitCode in subclasses of CommandOutput.
+import 'dart:io' as io;
+
+import 'browser_controller.dart';
+import 'command.dart';
+import 'configuration.dart';
+import 'expectation.dart';
+import 'test_runner.dart';
+import 'utils.dart';
+
+/**
+ * CommandOutput records the output of a completed command: the process's exit
+ * code, the standard output and standard error, whether the process timed out,
+ * and the time the process took to run. It does not contain a pointer to the
+ * [TestCase] this is the output of, so some functions require the test case
+ * to be passed as an argument.
+ */
+abstract class CommandOutput {
+ Command get command;
+
+ Expectation result(TestCase testCase);
+
+ bool get hasCrashed;
+
+ bool get hasTimedOut;
+
+ bool didFail(TestCase testCase);
+
+ bool hasFailed(TestCase testCase);
+
+ bool get canRunDependendCommands;
+
+ bool get successful; // otherwise we might to retry running
+
+ Duration get time;
+
+ int get exitCode;
+
+ int get pid;
+
+ List<int> get stdout;
+
+ List<int> get stderr;
+
+ List<String> get diagnostics;
+
+ bool get compilationSkipped;
+}
+
+class CommandOutputImpl extends UniqueObject implements CommandOutput {
+ Command command;
+ int exitCode;
+
+ bool timedOut;
+ List<int> stdout;
+ List<int> stderr;
+ Duration time;
+ List<String> diagnostics;
+ bool compilationSkipped;
+ int pid;
+
+ /**
+ * A flag to indicate we have already printed a warning about ignoring the VM
+ * crash, to limit the amount of output produced per test.
+ */
+ bool alreadyPrintedWarning = false;
+
+ CommandOutputImpl(
+ Command this.command,
+ int this.exitCode,
+ bool this.timedOut,
+ List<int> this.stdout,
+ List<int> this.stderr,
+ Duration this.time,
+ bool this.compilationSkipped,
+ int this.pid) {
+ diagnostics = [];
+ }
+
+ Expectation result(TestCase testCase) {
+ if (hasCrashed) return Expectation.crash;
+ if (hasTimedOut) return Expectation.timeout;
+ if (hasFailed(testCase)) return Expectation.fail;
+ if (hasNonUtf8) return Expectation.nonUtf8Error;
+ return Expectation.pass;
+ }
+
+ bool get hasCrashed {
+ // dart2js exits with code 253 in case of unhandled exceptions.
+ // The dart binary exits with code 253 in case of an API error such
+ // as an invalid snapshot file.
+ // In either case an exit code of 253 is considered a crash.
+ if (exitCode == 253) return true;
+ if (io.Platform.operatingSystem == 'windows') {
+ // The VM uses std::abort to terminate on asserts.
+ // std::abort terminates with exit code 3 on Windows.
+ if (exitCode == 3 || exitCode == CRASHING_BROWSER_EXITCODE) {
+ return !timedOut;
+ }
+ // If a program receives an uncaught system exception, the program
+ // terminates with the exception code as exit code.
+ // The 0x3FFFFF00 mask here tries to determine if an exception indicates
+ // a crash of the program.
+ // System exception codes can be found in 'winnt.h', for example
+ // "#define STATUS_ACCESS_VIOLATION ((DWORD) 0xC0000005)"
+ return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0));
+ }
+ return !timedOut && ((exitCode < 0));
+ }
+
+ bool get hasTimedOut => timedOut;
+
+ bool didFail(TestCase testCase) {
+ return (exitCode != 0 && !hasCrashed);
+ }
+
+ bool get canRunDependendCommands {
+ // FIXME(kustermann): We may need to change this
+ return !hasTimedOut && exitCode == 0;
+ }
+
+ bool get successful {
+ // FIXME(kustermann): We may need to change this
+ return !hasTimedOut && exitCode == 0;
+ }
+
+ // Reverse result of a negative test.
+ bool hasFailed(TestCase testCase) {
+ return testCase.isNegative ? !didFail(testCase) : didFail(testCase);
+ }
+
+ bool get hasNonUtf8 => exitCode == NON_UTF_FAKE_EXITCODE;
+
+ Expectation _negateOutcomeIfNegativeTest(
+ Expectation outcome, bool isNegative) {
+ if (!isNegative) return outcome;
+ if (outcome == Expectation.ignore) return outcome;
+ if (outcome.canBeOutcomeOf(Expectation.fail)) {
+ return Expectation.pass;
+ }
+ return Expectation.fail;
+ }
+}
+
+class ContentShellCommandOutputImpl extends CommandOutputImpl {
+ // Although tests are reported as passing, content shell sometimes exits with
+ // a nonzero exitcode which makes our dartium builders extremely falky.
+ // See: http://dartbug.com/15139.
+ // TODO(rnystrom): Is this still needed? The underlying bug is closed.
+ static int WHITELISTED_CONTENTSHELL_EXITCODE = -1073740022;
+ static bool isWindows = io.Platform.operatingSystem == 'windows';
+ static bool _failedBecauseOfFlakyInfrastructure(
+ Command command, bool timedOut, List<int> stderrBytes) {
+ // If the browser test failed, it may have been because content shell
+ // and the virtual framebuffer X server didn't hook up, or it crashed with
+ // a core dump. Sometimes content shell crashes after it has set the stdout
+ // to PASS, so we have to do this check first.
+ // Content shell also fails with a broken pipe message: Issue 26739
+ var zygoteCrash =
+ new RegExp(r"ERROR:zygote_linux\.cc\(\d+\)] write: Broken pipe");
+ var stderr = decodeUtf8(stderrBytes);
+ // TODO(7564): See http://dartbug.com/7564
+ // This may not be happening anymore. Test by removing this suppression.
+ if (stderr.contains(MESSAGE_CANNOT_OPEN_DISPLAY) ||
+ stderr.contains(MESSAGE_FAILED_TO_RUN_COMMAND)) {
+ DebugLogger.warning(
+ "Warning: Failure because of missing XDisplay. Test ignored");
+ return true;
+ }
+ // TODO(26739): See http://dartbug.com/26739
+ if (zygoteCrash.hasMatch(stderr)) {
+ DebugLogger.warning("Warning: Failure because of content_shell "
+ "zygote crash. Test ignored");
+ return true;
+ }
+ return false;
+ }
+
+ bool _infraFailure;
+
+ ContentShellCommandOutputImpl(
+ Command command,
+ int exitCode,
+ bool timedOut,
+ List<int> stdout,
+ List<int> stderr,
+ Duration time,
+ bool compilationSkipped)
+ : _infraFailure =
+ _failedBecauseOfFlakyInfrastructure(command, timedOut, stderr),
+ super(command, exitCode, timedOut, stdout, stderr, time,
+ compilationSkipped, 0);
+
+ Expectation result(TestCase testCase) {
+ if (_infraFailure) {
+ return Expectation.ignore;
+ }
+
+ // Handle crashes and timeouts first
+ if (hasCrashed) return Expectation.crash;
+ if (hasTimedOut) return Expectation.timeout;
+ if (hasNonUtf8) return Expectation.nonUtf8Error;
+
+ var outcome = _getOutcome();
+
+ if (testCase.hasRuntimeError) {
+ if (!outcome.canBeOutcomeOf(Expectation.runtimeError)) {
+ return Expectation.missingRuntimeError;
+ }
+ }
+ if (testCase.isNegative) {
+ if (outcome.canBeOutcomeOf(Expectation.fail)) return Expectation.pass;
+ return Expectation.fail;
+ }
+ return outcome;
+ }
+
+ bool get successful => canRunDependendCommands;
+
+ bool get canRunDependendCommands {
+ // We cannot rely on the exit code of content_shell as a method to
+ // determine if we were successful or not.
+ return super.canRunDependendCommands && !didFail(null);
+ }
+
+ bool get hasCrashed {
+ return super.hasCrashed || _rendererCrashed;
+ }
+
+ Expectation _getOutcome() {
+ if (_browserTestFailure) {
+ return Expectation.runtimeError;
+ }
+ return Expectation.pass;
+ }
+
+ bool get _rendererCrashed =>
+ decodeUtf8(super.stdout).contains("#CRASHED - rendere");
+
+ bool get _browserTestFailure {
+ // Browser tests fail unless stdout contains
+ // 'Content-Type: text/plain' followed by 'PASS'.
+ bool hasContentType = false;
+ var stdoutLines = decodeUtf8(super.stdout).split("\n");
+ var containsFail = false;
+ var containsPass = false;
+ for (String line in stdoutLines) {
+ switch (line) {
+ case 'Content-Type: text/plain':
+ hasContentType = true;
+ break;
+ case 'FAIL':
+ if (hasContentType) {
+ containsFail = true;
+ }
+ break;
+ case 'PASS':
+ if (hasContentType) {
+ containsPass = true;
+ }
+ break;
+ }
+ }
+ if (hasContentType) {
+ if (containsFail && containsPass) {
+ DebugLogger.warning("Test had 'FAIL' and 'PASS' in stdout. ($command)");
+ }
+ if (!containsFail && !containsPass) {
+ DebugLogger.warning("Test had neither 'FAIL' nor 'PASS' in stdout. "
+ "($command)");
+ return true;
+ }
+ if (containsFail) {
+ return true;
+ }
+ assert(containsPass);
+ if (exitCode != 0) {
+ var message = "All tests passed, but exitCode != 0. "
+ "Actual exitcode: $exitCode. "
+ "($command)";
+ DebugLogger.warning(message);
+ diagnostics.add(message);
+ }
+ return (!hasCrashed &&
+ exitCode != 0 &&
+ (!isWindows || exitCode != WHITELISTED_CONTENTSHELL_EXITCODE));
+ }
+ DebugLogger.warning("Couldn't find 'Content-Type: text/plain' in output. "
+ "($command).");
+ return true;
+ }
+}
+
+class HTMLBrowserCommandOutputImpl extends ContentShellCommandOutputImpl {
+ HTMLBrowserCommandOutputImpl(
+ Command command,
+ int exitCode,
+ bool timedOut,
+ List<int> stdout,
+ List<int> stderr,
+ Duration time,
+ bool compilationSkipped)
+ : super(command, exitCode, timedOut, stdout, stderr, time,
+ compilationSkipped);
+
+ bool didFail(TestCase testCase) {
+ return _getOutcome() != Expectation.pass;
+ }
+
+ bool get _browserTestFailure {
+ // We should not need to convert back and forward.
+ var output = decodeUtf8(super.stdout);
+ if (output.contains("FAIL")) return true;
+ return !output.contains("PASS");
+ }
+}
+
+class BrowserTestJsonResult {
+ static const ALLOWED_TYPES = const [
+ 'sync_exception',
+ 'window_onerror',
+ 'script_onerror',
+ 'window_compilationerror',
+ 'print',
+ 'message_received',
+ 'dom',
+ 'debug'
+ ];
+
+ final Expectation outcome;
+ final String htmlDom;
+ final List<dynamic> events;
+
+ BrowserTestJsonResult(this.outcome, this.htmlDom, this.events);
+
+ static BrowserTestJsonResult parseFromString(String content) {
+ void validate(String assertion, bool value) {
+ if (!value) {
+ throw "InvalidFormat sent from browser driving page: $assertion:\n\n"
+ "$content";
+ }
+ }
+
+ try {
+ var events = JSON.decode(content);
+ if (events != null) {
+ validate("Message must be a List", events is List);
+
+ var messagesByType = <String, List<String>>{};
+ ALLOWED_TYPES.forEach((type) => messagesByType[type] = <String>[]);
+
+ for (var entry in events) {
+ validate("An entry must be a Map", entry is Map);
+
+ var type = entry['type'];
+ var value = entry['value'] as String;
+ var timestamp = entry['timestamp'];
+
+ validate("'type' of an entry must be a String", type is String);
+ validate("'type' has to be in $ALLOWED_TYPES.",
+ ALLOWED_TYPES.contains(type));
+ validate(
+ "'timestamp' of an entry must be a number", timestamp is num);
+
+ messagesByType[type].add(value);
+ }
+ validate("The message must have exactly one 'dom' entry.",
+ messagesByType['dom'].length == 1);
+
+ var dom = messagesByType['dom'][0];
+ if (dom.endsWith('\n')) {
+ dom = '$dom\n';
+ }
+
+ return new BrowserTestJsonResult(
+ _getOutcome(messagesByType), dom, events as List<dynamic>);
+ }
+ } catch (error) {
+ // If something goes wrong, we know the content was not in the correct
+ // JSON format. So we can't parse it.
+ // The caller is responsible for falling back to the old way of
+ // determining if a test failed.
+ }
+
+ return null;
+ }
+
+ static Expectation _getOutcome(Map<String, List<String>> messagesByType) {
+ occured(String type) => messagesByType[type].length > 0;
+ searchForMsg(List<String> types, String message) {
+ return types.any((type) => messagesByType[type].contains(message));
+ }
+
+ // FIXME(kustermann,ricow): I think this functionality doesn't work in
+ // test_controller.js: So far I haven't seen anything being reported on
+ // "window.compilationerror"
+ if (occured('window_compilationerror')) {
+ return Expectation.compileTimeError;
+ }
+
+ if (occured('sync_exception') ||
+ occured('window_onerror') ||
+ occured('script_onerror')) {
+ return Expectation.runtimeError;
+ }
+
+ if (messagesByType['dom'][0].contains('FAIL')) {
+ return Expectation.runtimeError;
+ }
+
+ // We search for these messages in 'print' and 'message_received' because
+ // the unittest implementation posts these messages using
+ // "window.postMessage()" instead of the normal "print()" them.
+
+ var isAsyncTest = searchForMsg(
+ ['print', 'message_received'], 'unittest-suite-wait-for-done');
+ var isAsyncSuccess =
+ searchForMsg(['print', 'message_received'], 'unittest-suite-success') ||
+ searchForMsg(['print', 'message_received'], 'unittest-suite-done');
+
+ if (isAsyncTest) {
+ if (isAsyncSuccess) {
+ return Expectation.pass;
+ }
+ return Expectation.runtimeError;
+ }
+
+ var mainStarted =
+ searchForMsg(['print', 'message_received'], 'dart-calling-main');
+ var mainDone =
+ searchForMsg(['print', 'message_received'], 'dart-main-done');
+
+ if (mainStarted && mainDone) {
+ return Expectation.pass;
+ }
+ return Expectation.fail;
+ }
+}
+
+class BrowserCommandOutputImpl extends CommandOutputImpl
+ with UnittestSuiteMessagesMixin {
+ BrowserTestOutput _result;
+ Expectation _rawOutcome;
+
+ factory BrowserCommandOutputImpl(Command command, BrowserTestOutput result) {
+ String indent(String string, int numSpaces) {
+ var spaces = new List.filled(numSpaces, ' ').join('');
+ return string
+ .replaceAll('\r\n', '\n')
+ .split('\n')
+ .map((line) => "$spaces$line")
+ .join('\n');
+ }
+
+ String stdout = "";
+ String stderr = "";
+ Expectation outcome;
+
+ var parsedResult =
+ BrowserTestJsonResult.parseFromString(result.lastKnownMessage);
+ if (parsedResult != null) {
+ outcome = parsedResult.outcome;
+ } else {
+ // Old way of determining whether a test failed or passed.
+ if (result.lastKnownMessage.contains("FAIL")) {
+ outcome = Expectation.runtimeError;
+ } else if (result.lastKnownMessage.contains("PASS")) {
+ outcome = Expectation.pass;
+ } else {
+ outcome = Expectation.runtimeError;
+ }
+ }
+
+ if (result.didTimeout) {
+ if (result.delayUntilTestStarted != null) {
+ stderr = "This test timed out. The delay until the test actually "
+ "started was: ${result.delayUntilTestStarted}.";
+ } else {
+ stderr = "This test has not notified test.py that it started running.";
+ }
+ }
+
+ if (parsedResult != null) {
+ stdout = "events:\n${indent(prettifyJson(parsedResult.events), 2)}\n\n";
+ } else {
+ stdout = "message:\n${indent(result.lastKnownMessage, 2)}\n\n";
+ }
+
+ stderr = '$stderr\n\n'
+ 'BrowserOutput while running the test (* EXPERIMENTAL *):\n'
+ 'BrowserOutput.stdout:\n'
+ '${indent(result.browserOutput.stdout.toString(), 2)}\n'
+ 'BrowserOutput.stderr:\n'
+ '${indent(result.browserOutput.stderr.toString(), 2)}\n'
+ '\n';
+ return new BrowserCommandOutputImpl._internal(
+ command, result, outcome, encodeUtf8(stdout), encodeUtf8(stderr));
+ }
+
+ BrowserCommandOutputImpl._internal(Command command, BrowserTestOutput result,
+ this._rawOutcome, List<int> stdout, List<int> stderr)
+ : super(command, 0, result.didTimeout, stdout, stderr, result.duration,
+ false, 0) {
+ _result = result;
+ }
+
+ Expectation result(TestCase testCase) {
+ // Handle timeouts first
+ if (_result.didTimeout) return Expectation.timeout;
+ if (_result.didTimeout) {
+ if (testCase.configuration.runtime == Runtime.ie11) {
+ // TODO(28955): See http://dartbug.com/28955
+ DebugLogger.warning("Timeout of ie11 on test ${testCase.displayName}");
+ return Expectation.ignore;
+ }
+ return Expectation.timeout;
+ }
+
+ if (hasNonUtf8) return Expectation.nonUtf8Error;
+
+ // Multitests are handled specially
+ if (testCase.hasRuntimeError) {
+ if (_rawOutcome == Expectation.runtimeError) return Expectation.pass;
+ return Expectation.missingRuntimeError;
+ }
+
+ return _negateOutcomeIfNegativeTest(_rawOutcome, testCase.isNegative);
+ }
+}
+
+class AnalysisCommandOutputImpl extends CommandOutputImpl {
+ // An error line has 8 fields that look like:
+ // ERROR|COMPILER|MISSING_SOURCE|file:/tmp/t.dart|15|1|24|Missing source.
+ final int ERROR_LEVEL = 0;
+ final int ERROR_TYPE = 1;
+ final int FILENAME = 3;
+ final int FORMATTED_ERROR = 7;
+
+ AnalysisCommandOutputImpl(
+ Command command,
+ int exitCode,
+ bool timedOut,
+ List<int> stdout,
+ List<int> stderr,
+ Duration time,
+ bool compilationSkipped)
+ : super(command, exitCode, timedOut, stdout, stderr, time,
+ compilationSkipped, 0);
+
+ Expectation result(TestCase testCase) {
+ // TODO(kustermann): If we run the analyzer not in batch mode, make sure
+ // that command.exitCodes matches 2 (errors), 1 (warnings), 0 (no warnings,
+ // no errors)
+
+ // Handle crashes and timeouts first
+ if (hasCrashed) return Expectation.crash;
+ if (hasTimedOut) return Expectation.timeout;
+ if (hasNonUtf8) return Expectation.nonUtf8Error;
+
+ // Get the errors/warnings from the analyzer
+ List<String> errors = [];
+ List<String> warnings = [];
+ parseAnalyzerOutput(errors, warnings);
+
+ // Handle errors / missing errors
+ if (testCase.expectCompileError) {
+ if (errors.length > 0) {
+ return Expectation.pass;
+ }
+ return Expectation.missingCompileTimeError;
+ }
+ if (errors.length > 0) {
+ return Expectation.compileTimeError;
+ }
+
+ // Handle static warnings / missing static warnings
+ if (testCase.hasStaticWarning) {
+ if (warnings.length > 0) {
+ return Expectation.pass;
+ }
+ return Expectation.missingStaticWarning;
+ }
+ if (warnings.length > 0) {
+ return Expectation.staticWarning;
+ }
+
+ assert(errors.length == 0 && warnings.length == 0);
+ assert(!testCase.hasCompileError && !testCase.hasStaticWarning);
+ return Expectation.pass;
+ }
+
+ void parseAnalyzerOutput(List<String> outErrors, List<String> outWarnings) {
+ // Parse a line delimited by the | character using \ as an escape character
+ // like: FOO|BAR|FOO\|BAR|FOO\\BAZ as 4 fields: FOO BAR FOO|BAR FOO\BAZ
+ List<String> splitMachineError(String line) {
+ StringBuffer field = new StringBuffer();
+ List<String> result = [];
+ bool escaped = false;
+ for (var i = 0; i < line.length; i++) {
+ var c = line[i];
+ if (!escaped && c == '\\') {
+ escaped = true;
+ continue;
+ }
+ escaped = false;
+ if (c == '|') {
+ result.add(field.toString());
+ field = new StringBuffer();
+ continue;
+ }
+ field.write(c);
+ }
+ result.add(field.toString());
+ return result;
+ }
+
+ for (String line in decodeUtf8(super.stderr).split("\n")) {
+ if (line.length == 0) continue;
+ List<String> fields = splitMachineError(line);
+ // We only consider errors/warnings for files of interest.
+ if (fields.length > FORMATTED_ERROR) {
+ if (fields[ERROR_LEVEL] == 'ERROR') {
+ outErrors.add(fields[FORMATTED_ERROR]);
+ } else if (fields[ERROR_LEVEL] == 'WARNING') {
+ outWarnings.add(fields[FORMATTED_ERROR]);
+ }
+ // OK to Skip error output that doesn't match the machine format
+ }
+ }
+ }
+}
+
+class VmCommandOutputImpl extends CommandOutputImpl
+ with UnittestSuiteMessagesMixin {
+ static const DART_VM_EXITCODE_DFE_ERROR = 252;
+ static const DART_VM_EXITCODE_COMPILE_TIME_ERROR = 254;
+ static const DART_VM_EXITCODE_UNCAUGHT_EXCEPTION = 255;
+
+ VmCommandOutputImpl(Command command, int exitCode, bool timedOut,
+ List<int> stdout, List<int> stderr, Duration time, int pid)
+ : super(command, exitCode, timedOut, stdout, stderr, time, false, pid);
+
+ Expectation result(TestCase testCase) {
+ // Handle crashes and timeouts first
+ if (exitCode == DART_VM_EXITCODE_DFE_ERROR) return Expectation.dartkCrash;
+ if (hasCrashed) return Expectation.crash;
+ if (hasTimedOut) return Expectation.timeout;
+ if (hasNonUtf8) return Expectation.nonUtf8Error;
+
+ // Multitests are handled specially
+ if (testCase.expectCompileError) {
+ if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) {
+ return Expectation.pass;
+ }
+ return Expectation.missingCompileTimeError;
+ }
+ if (testCase.hasRuntimeError) {
+ // TODO(kustermann): Do we consider a "runtimeError" only an uncaught
+ // exception or does any nonzero exit code fullfil this requirement?
+ if (exitCode != 0) {
+ return Expectation.pass;
+ }
+ return Expectation.missingRuntimeError;
+ }
+
+ // The actual outcome depends on the exitCode
+ Expectation outcome;
+ if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) {
+ outcome = Expectation.compileTimeError;
+ } else if (exitCode == DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) {
+ outcome = Expectation.runtimeError;
+ } else if (exitCode != 0) {
+ // This is a general fail, in case we get an unknown nonzero exitcode.
+ outcome = Expectation.fail;
+ } else {
+ outcome = Expectation.pass;
+ }
+ outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout));
+ return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
+ }
+}
+
+class CompilationCommandOutputImpl extends CommandOutputImpl {
+ static const DART2JS_EXITCODE_CRASH = 253;
+
+ CompilationCommandOutputImpl(
+ Command command,
+ int exitCode,
+ bool timedOut,
+ List<int> stdout,
+ List<int> stderr,
+ Duration time,
+ bool compilationSkipped)
+ : super(command, exitCode, timedOut, stdout, stderr, time,
+ compilationSkipped, 0);
+
+ Expectation result(TestCase testCase) {
+ // Handle general crash/timeout detection.
+ if (hasCrashed) return Expectation.crash;
+ if (hasTimedOut) {
+ bool isWindows = io.Platform.operatingSystem == 'windows';
+ bool isBrowserTestCase =
+ testCase.commands.any((command) => command is BrowserTestCommand);
+ // TODO(26060) Dart2js batch mode hangs on Windows under heavy load.
+ return (isWindows && isBrowserTestCase)
+ ? Expectation.ignore
+ : Expectation.timeout;
+ }
+ if (hasNonUtf8) return Expectation.nonUtf8Error;
+
+ // Handle dart2js specific crash detection
+ if (exitCode == DART2JS_EXITCODE_CRASH ||
+ exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_COMPILE_TIME_ERROR ||
+ exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) {
+ return Expectation.crash;
+ }
+
+ // Multitests are handled specially
+ if (testCase.expectCompileError) {
+ // Nonzero exit code of the compiler means compilation failed
+ // TODO(kustermann): Do we have a special exit code in that case???
+ if (exitCode != 0) {
+ return Expectation.pass;
+ }
+ return Expectation.missingCompileTimeError;
+ }
+
+ // TODO(kustermann): This is a hack, remove it
+ if (testCase.hasRuntimeError && testCase.commands.length > 1) {
+ // We expected to run the test, but we got an compile time error.
+ // If the compilation succeeded, we wouldn't be in here!
+ assert(exitCode != 0);
+ return Expectation.compileTimeError;
+ }
+
+ Expectation outcome =
+ exitCode == 0 ? Expectation.pass : Expectation.compileTimeError;
+ return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
+ }
+}
+
+class KernelCompilationCommandOutputImpl extends CompilationCommandOutputImpl {
+ KernelCompilationCommandOutputImpl(
+ Command command,
+ int exitCode,
+ bool timedOut,
+ List<int> stdout,
+ List<int> stderr,
+ Duration time,
+ bool compilationSkipped)
+ : super(command, exitCode, timedOut, stdout, stderr, time,
+ compilationSkipped);
+
+ bool get canRunDependendCommands {
+ // See [BatchRunnerProcess]: 0 means success, 1 means compile-time error.
+ // TODO(asgerf): When the frontend supports it, continue running even if
+ // there were compile-time errors. See kernel_sdk issue #18.
+ return !hasCrashed && !timedOut && exitCode == 0;
+ }
+
+ Expectation result(TestCase testCase) {
+ Expectation result = super.result(testCase);
+ if (result.canBeOutcomeOf(Expectation.crash)) {
+ return Expectation.dartkCrash;
+ } else if (result.canBeOutcomeOf(Expectation.timeout)) {
+ return Expectation.dartkTimeout;
+ } else if (result.canBeOutcomeOf(Expectation.compileTimeError)) {
+ return Expectation.dartkCompileTimeError;
+ }
+ return result;
+ }
+
+ // If the compiler was able to produce a Kernel IR file we want to run the
+ // result on the Dart VM. We therefore mark the [KernelCompilationCommand] as
+ // successful.
+ // => This ensures we test that the DartVM produces correct CompileTime errors
+ // as it is supposed to for our test suites.
+ bool get successful => canRunDependendCommands;
+}
+
+class JsCommandlineOutputImpl extends CommandOutputImpl
+ with UnittestSuiteMessagesMixin {
+ JsCommandlineOutputImpl(Command command, int exitCode, bool timedOut,
+ List<int> stdout, List<int> stderr, Duration time)
+ : super(command, exitCode, timedOut, stdout, stderr, time, false, 0);
+
+ Expectation result(TestCase testCase) {
+ // Handle crashes and timeouts first
+ if (hasCrashed) return Expectation.crash;
+ if (hasTimedOut) return Expectation.timeout;
+ if (hasNonUtf8) return Expectation.nonUtf8Error;
+
+ if (testCase.hasRuntimeError) {
+ if (exitCode != 0) return Expectation.pass;
+ return Expectation.missingRuntimeError;
+ }
+
+ var outcome = exitCode == 0 ? Expectation.pass : Expectation.runtimeError;
+ outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout));
+ return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative);
+ }
+}
+
+class PubCommandOutputImpl extends CommandOutputImpl {
+ PubCommandOutputImpl(PubCommand command, int exitCode, bool timedOut,
+ List<int> stdout, List<int> stderr, Duration time)
+ : super(command, exitCode, timedOut, stdout, stderr, time, false, 0);
+
+ Expectation result(TestCase testCase) {
+ // Handle crashes and timeouts first
+ if (hasCrashed) return Expectation.crash;
+ if (hasTimedOut) return Expectation.timeout;
+ if (hasNonUtf8) return Expectation.nonUtf8Error;
+
+ if (exitCode == 0) {
+ return Expectation.pass;
+ } else if ((command as PubCommand).command == 'get') {
+ return Expectation.pubGetError;
+ } else {
+ return Expectation.fail;
+ }
+ }
+}
+
+class ScriptCommandOutputImpl extends CommandOutputImpl {
+ final Expectation _result;
+
+ ScriptCommandOutputImpl(ScriptCommand command, this._result,
+ String scriptExecutionInformation, Duration time)
+ : super(command, 0, false, [], [], time, false, 0) {
+ var lines = scriptExecutionInformation.split("\n");
+ diagnostics.addAll(lines);
+ }
+
+ Expectation result(TestCase testCase) => _result;
+
+ bool get canRunDependendCommands => _result == Expectation.pass;
+
+ bool get successful => _result == Expectation.pass;
+}
+
+CommandOutput createCommandOutput(Command command, int exitCode, bool timedOut,
+ List<int> stdout, List<int> stderr, Duration time, bool compilationSkipped,
+ [int pid = 0]) {
+ if (command is ContentShellCommand) {
+ return new ContentShellCommandOutputImpl(
+ command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
+ } else if (command is BrowserTestCommand) {
+ return new HTMLBrowserCommandOutputImpl(
+ command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
+ } else if (command is AnalysisCommand) {
+ return new AnalysisCommandOutputImpl(
+ command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
+ } else if (command is VmCommand) {
+ return new VmCommandOutputImpl(
+ command, exitCode, timedOut, stdout, stderr, time, pid);
+ } else if (command is KernelCompilationCommand) {
+ return new KernelCompilationCommandOutputImpl(
+ command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
+ } else if (command is AdbPrecompilationCommand) {
+ return new VmCommandOutputImpl(
+ command, exitCode, timedOut, stdout, stderr, time, pid);
+ } else if (command is CompilationCommand) {
+ if (command.displayName == 'precompiler' ||
+ command.displayName == 'app_jit') {
+ return new VmCommandOutputImpl(
+ command, exitCode, timedOut, stdout, stderr, time, pid);
+ }
+ return new CompilationCommandOutputImpl(
+ command, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
+ } else if (command is JSCommandlineCommand) {
+ return new JsCommandlineOutputImpl(
+ command, exitCode, timedOut, stdout, stderr, time);
+ } else if (command is PubCommand) {
+ return new PubCommandOutputImpl(
+ command, exitCode, timedOut, stdout, stderr, time);
+ }
+
+ return new CommandOutputImpl(command, exitCode, timedOut, stdout, stderr,
+ time, compilationSkipped, pid);
+}
+
+class UnittestSuiteMessagesMixin {
+ bool _isAsyncTest(String testOutput) {
+ return testOutput.contains("unittest-suite-wait-for-done");
+ }
+
+ bool _isAsyncTestSuccessful(String testOutput) {
+ return testOutput.contains("unittest-suite-success");
+ }
+
+ Expectation _negateOutcomeIfIncompleteAsyncTest(
+ Expectation outcome, String testOutput) {
+ // If this is an asynchronous test and the asynchronous operation didn't
+ // complete successfully, it's outcome is Expectation.FAIL.
+ // TODO: maybe we should introduce a AsyncIncomplete marker or so
+ if (outcome == Expectation.pass) {
+ if (_isAsyncTest(testOutput) && !_isAsyncTestSuccessful(testOutput)) {
+ return Expectation.fail;
+ }
+ }
+ return outcome;
+ }
+}
« no previous file with comments | « tools/testing/dart/command.dart ('k') | tools/testing/dart/compiler_configuration.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698