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

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

Issue 807193003: Re-apply "Remove unittest and matcher from the repo." (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Code review changes Created 6 years 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 | « pkg/unittest/lib/test_controller.js ('k') | pkg/unittest/lib/vm_config.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) 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
3 // BSD-style license that can be found in the LICENSE file.
4
5 /// Support for writing Dart unit tests.
6 ///
7 /// For information on installing and importing this library, see the
8 /// [unittest package on pub.dartlang.org]
9 /// (http://pub.dartlang.org/packages/unittest).
10 ///
11 /// **See also:**
12 /// [Unit Testing with Dart]
13 /// (http://www.dartlang.org/articles/dart-unit-tests/)
14 ///
15 /// ##Concepts
16 ///
17 /// * __Tests__: Tests are specified via the top-level function [test], they ca n be
18 /// organized together using [group].
19 ///
20 /// * __Checks__: Test expectations can be specified via [expect]
21 ///
22 /// * __Matchers__: [expect] assertions are written declaratively using the
23 /// [Matcher] class.
24 ///
25 /// * __Configuration__: The framework can be adapted by setting
26 /// [unittestConfiguration] with a [Configuration]. See the other libraries
27 /// in the `unittest` package for alternative implementations of
28 /// [Configuration] including `compact_vm_config.dart`, `html_config.dart`
29 /// and `html_enhanced_config.dart`.
30 ///
31 /// ##Examples
32 ///
33 /// A trivial test:
34 ///
35 /// import 'package:unittest/unittest.dart';
36 /// main() {
37 /// test('this is a test', () {
38 /// int x = 2 + 3;
39 /// expect(x, equals(5));
40 /// });
41 /// }
42 ///
43 /// Multiple tests:
44 ///
45 /// import 'package:unittest/unittest.dart';
46 /// main() {
47 /// test('this is a test', () {
48 /// int x = 2 + 3;
49 /// expect(x, equals(5));
50 /// });
51 /// test('this is another test', () {
52 /// int x = 2 + 3;
53 /// expect(x, equals(5));
54 /// });
55 /// }
56 ///
57 /// Multiple tests, grouped by category:
58 ///
59 /// import 'package:unittest/unittest.dart';
60 /// main() {
61 /// group('group A', () {
62 /// test('test A.1', () {
63 /// int x = 2 + 3;
64 /// expect(x, equals(5));
65 /// });
66 /// test('test A.2', () {
67 /// int x = 2 + 3;
68 /// expect(x, equals(5));
69 /// });
70 /// });
71 /// group('group B', () {
72 /// test('this B.1', () {
73 /// int x = 2 + 3;
74 /// expect(x, equals(5));
75 /// });
76 /// });
77 /// }
78 ///
79 /// Asynchronous tests: if callbacks expect between 0 and 6 positional
80 /// arguments, [expectAsync] will wrap a function into a new callback and will
81 /// not consider the test complete until that callback is run. A count argument
82 /// can be provided to specify the number of times the callback should be called
83 /// (the default is 1).
84 ///
85 /// import 'dart:async';
86 /// import 'package:unittest/unittest.dart';
87 /// void main() {
88 /// test('callback is executed once', () {
89 /// // wrap the callback of an asynchronous call with [expectAsync] if
90 /// // the callback takes 0 arguments...
91 /// var timer = Timer.run(expectAsync(() {
92 /// int x = 2 + 3;
93 /// expect(x, equals(5));
94 /// }));
95 /// });
96 ///
97 /// test('callback is executed twice', () {
98 /// var callback = expectAsync(() {
99 /// int x = 2 + 3;
100 /// expect(x, equals(5));
101 /// }, count: 2); // <-- we can indicate multiplicity to [expectAsync]
102 /// Timer.run(callback);
103 /// Timer.run(callback);
104 /// });
105 /// }
106 ///
107 /// There may be times when the number of times a callback should be called is
108 /// non-deterministic. In this case a dummy callback can be created with
109 /// expectAsync((){}) and this can be called from the real callback when it is
110 /// finally complete.
111 ///
112 /// A variation on this is [expectAsyncUntil], which takes a callback as the
113 /// first parameter and a predicate function as the second parameter. After each
114 /// time the callback is called, the predicate function will be called. If it
115 /// returns `false` the test will still be considered incomplete.
116 ///
117 /// Test functions can return [Future]s, which provide another way of doing
118 /// asynchronous tests. The test framework will handle exceptions thrown by
119 /// the Future, and will advance to the next test when the Future is complete.
120 ///
121 /// import 'dart:async';
122 /// import 'package:unittest/unittest.dart';
123 /// void main() {
124 /// test('test that time has passed', () {
125 /// var duration = const Duration(milliseconds: 200);
126 /// var time = new DateTime.now();
127 ///
128 /// return new Future.delayed(duration).then((_) {
129 /// var delta = new DateTime.now().difference(time);
130 ///
131 /// expect(delta, greaterThanOrEqualTo(duration));
132 /// });
133 /// });
134 /// }
135 library unittest;
136
137 import 'dart:async';
138 import 'dart:collection';
139 import 'dart:isolate';
140
141 import 'package:matcher/matcher.dart' show DefaultFailureHandler,
142 configureExpectFailureHandler, TestFailure, wrapAsync;
143 export 'package:matcher/matcher.dart';
144
145 import 'src/utils.dart';
146
147 import 'src/configuration.dart';
148 export 'src/configuration.dart';
149
150 part 'src/simple_configuration.dart';
151 part 'src/group_context.dart';
152 part 'src/spread_args_helper.dart';
153 part 'src/test_case.dart';
154 part 'src/test_environment.dart';
155
156 const Symbol _UNITTEST_ENVIRONMENT = #unittest.environment;
157
158 final _TestEnvironment _defaultEnvironment = new _TestEnvironment();
159
160 /**
161 * Internal getter for the current unittest config.
162 */
163 _TestEnvironment get _environment {
164 var environment = Zone.current[_UNITTEST_ENVIRONMENT];
165 if (environment == null) return _defaultEnvironment;
166 return environment;
167 }
168
169 // Convenience getter for the current environment's config.
170 Configuration get _config => _environment.config;
171
172 // Convenience setter for the current environment's config.
173 void set _config(Configuration config) {
174 _environment.config = config;
175 }
176
177 // Convenience getter for the current environment's test cases.
178 List<TestCase> get _testCases => _environment.testCases;
179
180 /// [Configuration] used by the unittest library.
181 ///
182 /// Note that if a configuration has not been set, calling this getter will
183 /// create a default configuration.
184 Configuration get unittestConfiguration {
185 if (_config == null) {
186 _config = new Configuration();
187 }
188 return _config;
189 }
190
191 /// Sets the [Configuration] used by the unittest library.
192 ///
193 /// Throws a [StateError] if there is an existing, incompatible value.
194 void set unittestConfiguration(Configuration value) {
195 if (!identical(_config, value)) {
196 if (_config != null) {
197 logMessage('Warning: The unittestConfiguration has already been set. New '
198 'unittestConfiguration ignored.');
199 } else {
200 _config = value;
201 }
202 }
203 }
204
205 /// Can be called by tests to log status. Tests should use this
206 /// instead of [print].
207 void logMessage(String message) =>
208 _config.onLogMessage(currentTestCase, message);
209
210 /// Separator used between group names and test names.
211 String groupSep = ' ';
212
213 /// Tests executed in this suite.
214 List<TestCase> get testCases =>
215 new UnmodifiableListView<TestCase>(_environment.testCases);
216
217 /// Interval (in msecs) after which synchronous tests will insert an async
218 /// delay to allow DOM or other updates.
219 const int BREATH_INTERVAL = 200;
220
221 /// [TestCase] currently being executed.
222 TestCase get currentTestCase =>
223 (_environment.currentTestCaseIndex >= 0 &&
224 _environment.currentTestCaseIndex < testCases.length)
225 ? testCases[_environment.currentTestCaseIndex]
226 : null;
227
228 /* Test case result strings. */
229 // TODO(gram) we should change these constants to use a different string
230 // (so that writing 'FAIL' in the middle of a test doesn't
231 // imply that the test fails). We can't do it without also changing
232 // the testrunner and test.dart though.
233 /// Result string for a passing test case.
234 const PASS = 'pass';
235 /// Result string for a failing test case.
236 const FAIL = 'fail';
237 /// Result string for an test case with an error.
238 const ERROR = 'error';
239
240 /// Creates a new test case with the given description and body. The
241 /// description will include the descriptions of any surrounding group()
242 /// calls.
243 void test(String spec, TestFunction body) {
244 _requireNotRunning();
245 ensureInitialized();
246 if (!_environment.soloTestSeen || _environment.soloNestingLevel > 0) {
247 var testcase = new TestCase._internal(testCases.length + 1, _fullSpec(spec),
248 body);
249 _testCases.add(testcase);
250 }
251 }
252
253 /// Convenience function for skipping a test.
254 void skip_test(String spec, TestFunction body) {}
255
256 /// Creates a new test case with the given description and body. The
257 /// description will include the descriptions of any surrounding group()
258 /// calls.
259 ///
260 /// If we use [solo_test] (or [solo_group]) instead of test, then all non-solo
261 /// tests will be disabled. Note that if we use [solo_group], all tests in
262 /// the group will be enabled, regardless of whether they use [test] or
263 /// [solo_test], or whether they are in a nested [group] vs [solo_group]. Put
264 /// another way, if there are any calls to [solo_test] or [solo_group] in a test
265 /// file, all tests that are not inside a [solo_group] will be disabled unless
266 /// they are [solo_test]s.
267 ///
268 /// [skip_test] and [skip_group] take precedence over soloing, by virtue of the
269 /// fact that they are effectively no-ops.
270 void solo_test(String spec, TestFunction body) {
271 _requireNotRunning();
272 ensureInitialized();
273 if (!_environment.soloTestSeen) {
274 _environment.soloTestSeen = true;
275 // This is the first solo-ed test. Discard all tests up to now.
276 _testCases.clear();
277 }
278 ++_environment.soloNestingLevel;
279 try {
280 test(spec, body);
281 } finally {
282 --_environment.soloNestingLevel;
283 }
284 }
285
286 /// Indicate that [callback] is expected to be called a [count] number of times
287 /// (by default 1).
288 ///
289 /// The unittest framework will wait for the callback to run the
290 /// specified [count] times before it continues with the following test. Using
291 /// [expectAsync] will also ensure that errors that occur within [callback] are
292 /// tracked and reported. [callback] should take 0 positional arguments (named
293 /// arguments are not supported). [id] can be used to provide more
294 /// descriptive error messages if the callback is called more often than
295 /// expected.
296 ///
297 /// [max] can be used to specify an upper bound on the number of
298 /// calls; if this is exceeded the test will fail (or be marked as in error if
299 /// it was already complete). A value of 0 for [max] (the default) will set
300 /// the upper bound to the same value as [count]; i.e. the callback should be
301 /// called exactly [count] times. A value of -1 for [max] will mean no upper
302 /// bound.
303 ///
304 /// [reason] is optional and is typically not supplied, as a reason is generated
305 /// by the unittest package; if reason is included it is appended to the
306 /// generated reason.
307 Function expectAsync(Function callback,
308 {int count: 1, int max: 0, String id, String reason}) =>
309 new _SpreadArgsHelper(callback, count, max, id, reason).func;
310
311 /// Indicate that [callback] is expected to be called until [isDone] returns
312 /// true.
313 ///
314 /// The unittest framework checks [isDone] after each callback and only
315 /// when it returns true will it continue with the following test. Using
316 /// [expectAsyncUntil] will also ensure that errors that occur within
317 /// [callback] are tracked and reported. [callback] should take 0 positional
318 /// arguments (named arguments are not supported). [id] can be used to
319 /// identify the callback in error messages (for example if it is called
320 /// after the test case is complete).
321 ///
322 /// [reason] is optional and is typically not supplied, as a reason is generated
323 /// by the unittest package; if reason is included it is appended to the
324 /// generated reason.
325 Function expectAsyncUntil(Function callback, bool isDone(),
326 {String id, String reason}) =>
327 new _SpreadArgsHelper(callback, 0, -1, id, reason, isDone: isDone).func;
328
329 /// Creates a new named group of tests.
330 ///
331 /// Calls to group() or test() within the body of the function passed to this
332 /// named group will inherit this group's description.
333 void group(String description, void body()) {
334 ensureInitialized();
335 _requireNotRunning();
336 _environment.currentContext =
337 new _GroupContext(_environment.currentContext, description);
338 try {
339 body();
340 } catch (e, trace) {
341 var stack = (trace == null) ? '' : ': ${trace.toString()}';
342 _environment.uncaughtErrorMessage = "${e.toString()}$stack";
343 } finally {
344 // Now that the group is over, restore the previous one.
345 _environment.currentContext = _environment.currentContext.parent;
346 }
347 }
348
349 /// Like [skip_test], but for groups.
350 void skip_group(String description, void body()) {}
351
352 /// Like [solo_test], but for groups.
353 void solo_group(String description, void body()) {
354 _requireNotRunning();
355 ensureInitialized();
356 if (!_environment.soloTestSeen) {
357 _environment.soloTestSeen = true;
358 // This is the first solo-ed group. Discard all tests up to now.
359 _testCases.clear();
360 }
361 ++_environment.soloNestingLevel;
362 try {
363 group(description, body);
364 } finally {
365 --_environment.soloNestingLevel;
366 }
367 }
368
369 /// Register a [setUp] function for a test [group].
370 ///
371 /// This function will be called before each test in the group is run.
372 /// [setUp] and [tearDown] should be called within the [group] before any
373 /// calls to [test]. The [setupTest] function can be asynchronous; in this
374 /// case it must return a [Future].
375 void setUp(Function setupTest) {
376 _requireNotRunning();
377 _environment.currentContext.testSetup = setupTest;
378 }
379
380 /// Register a [tearDown] function for a test [group].
381 ///
382 /// This function will be called after each test in the group is run.
383 ///
384 /// Note that if groups are nested only the most locally scoped [teardownTest]
385 /// function will be run. [setUp] and [tearDown] should be called within the
386 /// [group] before any calls to [test]. The [teardownTest] function can be
387 /// asynchronous; in this case it must return a [Future].
388 void tearDown(Function teardownTest) {
389 _requireNotRunning();
390 _environment.currentContext.testTeardown = teardownTest;
391 }
392
393 /// Advance to the next test case.
394 void _nextTestCase() {
395 _environment.currentTestCaseIndex++;
396 _runTest();
397 }
398
399 /// Handle errors that happen outside the tests.
400 // TODO(vsm): figure out how to expose the stack trace here
401 // Currently e.message works in dartium, but not in dartc.
402 void handleExternalError(e, String message, [stack]) {
403 var msg = '$message\nCaught $e';
404
405 if (currentTestCase != null) {
406 currentTestCase._error(msg, stack);
407 } else {
408 _environment.uncaughtErrorMessage = "$msg: $stack";
409 }
410 }
411
412 /// Filter the tests by [testFilter].
413 ///
414 /// [testFilter] can be a [RegExp], a [String] or a
415 /// predicate function. This is different from enabling or disabling tests
416 /// in that it removes the tests completely.
417 void filterTests(testFilter) {
418 var filterFunction;
419 if (testFilter is String) {
420 RegExp re = new RegExp(testFilter);
421 filterFunction = (t) => re.hasMatch(t.description);
422 } else if (testFilter is RegExp) {
423 filterFunction = (t) => testFilter.hasMatch(t.description);
424 } else if (testFilter is Function) {
425 filterFunction = testFilter;
426 }
427 _testCases.retainWhere(filterFunction);
428 }
429
430 /// Runs all queued tests, one at a time.
431 void runTests() {
432 _requireNotRunning();
433 _ensureInitialized(false);
434 _environment.currentTestCaseIndex = 0;
435 _config.onStart();
436 _runTest();
437 }
438
439 /// Registers that an exception was caught for the current test.
440 void registerException(e, [trace]) {
441 _registerException(currentTestCase, e, trace);
442 }
443
444 /// Registers that an exception was caught for the current test.
445 void _registerException(TestCase testCase, e, [trace]) {
446 String message = (e is TestFailure) ? e.message : 'Caught $e';
447 if (testCase.result == null) {
448 testCase._fail(message, trace);
449 } else {
450 testCase._error(message, trace);
451 }
452 }
453
454 /// Runs the next test.
455 void _runTest() {
456 if (_environment.currentTestCaseIndex >= testCases.length) {
457 assert(_environment.currentTestCaseIndex == testCases.length);
458 _completeTests();
459 } else {
460 var testCase = testCases[_environment.currentTestCaseIndex];
461 Future f = runZoned(testCase._run, onError: (error, stack) {
462 // TODO(kevmoo) Do a better job of flagging these are async errors.
463 // https://code.google.com/p/dart/issues/detail?id=16530
464 _registerException(testCase, error, stack);
465 });
466
467 var timeout = unittestConfiguration.timeout;
468
469 Timer timer;
470 if (timeout != null) {
471 try {
472 timer = new Timer(timeout, () {
473 testCase._error("Test timed out after ${timeout.inSeconds} seconds.");
474 _nextTestCase();
475 });
476 } on UnsupportedError catch (e) {
477 if (e.message != "Timer greater than 0.") rethrow;
478 // Support running on d8 and jsshell which don't support timers.
479 }
480 }
481 f.whenComplete(() {
482 if (timer != null) timer.cancel();
483 var now = new DateTime.now().millisecondsSinceEpoch;
484 if ((now - _environment.lastBreath) >= BREATH_INTERVAL) {
485 _environment.lastBreath = now;
486 Timer.run(_nextTestCase);
487 } else {
488 scheduleMicrotask(_nextTestCase); // Schedule the next test.
489 }
490 });
491 }
492 }
493
494 /// Publish results on the page and notify controller.
495 void _completeTests() {
496 if (!_environment.initialized) return;
497 int passed = 0;
498 int failed = 0;
499 int errors = 0;
500
501 for (TestCase t in testCases) {
502 switch (t.result) {
503 case PASS: passed++; break;
504 case FAIL: failed++; break;
505 case ERROR: errors++; break;
506 }
507 }
508 _config.onSummary(passed, failed, errors, testCases,
509 _environment.uncaughtErrorMessage);
510 _config.onDone(passed > 0 && failed == 0 && errors == 0 &&
511 _environment.uncaughtErrorMessage == null);
512 _environment.initialized = false;
513 _environment.currentTestCaseIndex = -1;
514 }
515
516 String _fullSpec(String spec) {
517 var group = '${_environment.currentContext.fullName}';
518 if (spec == null) return group;
519 return group != '' ? '$group$groupSep$spec' : spec;
520 }
521
522 /// Lazily initializes the test library if not already initialized.
523 void ensureInitialized() {
524 _ensureInitialized(true);
525 }
526
527 void _ensureInitialized(bool configAutoStart) {
528 if (_environment.initialized) {
529 return;
530 }
531 _environment.initialized = true;
532 // Hook our async guard into the matcher library.
533 wrapAsync = (f, [id]) => expectAsync(f, id: id);
534
535 _environment.uncaughtErrorMessage = null;
536
537 unittestConfiguration.onInit();
538
539 if (configAutoStart && _config.autoStart) {
540 // Immediately queue the suite up. It will run after a timeout (i.e. after
541 // main() has returned).
542 scheduleMicrotask(runTests);
543 }
544 }
545
546 /// Select a solo test by ID.
547 void setSoloTest(int id) => _testCases.retainWhere((t) => t.id == id);
548
549 /// Enable/disable a test by ID.
550 void _setTestEnabledState(int testId, bool state) {
551 // Try fast path first.
552 if (testCases.length > testId && testCases[testId].id == testId) {
553 testCases[testId]._enabled = state;
554 } else {
555 for (var i = 0; i < testCases.length; i++) {
556 if (testCases[i].id == testId) {
557 testCases[i]._enabled = state;
558 break;
559 }
560 }
561 }
562 }
563
564 /// Enable a test by ID.
565 void enableTest(int testId) => _setTestEnabledState(testId, true);
566
567 /// Disable a test by ID.
568 void disableTest(int testId) => _setTestEnabledState(testId, false);
569
570 /// Signature for a test function.
571 typedef dynamic TestFunction();
572
573 /// A flag that controls whether we hide unittest and core library details in
574 /// exception stacks.
575 ///
576 /// Useful to disable when debugging unittest or matcher customizations.
577 bool formatStacks = true;
578
579 /// A flag that controls whether we try to filter out irrelevant frames from
580 /// the stack trace.
581 ///
582 /// Requires [formatStacks] to be set.
583 bool filterStacks = true;
584
585 void _requireNotRunning() {
586 if (_environment.currentTestCaseIndex != -1) {
587 throw new StateError('Not allowed when tests are running.');
588 }
589 }
590
591 /// Method to create a test environment running in its own zone scope.
592 ///
593 /// This allows for multiple invocations of the unittest library in the same
594 /// application instance.
595 /// This is useful when, for example, creating a test runner application which
596 /// needs to create a new pristine test environment on each invocation to run
597 /// a given set of test.
598 dynamic withTestEnvironment(callback()) {
599 return runZoned(callback,
600 zoneValues: {_UNITTEST_ENVIRONMENT: new _TestEnvironment()});
601 }
OLDNEW
« no previous file with comments | « pkg/unittest/lib/test_controller.js ('k') | pkg/unittest/lib/vm_config.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698