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

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
« no previous file with comments | « lib/unittest/test_case.dart ('k') | lib/unittest/unittest_dom.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 * ##Concepts##
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 *
23 * #import('path-to-dart/lib/unittest/unitest.dart');
24 * main() {
25 * test('this is a test', () {
26 * int x = 2 + 3;
27 * expect(x).equals(5);
28 * });
29 * }
30 *
31 * Multiple tests:
32 *
33 * #import('path-to-dart/lib/unittest/unitest.dart');
34 * main() {
35 * test('this is a test', () {
36 * int x = 2 + 3;
37 * expect(x).equals(5);
38 * });
39 * test('this is another test', () {
40 * int x = 2 + 3;
41 * expect(x).equals(5);
42 * });
43 * }
44 *
45 * Multiple tests, grouped by category:
46 *
47 * #import('path-to-dart/lib/unittest/unitest.dart');
48 * main() {
49 * group('group A', () {
50 * test('test A.1', () {
51 * int x = 2 + 3;
52 * expect(x).equals(5);
53 * });
54 * test('test A.2', () {
55 * int x = 2 + 3;
56 * expect(x).equals(5);
57 * });
58 * });
59 * group('group B', () {
60 * test('this B.1', () {
61 * int x = 2 + 3;
62 * expect(x).equals(5);
63 * });
64 * });
65 * }
66 *
67 * Asynchronous tests: under the current API (soon to be deprecated):
68 *
69 * #import('path-to-dart/lib/unittest/unitest.dart');
70 * #import('dart:dom');
71 * main() {
72 * // use [asyncTest], indicate the expected number of callbacks:
73 * asyncTest('this is a test', 1, () {
74 * window.setTimeout(() {
75 * int x = 2 + 3;
76 * expect(x).equals(5);
77 * // invoke [callbackDone] at the end of the callback.
78 * callbackDone();
79 * }, 0);
80 * });
81 * }
82 *
83 * We plan to replace this with a different API, one API we are considering is:
84 *
85 * #import('path-to-dart/lib/unittest/unitest.dart');
86 * #import('dart:dom');
87 * main() {
88 * test('this is a test', () {
89 * // wrap the callback of an asynchronous call with [later]
90 * window.setTimeout(later(() {
91 * int x = 2 + 3;
92 * expect(x).equals(5);
93 * }), 0);
94 * });
95 * }
96 */
97 #library('unittest');
98
99 #import('dart:isolate');
100
101 #source('config.dart');
102 #source('expectation.dart');
103 #source('test_case.dart');
104
105 /** [Configuration] used by the unittest library. */
106 Configuration _config = null;
107
108 /** Set the [Configuration] used by the unittest library. */
109 void configure(Configuration config) {
110 _config = config;
111 }
112
113 /**
114 * Description text of the current test group. If multiple groups are nested,
115 * this will contain all of their text concatenated.
116 */
117 String _currentGroup = '';
118
119 /** Tests executed in this suite. */
120 List<TestCase> _tests;
121
122 /**
123 * Callback used to run tests. Entrypoints can replace this with their own
124 * if they want.
125 */
126 Function _testRunner;
127
128 /** Current test being executed. */
129 int _currentTest = 0;
130
131 /** Total number of callbacks that have been executed in the current test. */
132 int _callbacksCalled = 0;
133
134 final _UNINITIALIZED = 0;
135 final _READY = 1;
136 final _RUNNING_TEST = 2;
137
138 /**
139 * Whether an undetected error occurred while running the last test. These
140 * errors are commonly caused by DOM callbacks that were not guarded in a
141 * try-catch block.
142 */
143 final _UNCAUGHT_ERROR = 3;
144
145 int _state = _UNINITIALIZED;
146
147 final _PASS = 'pass';
148 final _FAIL = 'fail';
149 final _ERROR = 'error';
150
151 /** Creates an expectation for the given value. */
152 Expectation expect(value) => new Expectation(value);
153
154 /** Evaluates the given function and validates that it throws an exception. */
155 void expectThrow(function) {
156 bool threw = false;
157 try {
158 function();
159 } catch (var e) {
160 threw = true;
161 }
162 Expect.equals(true, threw, 'Expected exception but none was thrown.');
163 }
164
165 /**
166 * Creates a new test case with the given description and body. The
167 * description will include the descriptions of any surrounding group()
168 * calls.
169 */
170 void test(String spec, TestFunction body) {
171 _ensureInitialized();
172
173 _tests.add(new TestCase(_tests.length + 1, _fullSpec(spec), body, 0));
174 }
175
176 /**
177 * Creates a new async test case with the given description and body. The
178 * description will include the descriptions of any surrounding group()
179 * calls.
180 */
181 // TODO(sigmund): deprecate this API
182 void asyncTest(String spec, int callbacks, TestFunction body) {
183 _ensureInitialized();
184
185 final testCase = new TestCase(
186 _tests.length + 1, _fullSpec(spec), body, callbacks);
187 _tests.add(testCase);
188
189 if (callbacks < 1) {
190 testCase.error(
191 'Async tests must wait for at least one callback ', '');
192 }
193 }
194
195 class _Sentinel {
196 static final _sentinel = const _Sentinel();
197
198 const _Sentinel();
199 }
200
201
202 /**
203 * Indicate to the unittest framework that a callback is expected. [callback]
204 * can take any number of arguments between 0 and 4.
205 *
206 * The framework will wait for the callback to run before it continues with the
207 * following test. The callback must excute once and only once. Using [later]
208 * will also ensure that errors that occur within the callback are tracked and
209 * reported by the unittest framework.
210 */
211 // TODO(sigmund): expose this functionality
212 Function _later(Function callback) {
213 Expect.isTrue(_currentTest < _tests.length);
214 var testCase = _tests[_currentTest];
215 testCase.callbacks++;
216 // We simulate spread arguments using named arguments:
217 // Note: this works in the vm and dart2js, but not in frog.
218 return ([arg0 = _Sentinel.value, arg1 = _Sentinel.value,
219 arg2 = _Sentinel.value, arg3 = _Sentinel.value,
220 arg4 = _Sentinel.value]) {
221 _guard(() {
222 if (arg0 == _Sentinel.value) {
223 callback();
224 } else if (arg1 == _Sentinel.value) {
225 callback(arg0);
226 } else if (arg2 == _Sentinel.value) {
227 callback(arg0, arg1);
228 } else if (arg3 == _Sentinel.value) {
229 callback(arg0, arg1, arg2);
230 } else if (arg4 == _Sentinel.value) {
231 callback(arg0, arg1, arg2, arg3);
232 } else {
233 testCase.error(
234 'unittest lib does not support callbacks with more than 4 arguments',
235 '');
236 _state = _UNCAUGHT_ERROR;
237 }
238 }, callbackDone);
239 };
240 }
241
242 // TODO(sigmund): expose this functionality
243 Function _later0(Function callback) {
244 Expect.isTrue(_currentTest < _tests.length);
245 var testCase = _tests[_currentTest];
246 testCase.callbacks++;
247 return () {
248 _guard(() => callback(), callbackDone);
249 };
250 }
251
252 // TODO(sigmund): expose this functionality
253 Function _later1(Function callback) {
254 Expect.isTrue(_currentTest < _tests.length);
255 var testCase = _tests[_currentTest];
256 testCase.callbacks++;
257 return (arg0) {
258 _guard(() => callback(arg0), callbackDone);
259 };
260 }
261
262 // TODO(sigmund): expose this functionality
263 Function _later2(Function callback) {
264 Expect.isTrue(_currentTest < _tests.length);
265 var testCase = _tests[_currentTest];
266 testCase.callbacks++;
267 return (arg0, arg1) {
268 _guard(() => callback(arg0, arg1), callbackDone);
269 };
270 }
271
272 /**
273 * Creates a new named group of tests. Calls to group() or test() within the
274 * body of the function passed to this will inherit this group's description.
275 */
276 void group(String description, void body()) {
277 _ensureInitialized();
278
279 // Concatenate the new group.
280 final oldGroup = _currentGroup;
281 if (_currentGroup != '') {
282 // Add a space.
283 _currentGroup = '$_currentGroup $description';
284 } else {
285 // The first group.
286 _currentGroup = description;
287 }
288
289 try {
290 body();
291 } finally {
292 // Now that the group is over, restore the previous one.
293 _currentGroup = oldGroup;
294 }
295 }
296
297 /** Called by subclasses to indicate that an asynchronous test completed. */
298 void callbackDone() {
299 _callbacksCalled++;
300 final testCase = _tests[_currentTest];
301 if (_callbacksCalled > testCase.callbacks) {
302 final expected = testCase.callbacks;
303 testCase.error(
304 'More calls to callbackDone() than expected. '
305 + 'Actual: ${_callbacksCalled}, expected: ${expected}', '');
306 _state = _UNCAUGHT_ERROR;
307 } else if ((_callbacksCalled == testCase.callbacks) &&
308 (_state != _RUNNING_TEST)) {
309 if (testCase.result == null) testCase.pass();
310 _currentTest++;
311 _testRunner();
312 }
313 }
314
315 void notifyError(String msg, String trace) {
316 if (_currentTest < _tests.length) {
317 final testCase = _tests[_currentTest];
318 testCase.error(msg, trace);
319 _state = _UNCAUGHT_ERROR;
320 if (testCase.callbacks > 0) {
321 _currentTest++;
322 _testRunner();
323 }
324 }
325 }
326
327 /** Runs [callback] at the end of the event loop. */
328 _defer(void callback()) {
329 // Exploit isolate ports as a platform-independent mechanism to queue a
330 // message at the end of the event loop.
331 // TODO(sigmund): expose this functionality somewhere in our libraries.
332 final port = new ReceivePort();
333 port.receive((msg, reply) {
334 callback();
335 port.close();
336 });
337 port.toSendPort().send(null, null);
338 }
339
340 /** Runs all queued tests, one at a time. */
341 _runTests() {
342 _config.onStart();
343
344 _defer(() {
345 assert (_currentTest == 0);
346 _testRunner();
347 });
348 }
349
350 /**
351 * Run [tryBody] guarded in a try-catch block. If an exception is thrown, update
352 * the [_currentTest] status accordingly.
353 */
354 _guard(tryBody, [finallyBody]) {
355 final testCase = _tests[_currentTest];
356 try {
357 tryBody();
358 } catch (ExpectException e, var trace) {
359 if (_state != _UNCAUGHT_ERROR) {
360 //TODO(pquitslund) remove guard once dartc reliably propagates traces
361 testCase.fail(e.message, trace == null ? '' : trace.toString());
362 }
363 } catch (var e, var trace) {
364 if (_state != _UNCAUGHT_ERROR) {
365 //TODO(pquitslund) remove guard once dartc reliably propagates traces
366 testCase.error('Caught ${e}', trace == null ? '' : trace.toString());
367 }
368 } finally {
369 _state = _READY;
370 if (finallyBody != null) finallyBody();
371 }
372 }
373
374 /**
375 * Runs a batch of tests, yielding whenever an asynchronous test starts
376 * running. Tests will resume executing when such asynchronous test calls
377 * [done] or if it fails with an exception.
378 */
379 _nextBatch() {
380 while (_currentTest < _tests.length) {
381 final testCase = _tests[_currentTest];
382
383 _guard(() {
384 _callbacksCalled = 0;
385 _state = _RUNNING_TEST;
386
387 testCase.test();
388
389 if (_state != _UNCAUGHT_ERROR) {
390 if (testCase.callbacks == _callbacksCalled) {
391 testCase.pass();
392 }
393 }
394 });
395
396 if (!testCase.isComplete && testCase.callbacks > 0) return;
397
398 _currentTest++;
399 }
400
401 _completeTests();
402 }
403
404 /** Publish results on the page and notify controller. */
405 _completeTests() {
406 _state = _UNINITIALIZED;
407
408 int testsPassed_ = 0;
409 int testsFailed_ = 0;
410 int testsErrors_ = 0;
411
412 for (TestCase t in _tests) {
413 switch (t.result) {
414 case _PASS: testsPassed_++; break;
415 case _FAIL: testsFailed_++; break;
416 case _ERROR: testsErrors_++; break;
417 }
418 }
419
420 _config.onDone(testsPassed_, testsFailed_, testsErrors_, _tests);
421 }
422
423 String _fullSpec(String spec) {
424 if (spec === null) return '$_currentGroup';
425 return _currentGroup != '' ? '$_currentGroup $spec' : spec;
426 }
427
428 /**
429 * Lazily initializes the test library if not already initialized.
430 */
431 _ensureInitialized() {
432 if (_state != _UNINITIALIZED) return;
433
434 _tests = <TestCase>[];
435 _currentGroup = '';
436 _state = _READY;
437 _testRunner = _nextBatch;
438
439 if (_config == null) {
440 _config = new Configuration();
441 }
442 _config.onInit();
443
444 // Immediately queue the suite up. It will run after a timeout (i.e. after
445 // main() has returned).
446 _defer(_runTests);
447 }
448
449 /** Signature for a test function. */
450 typedef void TestFunction();
OLDNEW
« no previous file with comments | « lib/unittest/test_case.dart ('k') | lib/unittest/unittest_dom.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698