OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, 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 /** | |
6 * A library for writing dart unit tests. | |
7 * | |
8 * To import this library, specify the relative path to | |
9 * pkg/unittest/unittest.dart. | |
10 * | |
11 * ##Concepts## | |
12 * | |
13 * * Tests: Tests are specified via the top-level function [test], they can be | |
14 * organized together using [group]. | |
15 * * Checks: Test expectations can be specified via [expect] | |
16 * * Matchers: [expect] assertions are written declaratively using [Matcher]s | |
17 * * Configuration: The framework can be adapted by calling [configure] with a | |
18 * [Configuration]. Common configurations can be found in this package | |
19 * under: 'dom\_config.dart' (deprecated), 'html\_config.dart' (for running | |
20 * tests compiled to Javascript in a browser), and 'vm\_config.dart' (for | |
21 * running native Dart tests on the VM). | |
22 * | |
23 * ##Examples## | |
24 * | |
25 * A trivial test: | |
26 * | |
27 * #import('path-to-dart/pkg/unittest/unitest.dart'); | |
28 * main() { | |
29 * test('this is a test', () { | |
30 * int x = 2 + 3; | |
31 * expect(x, equals(5)); | |
32 * }); | |
33 * } | |
34 * | |
35 * Multiple tests: | |
36 * | |
37 * #import('path-to-dart/pkg/unittest/unitest.dart'); | |
38 * main() { | |
39 * test('this is a test', () { | |
40 * int x = 2 + 3; | |
41 * expect(x, equals(5)); | |
42 * }); | |
43 * test('this is another test', () { | |
44 * int x = 2 + 3; | |
45 * expect(x, equals(5)); | |
46 * }); | |
47 * } | |
48 * | |
49 * Multiple tests, grouped by category: | |
50 * | |
51 * #import('path-to-dart/pkg/unittest/unitest.dart'); | |
52 * main() { | |
53 * group('group A', () { | |
54 * test('test A.1', () { | |
55 * int x = 2 + 3; | |
56 * expect(x, equals(5)); | |
57 * }); | |
58 * test('test A.2', () { | |
59 * int x = 2 + 3; | |
60 * expect(x, equals(5)); | |
61 * }); | |
62 * }); | |
63 * group('group B', () { | |
64 * test('this B.1', () { | |
65 * int x = 2 + 3; | |
66 * expect(x, equals(5)); | |
67 * }); | |
68 * }); | |
69 * } | |
70 * | |
71 * Asynchronous tests: if callbacks expect between 0 and 2 positional arguments, | |
72 * depending on the suffix of expectAsyncX(). expectAsyncX() will wrap a | |
73 * function into a new callback and will not consider the test complete until | |
74 * that callback is run. A count argument can be provided to specify the number | |
75 * of times the callback should be called (the default is 1). | |
76 * | |
77 * #import('path-to-dart/pkg/unittest/unitest.dart'); | |
78 * #import('dart:html'); | |
79 * main() { | |
80 * test('calllback is executed once', () { | |
81 * // wrap the callback of an asynchronous call with [expectAsync0] if | |
82 * // the callback takes 0 arguments... | |
83 * window.setTimeout(expectAsync0(() { | |
84 * int x = 2 + 3; | |
85 * expect(x, equals(5)); | |
86 * }), 0); | |
87 * }); | |
88 * | |
89 * test('calllback is executed twice', () { | |
90 * var callback = expectAsync0(() { | |
91 * int x = 2 + 3; | |
92 * expect(x, equals(5)); | |
93 * }, count: 2); // <-- we can indicate multiplicity to [expectAsync0] | |
94 * window.setTimeout(callback, 0); | |
95 * window.setTimeout(callback, 0); | |
96 * }); | |
97 * } | |
98 * | |
99 * expectAsyncX() will wrap the callback code in a try/catch handler to handle | |
100 * exceptions (treated as test failures). There may be times when the number of | |
101 * times a callback should be called is non-deterministic. In this case a dummy | |
102 * callback can be created with expectAsync0((){}) and this can be called from | |
103 * the real callback when it is finally complete. In this case the body of the | |
104 * callback should be protected within a call to guardAsync(); this will ensure | |
105 * that exceptions are properly handled. | |
106 * | |
107 * Note: due to some language limitations we have to use different functions | |
108 * depending on the number of positional arguments of the callback. In the | |
109 * future, we plan to expose a single `expectAsync` function that can be used | |
110 * regardless of the number of positional arguments. This requires new langauge | |
111 * features or fixes to the current spec (e.g. see | |
112 * [Issue 2706](http://dartbug.com/2706)). | |
113 * | |
114 * Meanwhile, we plan to add this alternative API for callbacks of more than 2 | |
115 * arguments or that take named parameters. (this is not implemented yet, | |
116 * but will be coming here soon). | |
117 * | |
118 * #import('path-to-dart/pkg/unittest/unitest.dart'); | |
119 * #import('dart:html'); | |
120 * main() { | |
121 * test('calllback is executed', () { | |
122 * // indicate ahead of time that an async callback is expected. | |
123 * var async = startAsync(); | |
124 * window.setTimeout(() { | |
125 * // Guard the body of the callback, so errors are propagated | |
126 * // correctly | |
127 * guardAsync(() { | |
128 * int x = 2 + 3; | |
129 * expect(x, equals(5)); | |
130 * }); | |
131 * // indicate that the asynchronous callback was invoked. | |
132 * async.complete(); | |
133 * }), 0); | |
134 * }); | |
135 * | |
136 */ | |
137 #library('unittest'); | |
138 | |
139 #import('dart:isolate'); | |
140 | |
141 #source('collection_matchers.dart'); | |
142 #source('config.dart'); | |
143 #source('core_matchers.dart'); | |
144 #source('description.dart'); | |
145 #source('expect.dart'); | |
146 #source('future_matchers.dart'); | |
147 #source('interfaces.dart'); | |
148 #source('map_matchers.dart'); | |
149 #source('matcher.dart'); | |
150 #source('mock.dart'); | |
151 #source('numeric_matchers.dart'); | |
152 #source('operator_matchers.dart'); | |
153 #source('string_matchers.dart'); | |
154 #source('test_case.dart'); | |
155 | |
156 /** [Configuration] used by the unittest library. */ | |
157 Configuration _config = null; | |
158 | |
159 Configuration get config => _config; | |
160 | |
161 /** | |
162 * Set the [Configuration] used by the unittest library. Returns any | |
163 * previous configuration. | |
164 * TODO: consider deprecating in favor of a setter now we have a getter. | |
165 */ | |
166 Configuration configure(Configuration config) { | |
167 Configuration _oldConfig = _config; | |
168 _config = config; | |
169 return _oldConfig; | |
170 } | |
171 | |
172 void logMessage(String message) => _config.logMessage(message); | |
173 | |
174 /** | |
175 * Description text of the current test group. If multiple groups are nested, | |
176 * this will contain all of their text concatenated. | |
177 */ | |
178 String _currentGroup = ''; | |
179 | |
180 /** Separator used between group names and test names. */ | |
181 String groupSep = ' '; | |
182 | |
183 /** Tests executed in this suite. */ | |
184 List<TestCase> _tests; | |
185 | |
186 /** Get the list of tests. */ | |
187 get testCases => _tests; | |
188 | |
189 /** | |
190 * Callback used to run tests. Entrypoints can replace this with their own | |
191 * if they want. | |
192 */ | |
193 Function _testRunner; | |
194 | |
195 /** Setup function called before each test in a group */ | |
196 Function _testSetup; | |
197 | |
198 /** Teardown function called after each test in a group */ | |
199 Function _testTeardown; | |
200 | |
201 /** Current test being executed. */ | |
202 int _currentTest = 0; | |
203 | |
204 /** Whether the framework is in an initialized state. */ | |
205 bool _initialized = false; | |
206 | |
207 String _uncaughtErrorMessage = null; | |
208 | |
209 const _PASS = 'pass'; | |
210 const _FAIL = 'fail'; | |
211 const _ERROR = 'error'; | |
212 | |
213 /** If set, then all other test cases will be ignored. */ | |
214 TestCase _soloTest; | |
215 | |
216 /** | |
217 * (Deprecated) Evaluates the [function] and validates that it throws an | |
218 * exception. If [callback] is provided, then it will be invoked with the | |
219 * thrown exception. The callback may do any validation it wants. In addition, | |
220 * if it returns `false`, that also indicates an expectation failure. | |
221 */ | |
222 void expectThrow(function, [bool callback(exception)]) { | |
223 bool threw = false; | |
224 try { | |
225 function(); | |
226 } catch (e) { | |
227 threw = true; | |
228 | |
229 // Also let the callback look at it. | |
230 if (callback != null) { | |
231 var result = callback(e); | |
232 | |
233 // If the callback explicitly returned false, treat that like an | |
234 // expectation too. (If it returns null, though, don't.) | |
235 if (result == false) { | |
236 _fail('Exception:\n$e\ndid not match expectation.'); | |
237 } | |
238 } | |
239 } | |
240 | |
241 if (threw != true) _fail('An expected exception was not thrown.'); | |
242 } | |
243 | |
244 /** | |
245 * Creates a new test case with the given description and body. The | |
246 * description will include the descriptions of any surrounding group() | |
247 * calls. | |
248 */ | |
249 void test(String spec, TestFunction body) { | |
250 ensureInitialized(); | |
251 _tests.add(new TestCase(_tests.length + 1, _fullSpec(spec), body, 0)); | |
252 } | |
253 | |
254 /** | |
255 * (Deprecated) Creates a new async test case with the given description | |
256 * and body. The description will include the descriptions of any surrounding | |
257 * group() calls. | |
258 */ | |
259 // TODO(sigmund): deprecate this API | |
260 void asyncTest(String spec, int callbacks, TestFunction body) { | |
261 ensureInitialized(); | |
262 | |
263 final testCase = new TestCase( | |
264 _tests.length + 1, _fullSpec(spec), body, callbacks); | |
265 _tests.add(testCase); | |
266 | |
267 if (callbacks < 1) { | |
268 testCase.error( | |
269 'Async tests must wait for at least one callback ', ''); | |
270 } | |
271 } | |
272 | |
273 /** | |
274 * Creates a new test case with the given description and body. The | |
275 * description will include the descriptions of any surrounding group() | |
276 * calls. | |
277 * | |
278 * "solo_" means that this will be the only test that is run. All other tests | |
279 * will be skipped. This is a convenience function to let you quickly isolate | |
280 * a single test by adding "solo_" before it to temporarily disable all other | |
281 * tests. | |
282 */ | |
283 void solo_test(String spec, TestFunction body) { | |
284 // TODO(rnystrom): Support multiple solos. If more than one test is solo-ed, | |
285 // all of the solo-ed tests and none of the non-solo-ed ones should run. | |
286 if (_soloTest != null) { | |
287 throw new Exception('Only one test can be soloed right now.'); | |
288 } | |
289 | |
290 ensureInitialized(); | |
291 | |
292 _soloTest = new TestCase(_tests.length + 1, _fullSpec(spec), body, 0); | |
293 _tests.add(_soloTest); | |
294 } | |
295 | |
296 /** Sentinel value for [_SpreadArgsHelper]. */ | |
297 class _Sentinel { | |
298 const _Sentinel(); | |
299 } | |
300 | |
301 // TODO(sigmund): make a singleton const field when frog supports passing those | |
302 // as default values to named arguments. | |
303 const _sentinel = const _Sentinel(); | |
304 | |
305 /** Simulates spread arguments using named arguments. */ | |
306 // TODO(sigmund): remove this class and simply use a closure with named | |
307 // arguments (if still applicable). | |
308 class _SpreadArgsHelper { | |
309 Function _callback; | |
310 int _expectedCalls; | |
311 int _actualCalls = 0; | |
312 int _testNum; | |
313 TestCase _testCase; | |
314 Function _shouldCallBack; | |
315 Function _isDone; | |
316 | |
317 _init(Function callback, Function shouldCallBack, Function isDone, | |
318 [expectedCalls = 0]) { | |
319 ensureInitialized(); | |
320 assert(_currentTest < _tests.length); | |
321 _callback = callback; | |
322 _shouldCallBack = shouldCallBack; | |
323 _isDone = isDone; | |
324 _expectedCalls = expectedCalls; | |
325 _testNum = _currentTest; | |
326 _testCase = _tests[_currentTest]; | |
327 if (expectedCalls > 0) { | |
328 _testCase.callbackFunctionsOutstanding++; | |
329 } | |
330 } | |
331 | |
332 _SpreadArgsHelper(callback, shouldCallBack, isDone) { | |
333 _init(callback, shouldCallBack, isDone); | |
334 } | |
335 | |
336 _SpreadArgsHelper.fixedCallCount(callback, expectedCalls) { | |
337 _init(callback, _checkCallCount, _allCallsDone, expectedCalls); | |
338 } | |
339 | |
340 _SpreadArgsHelper.variableCallCount(callback, isDone) { | |
341 _init(callback, _always, isDone, 1); | |
342 } | |
343 | |
344 _SpreadArgsHelper.optionalCalls(callback) { | |
345 _init(callback, _always, () => false, 0); | |
346 } | |
347 | |
348 _after() { | |
349 if (_isDone()) { | |
350 _handleCallbackFunctionComplete(); | |
351 } | |
352 } | |
353 | |
354 _allCallsDone() => _actualCalls == _expectedCalls; | |
355 | |
356 _always() { | |
357 // Always run except if the test is done. | |
358 if (_testCase.isComplete) { | |
359 _testCase.error( | |
360 'Callback called after already being marked as done ($_actualCalls).', | |
361 ''); | |
362 return false; | |
363 } else { | |
364 return true; | |
365 } | |
366 } | |
367 | |
368 invoke([arg0 = _sentinel, arg1 = _sentinel, arg2 = _sentinel, | |
369 arg3 = _sentinel, arg4 = _sentinel]) { | |
370 return guardAsync(() { | |
371 ++_actualCalls; | |
372 if (!_shouldCallBack()) { | |
373 return; | |
374 } else if (arg0 == _sentinel) { | |
375 return _callback(); | |
376 } else if (arg1 == _sentinel) { | |
377 return _callback(arg0); | |
378 } else if (arg2 == _sentinel) { | |
379 return _callback(arg0, arg1); | |
380 } else if (arg3 == _sentinel) { | |
381 return _callback(arg0, arg1, arg2); | |
382 } else if (arg4 == _sentinel) { | |
383 return _callback(arg0, arg1, arg2, arg3); | |
384 } else { | |
385 _testCase.error( | |
386 'unittest lib does not support callbacks with more than' | |
387 ' 4 arguments.', | |
388 ''); | |
389 } | |
390 }, | |
391 _after, _testNum); | |
392 } | |
393 | |
394 invoke0() { | |
395 return guardAsync( | |
396 () { | |
397 ++_actualCalls; | |
398 if (_shouldCallBack()) { | |
399 return _callback(); | |
400 } | |
401 }, | |
402 _after, _testNum); | |
403 } | |
404 | |
405 invoke1(arg1) { | |
406 return guardAsync( | |
407 () { | |
408 ++_actualCalls; | |
409 if (_shouldCallBack()) { | |
410 return _callback(arg1); | |
411 } | |
412 }, | |
413 _after, _testNum); | |
414 } | |
415 | |
416 invoke2(arg1, arg2) { | |
417 return guardAsync( | |
418 () { | |
419 ++_actualCalls; | |
420 if (_shouldCallBack()) { | |
421 return _callback(arg1, arg2); | |
422 } | |
423 }, | |
424 _after, _testNum); | |
425 } | |
426 | |
427 /** Returns false if we exceded the number of expected calls. */ | |
428 bool _checkCallCount() { | |
429 if (_actualCalls > _expectedCalls) { | |
430 _testCase.error('Callback called more times than expected ' | |
431 '($_actualCalls > $_expectedCalls).', ''); | |
432 return false; | |
433 } | |
434 return true; | |
435 } | |
436 } | |
437 | |
438 /** | |
439 * Indicate that [callback] is expected to be called a [count] number of times | |
440 * (by default 1). The unittest framework will wait for the callback to run the | |
441 * specified [count] times before it continues with the following test. Using | |
442 * [_expectAsync] will also ensure that errors that occur within [callback] are | |
443 * tracked and reported. [callback] should take between 0 and 4 positional | |
444 * arguments (named arguments are not supported here). | |
445 */ | |
446 Function _expectAsync(Function callback, [int count = 1]) { | |
447 return new _SpreadArgsHelper.fixedCallCount(callback, count).invoke; | |
448 } | |
449 | |
450 /** | |
451 * Indicate that [callback] is expected to be called a [count] number of times | |
452 * (by default 1). The unittest framework will wait for the callback to run the | |
453 * specified [count] times before it continues with the following test. Using | |
454 * [expectAsync0] will also ensure that errors that occur within [callback] are | |
455 * tracked and reported. [callback] should take 0 positional arguments (named | |
456 * arguments are not supported). | |
457 */ | |
458 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | |
459 Function expectAsync0(Function callback, [int count = 1]) { | |
460 return new _SpreadArgsHelper.fixedCallCount(callback, count).invoke0; | |
461 } | |
462 | |
463 /** Like [expectAsync0] but [callback] should take 1 positional argument. */ | |
464 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | |
465 Function expectAsync1(Function callback, [int count = 1]) { | |
466 return new _SpreadArgsHelper.fixedCallCount(callback, count).invoke1; | |
467 } | |
468 | |
469 /** Like [expectAsync0] but [callback] should take 2 positional arguments. */ | |
470 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | |
471 Function expectAsync2(Function callback, [int count = 1]) { | |
472 return new _SpreadArgsHelper.fixedCallCount(callback, count).invoke2; | |
473 } | |
474 | |
475 /** | |
476 * Indicate that [callback] is expected to be called until [isDone] returns | |
477 * true. The unittest framework checks [isDone] after each callback and only | |
478 * when it returns true will it continue with the following test. Using | |
479 * [expectAsyncUntil] will also ensure that errors that occur within | |
480 * [callback] are tracked and reported. [callback] should take between 0 and | |
481 * 4 positional arguments (named arguments are not supported). | |
482 */ | |
483 Function _expectAsyncUntil(Function callback, Function isDone) { | |
484 return new _SpreadArgsHelper.variableCallCount(callback, isDone).invoke; | |
485 } | |
486 | |
487 /** | |
488 * Indicate that [callback] is expected to be called until [isDone] returns | |
489 * true. The unittest framework check [isDone] after each callback and only | |
490 * when it returns true will it continue with the following test. Using | |
491 * [expectAsyncUntil0] will also ensure that errors that occur within | |
492 * [callback] are tracked and reported. [callback] should take 0 positional | |
493 * arguments (named arguments are not supported). | |
494 */ | |
495 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | |
496 Function expectAsyncUntil0(Function callback, Function isDone) { | |
497 return new _SpreadArgsHelper.variableCallCount(callback, isDone).invoke0; | |
498 } | |
499 | |
500 /** | |
501 * Like [expectAsyncUntil0] but [callback] should take 1 positional argument. | |
502 */ | |
503 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | |
504 Function expectAsyncUntil1(Function callback, Function isDone) { | |
505 return new _SpreadArgsHelper.variableCallCount(callback, isDone).invoke1; | |
506 } | |
507 | |
508 /** | |
509 * Like [expectAsyncUntil0] but [callback] should take 2 positional arguments. | |
510 */ | |
511 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | |
512 Function expectAsyncUntil2(Function callback, Function isDone) { | |
513 return new _SpreadArgsHelper.variableCallCount(callback, isDone).invoke2; | |
514 } | |
515 | |
516 /** | |
517 * Wraps the [callback] in a new function and returns that function. The new | |
518 * function will be able to handle exceptions by directing them to the correct | |
519 * test. This is thus similar to expectAsync0. Use it to wrap any callbacks that | |
520 * might optionally be called but may never be called during the test. | |
521 * [callback] should take between 0 and 4 positional arguments (named arguments | |
522 * are not supported). | |
523 */ | |
524 Function _protectAsync(Function callback) { | |
525 return new _SpreadArgsHelper.optionalCalls(callback).invoke; | |
526 } | |
527 | |
528 /** | |
529 * Wraps the [callback] in a new function and returns that function. The new | |
530 * function will be able to handle exceptions by directing them to the correct | |
531 * test. This is thus similar to expectAsync0. Use it to wrap any callbacks that | |
532 * might optionally be called but may never be called during the test. | |
533 * [callback] should take 0 positional arguments (named arguments are not | |
534 * supported). | |
535 */ | |
536 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | |
537 Function protectAsync0(Function callback) { | |
538 return new _SpreadArgsHelper.optionalCalls(callback).invoke0; | |
539 } | |
540 | |
541 /** | |
542 * Like [protectAsync0] but [callback] should take 1 positional argument. | |
543 */ | |
544 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | |
545 Function protectAsync1(Function callback) { | |
546 return new _SpreadArgsHelper.optionalCalls(callback).invoke1; | |
547 } | |
548 | |
549 /** | |
550 * Like [protectAsync0] but [callback] should take 2 positional arguments. | |
551 */ | |
552 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | |
553 Function protectAsync2(Function callback) { | |
554 return new _SpreadArgsHelper.optionalCalls(callback).invoke2; | |
555 } | |
556 | |
557 /** | |
558 * Creates a new named group of tests. Calls to group() or test() within the | |
559 * body of the function passed to this will inherit this group's description. | |
560 */ | |
561 void group(String description, void body()) { | |
562 ensureInitialized(); | |
563 // Concatenate the new group. | |
564 final parentGroup = _currentGroup; | |
565 if (_currentGroup != '') { | |
566 // Add a space. | |
567 _currentGroup = '$_currentGroup$groupSep$description'; | |
568 } else { | |
569 // The first group. | |
570 _currentGroup = description; | |
571 } | |
572 | |
573 // Groups can be nested, so we need to preserve the current | |
574 // settings for test setup/teardown. | |
575 Function parentSetup = _testSetup; | |
576 Function parentTeardown = _testTeardown; | |
577 | |
578 try { | |
579 _testSetup = null; | |
580 _testTeardown = null; | |
581 body(); | |
582 } catch (e, trace) { | |
583 var stack = (trace == null) ? '' : ': ${trace.toString()}'; | |
584 _uncaughtErrorMessage = "${e.toString()}$stack"; | |
585 } finally { | |
586 // Now that the group is over, restore the previous one. | |
587 _currentGroup = parentGroup; | |
588 _testSetup = parentSetup; | |
589 _testTeardown = parentTeardown; | |
590 } | |
591 } | |
592 | |
593 /** | |
594 * Register a [setUp] function for a test [group]. This function will | |
595 * be called before each test in the group is run. Note that if groups | |
596 * are nested only the most locally scoped [setUp] function will be run. | |
597 * [setUp] and [tearDown] should be called within the [group] before any | |
598 * calls to [test]. | |
599 */ | |
600 void setUp(Function setupTest) { | |
601 _testSetup = setupTest; | |
602 } | |
603 | |
604 /** | |
605 * Register a [tearDown] function for a test [group]. This function will | |
606 * be called after each test in the group is run. Note that if groups | |
607 * are nested only the most locally scoped [tearDown] function will be run. | |
608 * [setUp] and [tearDown] should be called within the [group] before any | |
609 * calls to [test]. | |
610 */ | |
611 void tearDown(Function teardownTest) { | |
612 _testTeardown = teardownTest; | |
613 } | |
614 | |
615 /** | |
616 * Called when one of the callback functions is done with all expected | |
617 * calls. | |
618 */ | |
619 void _handleCallbackFunctionComplete() { | |
620 // TODO (gram): we defer this to give the nextBatch recursive | |
621 // stack a chance to unwind. This is a temporary hack but | |
622 // really a bunch of code here needs to be fixed. We have a | |
623 // single array that is being iterated through by nextBatch(), | |
624 // which is recursively invoked in the case of async tests that | |
625 // run synchronously. Bad things can then happen. | |
626 _defer(() { | |
627 if (_currentTest < _tests.length) { | |
628 final testCase = _tests[_currentTest]; | |
629 --testCase.callbackFunctionsOutstanding; | |
630 if (testCase.callbackFunctionsOutstanding < 0) { | |
631 // TODO(gram): Check: Can this even happen? | |
632 testCase.error( | |
633 'More calls to _handleCallbackFunctionComplete() than expected.', | |
634 ''); | |
635 } else if (testCase.callbackFunctionsOutstanding == 0) { | |
636 if (!testCase.isComplete) { | |
637 testCase.pass(); | |
638 } | |
639 _nextTestCase(); | |
640 } | |
641 } | |
642 }); | |
643 } | |
644 | |
645 /** Advance to the next test case. */ | |
646 void _nextTestCase() { | |
647 _currentTest++; | |
648 _testRunner(); | |
649 } | |
650 | |
651 /** | |
652 * Temporary hack: expose old API. | |
653 * TODO(gram) remove this when WebKit tests are working with new framework | |
654 */ | |
655 void callbackDone() { | |
656 _handleCallbackFunctionComplete(); | |
657 } | |
658 | |
659 /** | |
660 * Utility function that can be used to notify the test framework that an | |
661 * error was caught outside of this library. | |
662 */ | |
663 void _reportTestError(String msg, String trace) { | |
664 if (_currentTest < _tests.length) { | |
665 final testCase = _tests[_currentTest]; | |
666 testCase.error(msg, trace); | |
667 if (testCase.callbackFunctionsOutstanding > 0) { | |
668 _nextTestCase(); | |
669 } | |
670 } else { | |
671 _uncaughtErrorMessage = "$msg: $trace"; | |
672 } | |
673 } | |
674 | |
675 /** Runs [callback] at the end of the event loop. */ | |
676 _defer(void callback()) { | |
677 // Exploit isolate ports as a platform-independent mechanism to queue a | |
678 // message at the end of the event loop. | |
679 // TODO(sigmund): expose this functionality somewhere in our libraries. | |
680 final port = new ReceivePort(); | |
681 port.receive((msg, reply) { | |
682 callback(); | |
683 port.close(); | |
684 }); | |
685 port.toSendPort().send(null, null); | |
686 } | |
687 | |
688 rerunTests() { | |
689 _uncaughtErrorMessage = null; | |
690 _initialized = true; // We don't want to reset the test array. | |
691 runTests(); | |
692 } | |
693 | |
694 /** | |
695 * Filter the tests. [testFilter] can be a [RegExp], a [String] or a | |
696 * predicate function. This is different to enabling/disabling tests | |
697 * in that it removes the tests completely. | |
698 */ | |
699 void filterTests(testFilter) { | |
700 var filterFunction; | |
701 if (testFilter is String) { | |
702 RegExp re = new RegExp(testFilter); | |
703 filterFunction = (t) => re.hasMatch(t.description); | |
704 } else if (testFilter is RegExp) { | |
705 filterFunction = (t) => testFilter.hasMatch(t.description); | |
706 } else if (testFilter is Function) { | |
707 filterFunction = testFilter; | |
708 } | |
709 _tests = _tests.filter(filterFunction); | |
710 } | |
711 | |
712 /** Runs all queued tests, one at a time. */ | |
713 runTests() { | |
714 _currentTest = 0; | |
715 _currentGroup = ''; | |
716 | |
717 // If we are soloing a test, remove all the others. | |
718 if (_soloTest != null) { | |
719 filterTests((t) => t == _soloTest); | |
720 } | |
721 | |
722 _config.onStart(); | |
723 | |
724 _defer(() { | |
725 _testRunner(); | |
726 }); | |
727 } | |
728 | |
729 /** | |
730 * Run [tryBody] guarded in a try-catch block. If an exception is thrown, update | |
731 * the [_currentTest] status accordingly. | |
732 */ | |
733 guardAsync(tryBody, [finallyBody, testNum = -1]) { | |
734 if (testNum < 0) testNum = _currentTest; | |
735 try { | |
736 return tryBody(); | |
737 } catch (e, trace) { | |
738 _registerException(testNum, e, trace); | |
739 } finally { | |
740 if (finallyBody != null) finallyBody(); | |
741 } | |
742 } | |
743 | |
744 /** | |
745 * Registers that an exception was caught for the current test. | |
746 */ | |
747 registerException(e, [trace]) { | |
748 _registerException(_currentTest, e, trace); | |
749 } | |
750 | |
751 /** | |
752 * Registers that an exception was caught for the current test. | |
753 */ | |
754 _registerException(testNum, e, [trace]) { | |
755 trace = trace == null ? '' : trace.toString(); | |
756 if (_tests[testNum].result == null) { | |
757 String message = (e is ExpectException) ? e.message : 'Caught $e'; | |
758 _tests[testNum].fail(message, trace); | |
759 } else { | |
760 _tests[testNum].error('Caught $e', trace); | |
761 } | |
762 if (testNum == _currentTest) { | |
763 _nextTestCase(); | |
764 } | |
765 } | |
766 | |
767 /** | |
768 * Runs a batch of tests, yielding whenever an asynchronous test starts | |
769 * running. Tests will resume executing when such asynchronous test calls | |
770 * [done] or if it fails with an exception. | |
771 */ | |
772 _nextBatch() { | |
773 while (_currentTest < _tests.length) { | |
774 final testCase = _tests[_currentTest]; | |
775 guardAsync(() { | |
776 testCase.run(); | |
777 if (!testCase.isComplete && testCase.callbackFunctionsOutstanding == 0) { | |
778 testCase.pass(); | |
779 } | |
780 }, testNum:_currentTest); | |
781 | |
782 if (!testCase.isComplete && | |
783 testCase.callbackFunctionsOutstanding > 0) return; | |
784 _currentTest++; | |
785 } | |
786 | |
787 _completeTests(); | |
788 } | |
789 | |
790 /** Publish results on the page and notify controller. */ | |
791 _completeTests() { | |
792 int testsPassed_ = 0; | |
793 int testsFailed_ = 0; | |
794 int testsErrors_ = 0; | |
795 | |
796 for (TestCase t in _tests) { | |
797 switch (t.result) { | |
798 case _PASS: testsPassed_++; break; | |
799 case _FAIL: testsFailed_++; break; | |
800 case _ERROR: testsErrors_++; break; | |
801 } | |
802 } | |
803 _config.onDone(testsPassed_, testsFailed_, testsErrors_, _tests, | |
804 _uncaughtErrorMessage); | |
805 _initialized = false; | |
806 } | |
807 | |
808 String _fullSpec(String spec) { | |
809 if (spec === null) return '$_currentGroup'; | |
810 return _currentGroup != '' ? '$_currentGroup$groupSep$spec' : spec; | |
811 } | |
812 | |
813 void _fail(String message) { | |
814 throw new ExpectException(message); | |
815 } | |
816 | |
817 /** | |
818 * Lazily initializes the test library if not already initialized. | |
819 */ | |
820 ensureInitialized() { | |
821 if (_initialized) { | |
822 return; | |
823 } | |
824 _initialized = true; | |
825 | |
826 _tests = <TestCase>[]; | |
827 _testRunner = _nextBatch; | |
828 _uncaughtErrorMessage = null; | |
829 | |
830 if (_config == null) { | |
831 _config = new Configuration(); | |
832 } | |
833 _config.onInit(); | |
834 | |
835 if (_config.autoStart) { | |
836 // Immediately queue the suite up. It will run after a timeout (i.e. after | |
837 // main() has returned). | |
838 _defer(runTests); | |
839 } | |
840 } | |
841 | |
842 /** Select a solo test by ID. */ | |
843 void setSoloTest(int id) { | |
844 for (var i = 0; i < _tests.length; i++) { | |
845 if (_tests[i].id == id) { | |
846 _soloTest = _tests[i]; | |
847 break; | |
848 } | |
849 } | |
850 } | |
851 | |
852 /** Enable/disable a test by ID. */ | |
853 void _setTestEnabledState(int testId, bool state) { | |
854 // Try fast path first. | |
855 if (_tests.length > testId && _tests[testId].id == testId) { | |
856 _tests[testId].enabled = state; | |
857 } else { | |
858 for (var i = 0; i < _tests.length; i++) { | |
859 if (_tests[i].id == testId) { | |
860 _tests[i].enabled = state; | |
861 break; | |
862 } | |
863 } | |
864 } | |
865 } | |
866 | |
867 /** Enable a test by ID. */ | |
868 void enableTest(int testId) => _setTestEnabledState(testId, true); | |
869 | |
870 /** Disable a test by ID. */ | |
871 void disableTest(int testId) => _setTestEnabledState(testId, false); | |
872 | |
873 /** Signature for a test function. */ | |
874 typedef void TestFunction(); | |
OLD | NEW |