OLD | NEW |
(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 /// A test configuration that generates a compact 1-line progress bar. The bar |
| 6 /// is updated in-place before and after each test is executed. If all tests |
| 7 /// pass, only a couple of lines are printed in the terminal. If a test fails, |
| 8 /// the failure is shown and the progress bar continues to be updated below it. |
| 9 library unittest.compact_vm_config; |
| 10 |
| 11 import 'dart:async'; |
| 12 import 'dart:io'; |
| 13 import 'dart:isolate'; |
| 14 |
| 15 import 'unittest.dart'; |
| 16 import 'src/utils.dart'; |
| 17 import 'vm_config.dart'; |
| 18 |
| 19 const String _GREEN = '\u001b[32m'; |
| 20 const String _RED = '\u001b[31m'; |
| 21 const String _NONE = '\u001b[0m'; |
| 22 |
| 23 const int MAX_LINE = 80; |
| 24 |
| 25 class CompactVMConfiguration extends VMConfiguration { |
| 26 // The VM won't shut down if a receive port is open. Use this to make sure |
| 27 // we correctly wait for asynchronous tests. |
| 28 ReceivePort _receivePort; |
| 29 |
| 30 DateTime _start; |
| 31 Set<int> _passing = new Set(); |
| 32 Set<int> _failing = new Set(); |
| 33 int get _pass => _passing.length; |
| 34 int get _fail => _failing.length; |
| 35 |
| 36 void onInit() { |
| 37 _receivePort = new ReceivePort(); |
| 38 // Override and don't call the superclass onInit() to avoid printing the |
| 39 // "unittest-suite-..." boilerplate. |
| 40 } |
| 41 |
| 42 void onStart() { |
| 43 _start = new DateTime.now(); |
| 44 } |
| 45 |
| 46 void onTestStart(TestCase test) { |
| 47 super.onTestStart(test); |
| 48 _progressLine(test.description); |
| 49 } |
| 50 |
| 51 void onTestResult(TestCase test) { |
| 52 super.onTestResult(test); |
| 53 if (test.result == PASS) { |
| 54 _passing.add(test.id); |
| 55 _progressLine(test.description); |
| 56 } else { |
| 57 _failing.add(test.id); |
| 58 _progressLine(test.description); |
| 59 _print(); |
| 60 if (test.message != '') { |
| 61 _print(indent(test.message)); |
| 62 } |
| 63 |
| 64 if (test.stackTrace != null) { |
| 65 _print(indent(test.stackTrace.toString())); |
| 66 } |
| 67 } |
| 68 } |
| 69 |
| 70 void onTestResultChanged(TestCase test) { |
| 71 _passing.remove(test.id); |
| 72 _failing.add(test.id); |
| 73 _progressLine(test.description); |
| 74 _print(); |
| 75 if (test.message != '') { |
| 76 _print(indent(test.message)); |
| 77 } |
| 78 |
| 79 if (test.stackTrace != null) { |
| 80 _print(indent(test.stackTrace.toString())); |
| 81 } |
| 82 } |
| 83 |
| 84 void onDone(bool success) { |
| 85 // Override and don't call the superclass onDone() to avoid printing the |
| 86 // "unittest-suite-..." boilerplate. |
| 87 Future.wait([stdout.close(), stderr.close()]).then((_) { |
| 88 _receivePort.close(); |
| 89 exit(success ? 0 : 1); |
| 90 }); |
| 91 } |
| 92 |
| 93 void onSummary(int passed, int failed, int errors, List<TestCase> results, |
| 94 String uncaughtError) { |
| 95 if (passed == 0 && failed == 0 && errors == 0 && uncaughtError == null) { |
| 96 _print('\nNo tests ran.'); |
| 97 } else if (failed == 0 && errors == 0 && uncaughtError == null) { |
| 98 _progressLine('All tests passed!', _NONE); |
| 99 _print(); |
| 100 } else { |
| 101 _progressLine('Some tests failed.', _RED); |
| 102 _print(); |
| 103 if (uncaughtError != null) { |
| 104 _print('Top-level uncaught error: $uncaughtError'); |
| 105 } |
| 106 _print('$passed PASSED, $failed FAILED, $errors ERRORS'); |
| 107 } |
| 108 } |
| 109 |
| 110 int _lastLength = 0; |
| 111 |
| 112 final int _nonVisiblePrefix = 1 + _GREEN.length + _NONE.length; |
| 113 |
| 114 void _progressLine(String message, [String color = _NONE]) { |
| 115 var duration = (new DateTime.now()).difference(_start); |
| 116 var buffer = new StringBuffer(); |
| 117 // \r moves back to the beginning of the current line. |
| 118 buffer.write('\r${_timeString(duration)} '); |
| 119 buffer.write(_GREEN); |
| 120 buffer.write('+'); |
| 121 buffer.write(_pass); |
| 122 buffer.write(_NONE); |
| 123 if (_fail != 0) { |
| 124 buffer.write(_RED); |
| 125 buffer.write(' -'); |
| 126 buffer.write(_fail); |
| 127 buffer.write(_NONE); |
| 128 } |
| 129 buffer.write(': '); |
| 130 buffer.write(color); |
| 131 |
| 132 // Ensure the line fits under MAX_LINE. [buffer] includes the color escape |
| 133 // sequences too. Because these sequences are not visible characters, we |
| 134 // make sure they are not counted towards the limit. |
| 135 int nonVisible = _nonVisiblePrefix + |
| 136 color.length + |
| 137 (_fail != 0 ? (_RED.length + _NONE.length) : 0); |
| 138 int len = buffer.length - nonVisible; |
| 139 buffer.write(_snippet(message, MAX_LINE - len)); |
| 140 buffer.write(_NONE); |
| 141 |
| 142 // Pad the rest of the line so that it looks erased. |
| 143 len = buffer.length - nonVisible - _NONE.length; |
| 144 if (len > _lastLength) { |
| 145 _lastLength = len; |
| 146 } else { |
| 147 while (len < _lastLength) { |
| 148 buffer.write(' '); |
| 149 _lastLength--; |
| 150 } |
| 151 } |
| 152 stdout.write(buffer.toString()); |
| 153 } |
| 154 |
| 155 String _padTime(int time) => |
| 156 (time == 0) ? '00' : ((time < 10) ? '0$time' : '$time'); |
| 157 |
| 158 String _timeString(Duration duration) { |
| 159 var min = duration.inMinutes; |
| 160 var sec = duration.inSeconds % 60; |
| 161 return '${_padTime(min)}:${_padTime(sec)}'; |
| 162 } |
| 163 |
| 164 String _snippet(String text, int maxLength) { |
| 165 // Return the full message if it fits |
| 166 if (text.length <= maxLength) return text; |
| 167 |
| 168 // If we can fit the first and last three words, do so. |
| 169 var words = text.split(' '); |
| 170 if (words.length > 1) { |
| 171 int i = words.length; |
| 172 var len = words.first.length + 4; |
| 173 do { |
| 174 len += 1 + words[--i].length; |
| 175 } while (len <= maxLength && i > 0); |
| 176 if (len > maxLength || i == 0) i++; |
| 177 if (i < words.length - 4) { |
| 178 // Require at least 3 words at the end. |
| 179 var buffer = new StringBuffer(); |
| 180 buffer.write(words.first); |
| 181 buffer.write(' ...'); |
| 182 for ( ; i < words.length; i++) { |
| 183 buffer.write(' '); |
| 184 buffer.write(words[i]); |
| 185 } |
| 186 return buffer.toString(); |
| 187 } |
| 188 } |
| 189 |
| 190 // Otherwise truncate to return the trailing text, but attempt to start at |
| 191 // the beginning of a word. |
| 192 var res = text.substring(text.length - maxLength + 4); |
| 193 var firstSpace = res.indexOf(' '); |
| 194 if (firstSpace > 0) { |
| 195 res = res.substring(firstSpace); |
| 196 } |
| 197 return '...$res'; |
| 198 } |
| 199 } |
| 200 |
| 201 // TODO(sigmund): delete when dartbug.com/17269 is fixed (use `print` instead). |
| 202 _print([value = '']) => stdout.write('$value\n'); |
| 203 |
| 204 void useCompactVMConfiguration() { |
| 205 // If the test is running on the Dart buildbots, we don't want to use this |
| 206 // config since it's output may not be what the bots expect. |
| 207 if (Platform.environment['LOGNAME'] == 'chrome-bot') { |
| 208 return; |
| 209 } |
| 210 |
| 211 unittestConfiguration = _singleton; |
| 212 } |
| 213 |
| 214 final _singleton = new CompactVMConfiguration(); |
OLD | NEW |