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 var success = false; | |
96 if (passed == 0 && failed == 0 && errors == 0 && uncaughtError == null) { | |
97 _print('\nNo tests ran.'); | |
98 } else if (failed == 0 && errors == 0 && uncaughtError == null) { | |
99 _progressLine('All tests passed!', _NONE); | |
100 _print(); | |
101 success = true; | |
102 } else { | |
103 _progressLine('Some tests failed.', _RED); | |
104 _print(); | |
105 if (uncaughtError != null) { | |
106 _print('Top-level uncaught error: $uncaughtError'); | |
107 } | |
108 _print('$passed PASSED, $failed FAILED, $errors ERRORS'); | |
109 } | |
110 } | |
111 | |
112 int _lastLength = 0; | |
113 | |
114 final int _nonVisiblePrefix = 1 + _GREEN.length + _NONE.length; | |
115 | |
116 void _progressLine(String message, [String color = _NONE]) { | |
117 var duration = (new DateTime.now()).difference(_start); | |
118 var buffer = new StringBuffer(); | |
119 // \r moves back to the beginning of the current line. | |
120 buffer.write('\r${_timeString(duration)} '); | |
121 buffer.write(_GREEN); | |
122 buffer.write('+'); | |
123 buffer.write(_pass); | |
124 buffer.write(_NONE); | |
125 if (_fail != 0) { | |
126 buffer.write(_RED); | |
127 buffer.write(' -'); | |
128 buffer.write(_fail); | |
129 buffer.write(_NONE); | |
130 } | |
131 buffer.write(': '); | |
132 buffer.write(color); | |
133 | |
134 // Ensure the line fits under MAX_LINE. [buffer] includes the color escape | |
135 // sequences too. Because these sequences are not visible characters, we | |
136 // make sure they are not counted towards the limit. | |
137 int nonVisible = _nonVisiblePrefix + color.length + | |
138 (_fail != 0 ? (_RED.length + _NONE.length) : 0); | |
139 int len = buffer.length - nonVisible; | |
140 var mx = MAX_LINE - len; | |
141 buffer.write(_snippet(message, MAX_LINE - len)); | |
142 buffer.write(_NONE); | |
143 | |
144 // Pad the rest of the line so that it looks erased. | |
145 len = buffer.length - nonVisible - _NONE.length; | |
146 if (len > _lastLength) { | |
147 _lastLength = len; | |
148 } else { | |
149 while (len < _lastLength) { | |
150 buffer.write(' '); | |
151 _lastLength--; | |
152 } | |
153 } | |
154 stdout.write(buffer.toString()); | |
155 } | |
156 | |
157 String _padTime(int time) => | |
158 (time == 0) ? '00' : ((time < 10) ? '0$time' : '$time'); | |
159 | |
160 String _timeString(Duration duration) { | |
161 var min = duration.inMinutes; | |
162 var sec = duration.inSeconds % 60; | |
163 return '${_padTime(min)}:${_padTime(sec)}'; | |
164 } | |
165 | |
166 String _snippet(String text, int maxLength) { | |
167 // Return the full message if it fits | |
168 if (text.length <= maxLength) return text; | |
169 | |
170 // If we can fit the first and last three words, do so. | |
171 var words = text.split(' '); | |
172 if (words.length > 1) { | |
173 int i = words.length; | |
174 var len = words.first.length + 4; | |
175 do { | |
176 len += 1 + words[--i].length; | |
177 } while (len <= maxLength && i > 0); | |
178 if (len > maxLength || i == 0) i++; | |
179 if (i < words.length - 4) { | |
180 // Require at least 3 words at the end. | |
181 var buffer = new StringBuffer(); | |
182 buffer.write(words.first); | |
183 buffer.write(' ...'); | |
184 for (; i < words.length; i++) { | |
185 buffer.write(' '); | |
186 buffer.write(words[i]); | |
187 } | |
188 return buffer.toString(); | |
189 } | |
190 } | |
191 | |
192 // Otherwise truncate to return the trailing text, but attempt to start at | |
193 // the beginning of a word. | |
194 var res = text.substring(text.length - maxLength + 4); | |
195 var firstSpace = res.indexOf(' '); | |
196 if (firstSpace > 0) { | |
197 res = res.substring(firstSpace); | |
198 } | |
199 return '...$res'; | |
200 } | |
201 } | |
202 | |
203 // TODO(sigmund): delete when dartbug.com/17269 is fixed (use `print` instead). | |
204 _print([value = '']) => stdout.write('$value\n'); | |
205 | |
206 void useCompactVMConfiguration() { | |
207 // If the test is running on the Dart buildbots, we don't want to use this | |
208 // config since it's output may not be what the bots expect. | |
209 if (Platform.environment['LOGNAME'] == 'chrome-bot') { | |
210 return; | |
211 } | |
212 | |
213 unittestConfiguration = _singleton; | |
214 } | |
215 | |
216 final _singleton = new CompactVMConfiguration(); | |
OLD | NEW |