// 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. // Test the basic StreamController and StreamController.broadcast. library stream_controller_async_test; import 'dart:async'; import 'package:expect/expect.dart'; import 'package:expect/legacy/async_minitest.dart'; // ignore: deprecated_member_use import 'event_helper.dart'; import 'stream_state_helper.dart'; void cancelSub(StreamSubscription sub) { sub.cancel(); } testController() { // Test fold test("StreamController.fold", () { StreamController c = new StreamController(); Stream stream = c.stream.asBroadcastStream(onCancel: cancelSub); stream .fold(0, (a, b) => a + b) .then( expectAsync((int v) { Expect.equals(42, v); }), ); c.add(10); c.add(32); c.close(); }); test("StreamController.fold throws", () { StreamController c = new StreamController(); Stream stream = c.stream.asBroadcastStream(onCancel: cancelSub); Future.value( stream.fold(0, (a, b) { throw "Fnyf!"; }), ).catchError( expectAsync((error) { Expect.equals("Fnyf!", error); }), ); c.add(42); }); } testSingleController() { test("Single-subscription StreamController.fold", () { StreamController c = new StreamController(); Stream stream = c.stream; stream .fold(0, (a, b) => a + b) .then( expectAsync((int v) { Expect.equals(42, v); }), ); c.add(10); c.add(32); c.close(); }); test("Single-subscription StreamController.fold throws", () { StreamController c = new StreamController(); Stream stream = c.stream; Future.value( stream.fold(0, (a, b) { throw "Fnyf!"; }), ).catchError( expectAsync((e) { Expect.equals("Fnyf!", e); }), ); c.add(42); }); test("Single-subscription StreamController events are buffered when" " there is no subscriber", () { StreamController c = new StreamController(); EventSink sink = c.sink; var stream = c.stream; int counter = 0; sink.add(1); sink.add(2); sink.close(); stream.listen( (data) { counter += data; }, onDone: expectAsync(() { Expect.equals(3, counter); }), ); }); } testExtraMethods() { Events sentEvents = new Events() ..add(7) ..add(9) ..add(13) ..add(87) ..close(); test("forEach", () { StreamController c = new StreamController(); Events actualEvents = new Events(); Future f = c.stream.forEach(actualEvents.add); f.then( expectAsync((_) { actualEvents.close(); Expect.listEquals(sentEvents.events, actualEvents.events); }), ); sentEvents.replay(c); }); test("forEachError", () { Events sentEvents = new Events() ..add(7) ..error("bad") ..add(87) ..close(); StreamController c = new StreamController(); Events actualEvents = new Events(); Future f = c.stream.forEach(actualEvents.add); f.catchError( expectAsync((error) { Expect.equals("bad", error); Expect.listEquals((new Events()..add(7)).events, actualEvents.events); }), ); sentEvents.replay(c); }); test("forEachError2", () { Events sentEvents = new Events() ..add(7) ..add(9) ..add(87) ..close(); StreamController c = new StreamController(); Events actualEvents = new Events(); Future f = c.stream.forEach((x) { if (x == 9) throw "bad"; actualEvents.add(x); }); f.catchError( expectAsync((error) { Expect.equals("bad", error); Expect.listEquals((new Events()..add(7)).events, actualEvents.events); }), ); sentEvents.replay(c); }); test("firstWhere", () { StreamController c = new StreamController(); Future f = c.stream.firstWhere((x) => (x % 3) == 0); f.then( expectAsync((v) { Expect.equals(9, v); }), ); sentEvents.replay(c); }); test("firstWhere 2", () { StreamController c = new StreamController(); Future f = c.stream.firstWhere((x) => (x % 4) == 0); f.catchError(expectAsync((e) {})); sentEvents.replay(c); }); test("firstWhere 3", () { StreamController c = new StreamController(); Future f = c.stream.firstWhere((x) => (x % 4) == 0, orElse: () => 999); f.then( expectAsync((v) { Expect.equals(999, v); }), ); sentEvents.replay(c); }); test("lastWhere", () { StreamController c = new StreamController(); Future f = c.stream.lastWhere((x) => (x % 3) == 0); f.then( expectAsync((v) { Expect.equals(87, v); }), ); sentEvents.replay(c); }); test("lastWhere 2", () { StreamController c = new StreamController(); Future f = c.stream.lastWhere((x) => (x % 4) == 0); f.catchError(expectAsync((e) {})); sentEvents.replay(c); }); test("lastWhere 3", () { StreamController c = new StreamController(); Future f = c.stream.lastWhere((x) => (x % 4) == 0, orElse: () => 999); f.then( expectAsync((v) { Expect.equals(999, v); }), ); sentEvents.replay(c); }); test("singleWhere", () { StreamController c = new StreamController(); Future f = c.stream.singleWhere((x) => (x % 9) == 0); f.then( expectAsync((v) { Expect.equals(9, v); }), ); sentEvents.replay(c); }); test("singleWhere 2", () { StreamController c = new StreamController(); Future f = c.stream.singleWhere((x) => (x % 3) == 0); // Matches 9 and 87.. f.catchError( expectAsync((error) { Expect.isTrue(error is StateError); }), ); sentEvents.replay(c); }); test("first", () { StreamController c = new StreamController(); Future f = c.stream.first; f.then( expectAsync((v) { Expect.equals(7, v); }), ); sentEvents.replay(c); }); test("first empty", () { StreamController c = new StreamController(); Future f = c.stream.first; f.catchError( expectAsync((error) { Expect.isTrue(error is StateError); }), ); Events emptyEvents = new Events()..close(); emptyEvents.replay(c); }); test("first error", () { StreamController c = new StreamController(); Future f = c.stream.first; f.catchError( expectAsync((error) { Expect.equals("error", error); }), ); Events errorEvents = new Events() ..error("error") ..close(); errorEvents.replay(c); }); test("first error 2", () { StreamController c = new StreamController(); Future f = c.stream.first; f.catchError( expectAsync((error) { Expect.equals("error", error); }), ); Events errorEvents = new Events() ..error("error") ..error("error2") ..close(); errorEvents.replay(c); }); test("last", () { StreamController c = new StreamController(); Future f = c.stream.last; f.then( expectAsync((v) { Expect.equals(87, v); }), ); sentEvents.replay(c); }); test("last empty", () { StreamController c = new StreamController(); Future f = c.stream.last; f.catchError( expectAsync((error) { Expect.isTrue(error is StateError); }), ); Events emptyEvents = new Events()..close(); emptyEvents.replay(c); }); test("last error", () { StreamController c = new StreamController(); Future f = c.stream.last; f.catchError( expectAsync((error) { Expect.equals("error", error); }), ); Events errorEvents = new Events() ..error("error") ..close(); errorEvents.replay(c); }); test("last error 2", () { StreamController c = new StreamController(); Future f = c.stream.last; f.catchError( expectAsync((error) { Expect.equals("error", error); }), ); Events errorEvents = new Events() ..error("error") ..error("error2") ..close(); errorEvents.replay(c); }); test("elementAt", () { StreamController c = new StreamController(); Future f = c.stream.elementAt(2); f.then( expectAsync((v) { Expect.equals(13, v); }), ); sentEvents.replay(c); }); test("elementAt 2", () { StreamController c = new StreamController(); Future f = c.stream.elementAt(20); f.catchError( expectAsync((error) { Expect.isTrue(error is RangeError); }), ); sentEvents.replay(c); }); test("drain", () { StreamController c = new StreamController(); Future f = c.stream.drain(); f.then( expectAsync((v) { Expect.equals(null, v); }), ); sentEvents.replay(c); }); test("drain error", () { StreamController c = new StreamController(); Future f = c.stream.drain(); f.catchError( expectAsync((error) { Expect.equals("error", error); }), ); Events errorEvents = new Events() ..error("error") ..error("error2") ..close(); errorEvents.replay(c); }); } testPause() { test("pause event-unpause", () { StreamProtocolTest test = new StreamProtocolTest(); Completer completer = new Completer(); test ..expectListen() ..expectData(42, () { test.pause(completer.future); }) ..expectPause(() { completer.complete(null); }) ..expectData(43) ..expectData(44) ..expectCancel() ..expectDone(test.terminate); test.listen(); test.add(42); test.add(43); test.add(44); test.close(); }); test("pause twice event-unpause", () { StreamProtocolTest test = new StreamProtocolTest(); Completer completer = new Completer(); Completer completer2 = new Completer(); test ..expectListen() ..expectData(42, () { test.pause(completer.future); test.pause(completer2.future); }) ..expectPause(() { completer.future.then(completer2.complete); completer.complete(null); }) ..expectData(43) ..expectData(44) ..expectCancel() ..expectDone(test.terminate); test ..listen() ..add(42) ..add(43) ..add(44) ..close(); }); test("pause twice direct-unpause", () { StreamProtocolTest test = new StreamProtocolTest(); test ..expectListen() ..expectData(42, () { test.pause(); test.pause(); }) ..expectPause(() { test.resume(); test.resume(); }) ..expectData(43) ..expectData(44) ..expectCancel() ..expectDone(test.terminate); test ..listen() ..add(42) ..add(43) ..add(44) ..close(); }); test("pause twice direct-event-unpause", () { StreamProtocolTest test = new StreamProtocolTest(); Completer completer = new Completer(); test ..expectListen() ..expectData(42, () { test.pause(); test.pause(completer.future); test.add(43); test.add(44); test.close(); }) ..expectPause(() { completer.future.then((v) => test.resume()); completer.complete(null); }) ..expectData(43) ..expectData(44) ..expectCancel() ..expectDone(test.terminate); test ..listen() ..add(42); }); } class TestError { const TestError(); } testRethrow() { TestError error = const TestError(); testStream(name, streamValueTransform) { test("rethrow-$name-value", () { StreamController c = new StreamController(); Stream s = streamValueTransform(c.stream, (v) { throw error; }); s.listen( (_) { Expect.fail("unexpected value"); }, onError: expectAsync((e) { Expect.identical(error, e); }), ); c.add(null); c.close(); }); } testStreamError(name, streamErrorTransform) { test("rethrow-$name-error", () { StreamController c = new StreamController(); Stream s = streamErrorTransform(c.stream, (e) { throw error; }); s.listen( (_) { Expect.fail("unexpected value"); }, onError: expectAsync((e) { Expect.identical(error, e); }), ); c.addError("SOME ERROR"); c.close(); }); } testFuture(name, streamValueTransform) { test("rethrow-$name-value", () { StreamController c = new StreamController(); Future f = streamValueTransform(c.stream, (v) { throw error; }); f.then( (v) { Expect.fail("unreachable"); }, onError: expectAsync((e) { Expect.identical(error, e); }), ); // Need two values to trigger compare for reduce. c.add(0); c.add(1); c.close(); }); } testStream("where", (s, act) => s.where(act)); testStream("map", (s, act) => s.map(act)); testStream("expand", (s, act) => s.expand(act)); testStream("where", (s, act) => s.where(act)); testStreamError("handleError", (s, act) => s.handleError(act)); testStreamError("handleTest", (s, act) => s.handleError((v) {}, test: act)); testFuture("forEach", (s, act) => s.forEach(act)); testFuture("every", (s, act) => s.every(act)); testFuture("any", (s, act) => s.any(act)); testFuture("reduce", (s, act) => s.reduce((a, b) => act(b))); testFuture("fold", (s, act) => s.fold(0, (a, b) => act(b))); testFuture("drain", (s, act) => s.drain().then(act)); } void testBroadcastController() { test("broadcast-controller-basic", () { StreamProtocolTest test = new StreamProtocolTest.broadcast(); test ..expectListen() ..expectData(42) ..expectCancel() ..expectDone(test.terminate); test ..listen() ..add(42) ..close(); }); test("broadcast-controller-listen-twice", () { StreamProtocolTest test = new StreamProtocolTest.broadcast(); test ..expectListen() ..expectData(42, () { test.listen(); test.add(37); test.close(); }) // Order is not guaranteed between subscriptions if not sync. ..expectData(37) ..expectData(37) ..expectDone() ..expectCancel() ..expectDone(test.terminate); test.listen(); test.add(42); }); test("broadcast-controller-listen-twice-non-overlap", () { StreamProtocolTest test = new StreamProtocolTest.broadcast(); test ..expectListen(() { test.add(42); }) ..expectData(42, () { test.cancel(); }) ..expectCancel(() { test.listen(); }) ..expectListen(() { test.add(37); }) ..expectData(37, () { test.close(); }) ..expectCancel() ..expectDone(test.terminate); test.listen(); }); test("broadcast-controller-individual-pause", () { StreamProtocolTest test = new StreamProtocolTest.broadcast(); var sub1; test ..expectListen() ..expectData(42) ..expectData(42, () { sub1.pause(); }) ..expectData(43, () { sub1.cancel(); test.listen(); test.add(44); test.expectData(44); test.expectData(44, test.terminate); }); sub1 = test.listen(); test.listen(); test.add(42); test.add(43); }); test("broadcast-controller-add-in-callback", () { StreamProtocolTest test = new StreamProtocolTest.broadcast(); test.expectListen(); var sub = test.listen(); test.add(42); sub.expectData(42, () { test.add(87); sub.cancel(); }); test.expectCancel(() { test.add(37); test.terminate(); }); }); } void testAsBroadcast() { test("asBroadcast-not-canceled", () { StreamProtocolTest test = new StreamProtocolTest.asBroadcast(); var sub; test ..expectListen() ..expectBroadcastListen((_) { test.add(42); }) ..expectData(42, () { sub.cancel(); }) ..expectBroadcastCancel((_) { sub = test.listen(); }) ..expectBroadcastListen((_) { test.terminate(); }); sub = test.listen(); }); test("asBroadcast-canceled", () { StreamProtocolTest test = new StreamProtocolTest.asBroadcast(); var sub; test ..expectListen() ..expectBroadcastListen((_) { test.add(42); }) ..expectData(42, () { sub.cancel(); }) ..expectBroadcastCancel((originalSub) { originalSub.cancel(); }) ..expectCancel(test.terminate); sub = test.listen(); }); test("asBroadcast-pause-original", () { StreamProtocolTest test = new StreamProtocolTest.asBroadcast(); var sub; test ..expectListen() ..expectBroadcastListen((_) { test.add(42); test.add(43); }) ..expectData(42, () { sub.cancel(); }) ..expectBroadcastCancel((originalSub) { originalSub.pause(); // Pause before sending 43 from original sub. }) ..expectPause(() { sub = test.listen(); }) ..expectBroadcastListen((originalSub) { originalSub.resume(); }) ..expectData(43) ..expectResume(() { test.close(); }) ..expectCancel() ..expectDone() ..expectBroadcastCancel((_) => test.terminate()); sub = test.listen(); }); } void testSink({ required bool sync, required bool broadcast, required bool asBroadcast, }) { String type = "${sync ? "S" : "A"}${broadcast ? "B" : "S"}${asBroadcast ? "aB" : ""}"; test("$type-controller-sink", () { var done = expectAsync(() {}); var c = broadcast ? new StreamController.broadcast(sync: sync) : new StreamController(sync: sync); var expected = new Events() ..add(42) ..error("error") ..add(1) ..add(2) ..add(3) ..add(4) ..add(5) ..add(43) ..close(); var actual = new Events.capture( asBroadcast ? c.stream.asBroadcastStream() : c.stream, ); var sink = c.sink; sink.add(42); sink.addError("error"); sink .addStream(new Stream.fromIterable([1, 2, 3, 4, 5])) .then((_) { sink.add(43); return sink.close(); }) .then((_) { Expect.listEquals(expected.events, actual.events); done(); }); }); test("$type-controller-sink-canceled", () { var done = expectAsync(() {}); var c = broadcast ? new StreamController.broadcast(sync: sync) : new StreamController(sync: sync); var expected = new Events() ..add(42) ..error("error") ..add(1) ..add(2) ..add(3); var stream = asBroadcast ? c.stream.asBroadcastStream() : c.stream; var actual = new Events(); var sub; // Cancel subscription after receiving "3" event. sub = stream.listen((v) { if (v == 3) sub.cancel(); actual.add(v); }, onError: actual.error); var sink = c.sink; sink.add(42); sink.addError("error"); sink .addStream(new Stream.fromIterable([1, 2, 3, 4, 5])) .then((_) { Expect.listEquals(expected.events, actual.events); // Close controller as well. It has no listener. If it is a broadcast // stream, it will still be open, and we read the "done" future before // closing. A normal stream is already done when its listener cancels. Future doneFuture = sink.done; sink.close(); return doneFuture; }) .then((_) { // No change in events. Expect.listEquals(expected.events, actual.events); done(); }); }); test("$type-controller-sink-paused", () { var done = expectAsync(() {}); var c = broadcast ? new StreamController.broadcast(sync: sync) : new StreamController(sync: sync); var expected = new Events() ..add(42) ..error("error") ..add(1) ..add(2) ..add(3) ..add(4) ..add(5) ..add(43) ..close(); var stream = asBroadcast ? c.stream.asBroadcastStream() : c.stream; var actual = new Events(); var sub; var pauseIsDone = false; sub = stream.listen( (v) { if (v == 3) { sub.pause( new Future.delayed(const Duration(milliseconds: 15), () { pauseIsDone = true; }), ); } actual.add(v); }, onError: actual.error, onDone: actual.close, ); var sink = c.sink; sink.add(42); sink.addError("error"); sink .addStream(new Stream.fromIterable([1, 2, 3, 4, 5])) .then((_) { sink.add(43); return sink.close(); }) .then((_) { if (asBroadcast || broadcast) { // The done-future of the sink completes when it passes // the done event to the asBroadcastStream controller, which is // before the final listener gets the event. // Wait for the done event to be *delivered* before testing the // events. actual.onDone(() { Expect.listEquals(expected.events, actual.events); done(); }); } else { Expect.listEquals(expected.events, actual.events); done(); } }); }); test("$type-controller-addstream-error-stop", () { // Check that addStream defaults to ending after the first error. var done = expectAsync(() {}); StreamController c = broadcast ? new StreamController.broadcast(sync: sync) : new StreamController(sync: sync); Stream stream = asBroadcast ? c.stream.asBroadcastStream() : c.stream; var actual = new Events.capture(stream); var source = new Events(); source ..add(1) ..add(2) ..error("BAD") ..add(3) ..error("FAIL") ..close(); var expected = new Events() ..add(1) ..add(2) ..error("BAD") ..close(); StreamController sourceController = new StreamController(); c.addStream(sourceController.stream, cancelOnError: true).then((_) { c.close().then((_) { Expect.listEquals(expected.events, actual.events); done(); }); }); source.replay(sourceController); }); test("$type-controller-addstream-error-forward", () { // Check that addStream with cancelOnError:false passes all data and errors // to the controller. var done = expectAsync(() {}); StreamController c = broadcast ? new StreamController.broadcast(sync: sync) : new StreamController(sync: sync); Stream stream = asBroadcast ? c.stream.asBroadcastStream() : c.stream; var actual = new Events.capture(stream); var source = new Events(); source ..add(1) ..add(2) ..addError("BAD") ..add(3) ..addError("FAIL") ..close(); StreamController sourceController = new StreamController(); c.addStream(sourceController.stream).then((_) { c.close().then((_) { Expect.listEquals(source.events, actual.events); done(); }); }); source.replay(sourceController); }); test("$type-controller-addstream-twice", () { // Using addStream twice on the same stream var done = expectAsync(() {}); StreamController c = broadcast ? new StreamController.broadcast(sync: sync) : new StreamController(sync: sync); Stream stream = asBroadcast ? c.stream.asBroadcastStream() : c.stream; var actual = new Events.capture(stream); // Streams of five events, throws on 3. Stream s1 = new Stream.fromIterable([ 1, 2, 3, 4, 5, ]).map((x) => (x == 3 ? throw x : x)); Stream s2 = new Stream.fromIterable([ 1, 2, 3, 4, 5, ]).map((x) => (x == 3 ? throw x : x)); Events expected = new Events(); expected ..add(1) ..add(2) ..error(3); expected ..add(1) ..add(2) ..error(3) ..add(4) ..add(5); expected..close(); c.addStream(s1, cancelOnError: true).then((_) { c.addStream(s2, cancelOnError: false).then((_) { c.close().then((_) { Expect.listEquals(expected.events, actual.events); done(); }); }); }); }); } main() { testController(); testSingleController(); testExtraMethods(); testPause(); testRethrow(); testBroadcastController(); testAsBroadcast(); testSink(sync: true, broadcast: false, asBroadcast: false); testSink(sync: true, broadcast: false, asBroadcast: true); testSink(sync: true, broadcast: true, asBroadcast: false); testSink(sync: false, broadcast: false, asBroadcast: false); testSink(sync: false, broadcast: false, asBroadcast: true); testSink(sync: false, broadcast: true, asBroadcast: false); }