 Chromium Code Reviews
 Chromium Code Reviews Issue 10037027:
  unittest step2: bye bye to multiple entrypoints for unittest  (Closed) 
  Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
    
  
    Issue 10037027:
  unittest step2: bye bye to multiple entrypoints for unittest  (Closed) 
  Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart| Index: lib/unittest/unittest.dart | 
| diff --git a/lib/unittest/shared.dart b/lib/unittest/unittest.dart | 
| similarity index 52% | 
| rename from lib/unittest/shared.dart | 
| rename to lib/unittest/unittest.dart | 
| index 879e4da362de7a6e90db33a16e504ea7b49c95f6..1db7cec37d9a3568a839618ae7b3d09b57649a5c 100644 | 
| --- a/lib/unittest/shared.dart | 
| +++ b/lib/unittest/unittest.dart | 
| @@ -3,14 +3,108 @@ | 
| // BSD-style license that can be found in the LICENSE file. | 
| /** | 
| - * This file is sourced from unittest_html, unittest_dom, and unittest_vm. | 
| - * These libraries shold also source 'config.dart' and should define a class | 
| - * called [PlatformConfiguration] that implements [Configuration]. | 
| + * A library for writing dart unit tests. | 
| + * | 
| + * **Concepts** | 
| 
Bob Nystrom
2012/04/12 16:45:49
Use Markdown, one of:
## Concepts
## Concepts ##
 
Siggi Cherem (dart-lang)
2012/04/12 17:36:28
Done.
 | 
| + * | 
| + * * Tests: Tests are specified via the top-level function [test], they can be | 
| + * organized together using [group]. | 
| + * * Checks: Test expectations can be specified via [expect] (see methods in | 
| + * [Expectation]), [expectThrows], or using assertions with the [Expect] | 
| + * class. | 
| + * * Configuration: The framework can be adapted by calling [configure] with a | 
| + * configuration. Common configurations can be found in this package under: | 
| + * 'dom\_config.dart', 'html\_config.dart', and 'vm\_config.dart'. | 
| + * | 
| + * **Examples** | 
| + * | 
| + * A trivial test: | 
| + * #import('path-to-dart/lib/unittest/unitest.dart'); | 
| 
Bob Nystrom
2012/04/12 16:45:49
Need blank line above this to separate out code bl
 
Siggi Cherem (dart-lang)
2012/04/12 17:36:28
Ok, it didn't seem to need it when I created the p
 
Bob Nystrom
2012/04/12 18:01:37
Hmm, yeah I think dartdoc will allow it but most m
 | 
| + * main() { | 
| + * test('this is a test', () { | 
| + * int x = 2 + 3; | 
| + * expect(x).equals(5); | 
| + * }); | 
| + * } | 
| + * | 
| + * Multiple tests: | 
| + * #import('path-to-dart/lib/unittest/unitest.dart'); | 
| + * main() { | 
| + * test('this is a test', () { | 
| + * int x = 2 + 3; | 
| + * expect(x).equals(5); | 
| + * }); | 
| + * test('this is another test', () { | 
| + * int x = 2 + 3; | 
| + * expect(x).equals(5); | 
| + * }); | 
| + * } | 
| + * | 
| + * Multiple tests, grouped by category: | 
| + * #import('path-to-dart/lib/unittest/unitest.dart'); | 
| + * main() { | 
| + * group('group A', () { | 
| + * test('test A.1', () { | 
| + * int x = 2 + 3; | 
| + * expect(x).equals(5); | 
| + * }); | 
| + * test('test A.2', () { | 
| + * int x = 2 + 3; | 
| + * expect(x).equals(5); | 
| + * }); | 
| + * }); | 
| + * group('group B', () { | 
| + * test('this B.1', () { | 
| + * int x = 2 + 3; | 
| + * expect(x).equals(5); | 
| + * }); | 
| + * }); | 
| + * } | 
| + * | 
| + * Asynchronous tests: under the current API (soon to be deprecated): | 
| + * #import('path-to-dart/lib/unittest/unitest.dart'); | 
| + * #import('dart:dom'); | 
| + * main() { | 
| + * // use [asyncTest], indicate the expected number of callbacks: | 
| + * asyncTest('this is a test', 1, () { | 
| + * window.setTimeout(() { | 
| + * int x = 2 + 3; | 
| + * expect(x).equals(5); | 
| + * // invoke [callbackDone] at the end of the callback. | 
| + * callbackDone(); | 
| + * }, 0); | 
| + * }); | 
| + * } | 
| + * | 
| + * We plan to replace this with a different API, one API we are considering is: | 
| + * #import('path-to-dart/lib/unittest/unitest.dart'); | 
| + * #import('dart:dom'); | 
| + * main() { | 
| + * test('this is a test', () { | 
| + * // wrap the callback of an asynchronous call with [later] | 
| + * window.setTimeout(later(() { | 
| + * int x = 2 + 3; | 
| + * expect(x).equals(5); | 
| + * }), 0); | 
| + * }); | 
| + * } | 
| 
Bob Nystrom
2012/04/12 16:45:49
This documentation is excellent!
 
Siggi Cherem (dart-lang)
2012/04/12 17:36:28
Thanks!
 
Emily Fortuna
2012/04/12 17:48:42
+1
 | 
| */ | 
| +#library('unittest'); | 
| + | 
| +#import('dart:isolate'); | 
| + | 
| +#source('config.dart'); | 
| +#source('expectation.dart'); | 
| +#source('testcase.dart'); | 
| 
Bob Nystrom
2012/04/12 16:45:49
"test_case"
 
Siggi Cherem (dart-lang)
2012/04/12 17:36:28
Done.
 | 
| /** [Configuration] used by the unittest library. */ | 
| Configuration _config = null; | 
| +/** Set the [Configuration] used by the unittest library. */ | 
| +void configure(Configuration c) { | 
| 
Bob Nystrom
2012/04/12 16:45:49
"c" -> "config".
 
Siggi Cherem (dart-lang)
2012/04/12 17:36:28
Done.
 | 
| + _config = c; | 
| +} | 
| + | 
| /** | 
| * Description text of the current test group. If multiple groups are nested, | 
| * this will contain all of their text concatenated. | 
| @@ -79,6 +173,7 @@ void test(String spec, TestFunction body) { | 
| * description will include the descriptions of any surrounding group() | 
| * calls. | 
| */ | 
| +// TODO(sigmund): deprecate this API | 
| void asyncTest(String spec, int callbacks, TestFunction body) { | 
| _ensureInitialized(); | 
| @@ -92,6 +187,81 @@ void asyncTest(String spec, int callbacks, TestFunction body) { | 
| } | 
| } | 
| +class _SentinelType { | 
| 
Bob Nystrom
2012/04/12 16:45:49
I'd just do "_Sentinel".
 
Siggi Cherem (dart-lang)
2012/04/12 17:36:28
Done.
 | 
| + const _SentinelType(); | 
| +} | 
| + | 
| +final _SentinelType _sentinel = const _SentinelType(); | 
| 
Bob Nystrom
2012/04/12 16:45:49
Move this into the class and rename, like:
class
 
Siggi Cherem (dart-lang)
2012/04/12 17:36:28
cool, done
 
Emily Fortuna
2012/04/12 17:48:42
+1
 | 
| + | 
| +/** | 
| + * Indicate to the unittest framework that a callback is expected. [callback] | 
| + * can take any number of arguments between 0 and 4. | 
| + * | 
| + * The framework will wait for the callback to run before it continues with the | 
| + * following test. The callback must excute once and only once. Using [later] | 
| + * will also ensure that errors that occur within the callback are tracked and | 
| + * reported by the unittest framework. | 
| + */ | 
| +// TODO(sigmund): expose this functionality | 
| +Function _later(Function callback) { | 
| 
Emily Fortuna
2012/04/12 17:48:42
Perhaps I missed it, but where is this function ca
 
Siggi Cherem (dart-lang)
2012/04/12 21:13:36
it is not yet (see note below about step 3 :-))
 | 
| + Expect.isTrue(_currentTest < _tests.length); | 
| + var testCase = _tests[_currentTest]; | 
| + testCase.callbacks++; | 
| + // We simulate spread arguments using named arguments: | 
| + // Note: this works in the vm and dart2js, but not in frog. | 
| + return ([arg0 = _sentinel, arg1 = _sentinel, arg2 = _sentinel, | 
| + arg3 = _sentinel, arg4 = _sentinel]) { | 
| + _guard(testCase, () { | 
| + if (arg0 == _sentinel) { | 
| + callback(); | 
| + } else if (arg1 == _sentinel) { | 
| + callback(arg0); | 
| + } else if (arg3 == _sentinel) { | 
| 
Emily Fortuna
2012/04/12 17:48:42
arg2
 
Siggi Cherem (dart-lang)
2012/04/12 21:13:36
nice catch
 | 
| + callback(arg0, arg1); | 
| + } else if (arg3 == _sentinel) { | 
| + callback(arg0, arg1, arg2); | 
| + } else if (arg4 == _sentinel) { | 
| + callback(arg0, arg1, arg2, arg3); | 
| + } else { | 
| + testCase.error( | 
| + 'unittest lib does not support callbacks with more than 4 arguments', | 
| + ''); | 
| + _state = _UNCAUGHT_ERROR; | 
| + } | 
| + }, callbackDone); | 
| + }; | 
| +} | 
| + | 
| +// TODO(sigmund): expose this functionality | 
| +Function _later0(Function callback) { | 
| + Expect.isTrue(_currentTest < _tests.length); | 
| + var testCase = _tests[_currentTest]; | 
| + testCase.callbacks++; | 
| + return () { | 
| + _guard(testCase, () => callback(), callbackDone); | 
| + }; | 
| +} | 
| + | 
| +// TODO(sigmund): expose this functionality | 
| +Function _later1(Function callback) { | 
| + Expect.isTrue(_currentTest < _tests.length); | 
| + var testCase = _tests[_currentTest]; | 
| + testCase.callbacks++; | 
| + return (arg0) { | 
| + _guard(testCase, () => callback(arg0), callbackDone); | 
| + }; | 
| +} | 
| + | 
| +// TODO(sigmund): expose this functionality | 
| +Function _later2(Function callback) { | 
| 
Siggi Cherem (dart-lang)
2012/04/12 01:13:53
this is something I'll need for "step 3" (see emai
 
Bob Nystrom
2012/04/12 16:45:49
Nope, it's fine. Thanks for clarifying.
 | 
| + Expect.isTrue(_currentTest < _tests.length); | 
| + var testCase = _tests[_currentTest]; | 
| + testCase.callbacks++; | 
| + return (arg0, arg1) { | 
| + _guard(testCase, () => callback(arg0, arg1), callbackDone); | 
| + }; | 
| +} | 
| + | 
| /** | 
| * Creates a new named group of tests. Calls to group() or test() within the | 
| * body of the function passed to this will inherit this group's description. | 
| @@ -121,11 +291,7 @@ void group(String description, void body()) { | 
| void callbackDone() { | 
| _callbacksCalled++; | 
| final testCase = _tests[_currentTest]; | 
| - if (testCase.callbacks == 0) { | 
| - testCase.error( | 
| - "Can't call callbackDone() on a synchronous test", ''); | 
| - _state = _UNCAUGHT_ERROR; | 
| - } else if (_callbacksCalled > testCase.callbacks) { | 
| + if (_callbacksCalled > testCase.callbacks) { | 
| final expected = testCase.callbacks; | 
| testCase.error( | 
| 'More calls to callbackDone() than expected. ' | 
| @@ -133,12 +299,24 @@ void callbackDone() { | 
| _state = _UNCAUGHT_ERROR; | 
| } else if ((_callbacksCalled == testCase.callbacks) && | 
| (_state != _RUNNING_TEST)) { | 
| - testCase.pass(); | 
| + if (testCase.result == null) testCase.pass(); | 
| _currentTest++; | 
| _testRunner(); | 
| } | 
| } | 
| +void notifyError(String msg, String trace) { | 
| + if (_currentTest < _tests.length) { | 
| + final testCase = _tests[_currentTest]; | 
| + testCase.error(msg, trace); | 
| + _state = _UNCAUGHT_ERROR; | 
| + if (testCase.callbacks > 0) { | 
| + _currentTest++; | 
| + _testRunner(); | 
| + } | 
| + } | 
| +} | 
| + | 
| /** Runs [callback] at the end of the event loop. */ | 
| _defer(void callback()) { | 
| // Exploit isolate ports as a platform-independent mechanism to queue a | 
| @@ -164,7 +342,7 @@ _runTests() { | 
| /** Runs a single test. */ | 
| _runTest(TestCase testCase) { | 
| 
Bob Nystrom
2012/04/12 16:45:49
I don't see this being called anywhere, can it be
 
Siggi Cherem (dart-lang)
2012/04/12 17:36:28
It's actually called by [nextBatch] below. I decid
 | 
| - try { | 
| + _guard(testCase, () { | 
| _callbacksCalled = 0; | 
| _state = _RUNNING_TEST; | 
| @@ -175,7 +353,12 @@ _runTest(TestCase testCase) { | 
| testCase.pass(); | 
| } | 
| } | 
| + }); | 
| +} | 
| +_guard(TestCase testCase, tryBody, [finallyBody]) { | 
| 
Bob Nystrom
2012/04/12 16:45:49
Nice. In every callsite I see, testCase is always
 
Siggi Cherem (dart-lang)
2012/04/12 17:36:28
Done.
 | 
| + try { | 
| + tryBody(); | 
| } catch (ExpectException e, var trace) { | 
| if (_state != _UNCAUGHT_ERROR) { | 
| //TODO(pquitslund) remove guard once dartc reliably propagates traces | 
| @@ -188,6 +371,7 @@ _runTest(TestCase testCase) { | 
| } | 
| } finally { | 
| _state = _READY; | 
| + if (finallyBody != null) finallyBody(); | 
| } | 
| } | 
| @@ -246,9 +430,7 @@ _ensureInitialized() { | 
| _testRunner = _nextBatch; | 
| if (_config == null) { | 
| - // TODO(sigmund): make this [new Configuration], set configuration | 
| - // for each platform in test.dart | 
| - _config = new PlatformConfiguration(); | 
| + _config = new Configuration(); | 
| } | 
| _config.onInit(); | 
| @@ -257,122 +439,5 @@ _ensureInitialized() { | 
| _defer(_runTests); | 
| } | 
| -/** | 
| - * Wraps an value and provides an "==" operator that can be used to verify that | 
| - * the value matches a given expectation. | 
| - */ | 
| -class Expectation { | 
| - final _value; | 
| - | 
| - Expectation(this._value); | 
| - | 
| - /** Asserts that the value is equivalent to [expected]. */ | 
| - void equals(expected) { | 
| - // Use the type-specialized versions when appropriate to give better | 
| - // error messages. | 
| - if (_value is String && expected is String) { | 
| - Expect.stringEquals(expected, _value); | 
| - } else if (_value is Map && expected is Map) { | 
| - Expect.mapEquals(expected, _value); | 
| - } else if (_value is Set && expected is Set) { | 
| - Expect.setEquals(expected, _value); | 
| - } else { | 
| - Expect.equals(expected, _value); | 
| - } | 
| - } | 
| - | 
| - /** | 
| - * Asserts that the difference between [expected] and the value is within | 
| - * [tolerance]. If no tolerance is given, it is assumed to be the value 4 | 
| - * significant digits smaller than the expected value. | 
| - */ | 
| - void approxEquals(num expected, | 
| - [num tolerance = null, String reason = null]) { | 
| - Expect.approxEquals(expected, _value, tolerance: tolerance, reason: reason); | 
| - } | 
| - | 
| - /** Asserts that the value is [null]. */ | 
| - void isNull() { | 
| - Expect.equals(null, _value); | 
| - } | 
| - | 
| - /** Asserts that the value is not [null]. */ | 
| - void isNotNull() { | 
| - Expect.notEquals(null, _value); | 
| - } | 
| - | 
| - /** Asserts that the value is [true]. */ | 
| - void isTrue() { | 
| - Expect.equals(true, _value); | 
| - } | 
| - | 
| - /** Asserts that the value is [false]. */ | 
| - void isFalse() { | 
| - Expect.equals(false, _value); | 
| - } | 
| - | 
| - /** Asserts that the value has the same elements as [expected]. */ | 
| - void equalsCollection(Collection expected) { | 
| - Expect.listEquals(expected, _value); | 
| - } | 
| - | 
| - /** | 
| - * Checks that every element of [expected] is also in [actual], and that | 
| - * every element of [actual] is also in [expected]. | 
| - */ | 
| - void equalsSet(Iterable expected) { | 
| - Expect.setEquals(expected, _value); | 
| - } | 
| -} | 
| - | 
| -/** Summarizes information about a single test case. */ | 
| -class TestCase { | 
| - /** Identifier for this test. */ | 
| - final id; | 
| - | 
| - /** A description of what the test is specifying. */ | 
| - final String description; | 
| - | 
| - /** The body of the test case. */ | 
| - final TestFunction test; | 
| - | 
| - /** Total number of callbacks to wait for before the test completes. */ | 
| - int callbacks; | 
| - | 
| - /** Error or failure message. */ | 
| - String message = ''; | 
| - | 
| - /** | 
| - * One of [_PASS], [_FAIL], or [_ERROR] or [null] if the test hasn't run yet. | 
| - */ | 
| - String result; | 
| - | 
| - /** Stack trace associated with this test, or null if it succeeded. */ | 
| - String stackTrace; | 
| - | 
| - Date startTime; | 
| - | 
| - Duration runningTime; | 
| - | 
| - TestCase(this.id, this.description, this.test, this.callbacks); | 
| - | 
| - bool get isComplete() => result != null; | 
| - | 
| - void pass() { | 
| - result = _PASS; | 
| - } | 
| - | 
| - void fail(String message_, String stackTrace_) { | 
| - result = _FAIL; | 
| - this.message = message_; | 
| - this.stackTrace = stackTrace_; | 
| - } | 
| - | 
| - void error(String message_, String stackTrace_) { | 
| - result = _ERROR; | 
| - this.message = message_; | 
| - this.stackTrace = stackTrace_; | 
| - } | 
| -} | 
| - | 
| +/** Signature for a test function. */ | 
| typedef void TestFunction(); |