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

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

Issue 841193003: cleanup to tools/testing/dart (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: one last bit Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « tools/testing/dart/test_options.dart ('k') | tools/testing/dart/test_runner.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2013, 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 library test_progress;
6
7 import "dart:async";
8 import "dart:io";
9 import "dart:io" as io;
10 import "dart:convert" show JSON;
11 import "path.dart";
12 import "status_file_parser.dart";
13 import "test_runner.dart";
14 import "test_suite.dart";
15 import "utils.dart";
16
17 String _pad(String s, int length) {
18 StringBuffer buffer = new StringBuffer();
19 for (int i = s.length; i < length; i++) {
20 buffer.write(' ');
21 }
22 buffer.write(s);
23 return buffer.toString();
24 }
25
26 String _padTime(int time) {
27 if (time == 0) {
28 return '00';
29 } else if (time < 10) {
30 return '0$time';
31 } else {
32 return '$time';
33 }
34 }
35
36 String _timeString(Duration d) {
37 var min = d.inMinutes;
38 var sec = d.inSeconds % 60;
39 return '${_padTime(min)}:${_padTime(sec)}';
40 }
41
42 class Formatter {
43 const Formatter();
44 String passed(msg) => msg;
45 String failed(msg) => msg;
46 }
47
48 class ColorFormatter extends Formatter {
49 static int BOLD = 1;
50 static int GREEN = 32;
51 static int RED = 31;
52 static int NONE = 0;
53 static String ESCAPE = decodeUtf8([27]);
54
55 String passed(String msg) => _color(msg, GREEN);
56 String failed(String msg) => _color(msg, RED);
57
58 static String _color(String msg, int color) {
59 return "$ESCAPE[${color}m$msg$ESCAPE[0m";
60 }
61 }
62
63
64 List<String> _buildFailureOutput(TestCase test,
65 [Formatter formatter = const Formatter()]) {
66
67 List<String> getLinesWithoutCarriageReturn(List<int> output) {
68 return decodeUtf8(output).replaceAll('\r\n', '\n')
69 .replaceAll('\r', '\n').split('\n');
70 }
71
72 List<String> output = new List<String>();
73 output.add('');
74 output.add(formatter.failed('FAILED: ${test.configurationString}'
75 ' ${test.displayName}'));
76 StringBuffer expected = new StringBuffer();
77 expected.write('Expected: ');
78 for (var expectation in test.expectedOutcomes) {
79 expected.write('$expectation ');
80 }
81 output.add(expected.toString());
82 output.add('Actual: ${test.result}');
83 if (!test.lastCommandOutput.hasTimedOut) {
84 if (test.commandOutputs.length != test.commands.length
85 && !test.expectCompileError) {
86 output.add('Unexpected compile-time error.');
87 } else {
88 if (test.expectCompileError) {
89 output.add('Compile-time error expected.');
90 }
91 if (test.hasRuntimeError) {
92 output.add('Runtime error expected.');
93 }
94 if (test.configuration['checked'] && test.isNegativeIfChecked) {
95 output.add('Dynamic type error expected.');
96 }
97 }
98 }
99 for (var i = 0; i < test.commands.length; i++) {
100 var command = test.commands[i];
101 var commandOutput = test.commandOutputs[command];
102 if (commandOutput != null) {
103 output.add("CommandOutput[${command.displayName}]:");
104 if (!commandOutput.diagnostics.isEmpty) {
105 String prefix = 'diagnostics:';
106 for (var s in commandOutput.diagnostics) {
107 output.add('$prefix ${s}');
108 prefix = ' ';
109 }
110 }
111 if (!commandOutput.stdout.isEmpty) {
112 output.add('');
113 output.add('stdout:');
114 output.addAll(getLinesWithoutCarriageReturn(commandOutput.stdout));
115 }
116 if (!commandOutput.stderr.isEmpty) {
117 output.add('');
118 output.add('stderr:');
119 output.addAll(getLinesWithoutCarriageReturn(commandOutput.stderr));
120 }
121 }
122 }
123 if (test is BrowserTestCase) {
124 // Additional command for rerunning the steps locally after the fact.
125 var command =
126 test.configuration["_servers_"].httpServerCommandline();
127 output.add('');
128 output.add('To retest, run: $command');
129 }
130 for (var i = 0; i < test.commands.length; i++) {
131 var command = test.commands[i];
132 var commandOutput = test.commandOutputs[command];
133 output.add('');
134 output.add('Command[${command.displayName}]: $command');
135 if (commandOutput != null) {
136 output.add('Took ${commandOutput.time}');
137 } else {
138 output.add('Did not run');
139 }
140 }
141
142 var arguments = ['python', 'tools/test.py'];
143 arguments.addAll(test.configuration['_reproducing_arguments_']);
144 arguments.add(test.displayName);
145 var testPyCommandline = arguments.map(escapeCommandLineArgument).join(' ');
146
147 output.add('');
148 output.add('Short reproduction command (experimental):');
149 output.add(" $testPyCommandline");
150 return output;
151 }
152
153 String _buildSummaryEnd(int failedTests) {
154 if (failedTests == 0) {
155 return '\n===\n=== All tests succeeded\n===\n';
156 } else {
157 var pluralSuffix = failedTests != 1 ? 's' : '';
158 return '\n===\n=== ${failedTests} test$pluralSuffix failed\n===\n';
159 }
160 }
161
162
163 class EventListener {
164 void testAdded() { }
165 void done(TestCase test) { }
166 void allTestsKnown() { }
167 void allDone() { }
168 }
169
170 class ExitCodeSetter extends EventListener {
171 void done(TestCase test) {
172 if (test.unexpectedOutput) {
173 io.exitCode = 1;
174 }
175 }
176 }
177
178 class FlakyLogWriter extends EventListener {
179 void done(TestCase test) {
180 if (test.isFlaky && test.result != Expectation.PASS) {
181 var buf = new StringBuffer();
182 for (var l in _buildFailureOutput(test)) {
183 buf.write("$l\n");
184 }
185 _appendToFlakyFile(buf.toString());
186 }
187 }
188
189 void _appendToFlakyFile(String msg) {
190 var file = new File(TestUtils.flakyFileName());
191 var fd = file.openSync(mode: FileMode.APPEND);
192 fd.writeStringSync(msg);
193 fd.closeSync();
194 }
195 }
196
197 class TestOutcomeLogWriter extends EventListener {
198 /*
199 * The ".test-outcome.log" file contains one line for every executed test.
200 * Such a line is an encoded JSON data structure of the following form:
201 * The durations are double values in milliseconds.
202 *
203 * {
204 * name: 'co19/LibTest/math/x',
205 * configuration: {
206 * mode : 'release',
207 * compiler : 'dart2js',
208 * ....
209 * },
210 * test_result: {
211 * outcome: 'RuntimeError',
212 * expected_outcomes: ['Pass', 'Fail'],
213 * duration: 2600.64,
214 * command_results: [
215 * {
216 * name: 'dart2js',
217 * duration: 2400.44,
218 * },
219 * {
220 * name: 'ff',
221 * duration: 200.2,
222 * },
223 * ],
224 * }
225 * },
226 */
227
228 static final INTERESTED_CONFIGURATION_PARAMETERS =
229 ['mode', 'arch', 'compiler', 'runtime', 'checked', 'host_checked',
230 'minified', 'csp', 'system', 'vm_options', 'use_sdk',
231 'use_repository_packages', 'use_public_packages', 'builder_tag'];
232
233 IOSink _sink;
234
235 void done(TestCase test) {
236 var name = test.displayName;
237 var configuration = {};
238 for (var key in INTERESTED_CONFIGURATION_PARAMETERS) {
239 configuration[key] = test.configuration[key];
240 }
241 var outcome = '${test.lastCommandOutput.result(test)}';
242 var expectations =
243 test.expectedOutcomes.map((expectation) => "$expectation").toList();
244
245 var commandResults = [];
246 double totalDuration = 0.0;
247 for (var command in test.commands) {
248 var output = test.commandOutputs[command];
249 if (output != null) {
250 double duration = output.time.inMicroseconds/1000.0;
251 totalDuration += duration;
252 commandResults.add({
253 'name': command.displayName,
254 'duration': duration,
255 });
256 }
257 }
258 _writeTestOutcomeRecord({
259 'name' : name,
260 'configuration' : configuration,
261 'test_result' : {
262 'outcome' : outcome,
263 'expected_outcomes' : expectations,
264 'duration' : totalDuration,
265 'command_results' : commandResults,
266 },
267 });
268 }
269
270 void allDone() {
271 if (_sink != null) _sink.close();
272 }
273
274 void _writeTestOutcomeRecord(Map record) {
275 if (_sink == null) {
276 _sink = new File(TestUtils.testOutcomeFileName())
277 .openWrite(mode: FileMode.APPEND);
278 }
279 _sink.write("${JSON.encode(record)}\n");
280 }
281 }
282
283
284 class UnexpectedCrashDumpArchiver extends EventListener {
285 void done(TestCase test) {
286 if (test.unexpectedOutput && test.result == Expectation.CRASH) {
287 var name = "core.dart.${test.lastCommandOutput.pid}";
288 var file = new File(name);
289 if (file.existsSync()) {
290 // Find the binary - we assume this is the first part of the command
291 var binName = test.lastCommandExecuted.toString().split(' ').first;
292 var binFile = new File(binName);
293 var binBaseName = new Path(binName).filename;
294 if (binFile.existsSync()) {
295 var tmpPath = new Path(Directory.systemTemp.path);
296 var dir = new Path(TestUtils.mkdirRecursive(tmpPath,
297 new Path('coredump_${test.lastCommandOutput.pid}')).path);
298 TestUtils.copyFile(new Path(name), dir.append(name));
299 TestUtils.copyFile(new Path(binName), dir.append(binBaseName));
300 print("\nCopied core dump and binary for unexpected crash to: "
301 "$dir");
302 }
303 }
304 }
305 }
306 }
307
308
309 class SummaryPrinter extends EventListener {
310 void allTestsKnown() {
311 if (SummaryReport.total > 0) {
312 SummaryReport.printReport();
313 }
314 }
315 }
316
317
318 class TimingPrinter extends EventListener {
319 final _command2testCases = new Map<Command, List<TestCase>>();
320 final _commandOutputs = new Set<CommandOutput>();
321 DateTime _startTime;
322
323 TimingPrinter(this._startTime);
324
325 void done(TestCase testCase) {
326 for (var commandOutput in testCase.commandOutputs.values) {
327 var command = commandOutput.command;
328 _commandOutputs.add(commandOutput);
329 _command2testCases.putIfAbsent(command, () => <TestCase>[]);
330 _command2testCases[command].add(testCase);
331 }
332 }
333
334 void allDone() {
335 Duration d = (new DateTime.now()).difference(_startTime);
336 print('\n--- Total time: ${_timeString(d)} ---');
337 var outputs = _commandOutputs.toList();
338 outputs.sort((a, b) {
339 return b.time.inMilliseconds - a.time.inMilliseconds;
340 });
341 for (int i = 0; i < 20 && i < outputs.length; i++) {
342 var commandOutput = outputs[i];
343 var command = commandOutput.command;
344 var testCases = _command2testCases[command];
345
346 var testCasesDescription = testCases.map((testCase) {
347 return "${testCase.configurationString}/${testCase.displayName}";
348 }).join(', ');
349
350 print('${commandOutput.time} - '
351 '${command.displayName} - '
352 '$testCasesDescription');
353 }
354 }
355 }
356
357 class StatusFileUpdatePrinter extends EventListener {
358 var statusToConfigs = new Map<String, List<String>>();
359 var _failureSummary = <String>[];
360
361 void done(TestCase test) {
362 if (test.unexpectedOutput) {
363 _printFailureOutput(test);
364 }
365 }
366
367 void allDone() {
368 _printFailureSummary();
369 }
370
371
372 void _printFailureOutput(TestCase test) {
373 String status = '${test.displayName}: ${test.result}';
374 List<String> configs =
375 statusToConfigs.putIfAbsent(status, () => <String>[]);
376 configs.add(test.configurationString);
377 if (test.lastCommandOutput.hasTimedOut) {
378 print('\n${test.displayName} timed out on ${test.configurationString}');
379 }
380 }
381
382 String _extractRuntime(String configuration) {
383 // Extract runtime from a configuration, for example,
384 // 'none-vm-checked release_ia32'.
385 List<String> runtime = configuration.split(' ')[0].split('-');
386 return '${runtime[0]}-${runtime[1]}';
387 }
388
389 void _printFailureSummary() {
390 var groupedStatuses = new Map<String, List<String>>();
391 statusToConfigs.forEach((String status, List<String> configs) {
392 var runtimeToConfiguration = new Map<String, List<String>>();
393 for (String config in configs) {
394 String runtime = _extractRuntime(config);
395 var runtimeConfigs =
396 runtimeToConfiguration.putIfAbsent(runtime, () => <String>[]);
397 runtimeConfigs.add(config);
398 }
399 runtimeToConfiguration.forEach((String runtime,
400 List<String> runtimeConfigs) {
401 runtimeConfigs.sort((a, b) => a.compareTo(b));
402 List<String> statuses =
403 groupedStatuses.putIfAbsent('$runtime: $runtimeConfigs',
404 () => <String>[]);
405 statuses.add(status);
406 });
407 });
408
409 print('\n\nNecessary status file updates:');
410 groupedStatuses.forEach((String config, List<String> statuses) {
411 print('');
412 print('$config:');
413 statuses.sort((a, b) => a.compareTo(b));
414 for (String status in statuses) {
415 print(' $status');
416 }
417 });
418 }
419 }
420
421 class SkippedCompilationsPrinter extends EventListener {
422 int _skippedCompilations = 0;
423
424 void done(TestCase test) {
425 for (var commandOutput in test.commandOutputs.values) {
426 if (commandOutput.compilationSkipped)
427 _skippedCompilations++;
428 }
429 }
430
431 void allDone() {
432 if (_skippedCompilations > 0) {
433 print('\n$_skippedCompilations compilations were skipped because '
434 'the previous output was already up to date\n');
435 }
436 }
437 }
438
439 class LeftOverTempDirPrinter extends EventListener {
440 final MIN_NUMBER_OF_TEMP_DIRS = 50;
441
442 static RegExp _getTemporaryDirectoryRegexp() {
443 // These are the patterns of temporary directory names created by
444 // 'Directory.systemTemp.createTemp()' on linux/macos and windows.
445 if (['macos', 'linux'].contains(Platform.operatingSystem)) {
446 return new RegExp(r'^temp_dir1_......$');
447 } else {
448 return new RegExp(r'tempdir-........-....-....-....-............$');
449 }
450 }
451
452 static Stream<Directory> getLeftOverTemporaryDirectories() {
453 var regExp = _getTemporaryDirectoryRegexp();
454 return Directory.systemTemp.list().where(
455 (FileSystemEntity fse) {
456 if (fse is Directory) {
457 if (regExp.hasMatch(new Path(fse.path).filename)) {
458 return true;
459 }
460 }
461 return false;
462 });
463 }
464
465 void allDone() {
466 getLeftOverTemporaryDirectories().length.then((int count) {
467 if (count > MIN_NUMBER_OF_TEMP_DIRS) {
468 DebugLogger.warning("There are ${count} directories "
469 "in the system tempdir "
470 "('${Directory.systemTemp.path}')! "
471 "Maybe left over directories?\n");
472 }
473 }).catchError((error) {
474 DebugLogger.warning("Could not list temp directories, got: $error");
475 });
476 }
477 }
478
479 class LineProgressIndicator extends EventListener {
480 void done(TestCase test) {
481 var status = 'pass';
482 if (test.unexpectedOutput) {
483 status = 'fail';
484 }
485 print('Done ${test.configurationString} ${test.displayName}: $status');
486 }
487 }
488
489
490 class TestFailurePrinter extends EventListener {
491 bool _printSummary;
492 var _formatter;
493 var _failureSummary = <String>[];
494 var _failedTests= 0;
495
496 TestFailurePrinter(this._printSummary,
497 [this._formatter = const Formatter()]);
498
499 void done(TestCase test) {
500 if (test.unexpectedOutput) {
501 _failedTests++;
502 var lines = _buildFailureOutput(test, _formatter);
503 for (var line in lines) {
504 print(line);
505 }
506 print('');
507 if (_printSummary) {
508 _failureSummary.addAll(lines);
509 _failureSummary.add('');
510 }
511 }
512 }
513
514 void allDone() {
515 if (_printSummary) {
516 if (!_failureSummary.isEmpty) {
517 print('\n=== Failure summary:\n');
518 for (String line in _failureSummary) {
519 print(line);
520 }
521 print('');
522
523 print(_buildSummaryEnd(_failedTests));
524 }
525 }
526 }
527 }
528
529 class ProgressIndicator extends EventListener {
530 ProgressIndicator(this._startTime);
531
532
533 void testAdded() { _foundTests++; }
534
535 void done(TestCase test) {
536 if (test.unexpectedOutput) {
537 _failedTests++;
538 } else {
539 _passedTests++;
540 }
541 _printDoneProgress(test);
542 }
543
544 void allTestsKnown() {
545 _allTestsKnown = true;
546 }
547
548 void _printDoneProgress(TestCase test) {}
549
550 int _completedTests() => _passedTests + _failedTests;
551
552 int _foundTests = 0;
553 int _passedTests = 0;
554 int _failedTests = 0;
555 bool _allTestsKnown = false;
556 DateTime _startTime;
557 }
558
559 abstract class CompactIndicator extends ProgressIndicator {
560 CompactIndicator(DateTime startTime)
561 : super(startTime);
562
563 void allDone() {
564 if (_failedTests > 0) {
565 // We may have printed many failure logs, so reprint the summary data.
566 _printProgress();
567 }
568 print('');
569 }
570
571 void _printDoneProgress(TestCase test) => _printProgress();
572
573 void _printProgress();
574 }
575
576
577 class CompactProgressIndicator extends CompactIndicator {
578 Formatter _formatter;
579
580 CompactProgressIndicator(DateTime startTime, this._formatter)
581 : super(startTime);
582
583 void _printProgress() {
584 var percent = ((_completedTests() / _foundTests) * 100).toInt().toString();
585 var progressPadded = _pad(_allTestsKnown ? percent : '--', 3);
586 var passedPadded = _pad(_passedTests.toString(), 5);
587 var failedPadded = _pad(_failedTests.toString(), 5);
588 Duration d = (new DateTime.now()).difference(_startTime);
589 var progressLine =
590 '\r[${_timeString(d)} | $progressPadded% | '
591 '+${_formatter.passed(passedPadded)} | '
592 '-${_formatter.failed(failedPadded)}]';
593 stdout.write(progressLine);
594 }
595 }
596
597
598 class VerboseProgressIndicator extends ProgressIndicator {
599 VerboseProgressIndicator(DateTime startTime)
600 : super(startTime);
601
602 void _printDoneProgress(TestCase test) {
603 var status = 'pass';
604 if (test.unexpectedOutput) {
605 status = 'fail';
606 }
607 print('Done ${test.configurationString} ${test.displayName}: $status');
608 }
609 }
610
611
612 class BuildbotProgressIndicator extends ProgressIndicator {
613 static String stepName;
614 var _failureSummary = <String>[];
615
616 BuildbotProgressIndicator(DateTime startTime) : super(startTime);
617
618 void done(TestCase test) {
619 super.done(test);
620 if (test.unexpectedOutput) {
621 _failureSummary.addAll(_buildFailureOutput(test));
622 }
623 }
624
625 void _printDoneProgress(TestCase test) {
626 var status = 'pass';
627 if (test.unexpectedOutput) {
628 status = 'fail';
629 }
630 var percent = ((_completedTests() / _foundTests) * 100).toInt().toString();
631 print('Done ${test.configurationString} ${test.displayName}: $status');
632 print('@@@STEP_CLEAR@@@');
633 print('@@@STEP_TEXT@ $percent% +$_passedTests -$_failedTests @@@');
634 }
635
636 void allDone() {
637 if (!_failureSummary.isEmpty) {
638 print('@@@STEP_FAILURE@@@');
639 if (stepName != null) {
640 print('@@@BUILD_STEP $stepName failures@@@');
641 }
642 for (String line in _failureSummary) {
643 print(line);
644 }
645 print('');
646 }
647 print(_buildSummaryEnd(_failedTests));
648 }
649 }
650
651
652 EventListener progressIndicatorFromName(String name,
653 DateTime startTime,
654 Formatter formatter) {
655 switch (name) {
656 case 'compact':
657 return new CompactProgressIndicator(startTime, formatter);
658 case 'line':
659 return new LineProgressIndicator();
660 case 'verbose':
661 return new VerboseProgressIndicator(startTime);
662 case 'status':
663 return new ProgressIndicator(startTime);
664 case 'buildbot':
665 return new BuildbotProgressIndicator(startTime);
666 default:
667 assert(false);
668 break;
669 }
670 }
OLDNEW
« no previous file with comments | « tools/testing/dart/test_options.dart ('k') | tools/testing/dart/test_runner.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698