| Index: lib/src/internal_test_case.dart
|
| diff --git a/lib/src/internal_test_case.dart b/lib/src/internal_test_case.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9949cf9c28173efaf1e2f09a23f3857094893631
|
| --- /dev/null
|
| +++ b/lib/src/internal_test_case.dart
|
| @@ -0,0 +1,227 @@
|
| +// Copyright (c) 2014, 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.
|
| +
|
| +library unittest.internal_test_case;
|
| +
|
| +import 'dart:async';
|
| +
|
| +import '../unittest.dart';
|
| +import 'test_environment.dart';
|
| +import 'utils.dart';
|
| +
|
| +/// An implementation of [TestCase] that exposes internal properties for other
|
| +/// unittest use.
|
| +class InternalTestCase implements TestCase {
|
| + final int id;
|
| + final String description;
|
| +
|
| + /// The setup function to call before the test, if any.
|
| + Function _setUp;
|
| +
|
| + /// The teardown function to call after the test, if any.
|
| + Function _tearDown;
|
| +
|
| + /// The body of the test case.
|
| + TestFunction _testFunction;
|
| +
|
| + /// Remaining number of callback functions that must reach a 'done' state
|
| + /// before the test completes.
|
| + int callbackFunctionsOutstanding = 0;
|
| +
|
| + /// The error or failure message for the tests.
|
| + ///
|
| + /// Initially an empty string.
|
| + String message = '';
|
| +
|
| + /// The result of the test case.
|
| + ///
|
| + /// If the test case has is completed, this will be one of [PASS], [FAIL], or
|
| + /// [ERROR]. Otherwise, it will be `null`.
|
| + String result;
|
| +
|
| + /// Returns whether this test case passed.
|
| + bool get passed => result == PASS;
|
| +
|
| + /// The stack trace for the error that caused this test case to fail, or
|
| + /// `null` if it succeeded.
|
| + StackTrace stackTrace;
|
| +
|
| + /// The name of the group within which this test is running.
|
| + final String currentGroup;
|
| +
|
| + /// The time the test case started running.
|
| + ///
|
| + /// `null` if the test hasn't yet begun running.
|
| + DateTime get startTime => _startTime;
|
| + DateTime _startTime;
|
| +
|
| + /// The amount of time the test case took.
|
| + ///
|
| + /// `null` if the test hasn't finished running.
|
| + Duration get runningTime => _runningTime;
|
| + Duration _runningTime;
|
| +
|
| + /// Whether this test is enabled.
|
| + ///
|
| + /// Disabled tests won't be run.
|
| + bool enabled = true;
|
| +
|
| + /// A completer that will complete when the test is finished.
|
| + ///
|
| + /// This is only non-`null` when outstanding callbacks exist.
|
| + Completer _testComplete;
|
| +
|
| + /// Whether this test case has finished running.
|
| + bool get isComplete => !enabled || result != null;
|
| +
|
| + InternalTestCase(this.id, this.description, this._testFunction)
|
| + : currentGroup = environment.currentContext.fullName,
|
| + _setUp = environment.currentContext.testSetUp,
|
| + _tearDown = environment.currentContext.testTearDown;
|
| +
|
| + /// A function that returns another function to handle errors from [Future]s.
|
| + ///
|
| + /// [stage] is a string description of the stage of testing that failed.
|
| + Function _errorHandler(String stage) => (e, stack) {
|
| + if (stack == null && e is Error) {
|
| + stack = e.stackTrace;
|
| + }
|
| + if (result == null || result == PASS) {
|
| + if (e is TestFailure) {
|
| + fail("$e", stack);
|
| + } else {
|
| + error("$stage failed: Caught $e", stack);
|
| + }
|
| + }
|
| + };
|
| +
|
| + /// Performs any associated [_setUp] function and runs the test.
|
| + ///
|
| + /// Returns a [Future] that can be used to schedule the next test. If the test
|
| + /// runs to completion synchronously, or is disabled, null is returned, to
|
| + /// tell unittest to schedule the next test immediately.
|
| + Future run() {
|
| + if (!enabled) return new Future.value();
|
| +
|
| + result = stackTrace = null;
|
| + message = '';
|
| +
|
| + // Avoid calling [new Future] to avoid issue 11911.
|
| + return new Future.value().then((_) {
|
| + if (_setUp != null) return _setUp();
|
| + }).catchError(_errorHandler('Setup')).then((_) {
|
| + // Skip the test if setup failed.
|
| + if (result != null) return new Future.value();
|
| + config.onTestStart(this);
|
| + _startTime = new DateTime.now();
|
| + _runningTime = null;
|
| + callbackFunctionsOutstanding++;
|
| + var testReturn = _testFunction();
|
| + // If _testFunction() returned a future, we want to wait for it like we
|
| + // would a callback, so if a failure occurs while waiting, we can abort.
|
| + if (testReturn is Future) {
|
| + callbackFunctionsOutstanding++;
|
| + testReturn.catchError(_errorHandler('Test'))
|
| + .whenComplete(markCallbackComplete);
|
| + }
|
| + }).catchError(_errorHandler('Test')).then((_) {
|
| + markCallbackComplete();
|
| + if (result == null) {
|
| + // Outstanding callbacks exist; we need to return a Future.
|
| + _testComplete = new Completer();
|
| + return _testComplete.future.whenComplete(() {
|
| + if (_tearDown != null) {
|
| + return _tearDown();
|
| + }
|
| + }).catchError(_errorHandler('Teardown'));
|
| + } else if (_tearDown != null) {
|
| + return _tearDown();
|
| + }
|
| + }).catchError(_errorHandler('Teardown')).whenComplete(() {
|
| + _setUp = null;
|
| + _tearDown = null;
|
| + _testFunction = null;
|
| + });
|
| + }
|
| +
|
| + /// Marks the test as having completed with [testResult], which should be one
|
| + /// of [PASS], [FAIL], or [ERROR].
|
| + void _complete(String testResult, [String messageText = '',
|
| + StackTrace stack]) {
|
| + if (runningTime == null) {
|
| + // The startTime can be `null` if an error happened during setup. In this
|
| + // case we simply report a running time of 0.
|
| + if (startTime != null) {
|
| + _runningTime = new DateTime.now().difference(startTime);
|
| + } else {
|
| + _runningTime = const Duration(seconds: 0);
|
| + }
|
| + }
|
| + _setResult(testResult, messageText, stack);
|
| + if (_testComplete != null) {
|
| + var t = _testComplete;
|
| + _testComplete = null;
|
| + t.complete(this);
|
| + }
|
| + }
|
| +
|
| + // Sets [this]'s fields to reflect the test result, and notifies the current
|
| + // configuration that the test has completed.
|
| + //
|
| + // Returns true if this is the first time the result has been set.
|
| + void _setResult(String testResult, String messageText, StackTrace stack) {
|
| + message = messageText;
|
| + stackTrace = getTrace(stack, formatStacks, filterStacks);
|
| + if (stackTrace == null) stackTrace = stack;
|
| + if (result == null) {
|
| + result = testResult;
|
| + config.onTestResult(this);
|
| + } else {
|
| + result = testResult;
|
| + config.onTestResultChanged(this);
|
| + }
|
| + }
|
| +
|
| + /// Marks the test as having passed.
|
| + void pass() {
|
| + _complete(PASS);
|
| + }
|
| +
|
| + void registerException(error, [StackTrace stackTrace]) {
|
| + var message = error is TestFailure ? error.message : 'Caught $error';
|
| + if (result == null) {
|
| + fail(message, stackTrace);
|
| + } else {
|
| + error(message, stackTrace);
|
| + }
|
| + }
|
| +
|
| + /// Marks the test as having failed.
|
| + void fail(String messageText, [StackTrace stack]) {
|
| + if (result != null) {
|
| + var newMessage = result == PASS
|
| + ? 'Test failed after initially passing: $messageText'
|
| + : 'Test failed more than once: $messageText';
|
| + // TODO(gram): Should we combine the stack with the old one?
|
| + _complete(ERROR, newMessage, stack);
|
| + } else {
|
| + _complete(FAIL, messageText, stack);
|
| + }
|
| + }
|
| +
|
| + /// Marks the test as having had an unexpected error.
|
| + void error(String messageText, [StackTrace stack]) {
|
| + _complete(ERROR, messageText, stack);
|
| + }
|
| +
|
| + /// Indicates that an asynchronous callback has completed, and marks the test
|
| + /// as passing if all outstanding callbacks are complete.
|
| + void markCallbackComplete() {
|
| + callbackFunctionsOutstanding--;
|
| + if (callbackFunctionsOutstanding == 0 && !isComplete) pass();
|
| + }
|
| +
|
| + String toString() => result != null ? "$description: $result" : description;
|
| +}
|
| +
|
|
|