blob: a1a0d3a5efde2131b932a215a922979ab1cf6683 [file] [log] [blame]
// Copyright (c) 2015, 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.backend.live_test_controller;
import 'dart:async';
import 'dart:collection';
import 'package:stack_trace/stack_trace.dart';
import '../utils.dart';
import 'live_test.dart';
import 'state.dart';
import 'suite.dart';
import 'test.dart';
/// An implementation of [LiveTest] that's controlled by a [LiveTestController].
class _LiveTest extends LiveTest {
final LiveTestController _controller;
Suite get suite => _controller._suite;
Test get test => _controller._test;
State get state => _controller._state;
Stream<State> get onStateChange =>
_controller._onStateChangeController.stream;
List<AsyncError> get errors => new UnmodifiableListView(_controller._errors);
Stream<AsyncError> get onError => _controller._onErrorController.stream;
Stream<String> get onPrint => _controller._onPrintController.stream;
Future get onComplete => _controller.completer.future;
Future run() => _controller._run();
Future close() => _controller._close();
_LiveTest(this._controller);
}
/// A controller that drives a [LiveTest].
///
/// This is a utility class to make it easier for implementors of [Test] to
/// create the [LiveTest] returned by [Test.load]. The [LiveTest] is accessible
/// through [LiveTestController.liveTest].
///
/// This automatically handles some of [LiveTest]'s guarantees, but for the most
/// part it's the caller's responsibility to make sure everything gets
/// dispatched in the correct order.
class LiveTestController {
/// The [LiveTest] controlled by [this].
LiveTest get liveTest => _liveTest;
LiveTest _liveTest;
/// The test suite that's running [this].
final Suite _suite;
/// The test that's being run.
final Test _test;
/// The function that will actually start the test running.
final Function _onRun;
/// A function to run when the test is closed.
///
/// This may be `null`.
final AsyncFunction _onClose;
/// The list of errors caught by the test.
final _errors = new List<AsyncError>();
/// The current state of the test.
var _state = const State(Status.pending, Result.success);
/// The controller for [LiveTest.onStateChange].
///
/// This is synchronous to ensure that events are well-ordered across multiple
/// streams.
final _onStateChangeController = new StreamController<State>
.broadcast(sync: true);
/// The controller for [LiveTest.onError].
///
/// This is synchronous to ensure that events are well-ordered across multiple
/// streams.
final _onErrorController = new StreamController<AsyncError>
.broadcast(sync: true);
/// The controller for [LiveTest.onPrint].
///
/// This is synchronous to ensure that events are well-ordered across multiple
/// streams.
final _onPrintController = new StreamController<String>.broadcast(sync: true);
/// The completer for [LiveTest.onComplete];
final completer = new Completer();
/// Whether [run] has been called.
var _runCalled = false;
/// Whether [close] has been called.
bool get _isClosed => _onErrorController.isClosed;
/// Creates a new controller for a [LiveTest].
///
/// [test] is the test being run; [suite] is the suite that contains it.
///
/// [onRun] is a function that will be called from [LiveTest.run]. It should
/// start the test running. The controller takes care of ensuring that
/// [LiveTest.run] isn't called more than once and that [LiveTest.onComplete]
/// is returned.
///
/// If [onClose] is passed, it's called the first [LiveTest.close] is called.
/// It should clean up any resources that have been allocated for the test. It
/// may return a [Future].
LiveTestController(this._suite, this._test, void onRun(), {onClose()})
: _onRun = onRun,
_onClose = onClose {
_liveTest = new _LiveTest(this);
}
/// Adds an error to the [LiveTest].
///
/// This both adds the error to [LiveTest.errors] and emits it via
/// [LiveTest.onError]. [stackTrace] is automatically converted into a [Chain]
/// if it's not one already.
void addError(error, StackTrace stackTrace) {
var asyncError = new AsyncError(error, new Chain.forTrace(stackTrace));
_errors.add(asyncError);
_onErrorController.add(asyncError);
}
/// Sets the current state of the [LiveTest] to [newState].
///
/// If [newState] is different than the old state, this both sets
/// [LiveTest.state] and emits the new state via [LiveTest.onStateChanged]. If
/// it's not different, this does nothing.
void setState(State newState) {
if (_state == newState) return;
_state = newState;
_onStateChangeController.add(newState);
}
/// Emits a line printed by the test over [LiveTest.onPrint].
void print(String line) => _onPrintController.add(line);
/// A wrapper for [_onRun] that ensures that it follows the guarantees for
/// [LiveTest.run].
Future _run() {
if (_runCalled) {
throw new StateError("LiveTest.run() may not be called more than once.");
}
_runCalled = true;
_onRun();
return liveTest.onComplete;
}
/// A wrapper for [_onClose] that ensures that all controllers are closed.
Future _close() {
if (_isClosed) return new Future.value();
_onStateChangeController.close();
_onErrorController.close();
if (!completer.isCompleted) completer.complete();
if (_onClose != null) return new Future.sync(_onClose);
return new Future.value();
}
}