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