OLD | NEW |
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 /** | 5 /** |
6 * Support for writing Dart unit tests. | 6 * Support for writing Dart unit tests. |
7 * | 7 * |
8 * For information on installing and importing this library, see the | 8 * For information on installing and importing this library, see the |
9 * [unittest package on pub.dartlang.org] | 9 * [unittest package on pub.dartlang.org] |
10 * (http://pub.dartlang.org/packages/unittest). | 10 * (http://pub.dartlang.org/packages/unittest). |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
139 import 'dart:collection'; | 139 import 'dart:collection'; |
140 import 'dart:isolate'; | 140 import 'dart:isolate'; |
141 import 'package:stack_trace/stack_trace.dart'; | 141 import 'package:stack_trace/stack_trace.dart'; |
142 | 142 |
143 import 'matcher.dart'; | 143 import 'matcher.dart'; |
144 export 'matcher.dart'; | 144 export 'matcher.dart'; |
145 | 145 |
146 import 'src/utils.dart'; | 146 import 'src/utils.dart'; |
147 | 147 |
148 part 'src/configuration.dart'; | 148 part 'src/configuration.dart'; |
| 149 part 'src/group_context.dart'; |
149 part 'src/simple_configuration.dart'; | 150 part 'src/simple_configuration.dart'; |
| 151 part 'src/spread_args_helper.dart'; |
150 part 'src/test_case.dart'; | 152 part 'src/test_case.dart'; |
151 | 153 |
152 Configuration _config; | 154 Configuration _config; |
153 | 155 |
154 /** | 156 /** |
155 * [Configuration] used by the unittest library. Note that if a | 157 * [Configuration] used by the unittest library. Note that if a |
156 * configuration has not been set, calling this getter will create | 158 * configuration has not been set, calling this getter will create |
157 * a default configuration. | 159 * a default configuration. |
158 */ | 160 */ |
159 Configuration get unittestConfiguration { | 161 Configuration get unittestConfiguration { |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
200 | 202 |
201 /** | 203 /** |
202 * The set of tests to run can be restricted by using [solo_test] and | 204 * The set of tests to run can be restricted by using [solo_test] and |
203 * [solo_group]. | 205 * [solo_group]. |
204 * As groups can be nested we use a counter to keep track of the nest level | 206 * As groups can be nested we use a counter to keep track of the nest level |
205 * of soloing, and a flag to tell if we have seen any solo tests. | 207 * of soloing, and a flag to tell if we have seen any solo tests. |
206 */ | 208 */ |
207 int _soloNestingLevel = 0; | 209 int _soloNestingLevel = 0; |
208 bool _soloTestSeen = false; | 210 bool _soloTestSeen = false; |
209 | 211 |
210 /** | |
211 * Setup and teardown functions for a group and its parents, the latter | |
212 * for chaining. | |
213 */ | |
214 class _GroupContext { | |
215 final _GroupContext parent; | |
216 | |
217 /** Description text of the current test group. */ | |
218 final String _name; | |
219 | |
220 /** Setup function called before each test in a group. */ | |
221 Function _testSetup; | |
222 | |
223 get testSetup => _testSetup; | |
224 | |
225 get parentSetup => (parent == null) ? null : parent.testSetup; | |
226 | |
227 set testSetup(Function setup) { | |
228 var preSetup = parentSetup; | |
229 if (preSetup == null) { | |
230 _testSetup = setup; | |
231 } else { | |
232 _testSetup = () { | |
233 var f = preSetup(); | |
234 if (f is Future) { | |
235 return f.then((_) => setup()); | |
236 } else { | |
237 return setup(); | |
238 } | |
239 }; | |
240 } | |
241 } | |
242 | |
243 /** Teardown function called after each test in a group. */ | |
244 Function _testTeardown; | |
245 | |
246 get testTeardown => _testTeardown; | |
247 | |
248 get parentTeardown => (parent == null) ? null : parent.testTeardown; | |
249 | |
250 set testTeardown(Function teardown) { | |
251 var postTeardown = parentTeardown; | |
252 if (postTeardown == null) { | |
253 _testTeardown = teardown; | |
254 } else { | |
255 _testTeardown = () { | |
256 var f = teardown(); | |
257 if (f is Future) { | |
258 return f.then((_) => postTeardown()); | |
259 } else { | |
260 return postTeardown(); | |
261 } | |
262 }; | |
263 } | |
264 } | |
265 | |
266 String get fullName => (parent == null || parent == _rootContext) | |
267 ? _name | |
268 : "${parent.fullName}$groupSep$_name"; | |
269 | |
270 _GroupContext([this.parent, this._name = '']) { | |
271 _testSetup = parentSetup; | |
272 _testTeardown = parentTeardown; | |
273 } | |
274 } | |
275 | |
276 // We use a 'dummy' context for the top level to eliminate null | 212 // We use a 'dummy' context for the top level to eliminate null |
277 // checks when querying the context. This allows us to easily | 213 // checks when querying the context. This allows us to easily |
278 // support top-level setUp/tearDown functions as well. | 214 // support top-level setUp/tearDown functions as well. |
279 final _rootContext = new _GroupContext(); | 215 final _rootContext = new _GroupContext(); |
280 _GroupContext _currentContext = _rootContext; | 216 _GroupContext _currentContext = _rootContext; |
281 | 217 |
282 /** | 218 /** |
283 * Represents the index of the currently running test case | 219 * Represents the index of the currently running test case |
284 * == -1 implies the test system is not running | 220 * == -1 implies the test system is not running |
285 * == [number of test cases] is a short-lived state flagging that the last test | 221 * == [number of test cases] is a short-lived state flagging that the last test |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
361 } finally { | 297 } finally { |
362 --_soloNestingLevel; | 298 --_soloNestingLevel; |
363 } | 299 } |
364 } | 300 } |
365 | 301 |
366 /** Sentinel value for [_SpreadArgsHelper]. */ | 302 /** Sentinel value for [_SpreadArgsHelper]. */ |
367 class _Sentinel { | 303 class _Sentinel { |
368 const _Sentinel(); | 304 const _Sentinel(); |
369 } | 305 } |
370 | 306 |
371 /** Simulates spread arguments using named arguments. */ | |
372 // TODO(sigmund): remove this class and simply use a closure with named | |
373 // arguments (if still applicable). | |
374 class _SpreadArgsHelper { | |
375 final Function callback; | |
376 final int minExpectedCalls; | |
377 final int maxExpectedCalls; | |
378 final Function isDone; | |
379 final String id; | |
380 int actualCalls = 0; | |
381 final TestCase testCase; | |
382 bool complete; | |
383 static const sentinel = const _Sentinel(); | |
384 | |
385 _SpreadArgsHelper(Function callback, int minExpected, int maxExpected, | |
386 Function isDone, String id) | |
387 : this.callback = callback, | |
388 minExpectedCalls = minExpected, | |
389 maxExpectedCalls = (maxExpected == 0 && minExpected > 0) | |
390 ? minExpected | |
391 : maxExpected, | |
392 this.isDone = isDone, | |
393 this.testCase = currentTestCase, | |
394 this.id = _makeCallbackId(id, callback) { | |
395 ensureInitialized(); | |
396 if (testCase == null) { | |
397 throw new StateError("No valid test. Did you forget to run your test " | |
398 "inside a call to test()?"); | |
399 } | |
400 | |
401 if (isDone != null || minExpected > 0) { | |
402 testCase._callbackFunctionsOutstanding++; | |
403 complete = false; | |
404 } else { | |
405 complete = true; | |
406 } | |
407 } | |
408 | |
409 static String _makeCallbackId(String id, Function callback) { | |
410 // Try to create a reasonable id. | |
411 if (id != null) { | |
412 return "$id "; | |
413 } else { | |
414 // If the callback is not an anonymous closure, try to get the | |
415 // name. | |
416 var fname = callback.toString(); | |
417 var prefix = "Function '"; | |
418 var pos = fname.indexOf(prefix); | |
419 if (pos > 0) { | |
420 pos += prefix.length; | |
421 var epos = fname.indexOf("'", pos); | |
422 if (epos > 0) { | |
423 return "${fname.substring(pos, epos)} "; | |
424 } | |
425 } | |
426 } | |
427 return ''; | |
428 } | |
429 | |
430 bool shouldCallBack() { | |
431 ++actualCalls; | |
432 if (testCase.isComplete) { | |
433 // Don't run if the test is done. We don't throw here as this is not | |
434 // the current test, but we do mark the old test as having an error | |
435 // if it previously passed. | |
436 if (testCase.result == PASS) { | |
437 testCase.error( | |
438 'Callback ${id}called ($actualCalls) after test case ' | |
439 '${testCase.description} has already been marked as ' | |
440 '${testCase.result}.'); | |
441 } | |
442 return false; | |
443 } else if (maxExpectedCalls >= 0 && actualCalls > maxExpectedCalls) { | |
444 throw new TestFailure('Callback ${id}called more times than expected ' | |
445 '($maxExpectedCalls).'); | |
446 } | |
447 return true; | |
448 } | |
449 | |
450 void after() { | |
451 if (!complete) { | |
452 if (minExpectedCalls > 0 && actualCalls < minExpectedCalls) return; | |
453 if (isDone != null && !isDone()) return; | |
454 | |
455 // Mark this callback as complete and remove it from the testcase | |
456 // oustanding callback count; if that hits zero the testcase is done. | |
457 complete = true; | |
458 testCase._markCallbackComplete(); | |
459 } | |
460 } | |
461 | |
462 invoke0() { | |
463 return _guardAsync( | |
464 () { | |
465 if (shouldCallBack()) { | |
466 return callback(); | |
467 } | |
468 }, | |
469 after, testCase); | |
470 } | |
471 | |
472 invoke1(arg1) { | |
473 return _guardAsync( | |
474 () { | |
475 if (shouldCallBack()) { | |
476 return callback(arg1); | |
477 } | |
478 }, | |
479 after, testCase); | |
480 } | |
481 | |
482 invoke2(arg1, arg2) { | |
483 return _guardAsync( | |
484 () { | |
485 if (shouldCallBack()) { | |
486 return callback(arg1, arg2); | |
487 } | |
488 }, | |
489 after, testCase); | |
490 } | |
491 } | |
492 | |
493 /** | 307 /** |
494 * Indicate that [callback] is expected to be called a [count] number of times | 308 * Indicate that [callback] is expected to be called a [count] number of times |
495 * (by default 1). The unittest framework will wait for the callback to run the | 309 * (by default 1). The unittest framework will wait for the callback to run the |
496 * specified [count] times before it continues with the following test. Using | 310 * specified [count] times before it continues with the following test. Using |
497 * [expectAsync0] will also ensure that errors that occur within [callback] are | 311 * [expectAsync0] will also ensure that errors that occur within [callback] are |
498 * tracked and reported. [callback] should take 0 positional arguments (named | 312 * tracked and reported. [callback] should take 0 positional arguments (named |
499 * arguments are not supported). [id] can be used to provide more | 313 * arguments are not supported). [id] can be used to provide more |
500 * descriptive error messages if the callback is called more often than | 314 * descriptive error messages if the callback is called more often than |
501 * expected. [max] can be used to specify an upper bound on the number of | 315 * expected. [max] can be used to specify an upper bound on the number of |
502 * calls; if this is exceeded the test will fail (or be marked as in error if | 316 * calls; if this is exceeded the test will fail (or be marked as in error if |
(...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
656 _runTest(); | 470 _runTest(); |
657 } | 471 } |
658 | 472 |
659 /** Handle errors that happen outside the tests. */ | 473 /** Handle errors that happen outside the tests. */ |
660 // TODO(vsm): figure out how to expose the stack trace here | 474 // TODO(vsm): figure out how to expose the stack trace here |
661 // Currently e.message works in dartium, but not in dartc. | 475 // Currently e.message works in dartium, but not in dartc. |
662 void handleExternalError(e, String message, [stack]) { | 476 void handleExternalError(e, String message, [stack]) { |
663 var msg = '$message\nCaught $e'; | 477 var msg = '$message\nCaught $e'; |
664 | 478 |
665 if (currentTestCase != null) { | 479 if (currentTestCase != null) { |
666 currentTestCase.error(msg, stack); | 480 currentTestCase._error(msg, stack); |
667 } else { | 481 } else { |
668 _uncaughtErrorMessage = "$msg: $stack"; | 482 _uncaughtErrorMessage = "$msg: $stack"; |
669 } | 483 } |
670 } | 484 } |
671 | 485 |
672 void rerunTests() { | 486 void rerunTests() { |
673 _uncaughtErrorMessage = null; | 487 _uncaughtErrorMessage = null; |
674 _initialized = true; // We don't want to reset the test array. | 488 _initialized = true; // We don't want to reset the test array. |
675 runTests(); | 489 runTests(); |
676 } | 490 } |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
729 void registerException(e, [trace]) { | 543 void registerException(e, [trace]) { |
730 _registerException(currentTestCase, e, trace); | 544 _registerException(currentTestCase, e, trace); |
731 } | 545 } |
732 | 546 |
733 /** | 547 /** |
734 * Registers that an exception was caught for the current test. | 548 * Registers that an exception was caught for the current test. |
735 */ | 549 */ |
736 void _registerException(TestCase testCase, e, [trace]) { | 550 void _registerException(TestCase testCase, e, [trace]) { |
737 String message = (e is TestFailure) ? e.message : 'Caught $e'; | 551 String message = (e is TestFailure) ? e.message : 'Caught $e'; |
738 if (testCase.result == null) { | 552 if (testCase.result == null) { |
739 testCase.fail(message, trace); | 553 testCase._fail(message, trace); |
740 } else { | 554 } else { |
741 testCase.error(message, trace); | 555 testCase._error(message, trace); |
742 } | 556 } |
743 } | 557 } |
744 | 558 |
745 /** | 559 /** |
746 * Runs the next test. | 560 * Runs the next test. |
747 */ | 561 */ |
748 void _runTest() { | 562 void _runTest() { |
749 if (_currentTestCaseIndex >= testCases.length) { | 563 if (_currentTestCaseIndex >= testCases.length) { |
750 assert(_currentTestCaseIndex == testCases.length); | 564 assert(_currentTestCaseIndex == testCases.length); |
751 _completeTests(); | 565 _completeTests(); |
752 } else { | 566 } else { |
753 var testCase = testCases[_currentTestCaseIndex]; | 567 var testCase = testCases[_currentTestCaseIndex]; |
754 Future f = _guardAsync(testCase._run, null, testCase); | 568 Future f = _guardAsync(testCase._run, null, testCase); |
755 var timeout = unittestConfiguration.timeout; | 569 var timeout = unittestConfiguration.timeout; |
756 | 570 |
757 Timer timer; | 571 Timer timer; |
758 if (timeout != null) { | 572 if (timeout != null) { |
759 try { | 573 try { |
760 timer = new Timer(timeout, () { | 574 timer = new Timer(timeout, () { |
761 testCase.error("Test timed out after ${timeout.inSeconds} seconds."); | 575 testCase._error("Test timed out after ${timeout.inSeconds} seconds."); |
762 _nextTestCase(); | 576 _nextTestCase(); |
763 }); | 577 }); |
764 } on UnsupportedError catch (e) { | 578 } on UnsupportedError catch (e) { |
765 if (e.message != "Timer greater than 0.") rethrow; | 579 if (e.message != "Timer greater than 0.") rethrow; |
766 // Support running on d8 and jsshell which don't support timers. | 580 // Support running on d8 and jsshell which don't support timers. |
767 } | 581 } |
768 } | 582 } |
769 f.whenComplete(() { | 583 f.whenComplete(() { |
770 if (timer != null) timer.cancel(); | 584 if (timer != null) timer.cancel(); |
771 var now = new DateTime.now().millisecondsSinceEpoch; | 585 var now = new DateTime.now().millisecondsSinceEpoch; |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
833 } | 647 } |
834 | 648 |
835 /** Select a solo test by ID. */ | 649 /** Select a solo test by ID. */ |
836 void setSoloTest(int id) => | 650 void setSoloTest(int id) => |
837 _testCases.retainWhere((t) => t.id == id); | 651 _testCases.retainWhere((t) => t.id == id); |
838 | 652 |
839 /** Enable/disable a test by ID. */ | 653 /** Enable/disable a test by ID. */ |
840 void _setTestEnabledState(int testId, bool state) { | 654 void _setTestEnabledState(int testId, bool state) { |
841 // Try fast path first. | 655 // Try fast path first. |
842 if (testCases.length > testId && testCases[testId].id == testId) { | 656 if (testCases.length > testId && testCases[testId].id == testId) { |
843 testCases[testId].enabled = state; | 657 testCases[testId]._enabled = state; |
844 } else { | 658 } else { |
845 for (var i = 0; i < testCases.length; i++) { | 659 for (var i = 0; i < testCases.length; i++) { |
846 if (testCases[i].id == testId) { | 660 if (testCases[i].id == testId) { |
847 testCases[i].enabled = state; | 661 testCases[i]._enabled = state; |
848 break; | 662 break; |
849 } | 663 } |
850 } | 664 } |
851 } | 665 } |
852 } | 666 } |
853 | 667 |
854 /** Enable a test by ID. */ | 668 /** Enable a test by ID. */ |
855 void enableTest(int testId) => _setTestEnabledState(testId, true); | 669 void enableTest(int testId) => _setTestEnabledState(testId, true); |
856 | 670 |
857 /** Disable a test by ID. */ | 671 /** Disable a test by ID. */ |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
897 | 711 |
898 if (!filterStacks) return trace; | 712 if (!filterStacks) return trace; |
899 | 713 |
900 // Format the stack trace by removing everything above TestCase._runTest, | 714 // Format the stack trace by removing everything above TestCase._runTest, |
901 // which is usually going to be irrelevant. Also fold together unittest and | 715 // which is usually going to be irrelevant. Also fold together unittest and |
902 // core library calls so only the function the user called is visible. | 716 // core library calls so only the function the user called is visible. |
903 return new Trace(trace.frames.takeWhile((frame) { | 717 return new Trace(trace.frames.takeWhile((frame) { |
904 return frame.package != 'unittest' || frame.member != 'TestCase._runTest'; | 718 return frame.package != 'unittest' || frame.member != 'TestCase._runTest'; |
905 })).terse.foldFrames((frame) => frame.package == 'unittest' || frame.isCore); | 719 })).terse.foldFrames((frame) => frame.package == 'unittest' || frame.isCore); |
906 } | 720 } |
OLD | NEW |