Chromium Code Reviews

Side by Side Diff: lib/unittest.dart

Issue 934413002: Replace the existing unittest APIs with the new runner infrastructure. (Closed) Base URL: git@github.com:dart-lang/unittest@master
Patch Set: Rebase Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff |
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 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 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 unittest; 5 library unittest;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection'; 8
9 import 'package:path/path.dart' as p;
9 10
10 import 'src/configuration.dart'; 11 import 'src/configuration.dart';
11 import 'src/expected_function.dart'; 12 import 'src/declarer.dart';
12 import 'src/group_context.dart'; 13 import 'src/console_reporter.dart';
13 import 'src/internal_test_case.dart'; 14 import 'src/invoker.dart';
15 import 'src/suite.dart';
14 import 'src/test_case.dart'; 16 import 'src/test_case.dart';
15 import 'src/test_environment.dart';
16 17
17 export 'package:matcher/matcher.dart' 18 export 'package:matcher/matcher.dart'
18 hide 19 hide
19 completes, 20 completes,
20 completion, 21 completion,
21 configureExpectFailureHandler, 22 configureExpectFailureHandler,
22 DefaultFailureHandler, 23 DefaultFailureHandler,
23 ErrorFormatter, 24 ErrorFormatter,
24 expect, 25 expect,
25 fail, 26 fail,
(...skipping 11 matching lines...)
37 throwsFormatException, 38 throwsFormatException,
38 throwsNoSuchMethodError, 39 throwsNoSuchMethodError,
39 throwsNullThrownError, 40 throwsNullThrownError,
40 throwsRangeError, 41 throwsRangeError,
41 throwsStateError, 42 throwsStateError,
42 throwsUnimplementedError, 43 throwsUnimplementedError,
43 throwsUnsupportedError; 44 throwsUnsupportedError;
44 45
45 export 'src/configuration.dart'; 46 export 'src/configuration.dart';
46 export 'src/expect.dart'; 47 export 'src/expect.dart';
47 export 'src/simple_configuration.dart'; 48 export 'src/expect_async.dart';
48 export 'src/future_matchers.dart'; 49 export 'src/future_matchers.dart';
49 export 'src/prints_matcher.dart'; 50 export 'src/prints_matcher.dart';
51 export 'src/simple_configuration.dart';
52 export 'src/test_case.dart';
50 export 'src/throws_matcher.dart'; 53 export 'src/throws_matcher.dart';
51 export 'src/throws_matchers.dart'; 54 export 'src/throws_matchers.dart';
52 export 'src/test_case.dart';
53 55
54 /// The signature for a function passed to [test]. 56 Declarer _globalDeclarer;
55 typedef dynamic TestFunction();
56 57
57 /// [Configuration] used by the unittest library. 58 Declarer get _declarer {
58 /// 59 var declarer = Zone.current[#unittest.declarer];
59 /// Note that if a configuration has not been set, calling this getter will 60 if (declarer != null) return declarer;
60 /// create a default configuration. 61 if (_globalDeclarer != null) return _globalDeclarer;
61 Configuration get unittestConfiguration { 62
62 if (config == null) environment.config = new Configuration(); 63 _globalDeclarer = new Declarer();
63 return config; 64 scheduleMicrotask(() {
65 var suite = new Suite(p.prettyUri(Uri.base), _globalDeclarer.tests);
66 // TODO(nweiz): Use a reporter that doesn't import dart:io here.
67 // TODO(nweiz): Set the exit code on the VM when issue 6943 is fixed.
68 new ConsoleReporter([suite]).run();
69 });
70 return _globalDeclarer;
64 } 71 }
65 72
66 /// If `true`, stack traces are reformatted to be more readable. 73 // TODO(nweiz): This and other top-level functions should throw exceptions if
67 bool formatStacks = true; 74 // they're called after the declarer has finished declaring.
75 void test(String description, body()) => _declarer.test(description, body);
68 76
69 /// If `true`, irrelevant frames are filtered from the stack trace. 77 void group(String description, void body()) =>
70 /// 78 _declarer.group(description, body);
71 /// This does nothing if [formatStacks] is false.
72 bool filterStacks = true;
73 79
74 /// Separator used between group names and test names. 80 void setUp(callback()) => _declarer.setUp(callback);
75 String groupSep = ' ';
76 81
77 /// Sets the [Configuration] used by the unittest library. 82 void tearDown(callback()) => _declarer.tearDown(callback);
78 ///
79 /// Throws a [StateError] if there is an existing, incompatible value.
80 void set unittestConfiguration(Configuration value) {
81 if (identical(config, value)) return;
82 if (config != null) {
83 logMessage('Warning: The unittestConfiguration has already been set. New '
84 'unittestConfiguration ignored.');
85 } else {
86 environment.config = value;
87 }
88 }
89
90 /// Logs [message] associated with the current test case.
91 ///
92 /// Tests should use this instead of [print].
93 void logMessage(String message) =>
94 config.onLogMessage(currentTestCase, message);
95
96 /// The test cases that have been defined so far.
97 List<TestCase> get testCases =>
98 new UnmodifiableListView<TestCase>(environment.testCases);
99
100 /// The interval (in milliseconds) after which a non-microtask asynchronous
101 /// delay will be scheduled between tests.
102 ///
103 /// This is used to avoid starving the DOM or other non-microtask events.
104 const int BREATH_INTERVAL = 200;
105
106 /// The [TestCase] currently being executed.
107 TestCase get currentTestCase => (environment.currentTestCaseIndex >= 0 &&
108 environment.currentTestCaseIndex < testCases.length)
109 ? testCases[environment.currentTestCaseIndex]
110 : null;
111
112 /// The same as [currentTestCase], but typed as an [InternalTestCase].
113 InternalTestCase get _currentTestCase => currentTestCase as InternalTestCase;
114
115 /// The result string for a passing test case.
116 const PASS = 'pass';
117
118 /// The result string for a failing test case.
119 const FAIL = 'fail';
120
121 /// The result string for an test case with an error.
122 const ERROR = 'error';
123
124 /// Creates a new test case with the given description and body.
125 ///
126 /// The description will be added to the descriptions of any surrounding
127 /// [group]s.
128 void test(String description, TestFunction body) {
129 _requireNotRunning();
130 ensureInitialized();
131
132 if (environment.soloTestSeen && environment.soloNestingLevel == 0) return;
133 var testCase = new InternalTestCase(
134 testCases.length + 1, _fullDescription(description), body);
135 environment.testCases.add(testCase);
136 }
137
138 /// Returns [description] with all of its group prefixes prepended.
139 String _fullDescription(String description) {
140 var group = environment.currentContext.fullName;
141 if (description == null) return group;
142 return group != '' ? '$group$groupSep$description' : description;
143 }
144
145 /// A convenience function for skipping a test.
146 void skip_test(String spec, TestFunction body) {}
147
148 /// Creates a new test case with the given description and body.
149 ///
150 /// If [solo_test] is used instead of [test], then all non-solo tests will be
151 /// disabled. Note that if [solo_group] is used as well, all tests in the group
152 /// will be enabled, regardless of whether they use [test] or [solo_test], or
153 /// whether they are in a nested [group] versus [solo_group]. Put another way,
154 /// if there are any calls to [solo_test] or [solo_group] in a test file, all
155 /// tests that are not inside a [solo_group] will be disabled unless they are
156 /// [solo_test]s.
157 void solo_test(String spec, TestFunction body) {
158 _requireNotRunning();
159 ensureInitialized();
160 if (!environment.soloTestSeen) {
161 environment.soloTestSeen = true;
162 // This is the first solo-ed test. Discard all tests up to now.
163 environment.testCases.clear();
164 }
165 environment.soloNestingLevel++;
166 try {
167 test(spec, body);
168 } finally {
169 environment.soloNestingLevel--;
170 }
171 }
172
173 /// Indicate that [callback] is expected to be called [count] number of times
174 /// (by default 1).
175 ///
176 /// The unittest framework will wait for the callback to run the [count] times
177 /// before it considers the current test to be complete. Using [expectAsync]
178 /// will also ensure that errors that occur within [callback] are tracked and
179 /// reported. [callback] may take up to six optional or required positional
180 /// arguments; named arguments are not supported.
181 ///
182 /// [max] can be used to specify an upper bound on the number of calls; if this
183 /// is exceeded the test will fail. If [max] is `0` (the default), the callback
184 /// is expected to be called exactly [count] times. If [max] is `-1`, the
185 /// callback is allowed to be called any number of times greater than [count].
186 ///
187 /// Both [id] and [reason] are optional and provide extra information about the
188 /// callback when debugging. [id] should be the name of the callback, while
189 /// [reason] should be the reason the callback is expected to be called.
190 Function expectAsync(Function callback,
191 {int count: 1, int max: 0, String id, String reason}) =>
192 new ExpectedFunction(callback, count, max, id: id, reason: reason).func;
193
194 /// Indicate that [callback] is expected to be called until [isDone] returns
195 /// true.
196 ///
197 /// [isDone] is called after each time the function is run. Only when it returns
198 /// true will the callback be considered complete. Using [expectAsyncUntil] will
199 /// also ensure that errors that occur within [callback] are tracked and
200 /// reported. [callback] may take up to six optional or required positional
201 /// arguments; named arguments are not supported.
202 ///
203 /// Both [id] and [reason] are optional and provide extra information about the
204 /// callback when debugging. [id] should be the name of the callback, while
205 /// [reason] should be the reason the callback is expected to be called.
206 Function expectAsyncUntil(Function callback, bool isDone(),
207 {String id, String reason}) => new ExpectedFunction(callback, 0, -1,
208 id: id, reason: reason, isDone: isDone).func;
209
210 /// Creates a group of tests.
211 ///
212 /// A group's description is included in the descriptions of any tests or
213 /// sub-groups it contains. [setUp] and [tearDown] are also scoped to the
214 /// containing group.
215 void group(String description, void body()) {
216 ensureInitialized();
217 _requireNotRunning();
218 environment.currentContext =
219 new GroupContext(environment.currentContext, description);
220 try {
221 body();
222 } catch (e, trace) {
223 var stack = (trace == null) ? '' : ': ${trace.toString()}';
224 environment.uncaughtErrorMessage = "${e.toString()}$stack";
225 } finally {
226 // Now that the group is over, restore the previous one.
227 environment.currentContext = environment.currentContext.parent;
228 }
229 }
230
231 /// A convenience function for skipping a group of tests.
232 void skip_group(String description, void body()) {}
233
234 /// Creates a group of tests.
235 ///
236 /// If [solo_group] is used instead of [group], then all tests not declared with
237 /// [solo_test] or in a [solo_group] will be disabled. Note that all tests in a
238 /// [solo_group] will be run, regardless of whether they're declared with [test]
239 /// or [solo_test].
240 ///
241 /// [skip_test] and [skip_group] take precedence over [solo_group].
242 void solo_group(String description, void body()) {
243 _requireNotRunning();
244 ensureInitialized();
245 if (!environment.soloTestSeen) {
246 environment.soloTestSeen = true;
247 // This is the first solo-ed group. Discard all tests up to now.
248 environment.testCases.clear();
249 }
250 ++environment.soloNestingLevel;
251 try {
252 group(description, body);
253 } finally {
254 --environment.soloNestingLevel;
255 }
256 }
257
258 /// Registers a function to be run before tests.
259 ///
260 /// This function will be called before each test is run. [callback] may be
261 /// asynchronous; if so, it must return a [Future].
262 ///
263 /// If this is called within a test group, it applies only to tests in that
264 /// group. [callback] will be run after any set-up callbacks in parent groups or
265 /// at the top level.
266 void setUp(Function callback) {
267 _requireNotRunning();
268 environment.currentContext.testSetUp = callback;
269 }
270
271 /// Registers a function to be run after tests.
272 ///
273 /// This function will be called after each test is run. [callback] may be
274 /// asynchronous; if so, it must return a [Future].
275 ///
276 /// If this is called within a test group, it applies only to tests in that
277 /// group. [callback] will be run before any tear-down callbacks in parent group s or
278 /// at the top level.
279 void tearDown(Function callback) {
280 _requireNotRunning();
281 environment.currentContext.testTearDown = callback;
282 }
283
284 /// Advance to the next test case.
285 void _nextTestCase() {
286 environment.currentTestCaseIndex++;
287 _runTest();
288 }
289 83
290 /// Handle an error that occurs outside of any test. 84 /// Handle an error that occurs outside of any test.
291 void handleExternalError(e, String message, [stackTrace]) { 85 void handleExternalError(error, String message, [stackTrace]) {
292 var msg = '$message\nCaught $e'; 86 // TODO(nweiz): handle this better.
293 87 registerException(error, stackTrace);
294 if (currentTestCase != null) {
295 _currentTestCase.error(msg, stackTrace);
296 } else {
297 environment.uncaughtErrorMessage = "$msg: $stackTrace";
298 }
299 }
300
301 /// Remove any tests that match [testFilter].
302 ///
303 /// [testFilter] can be a predicate function, a [RegExp], or a [String]. If it's
304 /// a function, it's called with each [TestCase]. If it's a [String], it's
305 /// parsed as a [RegExp] and matched against each [TestCase.description].
306 ///
307 /// This is different from enabling or disabling tests in that it removes the
308 /// tests completely.
309 void filterTests(testFilter) {
310 var filterFunction;
311 if (testFilter is String) {
312 var re = new RegExp(testFilter);
313 filterFunction = (t) => re.hasMatch(t.description);
314 } else if (testFilter is RegExp) {
315 filterFunction = (t) => testFilter.hasMatch(t.description);
316 } else if (testFilter is Function) {
317 filterFunction = testFilter;
318 }
319 environment.testCases.retainWhere(filterFunction);
320 }
321
322 /// Runs all queued tests, one at a time.
323 void runTests() {
324 _requireNotRunning();
325 _ensureInitialized(false);
326 environment.currentTestCaseIndex = 0;
327 config.onStart();
328 _runTest();
329 } 88 }
330 89
331 /// Registers an exception that was caught for the current test. 90 /// Registers an exception that was caught for the current test.
332 void registerException(error, [StackTrace stackTrace]) => 91 void registerException(error, [StackTrace stackTrace]) =>
333 _currentTestCase.registerException(error, stackTrace); 92 Invoker.current.handleError(error, stackTrace);
334 93
335 /// Runs the next test. 94 // What follows are stubs for various top-level names supported by unittest
336 void _runTest() { 95 // 0.11.*. These are preserved for the time being for ease of migration, but
337 if (environment.currentTestCaseIndex >= testCases.length) { 96 // should be removed before this is released as stable.
338 assert(environment.currentTestCaseIndex == testCases.length);
339 _completeTests();
340 return;
341 }
342 97
343 var testCase = _currentTestCase; 98 @deprecated
344 var f = runZoned(testCase.run, onError: (error, stack) { 99 typedef dynamic TestFunction();
345 // TODO(kevmoo) Do a better job of flagging these are async errors.
346 // https://code.google.com/p/dart/issues/detail?id=16530
347 testCase.registerException(error, stack);
348 });
349 100
350 var timer; 101 @deprecated
351 var timeout = unittestConfiguration.timeout; 102 Configuration unittestConfiguration = new Configuration();
352 if (timeout != null) {
353 try {
354 timer = new Timer(timeout, () {
355 testCase.error("Test timed out after ${timeout.inSeconds} seconds.");
356 _nextTestCase();
357 });
358 } on UnsupportedError catch (e) {
359 if (e.message != "Timer greater than 0.") rethrow;
360 // Support running on d8 and jsshell which don't support timers.
361 }
362 }
363 103
364 f.whenComplete(() { 104 @deprecated
365 if (timer != null) timer.cancel(); 105 bool formatStacks = true;
366 var now = new DateTime.now().millisecondsSinceEpoch;
367 if (now - environment.lastBreath >= BREATH_INTERVAL) {
368 environment.lastBreath = now;
369 Timer.run(_nextTestCase);
370 } else {
371 scheduleMicrotask(_nextTestCase); // Schedule the next test.
372 }
373 });
374 }
375 106
376 /// Notify the configuration that the testing has finished. 107 @deprecated
377 void _completeTests() { 108 bool filterStacks = true;
378 if (!environment.initialized) return;
379 109
380 var passed = 0; 110 @deprecated
381 var failed = 0; 111 String groupSep = ' ';
382 var errors = 0;
383 for (var testCase in testCases) {
384 switch (testCase.result) {
385 case PASS:
386 passed++;
387 break;
388 case FAIL:
389 failed++;
390 break;
391 case ERROR:
392 errors++;
393 break;
394 }
395 }
396 112
397 config.onSummary( 113 @deprecated
398 passed, failed, errors, testCases, environment.uncaughtErrorMessage); 114 void logMessage(String message) => print(message);
399 config.onDone(passed > 0 &&
400 failed == 0 &&
401 errors == 0 &&
402 environment.uncaughtErrorMessage == null);
403 environment.initialized = false;
404 environment.currentTestCaseIndex = -1;
405 }
406 115
407 /// Initializes the test environment if it hasn't already been initialized. 116 @deprecated
408 void ensureInitialized() { 117 final testCases = [];
409 _ensureInitialized(true);
410 }
411 118
412 /// Initializes the test environment. 119 @deprecated
413 /// 120 const int BREATH_INTERVAL = 200;
414 /// If [configAutoStart] is `true`, schedule a microtask to run the tests. This
415 /// microtask is expected to run after all the tests are defined.
416 void _ensureInitialized(bool configAutoStart) {
417 if (environment.initialized) return;
418 121
419 environment.initialized = true; 122 @deprecated
123 TestCase get currentTestCase => null;
420 124
421 environment.uncaughtErrorMessage = null; 125 @deprecated
126 const PASS = 'pass';
422 127
423 unittestConfiguration.onInit(); 128 @deprecated
129 const FAIL = 'fail';
424 130
425 // Immediately queue the suite up. It will run after a timeout (i.e. after 131 @deprecated
426 // main() has returned). 132 const ERROR = 'error';
427 if (configAutoStart && config.autoStart) scheduleMicrotask(runTests);
428 }
429 133
430 /// Remove all tests other than the one identified by [id]. 134 @deprecated
431 void setSoloTest(int id) => 135 void skip_test(String spec, TestFunction body) {}
432 environment.testCases.retainWhere((t) => t.id == id);
433 136
434 /// Enable the test identified by [id]. 137 @deprecated
435 void enableTest(int id) => _setTestEnabledState(id, enable: true); 138 void solo_test(String spec, TestFunction body) => test(spec, body);
436 139
437 /// Disable the test by [id]. 140 @deprecated
438 void disableTest(int id) => _setTestEnabledState(id, enable: false); 141 void skip_group(String description, void body()) {}
439 142
440 /// Enable or disable the test identified by [id]. 143 @deprecated
441 void _setTestEnabledState(int id, {bool enable: true}) { 144 void solo_group(String description, void body()) => group(description, body);
442 // Try fast path first.
443 if (testCases.length > id && testCases[id].id == id) {
444 environment.testCases[id].enabled = enable;
445 } else {
446 for (var i = 0; i < testCases.length; i++) {
447 if (testCases[i].id != id) continue;
448 environment.testCases[i].enabled = enable;
449 break;
450 }
451 }
452 }
453 145
454 /// Throws a [StateError] if tests are running. 146 @deprecated
455 void _requireNotRunning() { 147 void filterTests(testFilter) {}
456 if (environment.currentTestCaseIndex == -1) return;
457 throw new StateError('Not allowed when tests are running.');
458 }
459 148
460 /// Creates a test environment running in its own zone scope. 149 @deprecated
461 /// 150 void runTests() {}
462 /// This allows for multiple invocations of the unittest library in the same 151
463 /// application instance. This is useful when, for example, creating a test 152 @deprecated
464 /// runner application which needs to create a new pristine test environment on 153 void ensureInitialized() {}
465 /// each invocation to run a given set of tests. 154
466 withTestEnvironment(callback()) { 155 @deprecated
467 return runZoned(callback, 156 void setSoloTest(int id) {}
468 zoneValues: {#unittest.environment: new TestEnvironment()}); 157
469 } 158 @deprecated
159 void enableTest(int id) {}
160
161 @deprecated
162 void disableTest(int id) {}
163
164 @deprecated
165 withTestEnvironment(callback()) => callback();
OLDNEW

Powered by Google App Engine