OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 import 'dart:convert'; | |
6 // We need to use the 'io' prefix here, otherwise io.exitCode will shadow | |
7 // CommandOutput.exitCode in subclasses of CommandOutput. | |
8 import 'dart:io' as io; | |
9 | |
10 import 'browser_controller.dart'; | |
11 import 'command.dart'; | |
12 import 'configuration.dart'; | |
13 import 'expectation.dart'; | |
14 import 'test_runner.dart'; | |
15 import 'utils.dart'; | |
16 | |
17 /** | |
18 * CommandOutput records the output of a completed command: the process's exit | |
19 * code, the standard output and standard error, whether the process timed out, | |
20 * and the time the process took to run. It does not contain a pointer to the | |
21 * [TestCase] this is the output of, so some functions require the test case | |
22 * to be passed as an argument. | |
23 */ | |
24 abstract class CommandOutput { | |
25 Command get command; | |
26 | |
27 Expectation result(TestCase testCase); | |
28 | |
29 bool get hasCrashed; | |
30 | |
31 bool get hasTimedOut; | |
32 | |
33 bool didFail(TestCase testCase); | |
34 | |
35 bool hasFailed(TestCase testCase); | |
36 | |
37 bool get canRunDependendCommands; | |
38 | |
39 bool get successful; // otherwise we might to retry running | |
40 | |
41 Duration get time; | |
42 | |
43 int get exitCode; | |
44 | |
45 int get pid; | |
46 | |
47 List<int> get stdout; | |
48 | |
49 List<int> get stderr; | |
50 | |
51 List<String> get diagnostics; | |
52 | |
53 bool get compilationSkipped; | |
54 } | |
55 | |
56 class CommandOutputImpl extends UniqueObject implements CommandOutput { | |
57 Command command; | |
58 int exitCode; | |
59 | |
60 bool timedOut; | |
61 List<int> stdout; | |
62 List<int> stderr; | |
63 Duration time; | |
64 List<String> diagnostics; | |
65 bool compilationSkipped; | |
66 int pid; | |
67 | |
68 /** | |
69 * A flag to indicate we have already printed a warning about ignoring the VM | |
70 * crash, to limit the amount of output produced per test. | |
71 */ | |
72 bool alreadyPrintedWarning = false; | |
73 | |
74 CommandOutputImpl( | |
75 Command this.command, | |
76 int this.exitCode, | |
77 bool this.timedOut, | |
78 List<int> this.stdout, | |
79 List<int> this.stderr, | |
80 Duration this.time, | |
81 bool this.compilationSkipped, | |
82 int this.pid) { | |
83 diagnostics = []; | |
84 } | |
85 | |
86 Expectation result(TestCase testCase) { | |
87 if (hasCrashed) return Expectation.crash; | |
88 if (hasTimedOut) return Expectation.timeout; | |
89 if (hasFailed(testCase)) return Expectation.fail; | |
90 if (hasNonUtf8) return Expectation.nonUtf8Error; | |
91 return Expectation.pass; | |
92 } | |
93 | |
94 bool get hasCrashed { | |
95 // dart2js exits with code 253 in case of unhandled exceptions. | |
96 // The dart binary exits with code 253 in case of an API error such | |
97 // as an invalid snapshot file. | |
98 // In either case an exit code of 253 is considered a crash. | |
99 if (exitCode == 253) return true; | |
100 if (io.Platform.operatingSystem == 'windows') { | |
101 // The VM uses std::abort to terminate on asserts. | |
102 // std::abort terminates with exit code 3 on Windows. | |
103 if (exitCode == 3 || exitCode == CRASHING_BROWSER_EXITCODE) { | |
104 return !timedOut; | |
105 } | |
106 // If a program receives an uncaught system exception, the program | |
107 // terminates with the exception code as exit code. | |
108 // The 0x3FFFFF00 mask here tries to determine if an exception indicates | |
109 // a crash of the program. | |
110 // System exception codes can be found in 'winnt.h', for example | |
111 // "#define STATUS_ACCESS_VIOLATION ((DWORD) 0xC0000005)" | |
112 return (!timedOut && (exitCode < 0) && ((0x3FFFFF00 & exitCode) == 0)); | |
113 } | |
114 return !timedOut && ((exitCode < 0)); | |
115 } | |
116 | |
117 bool get hasTimedOut => timedOut; | |
118 | |
119 bool didFail(TestCase testCase) { | |
120 return (exitCode != 0 && !hasCrashed); | |
121 } | |
122 | |
123 bool get canRunDependendCommands { | |
124 // FIXME(kustermann): We may need to change this | |
125 return !hasTimedOut && exitCode == 0; | |
126 } | |
127 | |
128 bool get successful { | |
129 // FIXME(kustermann): We may need to change this | |
130 return !hasTimedOut && exitCode == 0; | |
131 } | |
132 | |
133 // Reverse result of a negative test. | |
134 bool hasFailed(TestCase testCase) { | |
135 return testCase.isNegative ? !didFail(testCase) : didFail(testCase); | |
136 } | |
137 | |
138 bool get hasNonUtf8 => exitCode == NON_UTF_FAKE_EXITCODE; | |
139 | |
140 Expectation _negateOutcomeIfNegativeTest( | |
141 Expectation outcome, bool isNegative) { | |
142 if (!isNegative) return outcome; | |
143 if (outcome == Expectation.ignore) return outcome; | |
144 if (outcome.canBeOutcomeOf(Expectation.fail)) { | |
145 return Expectation.pass; | |
146 } | |
147 return Expectation.fail; | |
148 } | |
149 } | |
150 | |
151 class BrowserCommandOutputImpl extends CommandOutputImpl { | |
Bill Hesse
2017/06/15 12:08:40
I just renamed BrowserCommandOutputImpl to Content
Bob Nystrom
2017/06/15 21:19:29
Yup, I saw that when I merged. Renamed it here too
| |
152 // Although tests are reported as passing, content shell sometimes exits with | |
153 // a nonzero exitcode which makes our dartium builders extremely falky. | |
154 // See: http://dartbug.com/15139. | |
155 // TODO(rnystrom): Is this still needed? The underlying bug is closed. | |
156 static int WHITELISTED_CONTENTSHELL_EXITCODE = -1073740022; | |
157 static bool isWindows = io.Platform.operatingSystem == 'windows'; | |
158 static bool _failedBecauseOfFlakyInfrastructure( | |
159 Command command, bool timedOut, List<int> stderrBytes) { | |
160 // If the browser test failed, it may have been because content shell | |
161 // and the virtual framebuffer X server didn't hook up, or it crashed with | |
162 // a core dump. Sometimes content shell crashes after it has set the stdout | |
163 // to PASS, so we have to do this check first. | |
164 // Content shell also fails with a broken pipe message: Issue 26739 | |
165 var zygoteCrash = | |
166 new RegExp(r"ERROR:zygote_linux\.cc\(\d+\)] write: Broken pipe"); | |
167 var stderr = decodeUtf8(stderrBytes); | |
168 // TODO(7564): See http://dartbug.com/7564 | |
169 // This may not be happening anymore. Test by removing this suppression. | |
170 if (stderr.contains(MESSAGE_CANNOT_OPEN_DISPLAY) || | |
171 stderr.contains(MESSAGE_FAILED_TO_RUN_COMMAND)) { | |
172 DebugLogger.warning( | |
173 "Warning: Failure because of missing XDisplay. Test ignored"); | |
174 return true; | |
175 } | |
176 // TODO(26739): See http://dartbug.com/26739 | |
177 if (zygoteCrash.hasMatch(stderr)) { | |
178 DebugLogger.warning("Warning: Failure because of content_shell " | |
179 "zygote crash. Test ignored"); | |
180 return true; | |
181 } | |
182 // TODO(28955): See http://dartbug.com/28955 | |
183 if (timedOut && | |
184 command is BrowserTestCommand && | |
185 command.browser == Runtime.ie11) { | |
186 DebugLogger.warning("Timeout of ie11 on test page ${command.url}"); | |
187 return true; | |
188 } | |
189 return false; | |
190 } | |
191 | |
192 bool _infraFailure; | |
193 | |
194 BrowserCommandOutputImpl( | |
195 Command command, | |
196 int exitCode, | |
197 bool timedOut, | |
198 List<int> stdout, | |
199 List<int> stderr, | |
200 Duration time, | |
201 bool compilationSkipped) | |
202 : _infraFailure = | |
203 _failedBecauseOfFlakyInfrastructure(command, timedOut, stderr), | |
204 super(command, exitCode, timedOut, stdout, stderr, time, | |
205 compilationSkipped, 0); | |
206 | |
207 Expectation result(TestCase testCase) { | |
208 if (_infraFailure) { | |
209 return Expectation.ignore; | |
210 } | |
211 | |
212 // Handle crashes and timeouts first | |
213 if (hasCrashed) return Expectation.crash; | |
214 if (hasTimedOut) return Expectation.timeout; | |
215 if (hasNonUtf8) return Expectation.nonUtf8Error; | |
216 | |
217 var outcome = _getOutcome(); | |
218 | |
219 if (testCase.hasRuntimeError) { | |
220 if (!outcome.canBeOutcomeOf(Expectation.runtimeError)) { | |
221 return Expectation.missingRuntimeError; | |
222 } | |
223 } | |
224 if (testCase.isNegative) { | |
225 if (outcome.canBeOutcomeOf(Expectation.fail)) return Expectation.pass; | |
226 return Expectation.fail; | |
227 } | |
228 return outcome; | |
229 } | |
230 | |
231 bool get successful => canRunDependendCommands; | |
232 | |
233 bool get canRunDependendCommands { | |
234 // We cannot rely on the exit code of content_shell as a method to | |
235 // determine if we were successful or not. | |
236 return super.canRunDependendCommands && !didFail(null); | |
237 } | |
238 | |
239 bool get hasCrashed { | |
240 return super.hasCrashed || _rendererCrashed; | |
241 } | |
242 | |
243 Expectation _getOutcome() { | |
244 if (_browserTestFailure) { | |
245 return Expectation.runtimeError; | |
246 } | |
247 return Expectation.pass; | |
248 } | |
249 | |
250 bool get _rendererCrashed => | |
251 decodeUtf8(super.stdout).contains("#CRASHED - rendere"); | |
252 | |
253 bool get _browserTestFailure { | |
254 // Browser tests fail unless stdout contains | |
255 // 'Content-Type: text/plain' followed by 'PASS'. | |
256 bool hasContentType = false; | |
257 var stdoutLines = decodeUtf8(super.stdout).split("\n"); | |
258 var containsFail = false; | |
259 var containsPass = false; | |
260 for (String line in stdoutLines) { | |
261 switch (line) { | |
262 case 'Content-Type: text/plain': | |
263 hasContentType = true; | |
264 break; | |
265 case 'FAIL': | |
266 if (hasContentType) { | |
267 containsFail = true; | |
268 } | |
269 break; | |
270 case 'PASS': | |
271 if (hasContentType) { | |
272 containsPass = true; | |
273 } | |
274 break; | |
275 } | |
276 } | |
277 if (hasContentType) { | |
278 if (containsFail && containsPass) { | |
279 DebugLogger.warning("Test had 'FAIL' and 'PASS' in stdout. ($command)"); | |
280 } | |
281 if (!containsFail && !containsPass) { | |
282 DebugLogger.warning("Test had neither 'FAIL' nor 'PASS' in stdout. " | |
283 "($command)"); | |
284 return true; | |
285 } | |
286 if (containsFail) { | |
287 return true; | |
288 } | |
289 assert(containsPass); | |
290 if (exitCode != 0) { | |
291 var message = "All tests passed, but exitCode != 0. " | |
292 "Actual exitcode: $exitCode. " | |
293 "($command)"; | |
294 DebugLogger.warning(message); | |
295 diagnostics.add(message); | |
296 } | |
297 return (!hasCrashed && | |
298 exitCode != 0 && | |
299 (!isWindows || exitCode != WHITELISTED_CONTENTSHELL_EXITCODE)); | |
300 } | |
301 DebugLogger.warning("Couldn't find 'Content-Type: text/plain' in output. " | |
302 "($command)."); | |
303 return true; | |
304 } | |
305 } | |
306 | |
307 class HTMLBrowserCommandOutputImpl extends BrowserCommandOutputImpl { | |
Bill Hesse
2017/06/15 12:08:40
I think this class may not be used any more. Can
Bob Nystrom
2017/06/15 21:19:29
It doesn't seem to be used, but the corresponding
| |
308 HTMLBrowserCommandOutputImpl( | |
309 Command command, | |
310 int exitCode, | |
311 bool timedOut, | |
312 List<int> stdout, | |
313 List<int> stderr, | |
314 Duration time, | |
315 bool compilationSkipped) | |
316 : super(command, exitCode, timedOut, stdout, stderr, time, | |
317 compilationSkipped); | |
318 | |
319 bool didFail(TestCase testCase) { | |
320 return _getOutcome() != Expectation.pass; | |
321 } | |
322 | |
323 bool get _browserTestFailure { | |
324 // We should not need to convert back and forward. | |
325 var output = decodeUtf8(super.stdout); | |
326 if (output.contains("FAIL")) return true; | |
327 return !output.contains("PASS"); | |
328 } | |
329 } | |
330 | |
331 class BrowserTestJsonResult { | |
332 static const ALLOWED_TYPES = const [ | |
333 'sync_exception', | |
334 'window_onerror', | |
335 'script_onerror', | |
336 'window_compilationerror', | |
337 'print', | |
338 'message_received', | |
339 'dom', | |
340 'debug' | |
341 ]; | |
342 | |
343 final Expectation outcome; | |
344 final String htmlDom; | |
345 final List<dynamic> events; | |
346 | |
347 BrowserTestJsonResult(this.outcome, this.htmlDom, this.events); | |
348 | |
349 static BrowserTestJsonResult parseFromString(String content) { | |
350 void validate(String assertion, bool value) { | |
351 if (!value) { | |
352 throw "InvalidFormat sent from browser driving page: $assertion:\n\n" | |
353 "$content"; | |
354 } | |
355 } | |
356 | |
357 try { | |
358 var events = JSON.decode(content); | |
359 if (events != null) { | |
360 validate("Message must be a List", events is List); | |
361 | |
362 var messagesByType = <String, List<String>>{}; | |
363 ALLOWED_TYPES.forEach((type) => messagesByType[type] = <String>[]); | |
364 | |
365 for (var entry in events) { | |
366 validate("An entry must be a Map", entry is Map); | |
367 | |
368 var type = entry['type']; | |
369 var value = entry['value'] as String; | |
370 var timestamp = entry['timestamp']; | |
371 | |
372 validate("'type' of an entry must be a String", type is String); | |
373 validate("'type' has to be in $ALLOWED_TYPES.", | |
374 ALLOWED_TYPES.contains(type)); | |
375 validate( | |
376 "'timestamp' of an entry must be a number", timestamp is num); | |
377 | |
378 messagesByType[type].add(value); | |
379 } | |
380 validate("The message must have exactly one 'dom' entry.", | |
381 messagesByType['dom'].length == 1); | |
382 | |
383 var dom = messagesByType['dom'][0]; | |
384 if (dom.endsWith('\n')) { | |
385 dom = '$dom\n'; | |
386 } | |
387 | |
388 return new BrowserTestJsonResult( | |
389 _getOutcome(messagesByType), dom, events as List<dynamic>); | |
390 } | |
391 } catch (error) { | |
392 // If something goes wrong, we know the content was not in the correct | |
393 // JSON format. So we can't parse it. | |
394 // The caller is responsible for falling back to the old way of | |
395 // determining if a test failed. | |
396 } | |
397 | |
398 return null; | |
399 } | |
400 | |
401 static Expectation _getOutcome(Map<String, List<String>> messagesByType) { | |
402 occured(String type) => messagesByType[type].length > 0; | |
403 searchForMsg(List<String> types, String message) { | |
404 return types.any((type) => messagesByType[type].contains(message)); | |
405 } | |
406 | |
407 // FIXME(kustermann,ricow): I think this functionality doesn't work in | |
408 // test_controller.js: So far I haven't seen anything being reported on | |
409 // "window.compilationerror" | |
410 if (occured('window_compilationerror')) { | |
411 return Expectation.compileTimeError; | |
412 } | |
413 | |
414 if (occured('sync_exception') || | |
415 occured('window_onerror') || | |
416 occured('script_onerror')) { | |
417 return Expectation.runtimeError; | |
418 } | |
419 | |
420 if (messagesByType['dom'][0].contains('FAIL')) { | |
421 return Expectation.runtimeError; | |
422 } | |
423 | |
424 // We search for these messages in 'print' and 'message_received' because | |
425 // the unittest implementation posts these messages using | |
426 // "window.postMessage()" instead of the normal "print()" them. | |
427 | |
428 var isAsyncTest = searchForMsg( | |
429 ['print', 'message_received'], 'unittest-suite-wait-for-done'); | |
430 var isAsyncSuccess = | |
431 searchForMsg(['print', 'message_received'], 'unittest-suite-success') || | |
432 searchForMsg(['print', 'message_received'], 'unittest-suite-done'); | |
433 | |
434 if (isAsyncTest) { | |
435 if (isAsyncSuccess) { | |
436 return Expectation.pass; | |
437 } | |
438 return Expectation.runtimeError; | |
439 } | |
440 | |
441 var mainStarted = | |
442 searchForMsg(['print', 'message_received'], 'dart-calling-main'); | |
443 var mainDone = | |
444 searchForMsg(['print', 'message_received'], 'dart-main-done'); | |
445 | |
446 if (mainStarted && mainDone) { | |
447 return Expectation.pass; | |
448 } | |
449 return Expectation.fail; | |
450 } | |
451 } | |
452 | |
453 class BrowserControllerTestOutcome extends CommandOutputImpl | |
Bill Hesse
2017/06/15 12:08:40
This really should be named like the others, as Br
Bob Nystrom
2017/06/15 21:19:29
Done.
| |
454 with UnittestSuiteMessagesMixin { | |
455 BrowserTestOutput _result; | |
456 Expectation _rawOutcome; | |
457 | |
458 factory BrowserControllerTestOutcome( | |
459 Command command, BrowserTestOutput result) { | |
460 String indent(String string, int numSpaces) { | |
461 var spaces = new List.filled(numSpaces, ' ').join(''); | |
462 return string | |
463 .replaceAll('\r\n', '\n') | |
464 .split('\n') | |
465 .map((line) => "$spaces$line") | |
466 .join('\n'); | |
467 } | |
468 | |
469 String stdout = ""; | |
470 String stderr = ""; | |
471 Expectation outcome; | |
472 | |
473 var parsedResult = | |
474 BrowserTestJsonResult.parseFromString(result.lastKnownMessage); | |
475 if (parsedResult != null) { | |
476 outcome = parsedResult.outcome; | |
477 } else { | |
478 // Old way of determining whether a test failed or passed. | |
479 if (result.lastKnownMessage.contains("FAIL")) { | |
480 outcome = Expectation.runtimeError; | |
481 } else if (result.lastKnownMessage.contains("PASS")) { | |
482 outcome = Expectation.pass; | |
483 } else { | |
484 outcome = Expectation.runtimeError; | |
485 } | |
486 } | |
487 | |
488 if (result.didTimeout) { | |
489 if (result.delayUntilTestStarted != null) { | |
490 stderr = "This test timed out. The delay until the test actually " | |
491 "started was: ${result.delayUntilTestStarted}."; | |
492 } else { | |
493 stderr = "This test has not notified test.py that it started running."; | |
494 } | |
495 } | |
496 | |
497 if (parsedResult != null) { | |
498 stdout = "events:\n${indent(prettifyJson(parsedResult.events), 2)}\n\n"; | |
499 } else { | |
500 stdout = "message:\n${indent(result.lastKnownMessage, 2)}\n\n"; | |
501 } | |
502 | |
503 stderr = '$stderr\n\n' | |
504 'BrowserOutput while running the test (* EXPERIMENTAL *):\n' | |
505 'BrowserOutput.stdout:\n' | |
506 '${indent(result.browserOutput.stdout.toString(), 2)}\n' | |
507 'BrowserOutput.stderr:\n' | |
508 '${indent(result.browserOutput.stderr.toString(), 2)}\n' | |
509 '\n'; | |
510 return new BrowserControllerTestOutcome._internal( | |
511 command, result, outcome, encodeUtf8(stdout), encodeUtf8(stderr)); | |
512 } | |
513 | |
514 BrowserControllerTestOutcome._internal( | |
515 Command command, | |
516 BrowserTestOutput result, | |
517 this._rawOutcome, | |
518 List<int> stdout, | |
519 List<int> stderr) | |
520 : super(command, 0, result.didTimeout, stdout, stderr, result.duration, | |
521 false, 0) { | |
522 _result = result; | |
523 } | |
524 | |
525 Expectation result(TestCase testCase) { | |
526 // Handle timeouts first | |
527 if (_result.didTimeout) return Expectation.timeout; | |
528 if (hasNonUtf8) return Expectation.nonUtf8Error; | |
529 | |
530 // Multitests are handled specially | |
531 if (testCase.hasRuntimeError) { | |
532 if (_rawOutcome == Expectation.runtimeError) return Expectation.pass; | |
533 return Expectation.missingRuntimeError; | |
534 } | |
535 | |
536 return _negateOutcomeIfNegativeTest(_rawOutcome, testCase.isNegative); | |
537 } | |
538 } | |
539 | |
540 class AnalysisCommandOutputImpl extends CommandOutputImpl { | |
541 // An error line has 8 fields that look like: | |
542 // ERROR|COMPILER|MISSING_SOURCE|file:/tmp/t.dart|15|1|24|Missing source. | |
543 final int ERROR_LEVEL = 0; | |
544 final int ERROR_TYPE = 1; | |
545 final int FILENAME = 3; | |
546 final int FORMATTED_ERROR = 7; | |
547 | |
548 AnalysisCommandOutputImpl( | |
549 Command command, | |
550 int exitCode, | |
551 bool timedOut, | |
552 List<int> stdout, | |
553 List<int> stderr, | |
554 Duration time, | |
555 bool compilationSkipped) | |
556 : super(command, exitCode, timedOut, stdout, stderr, time, | |
557 compilationSkipped, 0); | |
558 | |
559 Expectation result(TestCase testCase) { | |
560 // TODO(kustermann): If we run the analyzer not in batch mode, make sure | |
561 // that command.exitCodes matches 2 (errors), 1 (warnings), 0 (no warnings, | |
562 // no errors) | |
563 | |
564 // Handle crashes and timeouts first | |
565 if (hasCrashed) return Expectation.crash; | |
566 if (hasTimedOut) return Expectation.timeout; | |
567 if (hasNonUtf8) return Expectation.nonUtf8Error; | |
568 | |
569 // Get the errors/warnings from the analyzer | |
570 List<String> errors = []; | |
571 List<String> warnings = []; | |
572 parseAnalyzerOutput(errors, warnings); | |
573 | |
574 // Handle errors / missing errors | |
575 if (testCase.expectCompileError) { | |
576 if (errors.length > 0) { | |
577 return Expectation.pass; | |
578 } | |
579 return Expectation.missingCompileTimeError; | |
580 } | |
581 if (errors.length > 0) { | |
582 return Expectation.compileTimeError; | |
583 } | |
584 | |
585 // Handle static warnings / missing static warnings | |
586 if (testCase.hasStaticWarning) { | |
587 if (warnings.length > 0) { | |
588 return Expectation.pass; | |
589 } | |
590 return Expectation.missingStaticWarning; | |
591 } | |
592 if (warnings.length > 0) { | |
593 return Expectation.staticWarning; | |
594 } | |
595 | |
596 assert(errors.length == 0 && warnings.length == 0); | |
597 assert(!testCase.hasCompileError && !testCase.hasStaticWarning); | |
598 return Expectation.pass; | |
599 } | |
600 | |
601 void parseAnalyzerOutput(List<String> outErrors, List<String> outWarnings) { | |
602 // Parse a line delimited by the | character using \ as an escape character | |
603 // like: FOO|BAR|FOO\|BAR|FOO\\BAZ as 4 fields: FOO BAR FOO|BAR FOO\BAZ | |
604 List<String> splitMachineError(String line) { | |
605 StringBuffer field = new StringBuffer(); | |
606 List<String> result = []; | |
607 bool escaped = false; | |
608 for (var i = 0; i < line.length; i++) { | |
609 var c = line[i]; | |
610 if (!escaped && c == '\\') { | |
611 escaped = true; | |
612 continue; | |
613 } | |
614 escaped = false; | |
615 if (c == '|') { | |
616 result.add(field.toString()); | |
617 field = new StringBuffer(); | |
618 continue; | |
619 } | |
620 field.write(c); | |
621 } | |
622 result.add(field.toString()); | |
623 return result; | |
624 } | |
625 | |
626 for (String line in decodeUtf8(super.stderr).split("\n")) { | |
627 if (line.length == 0) continue; | |
628 List<String> fields = splitMachineError(line); | |
629 // We only consider errors/warnings for files of interest. | |
630 if (fields.length > FORMATTED_ERROR) { | |
631 if (fields[ERROR_LEVEL] == 'ERROR') { | |
632 outErrors.add(fields[FORMATTED_ERROR]); | |
633 } else if (fields[ERROR_LEVEL] == 'WARNING') { | |
634 outWarnings.add(fields[FORMATTED_ERROR]); | |
635 } | |
636 // OK to Skip error output that doesn't match the machine format | |
637 } | |
638 } | |
639 } | |
640 } | |
641 | |
642 class VmCommandOutputImpl extends CommandOutputImpl | |
643 with UnittestSuiteMessagesMixin { | |
644 static const DART_VM_EXITCODE_DFE_ERROR = 252; | |
645 static const DART_VM_EXITCODE_COMPILE_TIME_ERROR = 254; | |
646 static const DART_VM_EXITCODE_UNCAUGHT_EXCEPTION = 255; | |
647 | |
648 VmCommandOutputImpl(Command command, int exitCode, bool timedOut, | |
649 List<int> stdout, List<int> stderr, Duration time, int pid) | |
650 : super(command, exitCode, timedOut, stdout, stderr, time, false, pid); | |
651 | |
652 Expectation result(TestCase testCase) { | |
653 // Handle crashes and timeouts first | |
654 if (exitCode == DART_VM_EXITCODE_DFE_ERROR) return Expectation.dartkCrash; | |
655 if (hasCrashed) return Expectation.crash; | |
656 if (hasTimedOut) return Expectation.timeout; | |
657 if (hasNonUtf8) return Expectation.nonUtf8Error; | |
658 | |
659 // Multitests are handled specially | |
660 if (testCase.expectCompileError) { | |
661 if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) { | |
662 return Expectation.pass; | |
663 } | |
664 return Expectation.missingCompileTimeError; | |
665 } | |
666 if (testCase.hasRuntimeError) { | |
667 // TODO(kustermann): Do we consider a "runtimeError" only an uncaught | |
668 // exception or does any nonzero exit code fullfil this requirement? | |
669 if (exitCode != 0) { | |
670 return Expectation.pass; | |
671 } | |
672 return Expectation.missingRuntimeError; | |
673 } | |
674 | |
675 // The actual outcome depends on the exitCode | |
676 Expectation outcome; | |
677 if (exitCode == DART_VM_EXITCODE_COMPILE_TIME_ERROR) { | |
678 outcome = Expectation.compileTimeError; | |
679 } else if (exitCode == DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) { | |
680 outcome = Expectation.runtimeError; | |
681 } else if (exitCode != 0) { | |
682 // This is a general fail, in case we get an unknown nonzero exitcode. | |
683 outcome = Expectation.fail; | |
684 } else { | |
685 outcome = Expectation.pass; | |
686 } | |
687 outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout)); | |
688 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative); | |
689 } | |
690 } | |
691 | |
692 class CompilationCommandOutputImpl extends CommandOutputImpl { | |
693 static const DART2JS_EXITCODE_CRASH = 253; | |
694 | |
695 CompilationCommandOutputImpl( | |
696 Command command, | |
697 int exitCode, | |
698 bool timedOut, | |
699 List<int> stdout, | |
700 List<int> stderr, | |
701 Duration time, | |
702 bool compilationSkipped) | |
703 : super(command, exitCode, timedOut, stdout, stderr, time, | |
704 compilationSkipped, 0); | |
705 | |
706 Expectation result(TestCase testCase) { | |
707 // Handle general crash/timeout detection. | |
708 if (hasCrashed) return Expectation.crash; | |
709 if (hasTimedOut) { | |
710 bool isWindows = io.Platform.operatingSystem == 'windows'; | |
711 bool isBrowserTestCase = | |
712 testCase.commands.any((command) => command is BrowserTestCommand); | |
713 // TODO(26060) Dart2js batch mode hangs on Windows under heavy load. | |
714 return (isWindows && isBrowserTestCase) | |
715 ? Expectation.ignore | |
716 : Expectation.timeout; | |
717 } | |
718 if (hasNonUtf8) return Expectation.nonUtf8Error; | |
719 | |
720 // Handle dart2js specific crash detection | |
721 if (exitCode == DART2JS_EXITCODE_CRASH || | |
722 exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_COMPILE_TIME_ERROR || | |
723 exitCode == VmCommandOutputImpl.DART_VM_EXITCODE_UNCAUGHT_EXCEPTION) { | |
724 return Expectation.crash; | |
725 } | |
726 | |
727 // Multitests are handled specially | |
728 if (testCase.expectCompileError) { | |
729 // Nonzero exit code of the compiler means compilation failed | |
730 // TODO(kustermann): Do we have a special exit code in that case??? | |
731 if (exitCode != 0) { | |
732 return Expectation.pass; | |
733 } | |
734 return Expectation.missingCompileTimeError; | |
735 } | |
736 | |
737 // TODO(kustermann): This is a hack, remove it | |
738 if (testCase.hasRuntimeError && testCase.commands.length > 1) { | |
739 // We expected to run the test, but we got an compile time error. | |
740 // If the compilation succeeded, we wouldn't be in here! | |
741 assert(exitCode != 0); | |
742 return Expectation.compileTimeError; | |
743 } | |
744 | |
745 Expectation outcome = | |
746 exitCode == 0 ? Expectation.pass : Expectation.compileTimeError; | |
747 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative); | |
748 } | |
749 } | |
750 | |
751 class KernelCompilationCommandOutputImpl extends CompilationCommandOutputImpl { | |
752 KernelCompilationCommandOutputImpl( | |
753 Command command, | |
754 int exitCode, | |
755 bool timedOut, | |
756 List<int> stdout, | |
757 List<int> stderr, | |
758 Duration time, | |
759 bool compilationSkipped) | |
760 : super(command, exitCode, timedOut, stdout, stderr, time, | |
761 compilationSkipped); | |
762 | |
763 bool get canRunDependendCommands { | |
764 // See [BatchRunnerProcess]: 0 means success, 1 means compile-time error. | |
765 // TODO(asgerf): When the frontend supports it, continue running even if | |
766 // there were compile-time errors. See kernel_sdk issue #18. | |
767 return !hasCrashed && !timedOut && exitCode == 0; | |
768 } | |
769 | |
770 Expectation result(TestCase testCase) { | |
771 Expectation result = super.result(testCase); | |
772 if (result.canBeOutcomeOf(Expectation.crash)) { | |
773 return Expectation.dartkCrash; | |
774 } else if (result.canBeOutcomeOf(Expectation.timeout)) { | |
775 return Expectation.dartkTimeout; | |
776 } else if (result.canBeOutcomeOf(Expectation.compileTimeError)) { | |
777 return Expectation.dartkCompileTimeError; | |
778 } | |
779 return result; | |
780 } | |
781 | |
782 // If the compiler was able to produce a Kernel IR file we want to run the | |
783 // result on the Dart VM. We therefore mark the [KernelCompilationCommand] as | |
784 // successful. | |
785 // => This ensures we test that the DartVM produces correct CompileTime errors | |
786 // as it is supposed to for our test suites. | |
787 bool get successful => canRunDependendCommands; | |
788 } | |
789 | |
790 class JsCommandlineOutputImpl extends CommandOutputImpl | |
791 with UnittestSuiteMessagesMixin { | |
792 JsCommandlineOutputImpl(Command command, int exitCode, bool timedOut, | |
793 List<int> stdout, List<int> stderr, Duration time) | |
794 : super(command, exitCode, timedOut, stdout, stderr, time, false, 0); | |
795 | |
796 Expectation result(TestCase testCase) { | |
797 // Handle crashes and timeouts first | |
798 if (hasCrashed) return Expectation.crash; | |
799 if (hasTimedOut) return Expectation.timeout; | |
800 if (hasNonUtf8) return Expectation.nonUtf8Error; | |
801 | |
802 if (testCase.hasRuntimeError) { | |
803 if (exitCode != 0) return Expectation.pass; | |
804 return Expectation.missingRuntimeError; | |
805 } | |
806 | |
807 var outcome = exitCode == 0 ? Expectation.pass : Expectation.runtimeError; | |
808 outcome = _negateOutcomeIfIncompleteAsyncTest(outcome, decodeUtf8(stdout)); | |
809 return _negateOutcomeIfNegativeTest(outcome, testCase.isNegative); | |
810 } | |
811 } | |
812 | |
813 class PubCommandOutputImpl extends CommandOutputImpl { | |
814 PubCommandOutputImpl(PubCommand command, int exitCode, bool timedOut, | |
815 List<int> stdout, List<int> stderr, Duration time) | |
816 : super(command, exitCode, timedOut, stdout, stderr, time, false, 0); | |
817 | |
818 Expectation result(TestCase testCase) { | |
819 // Handle crashes and timeouts first | |
820 if (hasCrashed) return Expectation.crash; | |
821 if (hasTimedOut) return Expectation.timeout; | |
822 if (hasNonUtf8) return Expectation.nonUtf8Error; | |
823 | |
824 if (exitCode == 0) { | |
825 return Expectation.pass; | |
826 } else if ((command as PubCommand).command == 'get') { | |
827 return Expectation.pubGetError; | |
828 } else { | |
829 return Expectation.fail; | |
830 } | |
831 } | |
832 } | |
833 | |
834 class ScriptCommandOutputImpl extends CommandOutputImpl { | |
835 final Expectation _result; | |
836 | |
837 ScriptCommandOutputImpl(ScriptCommand command, this._result, | |
838 String scriptExecutionInformation, Duration time) | |
839 : super(command, 0, false, [], [], time, false, 0) { | |
840 var lines = scriptExecutionInformation.split("\n"); | |
841 diagnostics.addAll(lines); | |
842 } | |
843 | |
844 Expectation result(TestCase testCase) => _result; | |
845 | |
846 bool get canRunDependendCommands => _result == Expectation.pass; | |
847 | |
848 bool get successful => _result == Expectation.pass; | |
849 } | |
850 | |
851 CommandOutput createCommandOutput(Command command, int exitCode, bool timedOut, | |
852 List<int> stdout, List<int> stderr, Duration time, bool compilationSkipped, | |
853 [int pid = 0]) { | |
854 if (command is ContentShellCommand) { | |
855 return new BrowserCommandOutputImpl( | |
856 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped); | |
857 } else if (command is BrowserTestCommand) { | |
858 return new HTMLBrowserCommandOutputImpl( | |
859 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped); | |
860 } else if (command is AnalysisCommand) { | |
861 return new AnalysisCommandOutputImpl( | |
862 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped); | |
863 } else if (command is VmCommand) { | |
864 return new VmCommandOutputImpl( | |
865 command, exitCode, timedOut, stdout, stderr, time, pid); | |
866 } else if (command is KernelCompilationCommand) { | |
867 return new KernelCompilationCommandOutputImpl( | |
868 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped); | |
869 } else if (command is AdbPrecompilationCommand) { | |
870 return new VmCommandOutputImpl( | |
871 command, exitCode, timedOut, stdout, stderr, time, pid); | |
872 } else if (command is CompilationCommand) { | |
873 if (command.displayName == 'precompiler' || | |
874 command.displayName == 'app_jit') { | |
875 return new VmCommandOutputImpl( | |
876 command, exitCode, timedOut, stdout, stderr, time, pid); | |
877 } | |
878 return new CompilationCommandOutputImpl( | |
879 command, exitCode, timedOut, stdout, stderr, time, compilationSkipped); | |
880 } else if (command is JSCommandlineCommand) { | |
881 return new JsCommandlineOutputImpl( | |
882 command, exitCode, timedOut, stdout, stderr, time); | |
883 } else if (command is PubCommand) { | |
884 return new PubCommandOutputImpl( | |
885 command, exitCode, timedOut, stdout, stderr, time); | |
886 } | |
887 | |
888 return new CommandOutputImpl(command, exitCode, timedOut, stdout, stderr, | |
889 time, compilationSkipped, pid); | |
890 } | |
891 | |
892 class UnittestSuiteMessagesMixin { | |
893 bool _isAsyncTest(String testOutput) { | |
894 return testOutput.contains("unittest-suite-wait-for-done"); | |
895 } | |
896 | |
897 bool _isAsyncTestSuccessful(String testOutput) { | |
898 return testOutput.contains("unittest-suite-success"); | |
899 } | |
900 | |
901 Expectation _negateOutcomeIfIncompleteAsyncTest( | |
902 Expectation outcome, String testOutput) { | |
903 // If this is an asynchronous test and the asynchronous operation didn't | |
904 // complete successfully, it's outcome is Expectation.FAIL. | |
905 // TODO: maybe we should introduce a AsyncIncomplete marker or so | |
906 if (outcome == Expectation.pass) { | |
907 if (_isAsyncTest(testOutput) && !_isAsyncTestSuccessful(testOutput)) { | |
908 return Expectation.fail; | |
909 } | |
910 } | |
911 return outcome; | |
912 } | |
913 } | |
OLD | NEW |