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