4a38cb09f5
The underlying subscription would invoke the `onDone` handler even after a `cancel`. It also forgot to register the `onDone` callback in the zone before running it. Tweaked the behavior of `pause` and `resume` to make sure they make no difference after `cancel` or after a done event has been omitted. (Test now checks that the behavior matches other streams.) Fixes #53201 Bug: https://dartbug.com/53201 Change-Id: Iba35be2c4b44b5c4ec97d5a4dbcd3aff7fee8b75 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/320561 Reviewed-by: Aske Simon Christensen <askesc@google.com> Reviewed-by: Nate Bosch <nbosch@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Lasse Nielsen <lrn@google.com> Reviewed-by: Stephen Adams <sra@google.com>
165 lines
4.8 KiB
Dart
165 lines
4.8 KiB
Dart
// 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.
|
|
|
|
// Test empty stream.
|
|
import "package:expect/expect.dart";
|
|
import "dart:async";
|
|
import 'package:async_helper/async_helper.dart';
|
|
|
|
main() async {
|
|
await asyncTest(() async {
|
|
// Is a `const` constructor.
|
|
// Can be called with optional boolean to say whether broadcast or not.
|
|
await asyncTest(() => emptyTest(const Stream<int>.empty(), true));
|
|
await asyncTest(
|
|
() => emptyTest(const Stream<int>.empty(broadcast: true), true));
|
|
await asyncTest(
|
|
() => emptyTest(const Stream<int>.empty(broadcast: false), false));
|
|
|
|
// Check that the behavior is consistent with other empty multi-subscription
|
|
// streams.
|
|
await asyncTest(() => emptyTest(Stream<int>.fromIterable(<int>[]), false));
|
|
await asyncTest(() =>
|
|
emptyTest((StreamController<int>.broadcast()..close()).stream, true));
|
|
await asyncTest(
|
|
() => emptyTest(Stream<int>.multi((c) => c.close()), false));
|
|
});
|
|
await flushMicrotasks();
|
|
}
|
|
|
|
// Function which fails the test if it gets called.
|
|
void unreachable([a, b]) {
|
|
Expect.fail("Unreachable");
|
|
}
|
|
|
|
/// Check that something happens once.
|
|
class Checker {
|
|
bool checked = false;
|
|
void check() {
|
|
if (checked) Expect.fail("Checked more than once");
|
|
checked = true;
|
|
}
|
|
}
|
|
|
|
Future<void> emptyTest(Stream<int> s, bool broadcast) async {
|
|
var checker = Checker();
|
|
// Respects type parameter (not a `Stream<Never>`)
|
|
Expect.notType<Stream<String>>(s);
|
|
|
|
// Has the expected `isBroadcast`.
|
|
Expect.equals(broadcast, s.isBroadcast);
|
|
|
|
StreamSubscription<int> sub =
|
|
s.listen(unreachable, onError: unreachable, onDone: checker.check);
|
|
// Type parameter of subscription repspects stream.
|
|
// Not a `StreamSubscription<Never>`.
|
|
Expect.isFalse(sub is StreamSubscription<String>);
|
|
|
|
Expect.isFalse(sub.isPaused);
|
|
// Doesn't do callback immediately in response to listen.
|
|
Expect.isFalse(checker.checked);
|
|
await flushMicrotasks();
|
|
// Completes eventually, after a microtask.
|
|
Expect.isTrue(checker.checked);
|
|
Expect.isFalse(sub.isPaused);
|
|
|
|
// Can listen more than once, whether broadcast stream or not.
|
|
checker = Checker();
|
|
StreamSubscription<int> sub2 =
|
|
s.listen(unreachable, onError: unreachable, onDone: checker.check);
|
|
// Respects pauses.
|
|
sub2.pause();
|
|
Expect.isTrue(sub2.isPaused);
|
|
sub2.pause();
|
|
Expect.isTrue(sub2.isPaused);
|
|
sub2.pause(Future<int>.value(0));
|
|
Expect.isTrue(sub2.isPaused);
|
|
|
|
await flushMicrotasks();
|
|
Expect.isTrue(sub2.isPaused);
|
|
Expect.isFalse(checker.checked);
|
|
|
|
// Resumes when all pauses resumed.
|
|
sub2.resume();
|
|
Expect.isTrue(sub2.isPaused);
|
|
await flushMicrotasks();
|
|
Expect.isFalse(checker.checked);
|
|
Expect.isTrue(sub2.isPaused);
|
|
|
|
sub2.resume();
|
|
Expect.isFalse(sub2.isPaused);
|
|
await flushMicrotasks();
|
|
Expect.isTrue(checker.checked);
|
|
Expect.isFalse(sub2.isPaused);
|
|
|
|
// Can't pause after done.
|
|
sub2.pause(Future<int>.value(0));
|
|
Expect.isFalse(sub2.isPaused);
|
|
sub2.pause();
|
|
Expect.isFalse(sub2.isPaused);
|
|
|
|
// Respects cancel.
|
|
var sub3 = s.listen(unreachable, onError: unreachable, onDone: unreachable);
|
|
sub3.cancel();
|
|
sub3.onDone(unreachable);
|
|
Expect.isFalse(sub3.isPaused);
|
|
await flushMicrotasks();
|
|
Expect.isFalse(sub3.isPaused);
|
|
|
|
// Can't pause after cancel
|
|
sub3.pause();
|
|
Expect.isFalse(sub3.isPaused);
|
|
sub3.pause(Future<int>.value(0));
|
|
Expect.isFalse(sub3.isPaused);
|
|
// No errors.
|
|
|
|
// Respects cancel while paused.
|
|
var sub4 = s.listen(unreachable, onError: unreachable, onDone: unreachable);
|
|
sub4.pause();
|
|
sub4.cancel();
|
|
sub4.onDone(unreachable);
|
|
await flushMicrotasks();
|
|
sub4.resume();
|
|
await flushMicrotasks();
|
|
|
|
// Check that the stream is zone-aware.
|
|
// Registers onDone callback.
|
|
var log = [];
|
|
late final Zone zone;
|
|
|
|
void callback1() {
|
|
// Run in correct zone.
|
|
Expect.equals(zone, Zone.current);
|
|
log.add("don1");
|
|
}
|
|
|
|
void callback2() {
|
|
// Run in correct zone.
|
|
Expect.equals(zone, Zone.current);
|
|
log.add("don2");
|
|
}
|
|
|
|
zone = Zone.current.fork(
|
|
specification: ZoneSpecification(registerCallback: <R>(s, p, z, f) {
|
|
if (f == callback1) log.add("reg1");
|
|
if (f == callback2) log.add("reg2");
|
|
return p.registerCallback<R>(z, f);
|
|
}, run: <R>(s, p, z, f) {
|
|
if (f == callback1) log.add("run1");
|
|
if (f == callback2) log.add("run2");
|
|
return p.run<R>(z, f);
|
|
}));
|
|
|
|
await zone.run(() async {
|
|
var s = Stream<int>.empty();
|
|
|
|
var sub = s.listen(unreachable, onError: unreachable, onDone: callback1);
|
|
sub.onDone(callback2);
|
|
});
|
|
await flushMicrotasks();
|
|
Expect.listEquals(["reg1", "reg2", "run2", "don2"], log);
|
|
}
|
|
|
|
Future flushMicrotasks() => new Future.delayed(Duration.zero);
|