// 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. // Test Stream.fromIterable. import 'dart:async'; import 'package:async_helper/async_helper.dart'; import 'package:expect/expect.dart'; import 'package:async_helper/async_minitest.dart'; import 'event_helper.dart'; // Tests that various typed iterables that do not throw creates // suitable streams. void iterableTest(Iterable iterable) { asyncStart(); List expected = iterable.toList(); Stream stream = new Stream.fromIterable(iterable); var events = []; stream.listen(events.add, onDone: () { Expect.listEquals(expected, events, "fromIterable $iterable"); asyncEnd(); }); } main() { asyncStart(); iterableTest([]); iterableTest([]); iterableTest([1]); iterableTest([1, "two", true, null]); iterableTest([1, 2, 3, 4]); iterableTest(["one", "two", "three", "four"]); iterableTest(new Iterable.generate(1000, (i) => i)); iterableTest( new Iterable.generate(1000, (i) => i).map((i) => "$i")); Iterable iter = new Iterable.generate(25, (i) => i * 2); { // Test that the stream's .toList works. asyncStart(); new Stream.fromIterable(iter).toList().then((actual) { List expected = iter.toList(); Expect.equals(25, expected.length); Expect.listEquals(expected, actual); asyncEnd(); }); } { // Test that the stream's .map works. asyncStart(); new Stream.fromIterable(iter).map((i) => i * 3).toList().then((actual) { List expected = iter.map((i) => i * 3).toList(); Expect.listEquals(expected, actual); asyncEnd(); }); } { // Test that pause works. asyncStart(); int ctr = 0; var stream = new Stream.fromIterable(iter.map((x) { ctr++; return x; })); late StreamSubscription subscription; var actual = []; subscription = stream.listen((int value) { actual.add(value); // Do a 10 ms pause during the playback of the iterable. Duration duration = const Duration(milliseconds: 10); if (value == 20) { asyncStart(); int beforeCtr = ctr; subscription.pause(new Future.delayed(duration, () {}).whenComplete(() { Expect.equals(beforeCtr, ctr); asyncEnd(); })); } }, onDone: () { Expect.listEquals(iter.toList(), actual); asyncEnd(); }); } { // Test that you can't listen twice.. Stream stream = new Stream.fromIterable(iter); stream.listen((x) {}).cancel(); Expect.throws(() => stream.listen((x) {})); } { // Regression test for http://dartbug.com/14332. // This should succeed. var from = new Stream.fromIterable([1, 2, 3, 4, 5]); var c = new StreamController(); var sink = c.sink; asyncStart(2); // if this goes first, test failed (hanged). Swapping addStream and toList // made failure go away. sink.addStream(from).then((_) { c.close(); asyncEnd(); }); c.stream.toList().then((x) { Expect.listEquals([1, 2, 3, 4, 5], x); asyncEnd(); }); } { // Regression test for issue 14334 (#2) var from = new Stream.fromIterable([1, 2, 3, 4, 5]); // odd numbers as data events, even numbers as error events from = from.map((x) => x.isOdd ? x : throw x); var c = new StreamController(); asyncStart(); var data = [], errors = []; c.stream.listen(data.add, onError: errors.add, onDone: () { Expect.listEquals([1, 3, 5], data); Expect.listEquals([2, 4], errors); asyncEnd(); }); c.addStream(from).then((_) { c.close(); }); } { // Example from issue http://dartbug.com/33431. asyncStart(); var asyncStarStream = () async* { yield 1; yield 2; throw "bad"; }(); collectEvents(asyncStarStream).then((events2) { Expect.listEquals(["value", 1, "value", 2, "error", "bad"], events2); asyncEnd(); }); Iterable throwingIterable() sync* { yield 1; yield 2; throw "bad"; } // Sanity check behavior. var it = throwingIterable().iterator; Expect.isTrue(it.moveNext()); Expect.equals(1, it.current); Expect.isTrue(it.moveNext()); Expect.equals(2, it.current); Expect.throws(it.moveNext, (e) => e == "bad"); asyncStart(); var syncStarStream = new Stream.fromIterable(throwingIterable()); collectEvents(syncStarStream).then((events1) { Expect.listEquals(["value", 1, "value", 2, "error", "bad"], events1); asyncEnd(); }); } { // Test error behavior. Changed when fixing issue 33431. // Iterable where "current" throws for third value, moveNext on fifth call. var m = new MockIterable((n) { return n != 5 || (throw "moveNext"); }, (n) { return n != 3 ? n : throw "current"; }); asyncStart(); collectEvents(new Stream.fromIterable(m)).then((events) { // Error on "current" does not stop iteration. // Error on "moveNext" does. Expect.listEquals([ "value", 1, "value", 2, "error", "current", "value", 4, "error", "moveNext" ], events); asyncEnd(); }); } asyncEnd(); } // Collects value and error events in a list. // Value events preceeded by "value", error events by "error". // Completes on done event. Future> collectEvents(Stream stream) { var c = new Completer>(); var events = []; stream.listen((value) { events..add("value")..add(value); }, onError: (error) { events..add("error")..add(error); }, onDone: () { c.complete(events); }); return c.future; } // Mock iterable. // A `MockIterable(f1, f2)` calls `f1` on `moveNext` calls with incrementing // values starting from 1. Calls `f2` on `current` access, with the same integer // as the most recent `f1` call. class MockIterable extends Iterable { final bool Function(int) _onMoveNext; final T Function(int) _onCurrent; MockIterable(this._onMoveNext, this._onCurrent); Iterator get iterator => MockIterator(_onMoveNext, _onCurrent); } class MockIterator implements Iterator { final bool Function(int) _onMoveNext; final T Function(int) _onCurrent; int _counter = 0; MockIterator(this._onMoveNext, this._onCurrent); bool moveNext() { _counter += 1; return _onMoveNext(_counter); } T get current { return _onCurrent(_counter); } }