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; | 5 library test.runner; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:io'; | 8 import 'dart:io'; |
9 | 9 |
10 import 'package:async/async.dart'; | 10 import 'package:async/async.dart'; |
11 | 11 |
12 import 'backend/group.dart'; | |
13 import 'backend/group_entry.dart'; | |
14 import 'backend/test.dart'; | |
12 import 'backend/test_platform.dart'; | 15 import 'backend/test_platform.dart'; |
13 import 'runner/application_exception.dart'; | 16 import 'runner/application_exception.dart'; |
14 import 'runner/configuration.dart'; | 17 import 'runner/configuration.dart'; |
15 import 'runner/engine.dart'; | 18 import 'runner/engine.dart'; |
16 import 'runner/load_exception.dart'; | 19 import 'runner/load_exception.dart'; |
17 import 'runner/load_suite.dart'; | 20 import 'runner/load_suite.dart'; |
18 import 'runner/loader.dart'; | 21 import 'runner/loader.dart'; |
19 import 'runner/reporter.dart'; | 22 import 'runner/reporter.dart'; |
20 import 'runner/reporter/compact.dart'; | 23 import 'runner/reporter/compact.dart'; |
21 import 'runner/reporter/expanded.dart'; | 24 import 'runner/reporter/expanded.dart'; |
(...skipping 16 matching lines...) Expand all Loading... | |
38 | 41 |
39 /// The engine that runs the test suites. | 42 /// The engine that runs the test suites. |
40 final Engine _engine; | 43 final Engine _engine; |
41 | 44 |
42 /// The reporter that's emitting the test runner's results. | 45 /// The reporter that's emitting the test runner's results. |
43 final Reporter _reporter; | 46 final Reporter _reporter; |
44 | 47 |
45 /// The subscription to the stream returned by [_loadSuites]. | 48 /// The subscription to the stream returned by [_loadSuites]. |
46 StreamSubscription _suiteSubscription; | 49 StreamSubscription _suiteSubscription; |
47 | 50 |
51 /// The set of suite paths for which [_warnForUnknownTags] has already been | |
52 /// called. | |
53 /// | |
54 /// This is used to avoid printing duplicate warnings when a suite is loaded | |
55 /// on multiple platforms. | |
56 final _tagWarningSuites = new Set<String>(); | |
57 | |
48 /// The memoizer for ensuring [close] only runs once. | 58 /// The memoizer for ensuring [close] only runs once. |
49 final _closeMemo = new AsyncMemoizer(); | 59 final _closeMemo = new AsyncMemoizer(); |
50 bool get _closed => _closeMemo.hasRun; | 60 bool get _closed => _closeMemo.hasRun; |
51 | 61 |
52 /// Creates a new runner based on [configuration]. | 62 /// Creates a new runner based on [configuration]. |
53 factory Runner(Configuration config) { | 63 factory Runner(Configuration config) { |
54 var loader = new Loader(config); | 64 var loader = new Loader(config); |
55 var engine = new Engine(concurrency: config.concurrency); | 65 var engine = new Engine(concurrency: config.concurrency); |
56 | 66 |
57 var reporter; | 67 var reporter; |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
163 return mergeStreams(_config.paths.map((path) { | 173 return mergeStreams(_config.paths.map((path) { |
164 if (new Directory(path).existsSync()) return _loader.loadDir(path); | 174 if (new Directory(path).existsSync()) return _loader.loadDir(path); |
165 if (new File(path).existsSync()) return _loader.loadFile(path); | 175 if (new File(path).existsSync()) return _loader.loadFile(path); |
166 | 176 |
167 return new Stream.fromIterable([ | 177 return new Stream.fromIterable([ |
168 new LoadSuite("loading $path", () => | 178 new LoadSuite("loading $path", () => |
169 throw new LoadException(path, 'Does not exist.')) | 179 throw new LoadException(path, 'Does not exist.')) |
170 ]); | 180 ]); |
171 })).map((loadSuite) { | 181 })).map((loadSuite) { |
172 return loadSuite.changeSuite((suite) { | 182 return loadSuite.changeSuite((suite) { |
183 _warnForUnknownTags(suite); | |
184 | |
173 return suite.filter((test) { | 185 return suite.filter((test) { |
174 // Warn if any test has tags that don't appear on the command line. | |
175 // | |
176 // TODO(nweiz): Only print this once per test, even if it's run on | |
177 // multiple runners. | |
178 // | |
179 // TODO(nweiz): If groups or suites are tagged, don't print this for | |
180 // every test they contain. | |
181 // | |
182 // TODO(nweiz): Print this as part of the test's output so it's easy | |
183 // to associate with the correct test. | |
184 var specifiedTags = _config.tags.union(_config.excludeTags); | |
185 var unrecognizedTags = test.metadata.tags.difference(specifiedTags); | |
186 if (unrecognizedTags.isNotEmpty) { | |
187 // Pause the reporter while we print to ensure that we don't | |
188 // interfere with its output. | |
189 _reporter.pause(); | |
190 warn( | |
191 'Unknown ${pluralize('tag', unrecognizedTags.length)} ' | |
192 '${toSentence(unrecognizedTags)} in test "${test.name}".', | |
193 color: _config.color); | |
194 _reporter.resume(); | |
195 } | |
196 | |
197 // Skip any tests that don't match the given pattern. | 186 // Skip any tests that don't match the given pattern. |
198 if (_config.pattern != null && !test.name.contains(_config.pattern)) { | 187 if (_config.pattern != null && !test.name.contains(_config.pattern)) { |
199 return false; | 188 return false; |
200 } | 189 } |
201 | 190 |
202 // If the user provided tags, skip tests that don't match all of them. | 191 // If the user provided tags, skip tests that don't match all of them. |
203 if (!_config.tags.isEmpty && | 192 if (!_config.tags.isEmpty && |
204 !test.metadata.tags.containsAll(_config.tags)) { | 193 !test.metadata.tags.containsAll(_config.tags)) { |
205 return false; | 194 return false; |
206 } | 195 } |
207 | 196 |
208 // Skip tests that do match any tags the user wants to exclude. | 197 // Skip tests that do match any tags the user wants to exclude. |
209 if (_config.excludeTags.intersection(test.metadata.tags).isNotEmpty) { | 198 if (_config.excludeTags.intersection(test.metadata.tags).isNotEmpty) { |
210 return false; | 199 return false; |
211 } | 200 } |
212 | 201 |
213 return true; | 202 return true; |
214 }); | 203 }); |
215 }); | 204 }); |
216 }); | 205 }); |
217 } | 206 } |
218 | 207 |
208 /// Prints a warning for any unknown tags referenced in [suite] or its | |
209 /// children. | |
210 void _warnForUnknownTags(Suite suite) { | |
kevmoo
2015/12/02 04:57:01
missing an import containing Suite
nweiz
2015/12/02 22:21:16
Done.
| |
211 if (_tagWarningSuites.contains(suite.path)) return; | |
212 _tagWarningSuites.add(suite.path); | |
213 | |
214 var unknownTags = _collectUnknownTags(suite); | |
215 if (unknownTags.isEmpty) return; | |
216 | |
217 var yellow = _config.color ? '\u001b[33m' : ''; | |
218 var bold = _config.color ? '\u001b[1m' : ''; | |
219 var noColor = _config.color ? '\u001b[0m' : ''; | |
220 | |
221 var buffer = new StringBuffer() | |
222 ..write("${yellow}Warning:$noColor ") | |
223 ..write(unknownTags.length == 1 ? "A tag was " : "Tags were ") | |
224 ..write("used that ") | |
225 ..write(unknownTags.length == 1 ? "wasn't " : "weren't ") | |
226 ..writeln("specified on the command line."); | |
227 | |
228 unknownTags.forEach((tag, entries) { | |
229 buffer.write(" $bold$tag$noColor was used in"); | |
230 | |
231 if (entries.length == 1) { | |
232 buffer.writeln(" ${_entryDescription(entries.single)}"); | |
233 return; | |
234 } | |
235 | |
236 buffer.write(":"); | |
237 for (var entry in entries) { | |
238 buffer.write("\n ${_entryDescription(entry)}"); | |
239 } | |
240 buffer.writeln(); | |
241 }); | |
242 | |
243 print(buffer.toString()); | |
244 } | |
245 | |
246 /// Collects all tags used by [suite] or its children that aren't also passed | |
247 /// on the command line. | |
248 /// | |
249 /// This returns a map from tag names to lists of entries that use those tags. | |
250 Map<String, List<GroupEntry>> _collectUnknownTags(Suite suite) { | |
251 var knownTags = _config.tags.union(_config.excludeTags); | |
252 var unknownTags = {}; | |
253 var currentTags = new Set(); | |
254 | |
255 collect(entry) { | |
256 var newTags = new Set(); | |
257 for (var unknownTag in entry.metadata.tags.difference(knownTags)) { | |
258 if (currentTags.contains(unknownTag)) continue; | |
259 unknownTags.putIfAbsent(unknownTag, () => []).add(entry); | |
260 newTags.add(unknownTag); | |
261 } | |
262 | |
263 if (entry is! Group) return; | |
264 | |
265 currentTags.addAll(newTags); | |
266 for (var child in entry.entries) { | |
267 collect(child); | |
268 } | |
269 currentTags.removeAll(newTags); | |
270 } | |
271 | |
272 collect(suite.group); | |
273 return unknownTags; | |
274 } | |
275 | |
276 /// Returns a human-readable description of [entry], including its type. | |
277 String _entryDescription(GroupEntry entry) { | |
278 if (entry is Test) return 'the test "${entry.name}"'; | |
279 if (entry.name != null) return 'the group "${entry.name}"'; | |
280 return 'the suite itself'; | |
281 } | |
282 | |
219 /// Loads each suite in [suites] in order, pausing after load for platforms | 283 /// Loads each suite in [suites] in order, pausing after load for platforms |
220 /// that support debugging. | 284 /// that support debugging. |
221 Future<bool> _loadThenPause(Stream<LoadSuite> suites) async { | 285 Future<bool> _loadThenPause(Stream<LoadSuite> suites) async { |
222 if (_config.platforms.contains(TestPlatform.vm)) { | 286 if (_config.platforms.contains(TestPlatform.vm)) { |
223 warn("Debugging is currently unsupported on the Dart VM.", | 287 warn("Debugging is currently unsupported on the Dart VM.", |
224 color: _config.color); | 288 color: _config.color); |
225 } | 289 } |
226 | 290 |
227 _suiteSubscription = suites.asyncMap((loadSuite) async { | 291 _suiteSubscription = suites.asyncMap((loadSuite) async { |
228 // Make the underlying suite null so that the engine doesn't start running | 292 // Make the underlying suite null so that the engine doesn't start running |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
298 | 362 |
299 await inCompletionOrder([ | 363 await inCompletionOrder([ |
300 suite.environment.displayPause(), | 364 suite.environment.displayPause(), |
301 cancelableNext(stdinLines) | 365 cancelableNext(stdinLines) |
302 ]).first; | 366 ]).first; |
303 } finally { | 367 } finally { |
304 _reporter.resume(); | 368 _reporter.resume(); |
305 } | 369 } |
306 } | 370 } |
307 } | 371 } |
OLD | NEW |