blob: 8efa694757abfc6489fba2d0e1c1dfe953e77a54 [file] [log] [blame]
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of unittest;
/**
* testcase.dart: this file is sourced by unittest.dart. It defines [TestCase]
* and assumes unittest defines the type [TestFunction].
*/
/** Summarizes information about a single test case. */
class TestCase {
/** Identifier for this test. */
final int id;
/** A description of what the test is specifying. */
final String description;
/** The setup function to call before the test, if any. */
Function _setUp;
Function get setUp => _setUp;
set setUp(Function value) => _setUp = value;
/** The teardown function to call after the test, if any. */
Function _tearDown;
Function get tearDown => _tearDown;
set tearDown(Function value) => _tearDown = value;
/** The body of the test case. */
TestFunction test;
/**
* Remaining number of callbacks functions that must reach a 'done' state
* to wait for before the test completes.
*/
int callbackFunctionsOutstanding;
/** Error or failure message. */
String message = '';
/**
* One of [PASS], [FAIL], [ERROR], or [null] if the test hasn't run yet.
*/
String result;
/** Stack trace associated with this test, or null if it succeeded. */
String stackTrace;
/** The group (or groups) under which this test is running. */
final String currentGroup;
DateTime startTime;
Duration runningTime;
bool enabled = true;
bool _doneTeardown = false;
Completer _testComplete;
TestCase(this.id, this.description, this.test,
this.callbackFunctionsOutstanding)
: currentGroup = _currentGroup,
_setUp = _testSetup,
_tearDown = _testTeardown;
bool get isComplete => !enabled || result != null;
void _prepTest() {
_config.onTestStart(this);
startTime = new DateTime.now();
runningTime = null;
}
void _runTest() {
_prepTest();
test();
if (result == null && callbackFunctionsOutstanding == 0) {
pass();
}
}
/**
* Perform any associated [setUp] function and run the test. Returns
* a [Future] that can be used to schedule the next test. If the test runs
* to completion synchronously, or is disabled, we return null, to
* tell unittest to schedule the next test immediately.
*/
Future run() {
if (!enabled) return null;
result = stackTrace = null;
message = '';
_doneTeardown = false;
var rtn = _setUp == null ? null : _setUp();
if (rtn is Future) {
rtn.then(expectAsync1((_) =>_runTest(),
id: '[Async setUp completion handler]'))
.catchError((e) {
_prepTest();
// Calling error() will result in the tearDown being done.
// One could debate whether tearDown should be done after
// a failed setUp. There is no right answer, but doing it
// seems to be the more conservative approach, because
// unittest will not stop at a test failure.
error("$description: Test setup failed: ${e.error}");
});
} else {
_runTest();
}
if (result == null) { // Not complete.
_testComplete = new Completer();
return _testComplete.future;
}
return null;
}
void _notifyComplete() {
if (_testComplete != null) {
_testComplete.complete(this);
_testComplete = null;
}
}
void _complete() {
if (runningTime == null) {
// TODO(gram): currently the duration measurement code is blocked
// by issue 4437. When that is fixed replace the line below with:
// runningTime = new DateTime.now().difference(startTime);
runningTime = new Duration(milliseconds: 0);
}
if (!_doneTeardown) {
_doneTeardown = true;
if (_tearDown != null) {
var rtn = _tearDown();
if (rtn is Future) {
rtn.then((_) {
if (result == null) {
// The test passed. In some cases we will already
// have set this result (e.g. if the test was async
// and all callbacks completed). If not, we do it here.
pass();
} else {
// The test has already been marked as pass/fail.
// Just report the updated result.
_config.onTestResult(this);
}
_notifyComplete();
})
.catchError((e) {
// We don't call fail() as that will potentially result in
// spurious messages like 'test failed more than once'.
result = ERROR;
message = "$description: Test teardown failed: ${e.error}";
_config.onTestResult(this);
_notifyComplete();
});
return;
}
}
}
_config.onTestResult(this);
_notifyComplete();
}
void pass() {
result = PASS;
_complete();
}
void fail(String messageText, [String stack = '']) {
if (result != null) {
if (result == PASS) {
error('Test failed after initially passing: $messageText', stack);
} else if (result == FAIL) {
error('Test failed more than once: $messageText', stack);
}
} else {
result = FAIL;
message = messageText;
stackTrace = stack;
_complete();
}
}
void error(String messageText, [String stack = '']) {
result = ERROR;
message = messageText;
stackTrace = stack;
_complete();
}
}