OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library test.runner.reporter.no_io_compact; | 5 library test.runner.reporter.no_io_compact; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:isolate'; |
8 | 8 |
9 import '../../backend/live_test.dart'; | 9 import '../../backend/live_test.dart'; |
10 import '../../backend/state.dart'; | 10 import '../../backend/state.dart'; |
11 import '../../backend/suite.dart'; | |
12 import '../../utils.dart'; | 11 import '../../utils.dart'; |
13 import '../engine.dart'; | 12 import '../engine.dart'; |
| 13 import '../load_exception.dart'; |
14 | 14 |
15 /// The maximum console line length. | 15 /// The maximum console line length. |
16 /// | 16 /// |
17 /// Lines longer than this will be cropped. | 17 /// Lines longer than this will be cropped. |
18 const _lineLength = 100; | 18 const _lineLength = 100; |
19 | 19 |
20 /// A reporter that prints each test on its own line. | 20 /// A reporter that prints each test on its own line. |
21 /// | 21 /// |
22 /// This is currently used in place of [CompactReporter] by `lib/test.dart`, | 22 /// This is currently used in place of [CompactReporter] by `lib/test.dart`, |
23 /// which can't transitively import `dart:io` but still needs access to a runner | 23 /// which can't transitively import `dart:io` but still needs access to a runner |
24 /// so that test files can be run directly. This means that until issue 6943 is | 24 /// so that test files can be run directly. This means that until issue 6943 is |
25 /// fixed, this must not import `dart:io`. | 25 /// fixed, this must not import `dart:io`. |
26 class ExpandedReporter { | 26 class ExpandedReporter { |
| 27 /// Whether the reporter should emit terminal color escapes. |
| 28 final bool _color; |
| 29 |
27 /// The terminal escape for green text, or the empty string if this is Windows | 30 /// The terminal escape for green text, or the empty string if this is Windows |
28 /// or not outputting to a terminal. | 31 /// or not outputting to a terminal. |
29 final String _green; | 32 final String _green; |
30 | 33 |
31 /// The terminal escape for red text, or the empty string if this is Windows | 34 /// The terminal escape for red text, or the empty string if this is Windows |
32 /// or not outputting to a terminal. | 35 /// or not outputting to a terminal. |
33 final String _red; | 36 final String _red; |
34 | 37 |
35 /// The terminal escape for yellow text, or the empty string if this is | 38 /// The terminal escape for yellow text, or the empty string if this is |
36 /// Windows or not outputting to a terminal. | 39 /// Windows or not outputting to a terminal. |
37 final String _yellow; | 40 final String _yellow; |
38 | 41 |
39 /// The terminal escape for removing test coloring, or the empty string if | 42 /// The terminal escape for removing test coloring, or the empty string if |
40 /// this is Windows or not outputting to a terminal. | 43 /// this is Windows or not outputting to a terminal. |
41 final String _noColor; | 44 final String _noColor; |
42 | 45 |
43 /// Whether to use verbose stack traces. | 46 /// Whether to use verbose stack traces. |
44 final bool _verboseTrace; | 47 final bool _verboseTrace; |
45 | 48 |
46 /// The engine used to run the tests. | 49 /// The engine used to run the tests. |
47 final Engine _engine; | 50 final Engine _engine; |
48 | 51 |
49 /// Whether multiple test files are being run. | 52 /// Whether the path to each test's suite should be printed. |
50 final bool _multiplePaths; | 53 final bool _printPath; |
51 | 54 |
52 /// Whether tests are being run on multiple platforms. | 55 /// Whether the platform each test is running on should be printed. |
53 final bool _multiplePlatforms; | 56 final bool _printPlatform; |
54 | 57 |
55 /// A stopwatch that tracks the duration of the full run. | 58 /// A stopwatch that tracks the duration of the full run. |
56 final _stopwatch = new Stopwatch(); | 59 final _stopwatch = new Stopwatch(); |
57 | 60 |
58 /// Whether [close] has been called. | |
59 bool _closed = false; | |
60 | |
61 /// The size of `_engine.passed` last time a progress notification was | 61 /// The size of `_engine.passed` last time a progress notification was |
62 /// printed. | 62 /// printed. |
63 int _lastProgressPassed; | 63 int _lastProgressPassed; |
64 | 64 |
65 /// The size of `_engine.skipped` last time a progress notification was | 65 /// The size of `_engine.skipped` last time a progress notification was |
66 /// printed. | 66 /// printed. |
67 int _lastProgressSkipped; | 67 int _lastProgressSkipped; |
68 | 68 |
69 /// The size of `_engine.failed` last time a progress notification was | 69 /// The size of `_engine.failed` last time a progress notification was |
70 /// printed. | 70 /// printed. |
71 int _lastProgressFailed; | 71 int _lastProgressFailed; |
72 | 72 |
73 /// The message printed for the last progress notification. | 73 /// The message printed for the last progress notification. |
74 String _lastProgressMessage; | 74 String _lastProgressMessage; |
75 | 75 |
76 /// Creates a [NoIoCompactReporter] that will run all tests in [suites]. | 76 /// Watches the tests run by [engine] and prints their results to the |
| 77 /// terminal. |
77 /// | 78 /// |
78 /// [concurrency] controls how many suites are run at once. If [color] is | 79 /// If [color] is `true`, this will use terminal colors; if it's `false`, it |
79 /// `true`, this will use terminal colors; if it's `false`, it won't. If | 80 /// won't. If [verboseTrace] is `true`, this will print core library frames. |
80 /// [verboseTrace] is `true`, this will print core library frames. | 81 /// If [printPath] is `true`, this will print the path name as part of the |
81 ExpandedReporter(Iterable<Suite> suites, {int concurrency, bool color: true, | 82 /// test description. Likewise, if [printPlatform] is `true`, this will print |
82 bool verboseTrace: false}) | 83 /// the platform as part of the test description. |
83 : _multiplePaths = suites.map((suite) => suite.path).toSet().length > 1, | 84 static void watch(Engine engine, {bool color: true, bool verboseTrace: false, |
84 _multiplePlatforms = | 85 bool printPath: true, bool printPlatform: true}) { |
85 suites.map((suite) => suite.platform).toSet().length > 1, | 86 new ExpandedReporter._( |
86 _engine = new Engine(suites, concurrency: concurrency), | 87 engine, |
87 _verboseTrace = verboseTrace, | 88 color: color, |
| 89 verboseTrace: verboseTrace, |
| 90 printPath: printPath, |
| 91 printPlatform: printPlatform); |
| 92 } |
| 93 |
| 94 ExpandedReporter._(this._engine, {bool color: true, bool verboseTrace: false, |
| 95 bool printPath: true, bool printPlatform: true}) |
| 96 : _verboseTrace = verboseTrace, |
| 97 _printPath = printPath, |
| 98 _printPlatform = printPlatform, |
| 99 _color = color, |
88 _green = color ? '\u001b[32m' : '', | 100 _green = color ? '\u001b[32m' : '', |
89 _red = color ? '\u001b[31m' : '', | 101 _red = color ? '\u001b[31m' : '', |
90 _yellow = color ? '\u001b[33m' : '', | 102 _yellow = color ? '\u001b[33m' : '', |
91 _noColor = color ? '\u001b[0m' : '' { | 103 _noColor = color ? '\u001b[0m' : '' { |
92 _engine.onTestStarted.listen((liveTest) { | 104 _engine.onTestStarted.listen(_onTestStarted); |
93 // If this is the first test to start, print a progress line so the user | 105 _engine.success.then(_onDone); |
94 // knows what's running. | 106 } |
95 if (_engine.active.length == 1) _progressLine(_description(liveTest)); | |
96 | 107 |
97 liveTest.onStateChange.listen((state) { | 108 /// A callback called when the engine begins running [liveTest]. |
98 if (state.status != Status.complete) return; | 109 void _onTestStarted(LiveTest liveTest) { |
| 110 if (!_stopwatch.isRunning) _stopwatch.start(); |
99 | 111 |
100 if (liveTest.test.metadata.skip && | 112 // If this is the first test to start, print a progress line so |
101 liveTest.test.metadata.skipReason != null) { | 113 // the user knows what's running. |
102 _progressLine(_description(liveTest)); | 114 if (_engine.active.length == 1) _progressLine(_description(liveTest)); |
103 print(indent('${_yellow}Skip: ${liveTest.test.metadata.skipReason}' | |
104 '$_noColor')); | |
105 } else if (_engine.active.isNotEmpty) { | |
106 // If any tests are running, display the name of the oldest active | |
107 // test. | |
108 _progressLine(_description(_engine.active.first)); | |
109 } | |
110 }); | |
111 | 115 |
112 liveTest.onError.listen((error) { | 116 liveTest.onStateChange.listen((state) => _onStateChange(liveTest, state)); |
113 if (liveTest.state.status != Status.complete) return; | |
114 | 117 |
115 _progressLine(_description(liveTest)); | 118 liveTest.onError.listen((error) => |
116 print(indent(error.error.toString())); | 119 _onError(liveTest, error.error, error.stackTrace)); |
117 var chain = terseChain(error.stackTrace, verbose: _verboseTrace); | |
118 print(indent(chain.toString())); | |
119 }); | |
120 | 120 |
121 liveTest.onPrint.listen((line) { | 121 liveTest.onPrint.listen((line) { |
122 _progressLine(_description(liveTest)); | 122 _progressLine(_description(liveTest)); |
123 print(line); | 123 print(line); |
124 }); | |
125 }); | 124 }); |
126 } | 125 } |
127 | 126 |
128 /// Runs all tests in all provided suites. | 127 /// A callback called when [liveTest]'s state becomes [state]. |
| 128 void _onStateChange(LiveTest liveTest, State state) { |
| 129 if (state.status != Status.complete) return; |
| 130 |
| 131 if (liveTest.test.metadata.skip && |
| 132 liveTest.test.metadata.skipReason != null) { |
| 133 _progressLine(_description(liveTest)); |
| 134 print(indent('${_yellow}Skip: ${liveTest.test.metadata.skipReason}' |
| 135 '$_noColor')); |
| 136 } else if (_engine.active.isNotEmpty) { |
| 137 // If any tests are running, display the name of the oldest active |
| 138 // test. |
| 139 _progressLine(_description(_engine.active.first)); |
| 140 } |
| 141 } |
| 142 |
| 143 /// A callback called when [liveTest] throws [error]. |
| 144 void _onError(LiveTest liveTest, error, StackTrace stackTrace) { |
| 145 if (liveTest.state.status != Status.complete) return; |
| 146 |
| 147 _progressLine(_description(liveTest)); |
| 148 |
| 149 if (error is! LoadException) { |
| 150 print(indent(error.toString())); |
| 151 var chain = terseChain(stackTrace, verbose: _verboseTrace); |
| 152 print(indent(chain.toString())); |
| 153 return; |
| 154 } |
| 155 |
| 156 print(indent(error.toString(color: _color))); |
| 157 |
| 158 // Only print stack traces for load errors that come from the user's code. |
| 159 if (error.innerError is! IsolateSpawnException && |
| 160 error.innerError is! FormatException && |
| 161 error.innerError is! String) { |
| 162 print(indent(terseChain(stackTrace).toString())); |
| 163 } |
| 164 } |
| 165 |
| 166 /// A callback called when the engine is finished running tests. |
129 /// | 167 /// |
130 /// This returns `true` if all tests succeed, and `false` otherwise. It will | 168 /// [success] will be `true` if all tests passed, `false` if some tests |
131 /// only return once all tests have finished running. | 169 /// failed, and `null` if the engine was closed prematurely. |
132 Future<bool> run() async { | 170 void _onDone(bool success) { |
133 if (_stopwatch.isRunning) { | 171 // A null success value indicates that the engine was closed before the |
134 throw new StateError("ExpandedReporter.run() may not be called more than " | 172 // tests finished running, probably because of a signal from the user, in |
135 "once."); | 173 // which case we shouldn't print summary information. |
136 } | 174 if (success == null) return; |
137 | 175 |
138 if (_engine.liveTests.isEmpty) { | 176 if (_engine.liveTests.isEmpty) { |
139 print("No tests ran."); | 177 print("No tests ran."); |
140 return true; | 178 } else if (!success) { |
141 } | |
142 | |
143 _stopwatch.start(); | |
144 var success = await _engine.run(); | |
145 if (_closed) return false; | |
146 | |
147 if (!success) { | |
148 _progressLine('Some tests failed.', color: _red); | 179 _progressLine('Some tests failed.', color: _red); |
149 } else if (_engine.passed.isEmpty) { | 180 } else if (_engine.passed.isEmpty) { |
150 _progressLine("All tests skipped."); | 181 _progressLine("All tests skipped."); |
151 } else { | 182 } else { |
152 _progressLine("All tests passed!"); | 183 _progressLine("All tests passed!"); |
153 } | 184 } |
154 | |
155 return success; | |
156 } | 185 } |
157 | 186 |
158 /// Signals that the caller is done with any test output and the reporter | |
159 /// should release any resources it has allocated. | |
160 Future close() => _engine.close(); | |
161 | |
162 /// Prints a line representing the current state of the tests. | 187 /// Prints a line representing the current state of the tests. |
163 /// | 188 /// |
164 /// [message] goes after the progress report, and may be truncated to fit the | 189 /// [message] goes after the progress report, and may be truncated to fit the |
165 /// entire line within [_lineLength]. If [color] is passed, it's used as the | 190 /// entire line within [_lineLength]. If [color] is passed, it's used as the |
166 /// color for [message]. | 191 /// color for [message]. |
167 void _progressLine(String message, {String color}) { | 192 void _progressLine(String message, {String color}) { |
168 // Print nothing if nothing has changed since the last progress line. | 193 // Print nothing if nothing has changed since the last progress line. |
169 if (_engine.passed.length == _lastProgressPassed && | 194 if (_engine.passed.length == _lastProgressPassed && |
170 _engine.skipped.length == _lastProgressSkipped && | 195 _engine.skipped.length == _lastProgressSkipped && |
171 _engine.failed.length == _lastProgressFailed && | 196 _engine.failed.length == _lastProgressFailed && |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
224 "${(duration.inSeconds % 60).toString().padLeft(2, '0')}"; | 249 "${(duration.inSeconds % 60).toString().padLeft(2, '0')}"; |
225 } | 250 } |
226 | 251 |
227 /// Returns a description of [liveTest]. | 252 /// Returns a description of [liveTest]. |
228 /// | 253 /// |
229 /// This differs from the test's own description in that it may also include | 254 /// This differs from the test's own description in that it may also include |
230 /// the suite's name. | 255 /// the suite's name. |
231 String _description(LiveTest liveTest) { | 256 String _description(LiveTest liveTest) { |
232 var name = liveTest.test.name; | 257 var name = liveTest.test.name; |
233 | 258 |
234 if (_multiplePaths && liveTest.suite.path != null) { | 259 if (_printPath && liveTest.suite.path != null) { |
235 name = "${liveTest.suite.path}: $name"; | 260 name = "${liveTest.suite.path}: $name"; |
236 } | 261 } |
237 | 262 |
238 if (_multiplePlatforms && liveTest.suite.platform != null) { | 263 if (_printPlatform && liveTest.suite.platform != null) { |
239 name = "[${liveTest.suite.platform}] $name"; | 264 name = "[${liveTest.suite.platform}] $name"; |
240 } | 265 } |
241 | 266 |
242 return name; | 267 return name; |
243 } | 268 } |
244 } | 269 } |
OLD | NEW |