Index: lib/unittest.dart |
diff --git a/lib/unittest.dart b/lib/unittest.dart |
index d0083270f6bfff8beadd318417477e50cb3b3a2b..0e4be92da407534374dc336240fb9613cf27951b 100644 |
--- a/lib/unittest.dart |
+++ b/lib/unittest.dart |
@@ -5,14 +5,15 @@ |
library unittest; |
import 'dart:async'; |
-import 'dart:collection'; |
+ |
+import 'package:path/path.dart' as p; |
import 'src/configuration.dart'; |
-import 'src/expected_function.dart'; |
-import 'src/group_context.dart'; |
-import 'src/internal_test_case.dart'; |
+import 'src/declarer.dart'; |
+import 'src/console_reporter.dart'; |
+import 'src/invoker.dart'; |
+import 'src/suite.dart'; |
import 'src/test_case.dart'; |
-import 'src/test_environment.dart'; |
export 'package:matcher/matcher.dart' |
hide |
@@ -44,426 +45,133 @@ export 'package:matcher/matcher.dart' |
export 'src/configuration.dart'; |
export 'src/expect.dart'; |
-export 'src/simple_configuration.dart'; |
+export 'src/expect_async.dart'; |
export 'src/future_matchers.dart'; |
export 'src/prints_matcher.dart'; |
+export 'src/simple_configuration.dart'; |
+export 'src/test_case.dart'; |
export 'src/throws_matcher.dart'; |
export 'src/throws_matchers.dart'; |
-export 'src/test_case.dart'; |
-/// The signature for a function passed to [test]. |
-typedef dynamic TestFunction(); |
+/// The global declarer. |
+/// |
+/// This is used if a test file is run directly, rather than through the runner. |
+Declarer _globalDeclarer; |
+ |
+/// Gets the declarer for the current scope. |
+/// |
+/// When using the runner, this returns the [Zone]-scoped declarer that's set by |
+/// [VmListener]. If the test file is run directly, this returns |
+/// [_globalDeclarer] (and sets it up on the first call). |
+Declarer get _declarer { |
+ var declarer = Zone.current[#unittest.declarer]; |
+ if (declarer != null) return declarer; |
+ if (_globalDeclarer != null) return _globalDeclarer; |
+ |
+ // Since there's no Zone-scoped declarer, the test file is being run directly. |
+ // In order to run the tests, we set up our own Declarer via |
+ // [_globalDeclarer], and schedule a microtask to run the tests once they're |
+ // finished being defined. |
+ _globalDeclarer = new Declarer(); |
+ scheduleMicrotask(() { |
+ var suite = new Suite(p.prettyUri(Uri.base), _globalDeclarer.tests); |
+ // TODO(nweiz): Use a reporter that doesn't import dart:io here. |
+ // TODO(nweiz): Set the exit code on the VM when issue 6943 is fixed. |
+ new ConsoleReporter([suite]).run(); |
+ }); |
+ return _globalDeclarer; |
+} |
-/// [Configuration] used by the unittest library. |
-/// |
-/// Note that if a configuration has not been set, calling this getter will |
-/// create a default configuration. |
-Configuration get unittestConfiguration { |
- if (config == null) environment.config = new Configuration(); |
- return config; |
+// TODO(nweiz): This and other top-level functions should throw exceptions if |
+// they're called after the declarer has finished declaring. |
+void test(String description, body()) => _declarer.test(description, body); |
+ |
+void group(String description, void body()) => |
+ _declarer.group(description, body); |
+ |
+void setUp(callback()) => _declarer.setUp(callback); |
+ |
+void tearDown(callback()) => _declarer.tearDown(callback); |
+ |
+/// Handle an error that occurs outside of any test. |
+void handleExternalError(error, String message, [stackTrace]) { |
+ // TODO(nweiz): handle this better. |
+ registerException(error, stackTrace); |
} |
-/// If `true`, stack traces are reformatted to be more readable. |
+/// Registers an exception that was caught for the current test. |
+void registerException(error, [StackTrace stackTrace]) => |
+ Invoker.current.handleError(error, stackTrace); |
+ |
+// What follows are stubs for various top-level names supported by unittest |
+// 0.11.*. These are preserved for the time being for ease of migration, but |
+// should be removed before this is released as stable. |
+ |
+@deprecated |
+typedef dynamic TestFunction(); |
+ |
+@deprecated |
+Configuration unittestConfiguration = new Configuration(); |
+ |
+@deprecated |
bool formatStacks = true; |
-/// If `true`, irrelevant frames are filtered from the stack trace. |
-/// |
-/// This does nothing if [formatStacks] is false. |
+@deprecated |
bool filterStacks = true; |
-/// Separator used between group names and test names. |
+@deprecated |
String groupSep = ' '; |
-/// Sets the [Configuration] used by the unittest library. |
-/// |
-/// Throws a [StateError] if there is an existing, incompatible value. |
-void set unittestConfiguration(Configuration value) { |
- if (identical(config, value)) return; |
- if (config != null) { |
- logMessage('Warning: The unittestConfiguration has already been set. New ' |
- 'unittestConfiguration ignored.'); |
- } else { |
- environment.config = value; |
- } |
-} |
- |
-/// Logs [message] associated with the current test case. |
-/// |
-/// Tests should use this instead of [print]. |
-void logMessage(String message) => |
- config.onLogMessage(currentTestCase, message); |
+@deprecated |
+void logMessage(String message) => print(message); |
-/// The test cases that have been defined so far. |
-List<TestCase> get testCases => |
- new UnmodifiableListView<TestCase>(environment.testCases); |
+@deprecated |
+final testCases = []; |
-/// The interval (in milliseconds) after which a non-microtask asynchronous |
-/// delay will be scheduled between tests. |
-/// |
-/// This is used to avoid starving the DOM or other non-microtask events. |
+@deprecated |
const int BREATH_INTERVAL = 200; |
-/// The [TestCase] currently being executed. |
-TestCase get currentTestCase => (environment.currentTestCaseIndex >= 0 && |
- environment.currentTestCaseIndex < testCases.length) |
- ? testCases[environment.currentTestCaseIndex] |
- : null; |
+@deprecated |
+TestCase get currentTestCase => null; |
-/// The same as [currentTestCase], but typed as an [InternalTestCase]. |
-InternalTestCase get _currentTestCase => currentTestCase as InternalTestCase; |
- |
-/// The result string for a passing test case. |
+@deprecated |
const PASS = 'pass'; |
-/// The result string for a failing test case. |
+@deprecated |
const FAIL = 'fail'; |
-/// The result string for an test case with an error. |
+@deprecated |
const ERROR = 'error'; |
-/// Creates a new test case with the given description and body. |
-/// |
-/// The description will be added to the descriptions of any surrounding |
-/// [group]s. |
-void test(String description, TestFunction body) { |
- _requireNotRunning(); |
- ensureInitialized(); |
- |
- if (environment.soloTestSeen && environment.soloNestingLevel == 0) return; |
- var testCase = new InternalTestCase( |
- testCases.length + 1, _fullDescription(description), body); |
- environment.testCases.add(testCase); |
-} |
- |
-/// Returns [description] with all of its group prefixes prepended. |
-String _fullDescription(String description) { |
- var group = environment.currentContext.fullName; |
- if (description == null) return group; |
- return group != '' ? '$group$groupSep$description' : description; |
-} |
- |
-/// A convenience function for skipping a test. |
+@deprecated |
void skip_test(String spec, TestFunction body) {} |
-/// Creates a new test case with the given description and body. |
-/// |
-/// If [solo_test] is used instead of [test], then all non-solo tests will be |
-/// disabled. Note that if [solo_group] is used as well, all tests in the group |
-/// will be enabled, regardless of whether they use [test] or [solo_test], or |
-/// whether they are in a nested [group] versus [solo_group]. Put another way, |
-/// if there are any calls to [solo_test] or [solo_group] in a test file, all |
-/// tests that are not inside a [solo_group] will be disabled unless they are |
-/// [solo_test]s. |
-void solo_test(String spec, TestFunction body) { |
- _requireNotRunning(); |
- ensureInitialized(); |
- if (!environment.soloTestSeen) { |
- environment.soloTestSeen = true; |
- // This is the first solo-ed test. Discard all tests up to now. |
- environment.testCases.clear(); |
- } |
- environment.soloNestingLevel++; |
- try { |
- test(spec, body); |
- } finally { |
- environment.soloNestingLevel--; |
- } |
-} |
+@deprecated |
+void solo_test(String spec, TestFunction body) => test(spec, body); |
-/// Indicate that [callback] is expected to be called [count] number of times |
-/// (by default 1). |
-/// |
-/// The unittest framework will wait for the callback to run the [count] times |
-/// before it considers the current test to be complete. Using [expectAsync] |
-/// will also ensure that errors that occur within [callback] are tracked and |
-/// reported. [callback] may take up to six optional or required positional |
-/// arguments; named arguments are not supported. |
-/// |
-/// [max] can be used to specify an upper bound on the number of calls; if this |
-/// is exceeded the test will fail. If [max] is `0` (the default), the callback |
-/// is expected to be called exactly [count] times. If [max] is `-1`, the |
-/// callback is allowed to be called any number of times greater than [count]. |
-/// |
-/// Both [id] and [reason] are optional and provide extra information about the |
-/// callback when debugging. [id] should be the name of the callback, while |
-/// [reason] should be the reason the callback is expected to be called. |
-Function expectAsync(Function callback, |
- {int count: 1, int max: 0, String id, String reason}) => |
- new ExpectedFunction(callback, count, max, id: id, reason: reason).func; |
- |
-/// Indicate that [callback] is expected to be called until [isDone] returns |
-/// true. |
-/// |
-/// [isDone] is called after each time the function is run. Only when it returns |
-/// true will the callback be considered complete. Using [expectAsyncUntil] will |
-/// also ensure that errors that occur within [callback] are tracked and |
-/// reported. [callback] may take up to six optional or required positional |
-/// arguments; named arguments are not supported. |
-/// |
-/// Both [id] and [reason] are optional and provide extra information about the |
-/// callback when debugging. [id] should be the name of the callback, while |
-/// [reason] should be the reason the callback is expected to be called. |
-Function expectAsyncUntil(Function callback, bool isDone(), |
- {String id, String reason}) => new ExpectedFunction(callback, 0, -1, |
- id: id, reason: reason, isDone: isDone).func; |
- |
-/// Creates a group of tests. |
-/// |
-/// A group's description is included in the descriptions of any tests or |
-/// sub-groups it contains. [setUp] and [tearDown] are also scoped to the |
-/// containing group. |
-void group(String description, void body()) { |
- ensureInitialized(); |
- _requireNotRunning(); |
- environment.currentContext = |
- new GroupContext(environment.currentContext, description); |
- try { |
- body(); |
- } catch (e, trace) { |
- var stack = (trace == null) ? '' : ': ${trace.toString()}'; |
- environment.uncaughtErrorMessage = "${e.toString()}$stack"; |
- } finally { |
- // Now that the group is over, restore the previous one. |
- environment.currentContext = environment.currentContext.parent; |
- } |
-} |
- |
-/// A convenience function for skipping a group of tests. |
+@deprecated |
void skip_group(String description, void body()) {} |
-/// Creates a group of tests. |
-/// |
-/// If [solo_group] is used instead of [group], then all tests not declared with |
-/// [solo_test] or in a [solo_group] will be disabled. Note that all tests in a |
-/// [solo_group] will be run, regardless of whether they're declared with [test] |
-/// or [solo_test]. |
-/// |
-/// [skip_test] and [skip_group] take precedence over [solo_group]. |
-void solo_group(String description, void body()) { |
- _requireNotRunning(); |
- ensureInitialized(); |
- if (!environment.soloTestSeen) { |
- environment.soloTestSeen = true; |
- // This is the first solo-ed group. Discard all tests up to now. |
- environment.testCases.clear(); |
- } |
- ++environment.soloNestingLevel; |
- try { |
- group(description, body); |
- } finally { |
- --environment.soloNestingLevel; |
- } |
-} |
+@deprecated |
+void solo_group(String description, void body()) => group(description, body); |
-/// Registers a function to be run before tests. |
-/// |
-/// This function will be called before each test is run. [callback] may be |
-/// asynchronous; if so, it must return a [Future]. |
-/// |
-/// If this is called within a test group, it applies only to tests in that |
-/// group. [callback] will be run after any set-up callbacks in parent groups or |
-/// at the top level. |
-void setUp(Function callback) { |
- _requireNotRunning(); |
- environment.currentContext.testSetUp = callback; |
-} |
+@deprecated |
+void filterTests(testFilter) {} |
-/// Registers a function to be run after tests. |
-/// |
-/// This function will be called after each test is run. [callback] may be |
-/// asynchronous; if so, it must return a [Future]. |
-/// |
-/// If this is called within a test group, it applies only to tests in that |
-/// group. [callback] will be run before any tear-down callbacks in parent groups or |
-/// at the top level. |
-void tearDown(Function callback) { |
- _requireNotRunning(); |
- environment.currentContext.testTearDown = callback; |
-} |
+@deprecated |
+void runTests() {} |
-/// Advance to the next test case. |
-void _nextTestCase() { |
- environment.currentTestCaseIndex++; |
- _runTest(); |
-} |
+@deprecated |
+void ensureInitialized() {} |
-/// Handle an error that occurs outside of any test. |
-void handleExternalError(e, String message, [stackTrace]) { |
- var msg = '$message\nCaught $e'; |
- |
- if (currentTestCase != null) { |
- _currentTestCase.error(msg, stackTrace); |
- } else { |
- environment.uncaughtErrorMessage = "$msg: $stackTrace"; |
- } |
-} |
- |
-/// Remove any tests that match [testFilter]. |
-/// |
-/// [testFilter] can be a predicate function, a [RegExp], or a [String]. If it's |
-/// a function, it's called with each [TestCase]. If it's a [String], it's |
-/// parsed as a [RegExp] and matched against each [TestCase.description]. |
-/// |
-/// This is different from enabling or disabling tests in that it removes the |
-/// tests completely. |
-void filterTests(testFilter) { |
- var filterFunction; |
- if (testFilter is String) { |
- var re = new RegExp(testFilter); |
- filterFunction = (t) => re.hasMatch(t.description); |
- } else if (testFilter is RegExp) { |
- filterFunction = (t) => testFilter.hasMatch(t.description); |
- } else if (testFilter is Function) { |
- filterFunction = testFilter; |
- } |
- environment.testCases.retainWhere(filterFunction); |
-} |
+@deprecated |
+void setSoloTest(int id) {} |
-/// Runs all queued tests, one at a time. |
-void runTests() { |
- _requireNotRunning(); |
- _ensureInitialized(false); |
- environment.currentTestCaseIndex = 0; |
- config.onStart(); |
- _runTest(); |
-} |
+@deprecated |
+void enableTest(int id) {} |
-/// Registers an exception that was caught for the current test. |
-void registerException(error, [StackTrace stackTrace]) => |
- _currentTestCase.registerException(error, stackTrace); |
- |
-/// Runs the next test. |
-void _runTest() { |
- if (environment.currentTestCaseIndex >= testCases.length) { |
- assert(environment.currentTestCaseIndex == testCases.length); |
- _completeTests(); |
- return; |
- } |
- |
- var testCase = _currentTestCase; |
- var f = runZoned(testCase.run, onError: (error, stack) { |
- // TODO(kevmoo) Do a better job of flagging these are async errors. |
- // https://code.google.com/p/dart/issues/detail?id=16530 |
- testCase.registerException(error, stack); |
- }); |
+@deprecated |
+void disableTest(int id) {} |
- var timer; |
- var timeout = unittestConfiguration.timeout; |
- if (timeout != null) { |
- try { |
- timer = new Timer(timeout, () { |
- testCase.error("Test timed out after ${timeout.inSeconds} seconds."); |
- _nextTestCase(); |
- }); |
- } on UnsupportedError catch (e) { |
- if (e.message != "Timer greater than 0.") rethrow; |
- // Support running on d8 and jsshell which don't support timers. |
- } |
- } |
- |
- f.whenComplete(() { |
- if (timer != null) timer.cancel(); |
- var now = new DateTime.now().millisecondsSinceEpoch; |
- if (now - environment.lastBreath >= BREATH_INTERVAL) { |
- environment.lastBreath = now; |
- Timer.run(_nextTestCase); |
- } else { |
- scheduleMicrotask(_nextTestCase); // Schedule the next test. |
- } |
- }); |
-} |
- |
-/// Notify the configuration that the testing has finished. |
-void _completeTests() { |
- if (!environment.initialized) return; |
- |
- var passed = 0; |
- var failed = 0; |
- var errors = 0; |
- for (var testCase in testCases) { |
- switch (testCase.result) { |
- case PASS: |
- passed++; |
- break; |
- case FAIL: |
- failed++; |
- break; |
- case ERROR: |
- errors++; |
- break; |
- } |
- } |
- |
- config.onSummary( |
- passed, failed, errors, testCases, environment.uncaughtErrorMessage); |
- config.onDone(passed > 0 && |
- failed == 0 && |
- errors == 0 && |
- environment.uncaughtErrorMessage == null); |
- environment.initialized = false; |
- environment.currentTestCaseIndex = -1; |
-} |
- |
-/// Initializes the test environment if it hasn't already been initialized. |
-void ensureInitialized() { |
- _ensureInitialized(true); |
-} |
- |
-/// Initializes the test environment. |
-/// |
-/// If [configAutoStart] is `true`, schedule a microtask to run the tests. This |
-/// microtask is expected to run after all the tests are defined. |
-void _ensureInitialized(bool configAutoStart) { |
- if (environment.initialized) return; |
- |
- environment.initialized = true; |
- |
- environment.uncaughtErrorMessage = null; |
- |
- unittestConfiguration.onInit(); |
- |
- // Immediately queue the suite up. It will run after a timeout (i.e. after |
- // main() has returned). |
- if (configAutoStart && config.autoStart) scheduleMicrotask(runTests); |
-} |
- |
-/// Remove all tests other than the one identified by [id]. |
-void setSoloTest(int id) => |
- environment.testCases.retainWhere((t) => t.id == id); |
- |
-/// Enable the test identified by [id]. |
-void enableTest(int id) => _setTestEnabledState(id, enable: true); |
- |
-/// Disable the test by [id]. |
-void disableTest(int id) => _setTestEnabledState(id, enable: false); |
- |
-/// Enable or disable the test identified by [id]. |
-void _setTestEnabledState(int id, {bool enable: true}) { |
- // Try fast path first. |
- if (testCases.length > id && testCases[id].id == id) { |
- environment.testCases[id].enabled = enable; |
- } else { |
- for (var i = 0; i < testCases.length; i++) { |
- if (testCases[i].id != id) continue; |
- environment.testCases[i].enabled = enable; |
- break; |
- } |
- } |
-} |
- |
-/// Throws a [StateError] if tests are running. |
-void _requireNotRunning() { |
- if (environment.currentTestCaseIndex == -1) return; |
- throw new StateError('Not allowed when tests are running.'); |
-} |
- |
-/// Creates a test environment running in its own zone scope. |
-/// |
-/// This allows for multiple invocations of the unittest library in the same |
-/// application instance. This is useful when, for example, creating a test |
-/// runner application which needs to create a new pristine test environment on |
-/// each invocation to run a given set of tests. |
-withTestEnvironment(callback()) { |
- return runZoned(callback, |
- zoneValues: {#unittest.environment: new TestEnvironment()}); |
-} |
+@deprecated |
+withTestEnvironment(callback()) => callback(); |