// 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.singleSubscription. library stream_controller_test; import "package:expect/expect.dart"; import "package:async_helper/async_helper.dart"; import 'dart:async'; import 'event_helper.dart'; const MS = const Duration(milliseconds: 1); fail(e) { Expect.fail("Unexepected error: $e"); } void testMultiController() { // Test normal flow. { var c = new StreamController(sync: true); Events expectedEvents = new Events() ..add(42) ..add("dibs") ..error("error!") ..error("error too!") ..close(); CaptureEvents actualEvents = new Events.capture(c.stream.asBroadcastStream()); expectedEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test automatic unsubscription on error. { var c = new StreamController(sync: true); var expectedEvents = new Events() ..add(42) ..error("error"); var actualEvents = new Events.capture(c.stream.asBroadcastStream(), cancelOnError: true); Events sentEvents = new Events() ..add(42) ..error("error") ..add("Are you there?"); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test manual unsubscription. { var c = new StreamController(sync: true); var expectedEvents = new Events() ..add(42) ..error("error") ..add(37); dynamic actualEvents = new Events.capture(c.stream.asBroadcastStream(), cancelOnError: false); expectedEvents.replay(c); actualEvents.subscription.cancel(); c.add("Are you there"); // Not sent to actualEvents. Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test filter. { var c = new StreamController(sync: true); var expectedEvents = new Events() ..add("a string") ..add("another string") ..close(); var sentEvents = new Events() ..add("a string") ..add(42) ..add("another string") ..close(); var actualEvents = new Events.capture( c.stream.asBroadcastStream().where((v) => v is String)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test map. { var c = new StreamController(sync: true); var expectedEvents = new Events() ..add("abab") ..error("error") ..close(); var sentEvents = new Events() ..add("ab") ..error("error") ..close(); var actualEvents = new Events.capture(c.stream.asBroadcastStream().map((v) => "$v$v")); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test handleError. { var c = new StreamController(sync: true); var expectedEvents = new Events() ..add("ab") ..error("[foo]"); var sentEvents = new Events() ..add("ab") ..error("foo") ..add("ab") ..close(); var actualEvents = new Events.capture( c.stream.asBroadcastStream().handleError((error) { if (error is String) { // TODO(floitsch): this test originally changed the stacktrace. throw "[${error}]"; } }), cancelOnError: true); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // reduce is tested asynchronously and therefore not in this file. // Test expand { var c = new StreamController(sync: true); var sentEvents = new Events() ..add(3) ..add(2) ..add(4) ..close(); var expectedEvents = new Events() ..add(1) ..add(2) ..add(3) ..add(1) ..add(2) ..add(1) ..add(2) ..add(3) ..add(4) ..close(); var actualEvents = new Events.capture(c.stream.asBroadcastStream().expand((v) { var l = []; for (int i = 0; i < v; i++) l.add(i + 1); return l; })); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test transform. { var c = new StreamController(sync: true); var sentEvents = new Events() ..add("a") ..error(42) ..add("b") ..close(); var expectedEvents = new Events() ..error("a") ..add(42) ..error("b") ..add("foo") ..close(); var actualEvents = new Events.capture(c.stream .asBroadcastStream() .transform(new StreamTransformer.fromHandlers(handleData: (v, s) { s.addError(v); }, handleError: (e, st, s) { s.add(e); }, handleDone: (s) { s.add("foo"); s.close(); }))); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test multiple filters. { var c = new StreamController(sync: true); var sentEvents = new Events() ..add(42) ..add("snugglefluffy") ..add(7) ..add("42") ..error("not FormatException") // Unsubscribes. ..close(); var expectedEvents = new Events() ..add(42) ..error("not FormatException"); var actualEvents = new Events.capture( c.stream .asBroadcastStream() .where((v) => v is String) .map((v) => int.parse(v)) .handleError((error) { if (error is! FormatException) throw error; }).where((v) => v > 10), cancelOnError: true); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test subscription changes while firing. { var c = new StreamController(sync: true); var sink = c.sink; var stream = c.stream.asBroadcastStream(); var counter = 0; var subscription = stream.listen(null); subscription.onData((data) { counter += data; subscription.cancel(); stream.listen((data) { counter += 10 * data; }); var subscription2 = stream.listen(null); subscription2.onData((data) { counter += 100 * data; if (data == 4) subscription2.cancel(); }); }); sink.add(1); // seen by stream 1 sink.add(2); // seen by stream 10 and 100 sink.add(3); // -"- sink.add(4); // -"- sink.add(5); // seen by stream 10 Expect.equals(1 + 20 + 200 + 30 + 300 + 40 + 400 + 50, counter); } } testSingleController() { // Test normal flow. { var c = new StreamController(sync: true); Events expectedEvents = new Events() ..add(42) ..add("dibs") ..error("error!") ..error("error too!") ..close(); CaptureEvents actualEvents = new Events.capture(c.stream); expectedEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test automatic unsubscription on error. { var c = new StreamController(sync: true); var expectedEvents = new Events() ..add(42) ..error("error"); var actualEvents = new Events.capture(c.stream, cancelOnError: true); Events sentEvents = new Events() ..add(42) ..error("error") ..add("Are you there?"); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test manual unsubscription. { var c = new StreamController(sync: true); var expectedEvents = new Events() ..add(42) ..error("error") ..add(37); dynamic actualEvents = new Events.capture(c.stream, cancelOnError: false); expectedEvents.replay(c); actualEvents.subscription.cancel(); c.add("Are you there"); // Not sent to actualEvents. Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test filter. { var c = new StreamController(sync: true); var expectedEvents = new Events() ..add("a string") ..add("another string") ..close(); var sentEvents = new Events() ..add("a string") ..add(42) ..add("another string") ..close(); var actualEvents = new Events.capture(c.stream.where((v) => v is String)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test map. { var c = new StreamController(sync: true); var expectedEvents = new Events() ..add("abab") ..error("error") ..close(); var sentEvents = new Events() ..add("ab") ..error("error") ..close(); var actualEvents = new Events.capture(c.stream.map((v) => "$v$v")); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test handleError. { var c = new StreamController(sync: true); var expectedEvents = new Events() ..add("ab") ..error("[foo]"); var sentEvents = new Events() ..add("ab") ..error("foo") ..add("ab") ..close(); var actualEvents = new Events.capture(c.stream.handleError((error) { if (error is String) { // TODO(floitsch): this error originally changed the stack trace. throw "[${error}]"; } }), cancelOnError: true); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // reduce is tested asynchronously and therefore not in this file. // Test expand { var c = new StreamController(sync: true); var sentEvents = new Events() ..add(3) ..add(2) ..add(4) ..close(); var expectedEvents = new Events() ..add(1) ..add(2) ..add(3) ..add(1) ..add(2) ..add(1) ..add(2) ..add(3) ..add(4) ..close(); var actualEvents = new Events.capture(c.stream.expand((v) { var l = []; for (int i = 0; i < v; i++) l.add(i + 1); return l; })); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // test contains. { var c = new StreamController(sync: true); // Error after match is not important. var sentEvents = new Events() ..add("a") ..add("x") ..error("FAIL") ..close(); Future contains = c.stream.contains("x"); contains.then((var c) { Expect.isTrue(c); }); sentEvents.replay(c); } { var c = new StreamController(sync: true); // Not matching is ok. var sentEvents = new Events() ..add("a") ..add("x") ..add("b") ..close(); Future contains = c.stream.contains("y"); contains.then((var c) { Expect.isFalse(c); }); sentEvents.replay(c); } { var c = new StreamController(sync: true); // Error before match makes future err. var sentEvents = new Events() ..add("a") ..error("FAIL") ..add("b") ..close(); Future contains = c.stream.contains("b"); contains.then((var c) { Expect.fail("no value expected"); }).catchError((error) { Expect.equals("FAIL", error); }); sentEvents.replay(c); } // Test transform. { var c = new StreamController(sync: true); var sentEvents = new Events() ..add("a") ..error(42) ..add("b") ..close(); var expectedEvents = new Events() ..error("a") ..add(42) ..error("b") ..add("foo") ..close(); var actualEvents = new Events.capture(c.stream .transform(new StreamTransformer.fromHandlers(handleData: (v, s) { s.addError(v); }, handleError: (e, st, s) { s.add(e); }, handleDone: (s) { s.add("foo"); s.close(); }))); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test multiple filters. { var c = new StreamController(sync: true); var sentEvents = new Events() ..add(42) ..add("snugglefluffy") ..add(7) ..add("42") ..error("not FormatException") // Unsubscribes. ..close(); var expectedEvents = new Events() ..add(42) ..error("not FormatException"); var actualEvents = new Events.capture( c.stream .where((v) => v is String) .map((v) => int.parse(v)) .handleError((error) { if (error is! FormatException) throw error; }).where((v) => v > 10), cancelOnError: true); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } // Test that only one subscription is allowed. { var c = new StreamController(sync: true); var sink = c.sink; var stream = c.stream; var counter = 0; var subscription = stream.listen((data) { counter += data; }); Expect.throwsStateError(() => stream.listen(null)); sink.add(1); Expect.equals(1, counter); c.close(); } } testExtraMethods() { Events sentEvents = new Events() ..add(1) ..add(2) ..add(3) ..close(); var c = new StreamController(sync: true); Events expectedEvents = new Events() ..add(3) ..close(); Events actualEvents = new Events.capture(c.stream.skip(2)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); c = new StreamController(sync: true); expectedEvents = new Events()..close(); actualEvents = new Events.capture(c.stream.skip(3)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); c = new StreamController(sync: true); expectedEvents = new Events()..close(); actualEvents = new Events.capture(c.stream.skip(7)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); c = new StreamController(sync: true); expectedEvents = sentEvents; actualEvents = new Events.capture(c.stream.skip(0)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); c = new StreamController(sync: true); expectedEvents = new Events() ..add(3) ..close(); actualEvents = new Events.capture(c.stream.skipWhile((x) => x <= 2)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); c = new StreamController(sync: true); expectedEvents = new Events() ..add(2) ..add(3) ..close(); actualEvents = new Events.capture(c.stream.skipWhile((x) => x <= 1)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); c = new StreamController(sync: true); expectedEvents = new Events() ..add(1) ..add(2) ..add(3) ..close(); actualEvents = new Events.capture(c.stream.skipWhile((x) => false)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); c = new StreamController(sync: true); expectedEvents = new Events() ..add(1) ..add(2) ..close(); actualEvents = new Events.capture(c.stream.take(2)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); c = new StreamController(sync: true); expectedEvents = new Events() ..add(1) ..add(2) ..close(); actualEvents = new Events.capture(c.stream.takeWhile((x) => x <= 2)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); c = new StreamController(sync: true); sentEvents = new Events() ..add(1) ..add(1) ..add(2) ..add(1) ..add(2) ..add(2) ..add(2) ..close(); expectedEvents = new Events() ..add(1) ..add(2) ..add(1) ..add(2) ..close(); actualEvents = new Events.capture(c.stream.distinct()); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); c = new StreamController(sync: true); sentEvents = new Events() ..add(5) ..add(6) ..add(4) ..add(6) ..add(8) ..add(3) ..add(4) ..add(1) ..close(); expectedEvents = new Events() ..add(5) ..add(4) ..add(3) ..add(1) ..close(); // Use 'distinct' as a filter with access to the previously emitted event. actualEvents = new Events.capture(c.stream.distinct((a, b) => a < b)); sentEvents.replay(c); Expect.listEquals(expectedEvents.events, actualEvents.events); } void testClosed() { StreamController c = new StreamController(sync: true); Expect.isFalse(c.isClosed); c.add(42); Expect.isFalse(c.isClosed); c.addError("bad"); Expect.isFalse(c.isClosed); c.close(); Expect.isTrue(c.isClosed); } void testCloseFuture() { asyncStart(); asyncStart(); var c = new StreamController(); var f = c.close(); Expect.isTrue(c.isClosed); bool doneSeen = false; f.then((_) { Expect.isTrue(doneSeen); asyncEnd(); }); // Only listen after a while. new Timer(MS * 250, () { c.stream.listen(null, onDone: () { asyncEnd(); doneSeen = true; }); }); } void testCloseFuture2() { asyncStart(); asyncStart(); var c = new StreamController.broadcast(); var f = c.close(); Expect.isTrue(c.isClosed); bool doneSeen = false; f.then((_) { // Done future on broadcast stream can happen // before a listener is added. Expect.isFalse(doneSeen); asyncEnd(); }); // Only listen after a while. new Timer(MS * 250, () { c.stream.listen(null, onDone: () { doneSeen = true; asyncEnd(); }); }); } void testCloseFuture3() { asyncStart(); var c = new StreamController.broadcast(); c..add(1)..add(2)..add(3)..add(4); c.stream.listen(null).cancel(); var f = c.close(); Expect.isTrue(c.isClosed); f.then((_) { asyncEnd(); }); } void testStreamEquals() { StreamController c; c = new StreamController(sync: false); Expect.equals(c.stream, c.stream); c = new StreamController(sync: true); Expect.equals(c.stream, c.stream); c = new StreamController(sync: false, onListen: () {}); Expect.equals(c.stream, c.stream); c = new StreamController(sync: true, onListen: () {}); Expect.equals(c.stream, c.stream); c = new StreamController.broadcast(sync: false); Expect.equals(c.stream, c.stream); c = new StreamController.broadcast(sync: true); Expect.equals(c.stream, c.stream); c = new StreamController.broadcast(sync: false, onListen: () {}); Expect.equals(c.stream, c.stream); c = new StreamController.broadcast(sync: true, onListen: () {}); Expect.equals(c.stream, c.stream); } void testCancelThrow() { asyncStart(); asyncStart(); asyncStart(); StreamController c = new StreamController(onCancel: () { asyncEnd(); throw "ERROR"; }); c.add(1); c.add(2); c.add(3); Future done = c.close(); StreamSubscription sub; sub = c.stream.listen((v) { Expect.equals(1, v); Future f = sub.cancel(); f.catchError((e) { // Must complete with error from onCancel. Expect.equals("ERROR", e); asyncEnd(); }); }); done.catchError(fail).whenComplete(asyncEnd); // Must complete without error. } void testCancelThrow2() { asyncStart(); asyncStart(); asyncStart(); asyncStart(); asyncStart(); StreamController c2 = new StreamController(onCancel: () { asyncEnd(); throw "ERROR"; }); c2.add(1); c2.add(2); Future done2 = c2.close(); done2.catchError(fail).whenComplete(asyncEnd); // Should not get error; StreamController c = new StreamController(); var sub; sub = c.stream.listen((v) { Expect.equals(1, v); Future f = sub.cancel(); f.catchError((e) { // Error from addStream stream's cancel must go only here. asyncEnd(); Expect.equals("ERROR", e); }); }); var addDone = c.addStream(c2.stream); addDone.catchError(fail).whenComplete(asyncEnd); // Should not get error. var done = c.done; done.catchError(fail).whenComplete(asyncEnd); // Should not get error. } void testCancelThrow3() { asyncStart(); asyncStart(); asyncStart(); asyncStart(); asyncStart(); asyncStart(); StreamController c2 = new StreamController(onCancel: () { asyncEnd(); throw "ERROR2"; }); c2.add(1); c2.add(2); var done2 = c2.close(); done2.catchError(fail).whenComplete(asyncEnd); // Should not get error; StreamController c = new StreamController(onCancel: () { asyncEnd(); throw "ERROR1"; }); var sub; sub = c.stream.listen((v) { Expect.equals(1, v); Future f = sub.cancel(); f.catchError((e) { // Only the last error ends up here. Expect.equals("ERROR1", e); asyncEnd(); }); }); var addDone = c.addStream(c2.stream); addDone.catchError(fail).whenComplete(asyncEnd); // Error must not go here. c.done.catchError(fail).whenComplete(asyncEnd); // Error must not go here. } void testBroadcastListenAfterClose() { asyncStart(); StreamController c = new StreamController.broadcast(); var f = c.close(); f.then((_) { // Listening after close is allowed. The listener gets a done event. c.stream.listen(null, onDone: asyncEnd); }); } void testBroadcastListenAfterClosePaused() { asyncStart(); StreamController c = new StreamController.broadcast(); var f = c.close(); f.then((_) { // Listening after close is allowed. The listener gets a done event. var sub = c.stream.listen(null, onDone: () { Expect.fail("wrong done"); }); sub.pause(); sub.pause(); new Timer(MS * 100, () { sub.asFuture().whenComplete(() { Expect.fail("Bad complete"); }); sub.resume(); new Timer(MS * 100, () { sub.onDone(asyncEnd); sub.resume(); }); }); }); } void testAsBroadcastListenAfterClose() { asyncStart(); asyncStart(); StreamController c = new StreamController(); Stream s = c.stream.asBroadcastStream(); s.listen(null, onDone: asyncEnd); var f = c.close(); f.then((_) { // Listening after close is allowed. The listener gets a done event. s.listen(null, onDone: asyncEnd); }); } void testAsBroadcastListenAfterClosePaused() { asyncStart(); asyncStart(); StreamController c = new StreamController(); Stream s = c.stream.asBroadcastStream(); s.listen(null, onDone: asyncEnd); var f = c.close(); f.then((_) { // Listening after close is allowed. The listener gets a done event. var sub = s.listen(null, onDone: () { Expect.fail("wrong done"); }); sub.pause(); sub.pause(); new Timer(MS * 100, () { sub.asFuture().whenComplete(() { Expect.fail("Bad complete"); }); sub.resume(); new Timer(MS * 100, () { sub.onDone(asyncEnd); sub.resume(); }); }); }); } void testEventInListen() { asyncStart(); // Regression test for http://dartbug.com/19722 var c; void send() { c.add(1); } int i = 1; c = new StreamController.broadcast(onListen: send, sync: true); c.stream.listen((v) { Expect.equals(i++, v); }, onDone: asyncEnd); c.add(2); c.close(); } void testSyncControllerNotReentrant() { Stream emptyStream = (new StreamController.broadcast()..close()).stream; asyncStart(); for (int listenerCount = 1; listenerCount <= 2; listenerCount++) { StreamController c = new StreamController.broadcast(sync: true); for (int i = 0; i < listenerCount; i++) { asyncStart(); asyncStart(); c.stream.listen((v) { Expect.equals(42, v); Expect.throws(() { c.add(37); }); Expect.throws(() { c.addError(37); }); Expect.throws(() { c.addStream(emptyStream); }); Expect.throws(() { c.close(); }); asyncEnd(); }, onError: (e, s) { Expect.equals(87, e); Expect.throws(() { c.add(37); }); Expect.throws(() { c.addError(37); }); Expect.throws(() { c.addStream(emptyStream); }); Expect.throws(() { c.close(); }); asyncEnd(); }); } c.add(42); c.addError(87); } asyncEnd(); } void testSettingCallbacks() { const int initial = 0; const int running = 1; const int paused = 2; const int canceled = 3; var controller = new StreamController(); var stream = controller.stream; var state = initial; var onListen = () { state = running; }; var onPause = () { state = paused; }; var onResume = () { state = running; }; var onCancel = () { state = canceled; }; Expect.isNull(controller.onListen); Expect.isNull(controller.onPause); Expect.isNull(controller.onResume); Expect.isNull(controller.onCancel); controller ..onListen = onListen ..onPause = onPause ..onResume = onResume ..onCancel = onCancel; Expect.equals(onListen, controller.onListen); Expect.equals(onPause, controller.onPause); Expect.equals(onResume, controller.onResume); Expect.equals(onCancel, controller.onCancel); Expect.equals(initial, state); var sub = stream.listen(null); Expect.equals(running, state); sub.pause(); Expect.equals(paused, state); Expect.isTrue(controller.isPaused); sub.resume(); Expect.equals(running, state); Expect.isFalse(controller.isPaused); var onListen2 = () { state = -running; }; var onPause2 = () { state = -paused; }; var onResume2 = () { state = -running; }; var onCancel2 = () { state = -canceled; }; // Changing them later does make a difference. controller ..onListen = onListen2 ..onPause = onPause2 ..onResume = onResume2 ..onCancel = onCancel2; Expect.equals(onListen2, controller.onListen); Expect.equals(onPause2, controller.onPause); Expect.equals(onResume2, controller.onResume); Expect.equals(onCancel2, controller.onCancel); Expect.equals(running, state); sub.pause(); Expect.equals(-paused, state); Expect.isTrue(controller.isPaused); sub.resume(); Expect.equals(-running, state); Expect.isFalse(controller.isPaused); sub.cancel(); Expect.equals(-canceled, state); } void testSettingNullCallbacks() { failCallback() => fail("Callback should not be called"); var controller = new StreamController( onListen: failCallback, onPause: failCallback, onResume: failCallback, onCancel: failCallback); var stream = controller.stream; Expect.isFalse(controller.hasListener); Expect.isTrue(controller.isPaused); Expect.isNotNull(controller.onListen); controller.onListen = null; Expect.isNull(controller.onListen); var sub = stream.listen(null); Expect.isTrue(controller.hasListener); Expect.isFalse(controller.isPaused); Expect.isNotNull(controller.onPause); controller.onPause = null; Expect.isNull(controller.onPause); sub.pause(); Expect.isTrue(controller.hasListener); Expect.isTrue(controller.isPaused); Expect.isNotNull(controller.onResume); controller.onResume = null; Expect.isNull(controller.onResume); sub.resume(); Expect.isTrue(controller.hasListener); Expect.isFalse(controller.isPaused); Expect.isNotNull(controller.onCancel); controller.onCancel = null; Expect.isNull(controller.onCancel); sub.cancel(); Expect.isFalse(controller.hasListener); Expect.isFalse(controller.isPaused); } void testBroadcastSettingCallbacks() { const int initial = 0; const int running = 1; const int canceled = 2; var controller = new StreamController.broadcast(); var stream = controller.stream; var state = initial; Expect.throwsUnsupportedError(() => controller.onPause = () {}); Expect.throwsUnsupportedError(() => controller.onResume = () {}); controller ..onListen = () { state = running; } ..onCancel = () { state = canceled; }; Expect.equals(initial, state); var sub = stream.listen(null); Expect.equals(running, state); sub.cancel(); Expect.equals(canceled, state); // Changing them later does make a difference. controller ..onListen = () { state = -running; } ..onCancel = () { state = -canceled; }; var sub2 = stream.listen(null); Expect.equals(-running, state); sub2.cancel(); Expect.equals(-canceled, state); } void testBroadcastSettingNullCallbacks() { failCallback() => fail("Callback should not be called"); var controller = new StreamController.broadcast( onListen: failCallback, onCancel: failCallback); var stream = controller.stream; Expect.isFalse(controller.hasListener); controller.onListen = null; var sub = stream.listen(null); Expect.isTrue(controller.hasListener); controller.onCancel = null; sub.cancel(); Expect.isFalse(controller.hasListener); } main() { asyncStart(); testMultiController(); testSingleController(); testExtraMethods(); testClosed(); testCloseFuture(); testCloseFuture2(); testCloseFuture3(); testStreamEquals(); testCancelThrow(); testCancelThrow2(); testCancelThrow3(); testBroadcastListenAfterClose(); testBroadcastListenAfterClosePaused(); testAsBroadcastListenAfterClose(); testAsBroadcastListenAfterClosePaused(); testEventInListen(); testSyncControllerNotReentrant(); testSettingCallbacks(); testSettingNullCallbacks(); testBroadcastSettingCallbacks(); testBroadcastSettingNullCallbacks(); asyncEnd(); }