OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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 /// Support for writing Dart unit tests. | 5 /// Support for writing Dart unit tests. |
6 /// | 6 /// |
7 /// For information on installing and importing this library, see the | 7 /// For information on installing and importing this library, see the |
8 /// [unittest package on pub.dartlang.org] | 8 /// [unittest package on pub.dartlang.org] |
9 /// (http://pub.dartlang.org/packages/unittest). | 9 /// (http://pub.dartlang.org/packages/unittest). |
10 /// | 10 /// |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
129 /// var delta = new DateTime.now().difference(time); | 129 /// var delta = new DateTime.now().difference(time); |
130 /// | 130 /// |
131 /// expect(delta, greaterThanOrEqualTo(duration)); | 131 /// expect(delta, greaterThanOrEqualTo(duration)); |
132 /// }); | 132 /// }); |
133 /// }); | 133 /// }); |
134 /// } | 134 /// } |
135 library unittest; | 135 library unittest; |
136 | 136 |
137 import 'dart:async'; | 137 import 'dart:async'; |
138 import 'dart:collection'; | 138 import 'dart:collection'; |
139 import 'dart:isolate'; | 139 |
140 | 140 import 'package:matcher/matcher.dart' show TestFailure, wrapAsync; |
141 import 'package:matcher/matcher.dart' show DefaultFailureHandler, | 141 |
142 configureExpectFailureHandler, TestFailure, wrapAsync; | 142 import 'src/configuration.dart'; |
| 143 import 'src/expected_function.dart'; |
| 144 import 'src/group_context.dart'; |
| 145 import 'src/internal_test_case.dart'; |
| 146 import 'src/test_case.dart'; |
| 147 import 'src/test_environment.dart'; |
| 148 |
143 export 'package:matcher/matcher.dart'; | 149 export 'package:matcher/matcher.dart'; |
144 | 150 |
145 import 'src/utils.dart'; | |
146 | |
147 import 'src/configuration.dart'; | |
148 export 'src/configuration.dart'; | 151 export 'src/configuration.dart'; |
149 | 152 export 'src/simple_configuration.dart'; |
150 part 'src/simple_configuration.dart'; | 153 export 'src/test_case.dart'; |
151 part 'src/group_context.dart'; | 154 |
152 part 'src/spread_args_helper.dart'; | 155 /// The signature for a function passed to [test]. |
153 part 'src/test_case.dart'; | 156 typedef dynamic TestFunction(); |
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 | 157 |
180 /// [Configuration] used by the unittest library. | 158 /// [Configuration] used by the unittest library. |
181 /// | 159 /// |
182 /// Note that if a configuration has not been set, calling this getter will | 160 /// Note that if a configuration has not been set, calling this getter will |
183 /// create a default configuration. | 161 /// create a default configuration. |
184 Configuration get unittestConfiguration { | 162 Configuration get unittestConfiguration { |
185 if (_config == null) { | 163 if (config == null) environment.config = new Configuration(); |
186 _config = new Configuration(); | 164 return config; |
187 } | 165 } |
188 return _config; | 166 |
189 } | 167 /// If `true`, stack traces are reformatted to be more readable. |
| 168 bool formatStacks = true; |
| 169 |
| 170 /// If `true`, irrelevant frames are filtered from the stack trace. |
| 171 /// |
| 172 /// This does nothing if [formatStacks] is false. |
| 173 bool filterStacks = true; |
| 174 |
| 175 /// Separator used between group names and test names. |
| 176 String groupSep = ' '; |
190 | 177 |
191 /// Sets the [Configuration] used by the unittest library. | 178 /// Sets the [Configuration] used by the unittest library. |
192 /// | 179 /// |
193 /// Throws a [StateError] if there is an existing, incompatible value. | 180 /// Throws a [StateError] if there is an existing, incompatible value. |
194 void set unittestConfiguration(Configuration value) { | 181 void set unittestConfiguration(Configuration value) { |
195 if (!identical(_config, value)) { | 182 if (identical(config, value)) return; |
196 if (_config != null) { | 183 if (config != null) { |
197 logMessage('Warning: The unittestConfiguration has already been set. New ' | 184 logMessage('Warning: The unittestConfiguration has already been set. New ' |
198 'unittestConfiguration ignored.'); | 185 'unittestConfiguration ignored.'); |
199 } else { | 186 } else { |
200 _config = value; | 187 environment.config = value; |
201 } | 188 } |
202 } | 189 } |
203 } | 190 |
204 | 191 /// Logs [message] associated with the current test case. |
205 /// Can be called by tests to log status. Tests should use this | 192 /// |
206 /// instead of [print]. | 193 /// Tests should use this instead of [print]. |
207 void logMessage(String message) => | 194 void logMessage(String message) => |
208 _config.onLogMessage(currentTestCase, message); | 195 config.onLogMessage(currentTestCase, message); |
209 | 196 |
210 /// Separator used between group names and test names. | 197 /// The test cases that have been defined so far. |
211 String groupSep = ' '; | |
212 | |
213 /// Tests executed in this suite. | |
214 List<TestCase> get testCases => | 198 List<TestCase> get testCases => |
215 new UnmodifiableListView<TestCase>(_environment.testCases); | 199 new UnmodifiableListView<TestCase>(environment.testCases); |
216 | 200 |
217 /// Interval (in msecs) after which synchronous tests will insert an async | 201 /// The interval (in milliseconds) after which a non-microtask asynchronous |
218 /// delay to allow DOM or other updates. | 202 /// delay will be scheduled between tests. |
| 203 /// |
| 204 /// This is used to avoid starving the DOM or other non-microtask events. |
219 const int BREATH_INTERVAL = 200; | 205 const int BREATH_INTERVAL = 200; |
220 | 206 |
221 /// [TestCase] currently being executed. | 207 /// The [TestCase] currently being executed. |
222 TestCase get currentTestCase => | 208 TestCase get currentTestCase => |
223 (_environment.currentTestCaseIndex >= 0 && | 209 (environment.currentTestCaseIndex >= 0 && |
224 _environment.currentTestCaseIndex < testCases.length) | 210 environment.currentTestCaseIndex < testCases.length) |
225 ? testCases[_environment.currentTestCaseIndex] | 211 ? testCases[environment.currentTestCaseIndex] |
226 : null; | 212 : null; |
227 | 213 |
228 /* Test case result strings. */ | 214 /// The same as [currentTestCase], but typed as an [InternalTestCase]. |
229 // TODO(gram) we should change these constants to use a different string | 215 InternalTestCase get _currentTestCase => currentTestCase as InternalTestCase; |
230 // (so that writing 'FAIL' in the middle of a test doesn't | 216 |
231 // imply that the test fails). We can't do it without also changing | 217 /// The result string for a passing test case. |
232 // the testrunner and test.dart though. | |
233 /// Result string for a passing test case. | |
234 const PASS = 'pass'; | 218 const PASS = 'pass'; |
235 /// Result string for a failing test case. | 219 |
| 220 /// The result string for a failing test case. |
236 const FAIL = 'fail'; | 221 const FAIL = 'fail'; |
237 /// Result string for an test case with an error. | 222 |
| 223 /// The result string for an test case with an error. |
238 const ERROR = 'error'; | 224 const ERROR = 'error'; |
239 | 225 |
240 /// Creates a new test case with the given description and body. The | 226 /// Creates a new test case with the given description and body. |
241 /// description will include the descriptions of any surrounding group() | 227 /// |
242 /// calls. | 228 /// The description will be added to the descriptions of any surrounding |
243 void test(String spec, TestFunction body) { | 229 /// [group]s. |
| 230 void test(String description, TestFunction body) { |
244 _requireNotRunning(); | 231 _requireNotRunning(); |
245 ensureInitialized(); | 232 ensureInitialized(); |
246 if (!_environment.soloTestSeen || _environment.soloNestingLevel > 0) { | 233 |
247 var testcase = new TestCase._internal(testCases.length + 1, _fullSpec(spec), | 234 if (environment.soloTestSeen && environment.soloNestingLevel == 0) return; |
248 body); | 235 var testCase = new InternalTestCase( |
249 _testCases.add(testcase); | 236 testCases.length + 1, _fullDescription(description), body); |
250 } | 237 environment.testCases.add(testCase); |
251 } | 238 } |
252 | 239 |
253 /// Convenience function for skipping a test. | 240 /// Returns [description] with all of its group prefixes prepended. |
| 241 String _fullDescription(String description) { |
| 242 var group = environment.currentContext.fullName; |
| 243 if (description == null) return group; |
| 244 return group != '' ? '$group$groupSep$description' : description; |
| 245 } |
| 246 |
| 247 /// A convenience function for skipping a test. |
254 void skip_test(String spec, TestFunction body) {} | 248 void skip_test(String spec, TestFunction body) {} |
255 | 249 |
256 /// Creates a new test case with the given description and body. The | 250 /// Creates a new test case with the given description and body. |
257 /// description will include the descriptions of any surrounding group() | 251 /// |
258 /// calls. | 252 /// If [solo_test] is used instead of [test], then all non-solo tests will be |
259 /// | 253 /// disabled. Note that if [solo_group] is used as well, all tests in the group |
260 /// If we use [solo_test] (or [solo_group]) instead of test, then all non-solo | 254 /// will be enabled, regardless of whether they use [test] or [solo_test], or |
261 /// tests will be disabled. Note that if we use [solo_group], all tests in | 255 /// whether they are in a nested [group] versus [solo_group]. Put another way, |
262 /// the group will be enabled, regardless of whether they use [test] or | 256 /// if there are any calls to [solo_test] or [solo_group] in a test file, all |
263 /// [solo_test], or whether they are in a nested [group] vs [solo_group]. Put | 257 /// tests that are not inside a [solo_group] will be disabled unless they are |
264 /// another way, if there are any calls to [solo_test] or [solo_group] in a test | 258 /// [solo_test]s. |
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) { | 259 void solo_test(String spec, TestFunction body) { |
271 _requireNotRunning(); | 260 _requireNotRunning(); |
272 ensureInitialized(); | 261 ensureInitialized(); |
273 if (!_environment.soloTestSeen) { | 262 if (!environment.soloTestSeen) { |
274 _environment.soloTestSeen = true; | 263 environment.soloTestSeen = true; |
275 // This is the first solo-ed test. Discard all tests up to now. | 264 // This is the first solo-ed test. Discard all tests up to now. |
276 _testCases.clear(); | 265 environment.testCases.clear(); |
277 } | 266 } |
278 ++_environment.soloNestingLevel; | 267 environment.soloNestingLevel++; |
279 try { | 268 try { |
280 test(spec, body); | 269 test(spec, body); |
281 } finally { | 270 } finally { |
282 --_environment.soloNestingLevel; | 271 environment.soloNestingLevel--; |
283 } | 272 } |
284 } | 273 } |
285 | 274 |
286 /// Indicate that [callback] is expected to be called a [count] number of times | 275 /// Indicate that [callback] is expected to be called [count] number of times |
287 /// (by default 1). | 276 /// (by default 1). |
288 /// | 277 /// |
289 /// The unittest framework will wait for the callback to run the | 278 /// The unittest framework will wait for the callback to run the [count] times |
290 /// specified [count] times before it continues with the following test. Using | 279 /// before it considers the current test to be complete. Using [expectAsync] |
291 /// [expectAsync] will also ensure that errors that occur within [callback] are | 280 /// will also ensure that errors that occur within [callback] are tracked and |
292 /// tracked and reported. [callback] should take 0 positional arguments (named | 281 /// reported. [callback] may take up to six optional or required positional |
293 /// arguments are not supported). [id] can be used to provide more | 282 /// arguments; named arguments are not supported. |
294 /// descriptive error messages if the callback is called more often than | 283 /// |
295 /// expected. | 284 /// [max] can be used to specify an upper bound on the number of calls; if this |
296 /// | 285 /// is exceeded the test will fail. If [max] is `0` (the default), the callback |
297 /// [max] can be used to specify an upper bound on the number of | 286 /// is expected to be called exactly [count] times. If [max] is `-1`, the |
298 /// calls; if this is exceeded the test will fail (or be marked as in error if | 287 /// callback is allowed to be called any number of times greater than [count]. |
299 /// it was already complete). A value of 0 for [max] (the default) will set | 288 /// |
300 /// the upper bound to the same value as [count]; i.e. the callback should be | 289 /// Both [id] and [reason] are optional and provide extra information about the |
301 /// called exactly [count] times. A value of -1 for [max] will mean no upper | 290 /// callback when debugging. [id] should be the name of the callback, while |
302 /// bound. | 291 /// [reason] should be the reason the callback is expected to be called. |
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, | 292 Function expectAsync(Function callback, |
308 {int count: 1, int max: 0, String id, String reason}) => | 293 {int count: 1, int max: 0, String id, String reason}) => |
309 new _SpreadArgsHelper(callback, count, max, id, reason).func; | 294 new ExpectedFunction(callback, count, max, id: id, reason: reason).func; |
310 | 295 |
311 /// Indicate that [callback] is expected to be called until [isDone] returns | 296 /// Indicate that [callback] is expected to be called until [isDone] returns |
312 /// true. | 297 /// true. |
313 /// | 298 /// |
314 /// The unittest framework checks [isDone] after each callback and only | 299 /// [isDone] is called after each time the function is run. Only when it returns |
315 /// when it returns true will it continue with the following test. Using | 300 /// true will the callback be considered complete. Using [expectAsyncUntil] will |
316 /// [expectAsyncUntil] will also ensure that errors that occur within | 301 /// also ensure that errors that occur within [callback] are tracked and |
317 /// [callback] are tracked and reported. [callback] should take 0 positional | 302 /// reported. [callback] may take up to six optional or required positional |
318 /// arguments (named arguments are not supported). [id] can be used to | 303 /// arguments; named arguments are not supported. |
319 /// identify the callback in error messages (for example if it is called | 304 /// |
320 /// after the test case is complete). | 305 /// Both [id] and [reason] are optional and provide extra information about the |
321 /// | 306 /// callback when debugging. [id] should be the name of the callback, while |
322 /// [reason] is optional and is typically not supplied, as a reason is generated | 307 /// [reason] should be the reason the callback is expected to be called. |
323 /// by the unittest package; if reason is included it is appended to the | 308 Function expectAsyncUntil(Function callback, bool isDone(), {String id, |
324 /// generated reason. | 309 String reason}) => |
325 Function expectAsyncUntil(Function callback, bool isDone(), | 310 new ExpectedFunction(callback, 0, -1, id: id, reason: reason, isDone: isDone) |
326 {String id, String reason}) => | 311 .func; |
327 new _SpreadArgsHelper(callback, 0, -1, id, reason, isDone: isDone).func; | 312 |
328 | 313 /// Creates a group of tests. |
329 /// Creates a new named group of tests. | 314 /// |
330 /// | 315 /// A group's description is included in the descriptions of any tests or |
331 /// Calls to group() or test() within the body of the function passed to this | 316 /// sub-groups it contains. [setUp] and [tearDown] are also scoped to the |
332 /// named group will inherit this group's description. | 317 /// containing group. |
333 void group(String description, void body()) { | 318 void group(String description, void body()) { |
334 ensureInitialized(); | 319 ensureInitialized(); |
335 _requireNotRunning(); | 320 _requireNotRunning(); |
336 _environment.currentContext = | 321 environment.currentContext = |
337 new _GroupContext(_environment.currentContext, description); | 322 new GroupContext(environment.currentContext, description); |
338 try { | 323 try { |
339 body(); | 324 body(); |
340 } catch (e, trace) { | 325 } catch (e, trace) { |
341 var stack = (trace == null) ? '' : ': ${trace.toString()}'; | 326 var stack = (trace == null) ? '' : ': ${trace.toString()}'; |
342 _environment.uncaughtErrorMessage = "${e.toString()}$stack"; | 327 environment.uncaughtErrorMessage = "${e.toString()}$stack"; |
343 } finally { | 328 } finally { |
344 // Now that the group is over, restore the previous one. | 329 // Now that the group is over, restore the previous one. |
345 _environment.currentContext = _environment.currentContext.parent; | 330 environment.currentContext = environment.currentContext.parent; |
346 } | 331 } |
347 } | 332 } |
348 | 333 |
349 /// Like [skip_test], but for groups. | 334 /// A convenience function for skipping a group of tests. |
350 void skip_group(String description, void body()) {} | 335 void skip_group(String description, void body()) {} |
351 | 336 |
352 /// Like [solo_test], but for groups. | 337 /// Creates a group of tests. |
| 338 /// |
| 339 /// If [solo_group] is used instead of [group], then all tests not declared with |
| 340 /// [solo_test] or in a [solo_group] will be disabled. Note that all tests in a |
| 341 /// [solo_group] will be run, regardless of whether they're declared with [test] |
| 342 /// or [solo_test]. |
| 343 /// |
| 344 /// [skip_test] and [skip_group] take precedence over [solo_group]. |
353 void solo_group(String description, void body()) { | 345 void solo_group(String description, void body()) { |
354 _requireNotRunning(); | 346 _requireNotRunning(); |
355 ensureInitialized(); | 347 ensureInitialized(); |
356 if (!_environment.soloTestSeen) { | 348 if (!environment.soloTestSeen) { |
357 _environment.soloTestSeen = true; | 349 environment.soloTestSeen = true; |
358 // This is the first solo-ed group. Discard all tests up to now. | 350 // This is the first solo-ed group. Discard all tests up to now. |
359 _testCases.clear(); | 351 environment.testCases.clear(); |
360 } | 352 } |
361 ++_environment.soloNestingLevel; | 353 ++environment.soloNestingLevel; |
362 try { | 354 try { |
363 group(description, body); | 355 group(description, body); |
364 } finally { | 356 } finally { |
365 --_environment.soloNestingLevel; | 357 --environment.soloNestingLevel; |
366 } | 358 } |
367 } | 359 } |
368 | 360 |
369 /// Register a [setUp] function for a test [group]. | 361 /// Registers a function to be run before tests. |
370 /// | 362 /// |
371 /// This function will be called before each test in the group is run. | 363 /// This function will be called before each test is run. [callback] may be |
372 /// [setUp] and [tearDown] should be called within the [group] before any | 364 /// asynchronous; if so, it must return a [Future]. |
373 /// calls to [test]. The [setupTest] function can be asynchronous; in this | 365 /// |
374 /// case it must return a [Future]. | 366 /// If this is called within a test group, it applies only to tests in that |
375 void setUp(Function setupTest) { | 367 /// group. [callback] will be run after any set-up callbacks in parent groups or |
376 _requireNotRunning(); | 368 /// at the top level. |
377 _environment.currentContext.testSetup = setupTest; | 369 void setUp(Function callback) { |
378 } | 370 _requireNotRunning(); |
379 | 371 environment.currentContext.testSetUp = callback; |
380 /// Register a [tearDown] function for a test [group]. | 372 } |
381 /// | 373 |
382 /// This function will be called after each test in the group is run. | 374 /// Registers a function to be run after tests. |
383 /// | 375 /// |
384 /// Note that if groups are nested only the most locally scoped [teardownTest] | 376 /// This function will be called after each test is run. [callback] may be |
385 /// function will be run. [setUp] and [tearDown] should be called within the | 377 /// asynchronous; if so, it must return a [Future]. |
386 /// [group] before any calls to [test]. The [teardownTest] function can be | 378 /// |
387 /// asynchronous; in this case it must return a [Future]. | 379 /// If this is called within a test group, it applies only to tests in that |
388 void tearDown(Function teardownTest) { | 380 /// group. [callback] will be run before any tear-down callbacks in parent group
s or |
389 _requireNotRunning(); | 381 /// at the top level. |
390 _environment.currentContext.testTeardown = teardownTest; | 382 void tearDown(Function callback) { |
| 383 _requireNotRunning(); |
| 384 environment.currentContext.testTearDown = callback; |
391 } | 385 } |
392 | 386 |
393 /// Advance to the next test case. | 387 /// Advance to the next test case. |
394 void _nextTestCase() { | 388 void _nextTestCase() { |
395 _environment.currentTestCaseIndex++; | 389 environment.currentTestCaseIndex++; |
396 _runTest(); | 390 _runTest(); |
397 } | 391 } |
398 | 392 |
399 /// Handle errors that happen outside the tests. | 393 /// Handle an error that occurs outside of any test. |
400 // TODO(vsm): figure out how to expose the stack trace here | 394 void handleExternalError(e, String message, [stackTrace]) { |
401 // Currently e.message works in dartium, but not in dartc. | |
402 void handleExternalError(e, String message, [stack]) { | |
403 var msg = '$message\nCaught $e'; | 395 var msg = '$message\nCaught $e'; |
404 | 396 |
405 if (currentTestCase != null) { | 397 if (currentTestCase != null) { |
406 currentTestCase._error(msg, stack); | 398 _currentTestCase.error(msg, stackTrace); |
407 } else { | 399 } else { |
408 _environment.uncaughtErrorMessage = "$msg: $stack"; | 400 environment.uncaughtErrorMessage = "$msg: $stackTrace"; |
409 } | 401 } |
410 } | 402 } |
411 | 403 |
412 /// Filter the tests by [testFilter]. | 404 /// Remove any tests that match [testFilter]. |
413 /// | 405 /// |
414 /// [testFilter] can be a [RegExp], a [String] or a | 406 /// [testFilter] can be a predicate function, a [RegExp], or a [String]. If it's |
415 /// predicate function. This is different from enabling or disabling tests | 407 /// a function, it's called with each [TestCase]. If it's a [String], it's |
416 /// in that it removes the tests completely. | 408 /// parsed as a [RegExp] and matched against each [TestCase.description]. |
| 409 /// |
| 410 /// This is different from enabling or disabling tests in that it removes the |
| 411 /// tests completely. |
417 void filterTests(testFilter) { | 412 void filterTests(testFilter) { |
418 var filterFunction; | 413 var filterFunction; |
419 if (testFilter is String) { | 414 if (testFilter is String) { |
420 RegExp re = new RegExp(testFilter); | 415 var re = new RegExp(testFilter); |
421 filterFunction = (t) => re.hasMatch(t.description); | 416 filterFunction = (t) => re.hasMatch(t.description); |
422 } else if (testFilter is RegExp) { | 417 } else if (testFilter is RegExp) { |
423 filterFunction = (t) => testFilter.hasMatch(t.description); | 418 filterFunction = (t) => testFilter.hasMatch(t.description); |
424 } else if (testFilter is Function) { | 419 } else if (testFilter is Function) { |
425 filterFunction = testFilter; | 420 filterFunction = testFilter; |
426 } | 421 } |
427 _testCases.retainWhere(filterFunction); | 422 environment.testCases.retainWhere(filterFunction); |
428 } | 423 } |
429 | 424 |
430 /// Runs all queued tests, one at a time. | 425 /// Runs all queued tests, one at a time. |
431 void runTests() { | 426 void runTests() { |
432 _requireNotRunning(); | 427 _requireNotRunning(); |
433 _ensureInitialized(false); | 428 _ensureInitialized(false); |
434 _environment.currentTestCaseIndex = 0; | 429 environment.currentTestCaseIndex = 0; |
435 _config.onStart(); | 430 config.onStart(); |
436 _runTest(); | 431 _runTest(); |
437 } | 432 } |
438 | 433 |
439 /// Registers that an exception was caught for the current test. | 434 /// Registers an exception that was caught for the current test. |
440 void registerException(e, [trace]) { | 435 void registerException(error, [StackTrace stackTrace]) => |
441 _registerException(currentTestCase, e, trace); | 436 _currentTestCase.registerException(error, stackTrace); |
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 | 437 |
454 /// Runs the next test. | 438 /// Runs the next test. |
455 void _runTest() { | 439 void _runTest() { |
456 if (_environment.currentTestCaseIndex >= testCases.length) { | 440 if (environment.currentTestCaseIndex >= testCases.length) { |
457 assert(_environment.currentTestCaseIndex == testCases.length); | 441 assert(environment.currentTestCaseIndex == testCases.length); |
458 _completeTests(); | 442 _completeTests(); |
459 } else { | 443 return; |
460 var testCase = testCases[_environment.currentTestCaseIndex]; | 444 } |
461 Future f = runZoned(testCase._run, onError: (error, stack) { | 445 |
462 // TODO(kevmoo) Do a better job of flagging these are async errors. | 446 var testCase = _currentTestCase; |
463 // https://code.google.com/p/dart/issues/detail?id=16530 | 447 var f = runZoned(testCase.run, onError: (error, stack) { |
464 _registerException(testCase, error, stack); | 448 // TODO(kevmoo) Do a better job of flagging these are async errors. |
465 }); | 449 // https://code.google.com/p/dart/issues/detail?id=16530 |
466 | 450 testCase.registerException(error, stack); |
467 var timeout = unittestConfiguration.timeout; | 451 }); |
468 | 452 |
469 Timer timer; | 453 var timer; |
470 if (timeout != null) { | 454 var timeout = unittestConfiguration.timeout; |
471 try { | 455 if (timeout != null) { |
472 timer = new Timer(timeout, () { | 456 try { |
473 testCase._error("Test timed out after ${timeout.inSeconds} seconds."); | 457 timer = new Timer(timeout, () { |
474 _nextTestCase(); | 458 testCase.error("Test timed out after ${timeout.inSeconds} seconds."); |
475 }); | 459 _nextTestCase(); |
476 } on UnsupportedError catch (e) { | 460 }); |
477 if (e.message != "Timer greater than 0.") rethrow; | 461 } on UnsupportedError catch (e) { |
478 // Support running on d8 and jsshell which don't support timers. | 462 if (e.message != "Timer greater than 0.") rethrow; |
479 } | 463 // Support running on d8 and jsshell which don't support timers. |
480 } | 464 } |
481 f.whenComplete(() { | 465 } |
482 if (timer != null) timer.cancel(); | 466 |
483 var now = new DateTime.now().millisecondsSinceEpoch; | 467 f.whenComplete(() { |
484 if ((now - _environment.lastBreath) >= BREATH_INTERVAL) { | 468 if (timer != null) timer.cancel(); |
485 _environment.lastBreath = now; | 469 var now = new DateTime.now().millisecondsSinceEpoch; |
486 Timer.run(_nextTestCase); | 470 if (now - environment.lastBreath >= BREATH_INTERVAL) { |
487 } else { | 471 environment.lastBreath = now; |
488 scheduleMicrotask(_nextTestCase); // Schedule the next test. | 472 Timer.run(_nextTestCase); |
489 } | 473 } else { |
490 }); | 474 scheduleMicrotask(_nextTestCase); // Schedule the next test. |
491 } | 475 } |
492 } | 476 }); |
493 | 477 } |
494 /// Publish results on the page and notify controller. | 478 |
| 479 /// Notify the configuration that the testing has finished. |
495 void _completeTests() { | 480 void _completeTests() { |
496 if (!_environment.initialized) return; | 481 if (!environment.initialized) return; |
497 int passed = 0; | 482 |
498 int failed = 0; | 483 var passed = 0; |
499 int errors = 0; | 484 var failed = 0; |
500 | 485 var errors = 0; |
501 for (TestCase t in testCases) { | 486 for (var testCase in testCases) { |
502 switch (t.result) { | 487 switch (testCase.result) { |
503 case PASS: passed++; break; | 488 case PASS: passed++; break; |
504 case FAIL: failed++; break; | 489 case FAIL: failed++; break; |
505 case ERROR: errors++; break; | 490 case ERROR: errors++; break; |
506 } | 491 } |
507 } | 492 } |
508 _config.onSummary(passed, failed, errors, testCases, | 493 |
509 _environment.uncaughtErrorMessage); | 494 config.onSummary(passed, failed, errors, testCases, |
510 _config.onDone(passed > 0 && failed == 0 && errors == 0 && | 495 environment.uncaughtErrorMessage); |
511 _environment.uncaughtErrorMessage == null); | 496 config.onDone(passed > 0 && failed == 0 && errors == 0 && |
512 _environment.initialized = false; | 497 environment.uncaughtErrorMessage == null); |
513 _environment.currentTestCaseIndex = -1; | 498 environment.initialized = false; |
514 } | 499 environment.currentTestCaseIndex = -1; |
515 | 500 } |
516 String _fullSpec(String spec) { | 501 |
517 var group = '${_environment.currentContext.fullName}'; | 502 /// Initializes the test environment if it hasn't already been initialized. |
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() { | 503 void ensureInitialized() { |
524 _ensureInitialized(true); | 504 _ensureInitialized(true); |
525 } | 505 } |
526 | 506 |
| 507 /// Initializes the test environment. |
| 508 /// |
| 509 /// If [configAutoStart] is `true`, schedule a microtask to run the tests. This |
| 510 /// microtask is expected to run after all the tests are defined. |
527 void _ensureInitialized(bool configAutoStart) { | 511 void _ensureInitialized(bool configAutoStart) { |
528 if (_environment.initialized) { | 512 if (environment.initialized) return; |
529 return; | 513 |
530 } | 514 environment.initialized = true; |
531 _environment.initialized = true; | |
532 // Hook our async guard into the matcher library. | 515 // Hook our async guard into the matcher library. |
533 wrapAsync = (f, [id]) => expectAsync(f, id: id); | 516 wrapAsync = (f, [id]) => expectAsync(f, id: id); |
534 | 517 |
535 _environment.uncaughtErrorMessage = null; | 518 environment.uncaughtErrorMessage = null; |
536 | 519 |
537 unittestConfiguration.onInit(); | 520 unittestConfiguration.onInit(); |
538 | 521 |
539 if (configAutoStart && _config.autoStart) { | 522 // Immediately queue the suite up. It will run after a timeout (i.e. after |
540 // Immediately queue the suite up. It will run after a timeout (i.e. after | 523 // main() has returned). |
541 // main() has returned). | 524 if (configAutoStart && config.autoStart) scheduleMicrotask(runTests); |
542 scheduleMicrotask(runTests); | 525 } |
543 } | 526 |
544 } | 527 /// Remove all tests other than the one identified by [id]. |
545 | 528 void setSoloTest(int id) => |
546 /// Select a solo test by ID. | 529 environment.testCases.retainWhere((t) => t.id == id); |
547 void setSoloTest(int id) => _testCases.retainWhere((t) => t.id == id); | 530 |
548 | 531 /// Enable the test identified by [id]. |
549 /// Enable/disable a test by ID. | 532 void enableTest(int id) => _setTestEnabledState(id, enable: true); |
550 void _setTestEnabledState(int testId, bool state) { | 533 |
| 534 /// Disable the test by [id]. |
| 535 void disableTest(int id) => _setTestEnabledState(id, enable: false); |
| 536 |
| 537 /// Enable or disable the test identified by [id]. |
| 538 void _setTestEnabledState(int id, {bool enable: true}) { |
551 // Try fast path first. | 539 // Try fast path first. |
552 if (testCases.length > testId && testCases[testId].id == testId) { | 540 if (testCases.length > id && testCases[id].id == id) { |
553 testCases[testId]._enabled = state; | 541 environment.testCases[id].enabled = enable; |
554 } else { | 542 } else { |
555 for (var i = 0; i < testCases.length; i++) { | 543 for (var i = 0; i < testCases.length; i++) { |
556 if (testCases[i].id == testId) { | 544 if (testCases[i].id != id) continue; |
557 testCases[i]._enabled = state; | 545 environment.testCases[i].enabled = enable; |
558 break; | 546 break; |
559 } | |
560 } | 547 } |
561 } | 548 } |
562 } | 549 } |
563 | 550 |
564 /// Enable a test by ID. | 551 /// Throws a [StateError] if tests are running. |
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() { | 552 void _requireNotRunning() { |
586 if (_environment.currentTestCaseIndex != -1) { | 553 if (environment.currentTestCaseIndex == -1) return; |
587 throw new StateError('Not allowed when tests are running.'); | 554 throw new StateError('Not allowed when tests are running.'); |
588 } | 555 } |
589 } | 556 |
590 | 557 /// Creates a test environment running in its own zone scope. |
591 /// Method to create a test environment running in its own zone scope. | |
592 /// | 558 /// |
593 /// This allows for multiple invocations of the unittest library in the same | 559 /// This allows for multiple invocations of the unittest library in the same |
594 /// application instance. | 560 /// application instance. This is useful when, for example, creating a test |
595 /// This is useful when, for example, creating a test runner application which | 561 /// runner application which needs to create a new pristine test environment on |
596 /// needs to create a new pristine test environment on each invocation to run | 562 /// each invocation to run a given set of tests. |
597 /// a given set of test. | 563 withTestEnvironment(callback()) { |
598 dynamic withTestEnvironment(callback()) { | |
599 return runZoned(callback, | 564 return runZoned(callback, |
600 zoneValues: {_UNITTEST_ENVIRONMENT: new _TestEnvironment()}); | 565 zoneValues: {#unittest.environment: new TestEnvironment()}); |
601 } | 566 } |
OLD | NEW |