Chaining of setup/teardown.

Review URL: https://codereview.chromium.org//14092004

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@21819 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
gram@google.com 2013-04-22 17:11:02 +00:00
parent 1e92447b78
commit f435c7f836
5 changed files with 120 additions and 75 deletions

View file

@ -32,6 +32,9 @@ webdriver/test/webdriver_test: Skip
unittest/test/unittest_test: Pass, Fail, Crash # Bug in v8, http://dartbug.com/9407
[ $compiler == dart2js && $runtime == d8 ]
unittest/test/unittest_test: Pass, Fail # http://dartbug.com/10109
[$compiler == dart2dart]
*: Skip

View file

@ -79,46 +79,6 @@ void main() {
test('test 2', () => expect(onExceptionRun, isTrue));
}, passing: ['test 2']);
expectTestsPass("setUp doesn't apply to child groups", () {
var setUpRun = false;
setUp(() {
setUpRun = true;
currentSchedule.onComplete.schedule(() {
setUpRun = false;
});
});
test('outer', () {
expect(setUpRun, isTrue);
});
group('group', () {
test('inner', () {
expect(setUpRun, isFalse);
});
});
});
expectTestsPass("setUp doesn't apply to parent groups", () {
var setUpRun = false;
group('group', () {
setUp(() {
setUpRun = true;
currentSchedule.onComplete.schedule(() {
setUpRun = false;
});
});
test('inner', () {
expect(setUpRun, isTrue);
});
});
test('outer', () {
expect(setUpRun, isFalse);
});
});
expectTestsPass("setUp doesn't apply to sibling groups", () {
var setUpRun = false;
group('group 1', () {
@ -140,4 +100,4 @@ void main() {
});
});
});
}
}

View file

@ -61,9 +61,9 @@ class TestCase {
Completer _testComplete;
TestCase._internal(this.id, this.description, this.testFunction)
: currentGroup = _currentGroup,
setUp = _testSetup,
tearDown = _testTeardown;
: currentGroup = _currentContext.groupName,
setUp = _currentContext.testSetup,
tearDown = _currentContext.testTeardown;
bool get isComplete => !enabled || result != null;

View file

@ -205,12 +205,6 @@ void set unittestConfiguration(Configuration value) {
void logMessage(String message) =>
_config.onLogMessage(currentTestCase, message);
/**
* Description text of the current test group. If multiple groups are nested,
* this will contain all of their text concatenated.
*/
String _currentGroup = '';
/** Separator used between group names and test names. */
String groupSep = ' ';
@ -219,11 +213,29 @@ final List<TestCase> _testCases = new List<TestCase>();
/** Tests executed in this suite. */
final List<TestCase> testCases = new UnmodifiableListView<TestCase>(_testCases);
/** Setup function called before each test in a group */
Function _testSetup;
/**
* Setup and teardown functions for a group and its parents, the latter
* for chaining.
*/
class GroupContext {
/** Setup function called before each test in a group. */
Function testSetup = null;
/** Teardown function called after each test in a group */
Function _testTeardown;
/** Teardown function called after each test in a group. */
Function testTeardown = null;
/** Setup and teardown functions of parent group, for chaining. */
Function parentSetup = null;
Function parentTeardown = null;
/**
* Description text of the current test group. If multiple groups are nested,
* this will contain all of their text concatenated.
*/
String groupName = '';
}
GroupContext _currentContext = new GroupContext();
int _currentTestCaseIndex = 0;
@ -596,46 +608,55 @@ Function protectAsync2(Function callback, {String id}) {
*/
void group(String description, void body()) {
ensureInitialized();
// Groups can be nested, so we need to preserve the current
// settings for test setup/teardown. We use a local copy here so we
// can nest multiple levels; we also have the global parent variables
// which are used for chaining.
var oldContext = _currentContext;
_currentContext = new GroupContext();
_currentContext.testSetup = _currentContext.parentSetup =
oldContext.testSetup;
_currentContext.testTeardown = _currentContext.parentTeardown =
oldContext.testTeardown;
// Concatenate the new group.
final parentGroup = _currentGroup;
if (_currentGroup != '') {
if (oldContext.groupName != '') {
// Add a space.
_currentGroup = '$_currentGroup$groupSep$description';
_currentContext.groupName = '${oldContext.groupName}$groupSep$description';
} else {
// The first group.
_currentGroup = description;
_currentContext.groupName = description;
}
// Groups can be nested, so we need to preserve the current
// settings for test setup/teardown.
Function parentSetup = _testSetup;
Function parentTeardown = _testTeardown;
try {
_testSetup = null;
_testTeardown = null;
body();
} catch (e, trace) {
var stack = (trace == null) ? '' : ': ${trace.toString()}';
_uncaughtErrorMessage = "${e.toString()}$stack";
} finally {
// Now that the group is over, restore the previous one.
_currentGroup = parentGroup;
_testSetup = parentSetup;
_testTeardown = parentTeardown;
_currentContext = oldContext;
}
}
/**
* Register a [setUp] function for a test [group]. This function will
* be called before each test in the group is run. Note that if groups
* are nested only the most locally scoped [setUpTest] function will be run.
* be called before each test in the group is run.
* [setUp] and [tearDown] should be called within the [group] before any
* calls to [test]. The [setupTest] function can be asynchronous; in this
* case it must return a [Future].
*/
void setUp(Function setupTest) {
_testSetup = setupTest;
var parent = _currentContext.parentSetup;
_currentContext.testSetup = () {
var f = parent == null ? null : parent();
if (f is Future) {
return f.then((_) => setupTest());
} else {
return setupTest();
}
};
}
/**
@ -647,7 +668,19 @@ void setUp(Function setupTest) {
* case it must return a [Future].
*/
void tearDown(Function teardownTest) {
_testTeardown = teardownTest;
var parent = _currentContext.parentTeardown;
_currentContext.testTeardown = () {
var f = teardownTest();
if (parent == null) return f;
if (f is Future) {
// TODO(gram): as _parentTeardown is a global, do we need
// to first take a local copy so the value is fixed at the
// point that tearDown is called?
return f.then((_) => parent());
} else {
return parent();
}
};
}
/** Advance to the next test case. */
@ -712,7 +745,7 @@ void filterTests(testFilter) {
void runTests() {
_ensureInitialized(false);
_currentTestCaseIndex = 0;
_currentGroup = '';
_currentContext = new GroupContext();
// If we are soloing a test, remove all the others.
if (_soloTest != null) {
@ -811,8 +844,9 @@ void _completeTests() {
}
String _fullSpec(String spec) {
if (spec == null) return '$_currentGroup';
return _currentGroup != '' ? '$_currentGroup$groupSep$spec' : spec;
var group = '${_currentContext.groupName}';
if (spec == null) return group;
return group != '' ? '$group$groupSep$spec' : spec;
}
/**

View file

@ -68,6 +68,26 @@ class TestConfiguration extends Configuration {
}
}
makeDelayedSetup(index, s) => () {
return new Future.delayed(new Duration(milliseconds:1), () {
s.write('l$index U ');
});
};
makeDelayedTeardown(index, s) => () {
return new Future.delayed(new Duration(milliseconds:1), () {
s.write('l$index D ');
});
};
makeImmediateSetup(index, s) => () {
s.write('l$index U ');
};
makeImmediateTeardown(index, s) => () {
s.write('l$index D ');
};
runTest() {
port.receive((String testName, sendport) {
var testConfig = new TestConfiguration(sendport);
@ -299,6 +319,31 @@ runTest() {
});
} else if (testName == 'runTests without tests') {
runTests();
} else if (testName == 'nested groups setup/teardown') {
StringBuffer s = new StringBuffer();
group('level 1', () {
setUp(makeDelayedSetup(1, s));
group('level 2', () {
setUp(makeImmediateSetup(2, s));
tearDown(makeDelayedTeardown(2, s));
group('level 3', () {
group('level 4', () {
setUp(makeDelayedSetup(4, s));
tearDown(makeImmediateTeardown(4, s));
group('level 5', () {
setUp(makeImmediateSetup(5, s));
group('level 6', () {
tearDown(makeDelayedTeardown(6, s));
test('inner', () {});
});
});
});
});
});
});
test('after nest', () {
expect(s.toString(), "l1 U l2 U l4 U l5 U l6 D l4 D l2 D ");
});
}
});
}
@ -355,7 +400,10 @@ main() {
'foo6'),
'testCases immutable':
buildStatusString(1, 0, 0, 'testCases immutable'),
'runTests without tests': buildStatusString(0, 0, 0, null)
'runTests without tests': buildStatusString(0, 0, 0, null),
'nested groups setup/teardown':
buildStatusString(2, 0, 0,
'level 1 level 2 level 3 level 4 level 5 level 6 inner::after nest')
};
tests.forEach((String name, String expected) {