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; |
+ } |
+} |