Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(655)

Side by Side Diff: lib/unittest/unittest.dart

Issue 10037027: unittest step2: bye bye to multiple entrypoints for unittest (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file 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 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 * This file is sourced from unittest_html, unittest_dom, and unittest_vm. 6 * A library for writing dart unit tests.
7 * These libraries shold also source 'config.dart' and should define a class 7 *
8 * called [PlatformConfiguration] that implements [Configuration]. 8 * **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.
9 *
10 * * Tests: Tests are specified via the top-level function [test], they can be
11 * organized together using [group].
12 * * Checks: Test expectations can be specified via [expect] (see methods in
13 * [Expectation]), [expectThrows], or using assertions with the [Expect]
14 * class.
15 * * Configuration: The framework can be adapted by calling [configure] with a
16 * configuration. Common configurations can be found in this package under:
17 * 'dom\_config.dart', 'html\_config.dart', and 'vm\_config.dart'.
18 *
19 * **Examples**
20 *
21 * A trivial test:
22 * #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
23 * main() {
24 * test('this is a test', () {
25 * int x = 2 + 3;
26 * expect(x).equals(5);
27 * });
28 * }
29 *
30 * Multiple tests:
31 * #import('path-to-dart/lib/unittest/unitest.dart');
32 * main() {
33 * test('this is a test', () {
34 * int x = 2 + 3;
35 * expect(x).equals(5);
36 * });
37 * test('this is another test', () {
38 * int x = 2 + 3;
39 * expect(x).equals(5);
40 * });
41 * }
42 *
43 * Multiple tests, grouped by category:
44 * #import('path-to-dart/lib/unittest/unitest.dart');
45 * main() {
46 * group('group A', () {
47 * test('test A.1', () {
48 * int x = 2 + 3;
49 * expect(x).equals(5);
50 * });
51 * test('test A.2', () {
52 * int x = 2 + 3;
53 * expect(x).equals(5);
54 * });
55 * });
56 * group('group B', () {
57 * test('this B.1', () {
58 * int x = 2 + 3;
59 * expect(x).equals(5);
60 * });
61 * });
62 * }
63 *
64 * Asynchronous tests: under the current API (soon to be deprecated):
65 * #import('path-to-dart/lib/unittest/unitest.dart');
66 * #import('dart:dom');
67 * main() {
68 * // use [asyncTest], indicate the expected number of callbacks:
69 * asyncTest('this is a test', 1, () {
70 * window.setTimeout(() {
71 * int x = 2 + 3;
72 * expect(x).equals(5);
73 * // invoke [callbackDone] at the end of the callback.
74 * callbackDone();
75 * }, 0);
76 * });
77 * }
78 *
79 * We plan to replace this with a different API, one API we are considering is:
80 * #import('path-to-dart/lib/unittest/unitest.dart');
81 * #import('dart:dom');
82 * main() {
83 * test('this is a test', () {
84 * // wrap the callback of an asynchronous call with [later]
85 * window.setTimeout(later(() {
86 * int x = 2 + 3;
87 * expect(x).equals(5);
88 * }), 0);
89 * });
90 * }
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
9 */ 91 */
92 #library('unittest');
93
94 #import('dart:isolate');
95
96 #source('config.dart');
97 #source('expectation.dart');
98 #source('testcase.dart');
Bob Nystrom 2012/04/12 16:45:49 "test_case"
Siggi Cherem (dart-lang) 2012/04/12 17:36:28 Done.
10 99
11 /** [Configuration] used by the unittest library. */ 100 /** [Configuration] used by the unittest library. */
12 Configuration _config = null; 101 Configuration _config = null;
13 102
103 /** Set the [Configuration] used by the unittest library. */
104 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.
105 _config = c;
106 }
107
14 /** 108 /**
15 * Description text of the current test group. If multiple groups are nested, 109 * Description text of the current test group. If multiple groups are nested,
16 * this will contain all of their text concatenated. 110 * this will contain all of their text concatenated.
17 */ 111 */
18 String _currentGroup = ''; 112 String _currentGroup = '';
19 113
20 /** Tests executed in this suite. */ 114 /** Tests executed in this suite. */
21 List<TestCase> _tests; 115 List<TestCase> _tests;
22 116
23 /** 117 /**
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
72 _ensureInitialized(); 166 _ensureInitialized();
73 167
74 _tests.add(new TestCase(_tests.length + 1, _fullSpec(spec), body, 0)); 168 _tests.add(new TestCase(_tests.length + 1, _fullSpec(spec), body, 0));
75 } 169 }
76 170
77 /** 171 /**
78 * Creates a new async test case with the given description and body. The 172 * Creates a new async test case with the given description and body. The
79 * description will include the descriptions of any surrounding group() 173 * description will include the descriptions of any surrounding group()
80 * calls. 174 * calls.
81 */ 175 */
176 // TODO(sigmund): deprecate this API
82 void asyncTest(String spec, int callbacks, TestFunction body) { 177 void asyncTest(String spec, int callbacks, TestFunction body) {
83 _ensureInitialized(); 178 _ensureInitialized();
84 179
85 final testCase = new TestCase( 180 final testCase = new TestCase(
86 _tests.length + 1, _fullSpec(spec), body, callbacks); 181 _tests.length + 1, _fullSpec(spec), body, callbacks);
87 _tests.add(testCase); 182 _tests.add(testCase);
88 183
89 if (callbacks < 1) { 184 if (callbacks < 1) {
90 testCase.error( 185 testCase.error(
91 'Async tests must wait for at least one callback ', ''); 186 'Async tests must wait for at least one callback ', '');
92 } 187 }
93 } 188 }
94 189
190 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.
191 const _SentinelType();
192 }
193
194 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
195
196 /**
197 * Indicate to the unittest framework that a callback is expected. [callback]
198 * can take any number of arguments between 0 and 4.
199 *
200 * The framework will wait for the callback to run before it continues with the
201 * following test. The callback must excute once and only once. Using [later]
202 * will also ensure that errors that occur within the callback are tracked and
203 * reported by the unittest framework.
204 */
205 // TODO(sigmund): expose this functionality
206 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 :-))
207 Expect.isTrue(_currentTest < _tests.length);
208 var testCase = _tests[_currentTest];
209 testCase.callbacks++;
210 // We simulate spread arguments using named arguments:
211 // Note: this works in the vm and dart2js, but not in frog.
212 return ([arg0 = _sentinel, arg1 = _sentinel, arg2 = _sentinel,
213 arg3 = _sentinel, arg4 = _sentinel]) {
214 _guard(testCase, () {
215 if (arg0 == _sentinel) {
216 callback();
217 } else if (arg1 == _sentinel) {
218 callback(arg0);
219 } 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
220 callback(arg0, arg1);
221 } else if (arg3 == _sentinel) {
222 callback(arg0, arg1, arg2);
223 } else if (arg4 == _sentinel) {
224 callback(arg0, arg1, arg2, arg3);
225 } else {
226 testCase.error(
227 'unittest lib does not support callbacks with more than 4 arguments',
228 '');
229 _state = _UNCAUGHT_ERROR;
230 }
231 }, callbackDone);
232 };
233 }
234
235 // TODO(sigmund): expose this functionality
236 Function _later0(Function callback) {
237 Expect.isTrue(_currentTest < _tests.length);
238 var testCase = _tests[_currentTest];
239 testCase.callbacks++;
240 return () {
241 _guard(testCase, () => callback(), callbackDone);
242 };
243 }
244
245 // TODO(sigmund): expose this functionality
246 Function _later1(Function callback) {
247 Expect.isTrue(_currentTest < _tests.length);
248 var testCase = _tests[_currentTest];
249 testCase.callbacks++;
250 return (arg0) {
251 _guard(testCase, () => callback(arg0), callbackDone);
252 };
253 }
254
255 // TODO(sigmund): expose this functionality
256 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.
257 Expect.isTrue(_currentTest < _tests.length);
258 var testCase = _tests[_currentTest];
259 testCase.callbacks++;
260 return (arg0, arg1) {
261 _guard(testCase, () => callback(arg0, arg1), callbackDone);
262 };
263 }
264
95 /** 265 /**
96 * Creates a new named group of tests. Calls to group() or test() within the 266 * Creates a new named group of tests. Calls to group() or test() within the
97 * body of the function passed to this will inherit this group's description. 267 * body of the function passed to this will inherit this group's description.
98 */ 268 */
99 void group(String description, void body()) { 269 void group(String description, void body()) {
100 _ensureInitialized(); 270 _ensureInitialized();
101 271
102 // Concatenate the new group. 272 // Concatenate the new group.
103 final oldGroup = _currentGroup; 273 final oldGroup = _currentGroup;
104 if (_currentGroup != '') { 274 if (_currentGroup != '') {
105 // Add a space. 275 // Add a space.
106 _currentGroup = '$_currentGroup $description'; 276 _currentGroup = '$_currentGroup $description';
107 } else { 277 } else {
108 // The first group. 278 // The first group.
109 _currentGroup = description; 279 _currentGroup = description;
110 } 280 }
111 281
112 try { 282 try {
113 body(); 283 body();
114 } finally { 284 } finally {
115 // Now that the group is over, restore the previous one. 285 // Now that the group is over, restore the previous one.
116 _currentGroup = oldGroup; 286 _currentGroup = oldGroup;
117 } 287 }
118 } 288 }
119 289
120 /** Called by subclasses to indicate that an asynchronous test completed. */ 290 /** Called by subclasses to indicate that an asynchronous test completed. */
121 void callbackDone() { 291 void callbackDone() {
122 _callbacksCalled++; 292 _callbacksCalled++;
123 final testCase = _tests[_currentTest]; 293 final testCase = _tests[_currentTest];
124 if (testCase.callbacks == 0) { 294 if (_callbacksCalled > testCase.callbacks) {
125 testCase.error(
126 "Can't call callbackDone() on a synchronous test", '');
127 _state = _UNCAUGHT_ERROR;
128 } else if (_callbacksCalled > testCase.callbacks) {
129 final expected = testCase.callbacks; 295 final expected = testCase.callbacks;
130 testCase.error( 296 testCase.error(
131 'More calls to callbackDone() than expected. ' 297 'More calls to callbackDone() than expected. '
132 + 'Actual: ${_callbacksCalled}, expected: ${expected}', ''); 298 + 'Actual: ${_callbacksCalled}, expected: ${expected}', '');
133 _state = _UNCAUGHT_ERROR; 299 _state = _UNCAUGHT_ERROR;
134 } else if ((_callbacksCalled == testCase.callbacks) && 300 } else if ((_callbacksCalled == testCase.callbacks) &&
135 (_state != _RUNNING_TEST)) { 301 (_state != _RUNNING_TEST)) {
136 testCase.pass(); 302 if (testCase.result == null) testCase.pass();
137 _currentTest++; 303 _currentTest++;
138 _testRunner(); 304 _testRunner();
139 } 305 }
140 } 306 }
141 307
308 void notifyError(String msg, String trace) {
309 if (_currentTest < _tests.length) {
310 final testCase = _tests[_currentTest];
311 testCase.error(msg, trace);
312 _state = _UNCAUGHT_ERROR;
313 if (testCase.callbacks > 0) {
314 _currentTest++;
315 _testRunner();
316 }
317 }
318 }
319
142 /** Runs [callback] at the end of the event loop. */ 320 /** Runs [callback] at the end of the event loop. */
143 _defer(void callback()) { 321 _defer(void callback()) {
144 // Exploit isolate ports as a platform-independent mechanism to queue a 322 // Exploit isolate ports as a platform-independent mechanism to queue a
145 // message at the end of the event loop. 323 // message at the end of the event loop.
146 // TODO(sigmund): expose this functionality somewhere in our libraries. 324 // TODO(sigmund): expose this functionality somewhere in our libraries.
147 final port = new ReceivePort(); 325 final port = new ReceivePort();
148 port.receive((msg, reply) { 326 port.receive((msg, reply) {
149 callback(); 327 callback();
150 port.close(); 328 port.close();
151 }); 329 });
152 port.toSendPort().send(null, null); 330 port.toSendPort().send(null, null);
153 } 331 }
154 332
155 /** Runs all queued tests, one at a time. */ 333 /** Runs all queued tests, one at a time. */
156 _runTests() { 334 _runTests() {
157 _config.onStart(); 335 _config.onStart();
158 336
159 _defer(() { 337 _defer(() {
160 assert (_currentTest == 0); 338 assert (_currentTest == 0);
161 _testRunner(); 339 _testRunner();
162 }); 340 });
163 } 341 }
164 342
165 /** Runs a single test. */ 343 /** Runs a single test. */
166 _runTest(TestCase testCase) { 344 _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
167 try { 345 _guard(testCase, () {
168 _callbacksCalled = 0; 346 _callbacksCalled = 0;
169 _state = _RUNNING_TEST; 347 _state = _RUNNING_TEST;
170 348
171 testCase.test(); 349 testCase.test();
172 350
173 if (_state != _UNCAUGHT_ERROR) { 351 if (_state != _UNCAUGHT_ERROR) {
174 if (testCase.callbacks == _callbacksCalled) { 352 if (testCase.callbacks == _callbacksCalled) {
175 testCase.pass(); 353 testCase.pass();
176 } 354 }
177 } 355 }
356 });
357 }
178 358
359 _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.
360 try {
361 tryBody();
179 } catch (ExpectException e, var trace) { 362 } catch (ExpectException e, var trace) {
180 if (_state != _UNCAUGHT_ERROR) { 363 if (_state != _UNCAUGHT_ERROR) {
181 //TODO(pquitslund) remove guard once dartc reliably propagates traces 364 //TODO(pquitslund) remove guard once dartc reliably propagates traces
182 testCase.fail(e.message, trace == null ? '' : trace.toString()); 365 testCase.fail(e.message, trace == null ? '' : trace.toString());
183 } 366 }
184 } catch (var e, var trace) { 367 } catch (var e, var trace) {
185 if (_state != _UNCAUGHT_ERROR) { 368 if (_state != _UNCAUGHT_ERROR) {
186 //TODO(pquitslund) remove guard once dartc reliably propagates traces 369 //TODO(pquitslund) remove guard once dartc reliably propagates traces
187 testCase.error('Caught ${e}', trace == null ? '' : trace.toString()); 370 testCase.error('Caught ${e}', trace == null ? '' : trace.toString());
188 } 371 }
189 } finally { 372 } finally {
190 _state = _READY; 373 _state = _READY;
374 if (finallyBody != null) finallyBody();
191 } 375 }
192 } 376 }
193 377
194 /** 378 /**
195 * Runs a batch of tests, yielding whenever an asynchronous test starts 379 * Runs a batch of tests, yielding whenever an asynchronous test starts
196 * running. Tests will resume executing when such asynchronous test calls 380 * running. Tests will resume executing when such asynchronous test calls
197 * [done] or if it fails with an exception. 381 * [done] or if it fails with an exception.
198 */ 382 */
199 _nextBatch() { 383 _nextBatch() {
200 while (_currentTest < _tests.length) { 384 while (_currentTest < _tests.length) {
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
239 */ 423 */
240 _ensureInitialized() { 424 _ensureInitialized() {
241 if (_state != _UNINITIALIZED) return; 425 if (_state != _UNINITIALIZED) return;
242 426
243 _tests = <TestCase>[]; 427 _tests = <TestCase>[];
244 _currentGroup = ''; 428 _currentGroup = '';
245 _state = _READY; 429 _state = _READY;
246 _testRunner = _nextBatch; 430 _testRunner = _nextBatch;
247 431
248 if (_config == null) { 432 if (_config == null) {
249 // TODO(sigmund): make this [new Configuration], set configuration 433 _config = new Configuration();
250 // for each platform in test.dart
251 _config = new PlatformConfiguration();
252 } 434 }
253 _config.onInit(); 435 _config.onInit();
254 436
255 // Immediately queue the suite up. It will run after a timeout (i.e. after 437 // Immediately queue the suite up. It will run after a timeout (i.e. after
256 // main() has returned). 438 // main() has returned).
257 _defer(_runTests); 439 _defer(_runTests);
258 } 440 }
259 441
260 /** 442 /** Signature for a test function. */
261 * Wraps an value and provides an "==" operator that can be used to verify that
262 * the value matches a given expectation.
263 */
264 class Expectation {
265 final _value;
266
267 Expectation(this._value);
268
269 /** Asserts that the value is equivalent to [expected]. */
270 void equals(expected) {
271 // Use the type-specialized versions when appropriate to give better
272 // error messages.
273 if (_value is String && expected is String) {
274 Expect.stringEquals(expected, _value);
275 } else if (_value is Map && expected is Map) {
276 Expect.mapEquals(expected, _value);
277 } else if (_value is Set && expected is Set) {
278 Expect.setEquals(expected, _value);
279 } else {
280 Expect.equals(expected, _value);
281 }
282 }
283
284 /**
285 * Asserts that the difference between [expected] and the value is within
286 * [tolerance]. If no tolerance is given, it is assumed to be the value 4
287 * significant digits smaller than the expected value.
288 */
289 void approxEquals(num expected,
290 [num tolerance = null, String reason = null]) {
291 Expect.approxEquals(expected, _value, tolerance: tolerance, reason: reason);
292 }
293
294 /** Asserts that the value is [null]. */
295 void isNull() {
296 Expect.equals(null, _value);
297 }
298
299 /** Asserts that the value is not [null]. */
300 void isNotNull() {
301 Expect.notEquals(null, _value);
302 }
303
304 /** Asserts that the value is [true]. */
305 void isTrue() {
306 Expect.equals(true, _value);
307 }
308
309 /** Asserts that the value is [false]. */
310 void isFalse() {
311 Expect.equals(false, _value);
312 }
313
314 /** Asserts that the value has the same elements as [expected]. */
315 void equalsCollection(Collection expected) {
316 Expect.listEquals(expected, _value);
317 }
318
319 /**
320 * Checks that every element of [expected] is also in [actual], and that
321 * every element of [actual] is also in [expected].
322 */
323 void equalsSet(Iterable expected) {
324 Expect.setEquals(expected, _value);
325 }
326 }
327
328 /** Summarizes information about a single test case. */
329 class TestCase {
330 /** Identifier for this test. */
331 final id;
332
333 /** A description of what the test is specifying. */
334 final String description;
335
336 /** The body of the test case. */
337 final TestFunction test;
338
339 /** Total number of callbacks to wait for before the test completes. */
340 int callbacks;
341
342 /** Error or failure message. */
343 String message = '';
344
345 /**
346 * One of [_PASS], [_FAIL], or [_ERROR] or [null] if the test hasn't run yet.
347 */
348 String result;
349
350 /** Stack trace associated with this test, or null if it succeeded. */
351 String stackTrace;
352
353 Date startTime;
354
355 Duration runningTime;
356
357 TestCase(this.id, this.description, this.test, this.callbacks);
358
359 bool get isComplete() => result != null;
360
361 void pass() {
362 result = _PASS;
363 }
364
365 void fail(String message_, String stackTrace_) {
366 result = _FAIL;
367 this.message = message_;
368 this.stackTrace = stackTrace_;
369 }
370
371 void error(String message_, String stackTrace_) {
372 result = _ERROR;
373 this.message = message_;
374 this.stackTrace = stackTrace_;
375 }
376 }
377
378 typedef void TestFunction(); 443 typedef void TestFunction();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698