blob: c433e391cb53ba8fb6fd818cd2d06b69bd810da6 [file] [log] [blame]
// Copyright (c) 2013, 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 stream_state_helper;
import "../../../pkg/unittest/lib/unittest.dart";
import "dart:async";
import "dart:collection";
class StreamProtocolTest {
bool trace = false;
StreamController _controller;
Stream _controllerStream;
StreamSubscription _subscription;
List<Event> _expectations = new List<Event>();
int _nextExpectationIndex = 0;
Function _onComplete;
StreamProtocolTest([bool broadcast = false]) {
_controller = new StreamController(
onListen: _onSubcription,
onPause: _onPause,
onResume: _onResume,
onCancel: _onCancel);
if (broadcast) {
_controllerStream = _controller.stream.asBroadcastStream();
} else {
_controllerStream = _controller.stream;
}
_onComplete = expectAsync0((){
_onComplete = null; // Being null marks the test to be complete.
});
}
// Actions on the stream and controller.
void add(var data) { _controller.add(data); }
void error(var error) { _controller.addError(error); }
void close() { _controller.close(); }
void subscribe({bool cancelOnError : false}) {
// TODO(lrn): Handle more subscriptions (e.g., a subscription-id
// per subscription, and an id on event _expectations).
if (_subscription != null) throw new StateError("Already subscribed");
_subscription = _controllerStream.listen(_onData,
onError: _onError,
onDone: _onDone,
cancelOnError:
cancelOnError);
}
void pause([Future resumeSignal]) {
if (_subscription == null) throw new StateError("Not subscribed");
_subscription.pause(resumeSignal);
}
void resume() {
if (_subscription == null) throw new StateError("Not subscribed");
_subscription.resume();
}
void cancel() {
if (_subscription == null) throw new StateError("Not subscribed");
_subscription.cancel();
_subscription = null;
}
// Handling of stream events.
void _onData(var data) {
if (trace) print("[Data : $data]");
_withNextExpectation((Event expect) {
if (!expect.matchData(data)) {
_fail("Expected: $expect\n"
"Found : [Data: $data]");
}
});
}
void _onError(error) {
if (trace) print("[Error : $error]");
_withNextExpectation((Event expect) {
if (!expect.matchError(error)) {
_fail("Expected: $expect\n"
"Found : [Error: ${error}]");
}
});
}
void _onDone() {
if (trace) print("[Done]");
_subscription = null;
_withNextExpectation((Event expect) {
if (!expect.matchDone()) {
_fail("Expected: $expect\n"
"Found : [Done]");
}
});
}
void _onPause() {
if (trace) print("[Pause]");
_withNextExpectation((Event expect) {
if (!expect.matchPause()) {
_fail("Expected: $expect\n"
"Found : [Paused]");
}
});
}
void _onResume() {
if (trace) print("[Resumed]");
_withNextExpectation((Event expect) {
if (!expect.matchResume()) {
_fail("Expected: $expect\n"
"Found : [Resumed]");
}
});
}
void _onSubcription() {
if (trace) print("[Subscribed]");
_withNextExpectation((Event expect) {
if (!expect.matchSubscribe()) {
_fail("Expected: $expect\n"
"Found: [Subscribed]");
}
});
}
void _onCancel() {
if (trace) print("[Cancelled]");
_withNextExpectation((Event expect) {
if (!expect.matchCancel()) {
_fail("Expected: $expect\n"
"Found: [Cancelled]");
}
});
}
void _withNextExpectation(void action(Event expect)) {
if (_nextExpectationIndex == _expectations.length) {
action(new MismatchEvent());
} else {
Event next = _expectations[_nextExpectationIndex];
action(next);
}
_nextExpectationIndex++;
_checkDone();
}
void _checkDone() {
if (_nextExpectationIndex == _expectations.length) {
_onComplete();
}
}
// Adds _expectations.
void expectAny([void action()]) {
if (_onComplete == null) {
_fail("Adding expectation after completing");
}
_expectations.add(new LogAnyEvent(action));
}
void expectData(var data, [void action()]) {
if (_onComplete == null) {
_fail("Adding expectation after completing");
}
_expectations.add(new DataEvent(data, action));
}
void expectError(var error, [void action()]) {
if (_onComplete == null) {
_fail("Adding expectation after completing");
}
_expectations.add(new ErrorEvent(error, action));
}
void expectDone([void action()]) {
if (_onComplete == null) {
_fail("Adding expectation after completing");
}
_expectations.add(new DoneEvent(action));
}
void expectPause([void action()]) {
if (_onComplete == null) {
_fail("Adding expectation after completing");
}
_expectations.add(new PauseCallbackEvent(action));
}
void expectResume([void action()]) {
if (_onComplete == null) {
_fail("Adding expectation after completing");
}
_expectations.add(new ResumeCallbackEvent(action));
}
void expectSubscription([void action()]) {
if (_onComplete == null) {
_fail("Adding expectation after completing");
}
_expectations.add(
new SubscriptionCallbackEvent(action));
}
void expectCancel([void action()]) {
if (_onComplete == null) {
_fail("Adding expectation after completing");
}
_expectations.add(
new CancelCallbackEvent(action));
}
void _fail(String message) {
if (_nextExpectationIndex == 0) {
throw "Unexpected event:\n$message\nNo earlier events matched.";
}
throw "Unexpected event:\n$message\nMatched so far:\n"
" ${_expectations.take(_nextExpectationIndex).join("\n ")}";
}
}
class Event {
Function _action;
Event(void this._action());
bool matchData(var data) {
if (!_testData(data)) return false;
if (_action != null) _action();
return true;
}
bool matchError(e) {
if (!_testError(e)) return false;
if (_action != null) _action();
return true;
}
bool matchDone() {
if (!_testDone()) return false;
if (_action != null) _action();
return true;
}
bool matchPause() {
if (!_testPause()) return false;
if (_action != null) _action();
return true;
}
bool matchResume() {
if (!_testResume()) return false;
if (_action != null) _action();
return true;
}
bool matchSubscribe() {
if (!_testSubscribe()) return false;
if (_action != null) _action();
return true;
}
bool matchCancel() {
if (!_testCancel()) return false;
if (_action != null) _action();
return true;
}
bool _testData(_) => false;
bool _testError(_) => false;
bool _testDone() => false;
bool _testPause() => false;
bool _testResume() => false;
bool _testSubscribe() => false;
bool _testCancel() => false;
}
class MismatchEvent extends Event {
MismatchEvent() : super(null);
toString() => "[No event expected]";
}
class DataEvent extends Event {
final data;
DataEvent(this.data, void action()) : super(action);
bool _testData(var data) => this.data == data;
String toString() => "[Data: $data]";
}
class ErrorEvent extends Event {
final error;
ErrorEvent(this.error, void action()) : super(action);
bool _testError(error) => this.error == error;
String toString() => "[Error: $error]";
}
class DoneEvent extends Event {
DoneEvent(void action()) : super(action);
bool _testDone() => true;
String toString() => "[Done]";
}
class PauseCallbackEvent extends Event {
PauseCallbackEvent(void action()) : super(action);
bool _testPause() => true;
String toString() => "[Paused]";
}
class ResumeCallbackEvent extends Event {
ResumeCallbackEvent(void action()) : super(action);
bool _testResume() => true;
String toString() => "[Resumed]";
}
class SubscriptionCallbackEvent extends Event {
SubscriptionCallbackEvent(void action()) : super(action);
bool _testSubscribe() => true;
String toString() => "[Subscribed]";
}
class CancelCallbackEvent extends Event {
CancelCallbackEvent(void action()) : super(action);
bool _testCancel() => true;
String toString() => "[Cancelled]";
}
class LogAnyEvent extends Event {
String _actual = "*Not matched yet*";
LogAnyEvent(void action()) : super(action);
bool _testData(var data) {
_actual = "*[Data $data]";
return true;
}
bool _testError(error) {
_actual = "*[Error ${error}]";
return true;
}
bool _testDone() {
_actual = "*[Done]";
return true;
}
bool _testPause() {
_actual = "*[Paused]";
return true;
}
bool _testResume() {
_actual = "*[Resumed]";
return true;
}
bool _testSubcribe() {
_actual = "*[Subscribed]";
return true;
}
bool _testCancel() {
_actual = "*[Cancelled]";
return true;
}
String toString() => _actual;
}