Chromium Code Reviews| Index: pkg/unittest/lib/unittest.dart |
| diff --git a/pkg/unittest/lib/unittest.dart b/pkg/unittest/lib/unittest.dart |
| index 05eca70f9fcc558fda37b68ee17d2ea35d2c2d3b..6a32e85bed27acfa5a5d08c3a43a88888aaa74c2 100644 |
| --- a/pkg/unittest/lib/unittest.dart |
| +++ b/pkg/unittest/lib/unittest.dart |
| @@ -151,8 +151,29 @@ part 'src/simple_configuration.dart'; |
| part 'src/group_context.dart'; |
| part 'src/spread_args_helper.dart'; |
| part 'src/test_case.dart'; |
| +part 'src/test_environment.dart'; |
| -Configuration _config; |
| +const Symbol _TEST_ENVIRONMENT = #test_environment; |
|
nweiz
2014/11/13 19:34:28
I don't think it's worth making a constant when th
wibling
2014/11/14 10:09:49
I prefer keeping the constant since it is used bot
nweiz
2014/11/17 23:02:54
Tests are for ensuring that changes don't break fu
wibling
2014/11/18 10:16:49
I agree that tests are for testing functionality a
|
| + |
| +final _TestEnvironment _defaultEnvironment = new _TestEnvironment(); |
| + |
| +/** |
| + * Internal getter for the current unittest config. |
| + */ |
| +_TestEnvironment get _environment { |
| + var environment = Zone.current[_TEST_ENVIRONMENT]; |
| + if (environment == null) { |
| + return _defaultEnvironment; |
|
nweiz
2014/11/13 19:34:27
We usually use bracketless ifs if they fit on a si
wibling
2014/11/14 10:09:49
Done.
nweiz
2014/11/17 23:02:54
Move the return to the previous line.
wibling
2014/11/18 10:16:48
Done.
|
| + } |
| + return environment; |
| +} |
| + |
| +// Convenience getter/setter for the current environment's config. |
| +Configuration get _config => _environment.config; |
| +void set _config(Configuration config) { _environment.config = config; } |
|
nweiz
2014/11/13 19:34:27
We don't usually do single-line methods without =>
wibling
2014/11/14 10:09:49
Done.
|
| + |
| +// Convenience getter for the current environment's test cases. |
| +List<TestCase> get _testCases => _environment.testCases; |
| /// [Configuration] used by the unittest library. Note that if a |
| /// configuration has not been set, calling this getter will create |
| @@ -184,48 +205,20 @@ void logMessage(String message) => |
| /// Separator used between group names and test names. |
| String groupSep = ' '; |
| -final List<TestCase> _testCases = new List<TestCase>(); |
| - |
| /// Tests executed in this suite. |
| -final List<TestCase> testCases = new UnmodifiableListView<TestCase>(_testCases); |
| +List<TestCase> get testCases => _environment.testCasesRO; |
| /// Interval (in msecs) after which synchronous tests will insert an async |
| /// delay to allow DOM or other updates. |
| const int BREATH_INTERVAL = 200; |
| -/// The set of tests to run can be restricted by using [solo_test] and |
| -/// [solo_group]. |
| -/// As groups can be nested we use a counter to keep track of the nest level |
| -/// of soloing, and a flag to tell if we have seen any solo tests. |
| -int _soloNestingLevel = 0; |
| -bool _soloTestSeen = false; |
| - |
| -// We use a 'dummy' context for the top level to eliminate null |
| -// checks when querying the context. This allows us to easily |
| -// support top-level setUp/tearDown functions as well. |
| -final _rootContext = new _GroupContext(); |
| -_GroupContext _currentContext = _rootContext; |
| - |
| -/// Represents the index of the currently running test case |
| -/// == -1 implies the test system is not running |
| -/// == [number of test cases] is a short-lived state flagging that the last test |
| -/// has completed |
| -int _currentTestCaseIndex = -1; |
| - |
| /// [TestCase] currently being executed. |
| TestCase get currentTestCase => |
| - (_currentTestCaseIndex >= 0 && _currentTestCaseIndex < testCases.length) |
| - ? testCases[_currentTestCaseIndex] |
| + (_environment.currentTestCaseIndex >= 0 && |
| + _environment.currentTestCaseIndex < testCases.length) |
| + ? testCases[_environment.currentTestCaseIndex] |
| : null; |
| -/// Whether the framework is in an initialized state. |
| -bool _initialized = false; |
| - |
| -String _uncaughtErrorMessage = null; |
| - |
| -/// Time since we last gave non-sync code a chance to be scheduled. |
| -int _lastBreath = new DateTime.now().millisecondsSinceEpoch; |
| - |
| /* Test case result strings. */ |
| // TODO(gram) we should change these constants to use a different string |
| // (so that writing 'FAIL' in the middle of a test doesn't |
| @@ -244,7 +237,7 @@ const ERROR = 'error'; |
| void test(String spec, TestFunction body) { |
| _requireNotRunning(); |
| ensureInitialized(); |
| - if (!_soloTestSeen || _soloNestingLevel > 0) { |
| + if (!_environment.soloTestSeen || _environment.soloNestingLevel > 0) { |
| var testcase = new TestCase._internal(testCases.length + 1, _fullSpec(spec), |
| body); |
| _testCases.add(testcase); |
| @@ -271,16 +264,16 @@ void skip_test(String spec, TestFunction body) {} |
| void solo_test(String spec, TestFunction body) { |
| _requireNotRunning(); |
| ensureInitialized(); |
| - if (!_soloTestSeen) { |
| - _soloTestSeen = true; |
| + if (!_environment.soloTestSeen) { |
| + _environment.soloTestSeen = true; |
| // This is the first solo-ed test. Discard all tests up to now. |
| _testCases.clear(); |
| } |
| - ++_soloNestingLevel; |
| + ++_environment.soloNestingLevel; |
| try { |
| test(spec, body); |
| } finally { |
| - --_soloNestingLevel; |
| + --_environment.soloNestingLevel; |
| } |
| } |
| @@ -317,15 +310,15 @@ Function expectAsyncUntil(Function callback, bool isDone(), {String id}) => |
| void group(String description, void body()) { |
| ensureInitialized(); |
| _requireNotRunning(); |
| - _currentContext = new _GroupContext(_currentContext, description); |
| + _environment.currentContext = new _GroupContext(_environment.currentContext, description); |
|
nweiz
2014/11/13 19:34:27
Long line.
wibling
2014/11/14 10:09:49
Done.
|
| try { |
| body(); |
| } catch (e, trace) { |
| var stack = (trace == null) ? '' : ': ${trace.toString()}'; |
| - _uncaughtErrorMessage = "${e.toString()}$stack"; |
| + _environment.uncaughtErrorMessage = "${e.toString()}$stack"; |
| } finally { |
| // Now that the group is over, restore the previous one. |
| - _currentContext = _currentContext.parent; |
| + _environment.currentContext = _environment.currentContext.parent; |
| } |
| } |
| @@ -336,16 +329,16 @@ void skip_group(String description, void body()) {} |
| void solo_group(String description, void body()) { |
| _requireNotRunning(); |
| ensureInitialized(); |
| - if (!_soloTestSeen) { |
| - _soloTestSeen = true; |
| + if (!_environment.soloTestSeen) { |
| + _environment.soloTestSeen = true; |
| // This is the first solo-ed group. Discard all tests up to now. |
| _testCases.clear(); |
| } |
| - ++_soloNestingLevel; |
| + ++_environment.soloNestingLevel; |
| try { |
| group(description, body); |
| } finally { |
| - --_soloNestingLevel; |
| + --_environment.soloNestingLevel; |
| } |
| } |
| @@ -356,7 +349,7 @@ void solo_group(String description, void body()) { |
| /// case it must return a [Future]. |
| void setUp(Function setupTest) { |
| _requireNotRunning(); |
| - _currentContext.testSetup = setupTest; |
| + _environment.currentContext.testSetup = setupTest; |
| } |
| /// Register a [tearDown] function for a test [group]. This function will |
| @@ -367,12 +360,12 @@ void setUp(Function setupTest) { |
| /// case it must return a [Future]. |
| void tearDown(Function teardownTest) { |
| _requireNotRunning(); |
| - _currentContext.testTeardown = teardownTest; |
| + _environment.currentContext.testTeardown = teardownTest; |
| } |
| /// Advance to the next test case. |
| void _nextTestCase() { |
| - _currentTestCaseIndex++; |
| + _environment.currentTestCaseIndex++; |
| _runTest(); |
| } |
| @@ -385,7 +378,7 @@ void handleExternalError(e, String message, [stack]) { |
| if (currentTestCase != null) { |
| currentTestCase._error(msg, stack); |
| } else { |
| - _uncaughtErrorMessage = "$msg: $stack"; |
| + _environment.uncaughtErrorMessage = "$msg: $stack"; |
| } |
| } |
| @@ -409,7 +402,7 @@ void filterTests(testFilter) { |
| void runTests() { |
| _requireNotRunning(); |
| _ensureInitialized(false); |
| - _currentTestCaseIndex = 0; |
| + _environment.currentTestCaseIndex = 0; |
| _config.onStart(); |
| _runTest(); |
| } |
| @@ -431,11 +424,11 @@ void _registerException(TestCase testCase, e, [trace]) { |
| /// Runs the next test. |
| void _runTest() { |
| - if (_currentTestCaseIndex >= testCases.length) { |
| - assert(_currentTestCaseIndex == testCases.length); |
| + if (_environment.currentTestCaseIndex >= testCases.length) { |
| + assert(_environment.currentTestCaseIndex == testCases.length); |
| _completeTests(); |
| } else { |
| - var testCase = testCases[_currentTestCaseIndex]; |
| + var testCase = testCases[_environment.currentTestCaseIndex]; |
| Future 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 |
| @@ -459,8 +452,8 @@ void _runTest() { |
| f.whenComplete(() { |
| if (timer != null) timer.cancel(); |
| var now = new DateTime.now().millisecondsSinceEpoch; |
| - if ((now - _lastBreath) >= BREATH_INTERVAL) { |
| - _lastBreath = now; |
| + if ((now - _environment.lastBreath) >= BREATH_INTERVAL) { |
| + _environment.lastBreath = now; |
| Timer.run(_nextTestCase); |
| } else { |
| scheduleMicrotask(_nextTestCase); // Schedule the next test. |
| @@ -471,7 +464,7 @@ void _runTest() { |
| /// Publish results on the page and notify controller. |
| void _completeTests() { |
| - if (!_initialized) return; |
| + if (!_environment.initialized) return; |
| int passed = 0; |
| int failed = 0; |
| int errors = 0; |
| @@ -483,15 +476,16 @@ void _completeTests() { |
| case ERROR: errors++; break; |
| } |
| } |
| - _config.onSummary(passed, failed, errors, testCases, _uncaughtErrorMessage); |
| + _config.onSummary(passed, failed, errors, testCases, |
| + _environment.uncaughtErrorMessage); |
| _config.onDone(passed > 0 && failed == 0 && errors == 0 && |
| - _uncaughtErrorMessage == null); |
| - _initialized = false; |
| - _currentTestCaseIndex = -1; |
| + _environment.uncaughtErrorMessage == null); |
| + _environment.initialized = false; |
| + _environment.currentTestCaseIndex = -1; |
| } |
| String _fullSpec(String spec) { |
| - var group = '${_currentContext.fullName}'; |
| + var group = '${_environment.currentContext.fullName}'; |
| if (spec == null) return group; |
| return group != '' ? '$group$groupSep$spec' : spec; |
| } |
| @@ -502,14 +496,14 @@ void ensureInitialized() { |
| } |
| void _ensureInitialized(bool configAutoStart) { |
| - if (_initialized) { |
| + if (_environment.initialized) { |
| return; |
| } |
| - _initialized = true; |
| + _environment.initialized = true; |
| // Hook our async guard into the matcher library. |
| wrapAsync = (f, [id]) => expectAsync(f, id: id); |
| - _uncaughtErrorMessage = null; |
| + _environment.uncaughtErrorMessage = null; |
| unittestConfiguration.onInit(); |
| @@ -558,7 +552,19 @@ bool formatStacks = true; |
| bool filterStacks = true; |
| void _requireNotRunning() { |
| - if (_currentTestCaseIndex != -1) { |
| + if (_environment.currentTestCaseIndex != -1) { |
| throw new StateError('Not allowed when tests are running.'); |
| } |
| } |
| + |
| +/// Method to create a private test environment. |
| +/// |
| +/// 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 test. |
|
nweiz
2014/11/13 19:34:28
Explain that the new environment is zone-scoped.
wibling
2014/11/14 10:09:49
Done.
|
| +dynamic withTestEnvironment(callback()) { |
| + return runZoned(callback, |
| + zoneValues: <Symbol, Object>{_TEST_ENVIRONMENT: new _TestEnvironment()}); |
|
nweiz
2014/11/13 19:34:27
We don't type generic objects within method bodies
wibling
2014/11/14 10:09:49
Done.
|
| +} |