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(); |