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

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

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 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
« no previous file with comments | « unittest/lib/test_controller.js ('k') | 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 library unittest;
6
7 import 'dart:async';
8 import 'dart:collection';
9
10 import 'src/configuration.dart';
11 import 'src/expected_function.dart';
12 import 'src/group_context.dart';
13 import 'src/internal_test_case.dart';
14 import 'src/matcher.dart' show TestFailure, wrapAsync;
15 import 'src/test_case.dart';
16 import 'src/test_environment.dart';
17
18 export 'src/configuration.dart';
19 export 'src/matcher.dart';
20 export 'src/simple_configuration.dart';
21 export 'src/test_case.dart';
22
23 /// The signature for a function passed to [test].
24 typedef dynamic TestFunction();
25
26 /// [Configuration] used by the unittest library.
27 ///
28 /// Note that if a configuration has not been set, calling this getter will
29 /// create a default configuration.
30 Configuration get unittestConfiguration {
31 if (config == null) environment.config = new Configuration();
32 return config;
33 }
34
35 /// If `true`, stack traces are reformatted to be more readable.
36 bool formatStacks = true;
37
38 /// If `true`, irrelevant frames are filtered from the stack trace.
39 ///
40 /// This does nothing if [formatStacks] is false.
41 bool filterStacks = true;
42
43 /// Separator used between group names and test names.
44 String groupSep = ' ';
45
46 /// Sets the [Configuration] used by the unittest library.
47 ///
48 /// Throws a [StateError] if there is an existing, incompatible value.
49 void set unittestConfiguration(Configuration value) {
50 if (identical(config, value)) return;
51 if (config != null) {
52 logMessage('Warning: The unittestConfiguration has already been set. New '
53 'unittestConfiguration ignored.');
54 } else {
55 environment.config = value;
56 }
57 }
58
59 /// Logs [message] associated with the current test case.
60 ///
61 /// Tests should use this instead of [print].
62 void logMessage(String message) =>
63 config.onLogMessage(currentTestCase, message);
64
65 /// The test cases that have been defined so far.
66 List<TestCase> get testCases =>
67 new UnmodifiableListView<TestCase>(environment.testCases);
68
69 /// The interval (in milliseconds) after which a non-microtask asynchronous
70 /// delay will be scheduled between tests.
71 ///
72 /// This is used to avoid starving the DOM or other non-microtask events.
73 const int BREATH_INTERVAL = 200;
74
75 /// The [TestCase] currently being executed.
76 TestCase get currentTestCase => (environment.currentTestCaseIndex >= 0 &&
77 environment.currentTestCaseIndex < testCases.length)
78 ? testCases[environment.currentTestCaseIndex]
79 : null;
80
81 /// The same as [currentTestCase], but typed as an [InternalTestCase].
82 InternalTestCase get _currentTestCase => currentTestCase as InternalTestCase;
83
84 /// The result string for a passing test case.
85 const PASS = 'pass';
86
87 /// The result string for a failing test case.
88 const FAIL = 'fail';
89
90 /// The result string for an test case with an error.
91 const ERROR = 'error';
92
93 /// Creates a new test case with the given description and body.
94 ///
95 /// The description will be added to the descriptions of any surrounding
96 /// [group]s.
97 void test(String description, TestFunction body) {
98 _requireNotRunning();
99 ensureInitialized();
100
101 if (environment.soloTestSeen && environment.soloNestingLevel == 0) return;
102 var testCase = new InternalTestCase(
103 testCases.length + 1, _fullDescription(description), body);
104 environment.testCases.add(testCase);
105 }
106
107 /// Returns [description] with all of its group prefixes prepended.
108 String _fullDescription(String description) {
109 var group = environment.currentContext.fullName;
110 if (description == null) return group;
111 return group != '' ? '$group$groupSep$description' : description;
112 }
113
114 /// A convenience function for skipping a test.
115 void skip_test(String spec, TestFunction body) {}
116
117 /// Creates a new test case with the given description and body.
118 ///
119 /// If [solo_test] is used instead of [test], then all non-solo tests will be
120 /// disabled. Note that if [solo_group] is used as well, all tests in the group
121 /// will be enabled, regardless of whether they use [test] or [solo_test], or
122 /// whether they are in a nested [group] versus [solo_group]. Put another way,
123 /// if there are any calls to [solo_test] or [solo_group] in a test file, all
124 /// tests that are not inside a [solo_group] will be disabled unless they are
125 /// [solo_test]s.
126 void solo_test(String spec, TestFunction body) {
127 _requireNotRunning();
128 ensureInitialized();
129 if (!environment.soloTestSeen) {
130 environment.soloTestSeen = true;
131 // This is the first solo-ed test. Discard all tests up to now.
132 environment.testCases.clear();
133 }
134 environment.soloNestingLevel++;
135 try {
136 test(spec, body);
137 } finally {
138 environment.soloNestingLevel--;
139 }
140 }
141
142 /// Indicate that [callback] is expected to be called [count] number of times
143 /// (by default 1).
144 ///
145 /// The unittest framework will wait for the callback to run the [count] times
146 /// before it considers the current test to be complete. Using [expectAsync]
147 /// will also ensure that errors that occur within [callback] are tracked and
148 /// reported. [callback] may take up to six optional or required positional
149 /// arguments; named arguments are not supported.
150 ///
151 /// [max] can be used to specify an upper bound on the number of calls; if this
152 /// is exceeded the test will fail. If [max] is `0` (the default), the callback
153 /// is expected to be called exactly [count] times. If [max] is `-1`, the
154 /// callback is allowed to be called any number of times greater than [count].
155 ///
156 /// Both [id] and [reason] are optional and provide extra information about the
157 /// callback when debugging. [id] should be the name of the callback, while
158 /// [reason] should be the reason the callback is expected to be called.
159 Function expectAsync(Function callback,
160 {int count: 1, int max: 0, String id, String reason}) =>
161 new ExpectedFunction(callback, count, max, id: id, reason: reason).func;
162
163 /// Indicate that [callback] is expected to be called until [isDone] returns
164 /// true.
165 ///
166 /// [isDone] is called after each time the function is run. Only when it returns
167 /// true will the callback be considered complete. Using [expectAsyncUntil] will
168 /// also ensure that errors that occur within [callback] are tracked and
169 /// reported. [callback] may take up to six optional or required positional
170 /// arguments; named arguments are not supported.
171 ///
172 /// Both [id] and [reason] are optional and provide extra information about the
173 /// callback when debugging. [id] should be the name of the callback, while
174 /// [reason] should be the reason the callback is expected to be called.
175 Function expectAsyncUntil(Function callback, bool isDone(),
176 {String id, String reason}) => new ExpectedFunction(callback, 0, -1,
177 id: id, reason: reason, isDone: isDone).func;
178
179 /// Creates a group of tests.
180 ///
181 /// A group's description is included in the descriptions of any tests or
182 /// sub-groups it contains. [setUp] and [tearDown] are also scoped to the
183 /// containing group.
184 void group(String description, void body()) {
185 ensureInitialized();
186 _requireNotRunning();
187 environment.currentContext =
188 new GroupContext(environment.currentContext, description);
189 try {
190 body();
191 } catch (e, trace) {
192 var stack = (trace == null) ? '' : ': ${trace.toString()}';
193 environment.uncaughtErrorMessage = "${e.toString()}$stack";
194 } finally {
195 // Now that the group is over, restore the previous one.
196 environment.currentContext = environment.currentContext.parent;
197 }
198 }
199
200 /// A convenience function for skipping a group of tests.
201 void skip_group(String description, void body()) {}
202
203 /// Creates a group of tests.
204 ///
205 /// If [solo_group] is used instead of [group], then all tests not declared with
206 /// [solo_test] or in a [solo_group] will be disabled. Note that all tests in a
207 /// [solo_group] will be run, regardless of whether they're declared with [test]
208 /// or [solo_test].
209 ///
210 /// [skip_test] and [skip_group] take precedence over [solo_group].
211 void solo_group(String description, void body()) {
212 _requireNotRunning();
213 ensureInitialized();
214 if (!environment.soloTestSeen) {
215 environment.soloTestSeen = true;
216 // This is the first solo-ed group. Discard all tests up to now.
217 environment.testCases.clear();
218 }
219 ++environment.soloNestingLevel;
220 try {
221 group(description, body);
222 } finally {
223 --environment.soloNestingLevel;
224 }
225 }
226
227 /// Registers a function to be run before tests.
228 ///
229 /// This function will be called before each test is run. [callback] may be
230 /// asynchronous; if so, it must return a [Future].
231 ///
232 /// If this is called within a test group, it applies only to tests in that
233 /// group. [callback] will be run after any set-up callbacks in parent groups or
234 /// at the top level.
235 void setUp(Function callback) {
236 _requireNotRunning();
237 environment.currentContext.testSetUp = callback;
238 }
239
240 /// Registers a function to be run after tests.
241 ///
242 /// This function will be called after each test is run. [callback] may be
243 /// asynchronous; if so, it must return a [Future].
244 ///
245 /// If this is called within a test group, it applies only to tests in that
246 /// group. [callback] will be run before any tear-down callbacks in parent group s or
247 /// at the top level.
248 void tearDown(Function callback) {
249 _requireNotRunning();
250 environment.currentContext.testTearDown = callback;
251 }
252
253 /// Advance to the next test case.
254 void _nextTestCase() {
255 environment.currentTestCaseIndex++;
256 _runTest();
257 }
258
259 /// Handle an error that occurs outside of any test.
260 void handleExternalError(e, String message, [stackTrace]) {
261 var msg = '$message\nCaught $e';
262
263 if (currentTestCase != null) {
264 _currentTestCase.error(msg, stackTrace);
265 } else {
266 environment.uncaughtErrorMessage = "$msg: $stackTrace";
267 }
268 }
269
270 /// Remove any tests that match [testFilter].
271 ///
272 /// [testFilter] can be a predicate function, a [RegExp], or a [String]. If it's
273 /// a function, it's called with each [TestCase]. If it's a [String], it's
274 /// parsed as a [RegExp] and matched against each [TestCase.description].
275 ///
276 /// This is different from enabling or disabling tests in that it removes the
277 /// tests completely.
278 void filterTests(testFilter) {
279 var filterFunction;
280 if (testFilter is String) {
281 var re = new RegExp(testFilter);
282 filterFunction = (t) => re.hasMatch(t.description);
283 } else if (testFilter is RegExp) {
284 filterFunction = (t) => testFilter.hasMatch(t.description);
285 } else if (testFilter is Function) {
286 filterFunction = testFilter;
287 }
288 environment.testCases.retainWhere(filterFunction);
289 }
290
291 /// Runs all queued tests, one at a time.
292 void runTests() {
293 _requireNotRunning();
294 _ensureInitialized(false);
295 environment.currentTestCaseIndex = 0;
296 config.onStart();
297 _runTest();
298 }
299
300 /// Registers an exception that was caught for the current test.
301 void registerException(error, [StackTrace stackTrace]) =>
302 _currentTestCase.registerException(error, stackTrace);
303
304 /// Runs the next test.
305 void _runTest() {
306 if (environment.currentTestCaseIndex >= testCases.length) {
307 assert(environment.currentTestCaseIndex == testCases.length);
308 _completeTests();
309 return;
310 }
311
312 var testCase = _currentTestCase;
313 var f = runZoned(testCase.run, onError: (error, stack) {
314 // TODO(kevmoo) Do a better job of flagging these are async errors.
315 // https://code.google.com/p/dart/issues/detail?id=16530
316 testCase.registerException(error, stack);
317 });
318
319 var timer;
320 var timeout = unittestConfiguration.timeout;
321 if (timeout != null) {
322 try {
323 timer = new Timer(timeout, () {
324 testCase.error("Test timed out after ${timeout.inSeconds} seconds.");
325 _nextTestCase();
326 });
327 } on UnsupportedError catch (e) {
328 if (e.message != "Timer greater than 0.") rethrow;
329 // Support running on d8 and jsshell which don't support timers.
330 }
331 }
332
333 f.whenComplete(() {
334 if (timer != null) timer.cancel();
335 var now = new DateTime.now().millisecondsSinceEpoch;
336 if (now - environment.lastBreath >= BREATH_INTERVAL) {
337 environment.lastBreath = now;
338 Timer.run(_nextTestCase);
339 } else {
340 scheduleMicrotask(_nextTestCase); // Schedule the next test.
341 }
342 });
343 }
344
345 /// Notify the configuration that the testing has finished.
346 void _completeTests() {
347 if (!environment.initialized) return;
348
349 var passed = 0;
350 var failed = 0;
351 var errors = 0;
352 for (var testCase in testCases) {
353 switch (testCase.result) {
354 case PASS:
355 passed++;
356 break;
357 case FAIL:
358 failed++;
359 break;
360 case ERROR:
361 errors++;
362 break;
363 }
364 }
365
366 config.onSummary(
367 passed, failed, errors, testCases, environment.uncaughtErrorMessage);
368 config.onDone(passed > 0 &&
369 failed == 0 &&
370 errors == 0 &&
371 environment.uncaughtErrorMessage == null);
372 environment.initialized = false;
373 environment.currentTestCaseIndex = -1;
374 }
375
376 /// Initializes the test environment if it hasn't already been initialized.
377 void ensureInitialized() {
378 _ensureInitialized(true);
379 }
380
381 /// Initializes the test environment.
382 ///
383 /// If [configAutoStart] is `true`, schedule a microtask to run the tests. This
384 /// microtask is expected to run after all the tests are defined.
385 void _ensureInitialized(bool configAutoStart) {
386 if (environment.initialized) return;
387
388 environment.initialized = true;
389 // Hook our async guard into the matcher library.
390 wrapAsync = (f, [id]) => expectAsync(f, id: id);
391
392 environment.uncaughtErrorMessage = null;
393
394 unittestConfiguration.onInit();
395
396 // Immediately queue the suite up. It will run after a timeout (i.e. after
397 // main() has returned).
398 if (configAutoStart && config.autoStart) scheduleMicrotask(runTests);
399 }
400
401 /// Remove all tests other than the one identified by [id].
402 void setSoloTest(int id) =>
403 environment.testCases.retainWhere((t) => t.id == id);
404
405 /// Enable the test identified by [id].
406 void enableTest(int id) => _setTestEnabledState(id, enable: true);
407
408 /// Disable the test by [id].
409 void disableTest(int id) => _setTestEnabledState(id, enable: false);
410
411 /// Enable or disable the test identified by [id].
412 void _setTestEnabledState(int id, {bool enable: true}) {
413 // Try fast path first.
414 if (testCases.length > id && testCases[id].id == id) {
415 environment.testCases[id].enabled = enable;
416 } else {
417 for (var i = 0; i < testCases.length; i++) {
418 if (testCases[i].id != id) continue;
419 environment.testCases[i].enabled = enable;
420 break;
421 }
422 }
423 }
424
425 /// Throws a [StateError] if tests are running.
426 void _requireNotRunning() {
427 if (environment.currentTestCaseIndex == -1) return;
428 throw new StateError('Not allowed when tests are running.');
429 }
430
431 /// Creates a test environment running in its own zone scope.
432 ///
433 /// This allows for multiple invocations of the unittest library in the same
434 /// application instance. This is useful when, for example, creating a test
435 /// runner application which needs to create a new pristine test environment on
436 /// each invocation to run a given set of tests.
437 withTestEnvironment(callback()) {
438 return runZoned(callback,
439 zoneValues: {#unittest.environment: new TestEnvironment()});
440 }
OLDNEW
« no previous file with comments | « unittest/lib/test_controller.js ('k') | unittest/lib/vm_config.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698