Chromium Code Reviews| 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 |