Skip to content

Fix calling endOfStream on a MockStreamHandlerEventSink causing widget tests to hang - #190808

Closed
SamJakob wants to merge 7 commits into
flutter:masterfrom
SamJakob:bugfix/closure-of-mock-stream-handler-event-sink-hangs
Closed

SamJakob wants to merge 7 commits into
flutter:masterfrom
SamJakob:bugfix/closure-of-mock-stream-handler-event-sink-hangs

Conversation

@SamJakob

@SamJakob SamJakob commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

This fixes the following issue:

Calling endOfStream on the MockStreamHandlerEventSink causes the test to hang as there is a tear-down method that closes the StreamController backing the EventStream. When the StreamController is already closed, the close method returns a future that never completes and thus the test hangs.

The regression test serves as a means of reproducing the issue on the current stable/beta versions of Flutter. The first test case (without the close) passes before and after the fix, the latter one times out without the fix.

AI tools were not used in relation to this PR (apart from the gemini-code-assist reviewer).

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@github-actions github-actions Bot added a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels. labels Aug 8, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modifies the teardown callback in TestDefaultBinaryMessenger to conditionally close the StreamController if it is not already closed, and introduces tests to verify that the closure of MockStreamHandlerEventSink does not hang. The feedback suggests simplifying the teardown callback by returning the Future directly instead of using async/await.

Apply PR suggestion.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@Piinks Piinks changed the title Fix calling endOfStream on a MockStreamHandlerEventSink causes widget tests to hang Fix calling endOfStream on a MockStreamHandlerEventSink causing widget tests to hang Aug 11, 2026
@Piinks
Piinks requested a review from victorsanni August 11, 2026 22:15
@SamJakob

Copy link
Copy Markdown
Contributor Author

Thank you for the review @victorsanni - I have accepted/applied your suggestions. :)

@Renzo-Olivares
Renzo-Olivares self-requested a review August 25, 2026 22:49

@Renzo-Olivares Renzo-Olivares left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable to me, though is the real root cause that the returned Future is complete but tagged with the test's Fake async zone which is no longer present during the tearDown phase, rather than it never completes?

final controller = StreamController<Object?>();
addTearDown(controller.close);
addTearDown(() {
if (!controller.isClosed) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Add a comment to this guard detailing why it is needed.

const EventChannel(
kTestEventChannelName,
).receiveBroadcastStream({'close': true}).listen((_) {});
}, timeout: const Timeout(Duration(seconds: 1)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why 1 second?

@Renzo-Olivares Renzo-Olivares added the waiting for response The Flutter team cannot make further progress on this issue until the original reporter responds label Sep 1, 2026
@github-actions

Copy link
Copy Markdown

This pull request is being closed because it has not been updated in the last 21 days after a request for more information.

If you are still working on this, please feel free to reopen it or file a new pull request.

Thanks for your contribution.

@github-actions github-actions Bot closed this Sep 22, 2026
TeddyYeung pushed a commit to TeddyYeung/flutter that referenced this pull request Nov 11, 2026
…r#196110)

Fixes flutter#190807

This continues flutter#190808 by @SamJakob, which was approved and then closed
by the bot while it waited for a reply. It has the same fix, with the
review comments on flutter#190808 applied.

`TestDefaultBinaryMessenger.setMockStreamHandler` adds
`controller.close` as a tear down. If the handler already closed the
stream, for example with `MockStreamHandlerEventSink.endOfStream` in a
`testWidgets` body, tear down calls `close()` again and waits for the
returned future. Tear down then does not finish, and the test times out.

The fix closes the controller in tear down only if it is not closed.

About the question on flutter#190808: yes, the future completes. The zone is
the cause. I checked this with a temporary probe in the tear down (not
part of this PR):
- The done future of the controller completes during the `testWidgets`
body, in the fake async zone.
- In tear down, `isClosed` is true, and `close()` returns the same done
future.
- A `then` callback that tear down adds to this future does not run,
also after 2 seconds of real time.

A completed `_Future` schedules its listeners as microtasks in its own
zone (`_addListener` in `future_impl.dart`). That zone is the fake async
zone, and nothing flushes its microtasks after the body ends. So tear
down waits forever. The comment on the guard says this.

On the other review comments on flutter#190808:
- The guard has a comment that says why it is necessary.
- The test uses a 5 second timeout (not 1 second), so that a hang fails
fast and a slow bot does not make the test flaky.

Tests:
- New regression test in `test_default_binary_messenger_test.dart`.
Without the fix it fails with `TimeoutException after 0:00:05`. With the
fix it passes.
- `packages/flutter_test` tests and `packages/flutter/test/services`
tests pass.


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant in-code documentation (doc comments with
`///`). No public API changed.
- [ ] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed). This PR does not add a feature.
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing. The `flutter_test` and
`flutter/test/services` tests pass locally. The other tests run only in
CI.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: SamJakob <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels. waiting for response The Flutter team cannot make further progress on this issue until the original reporter responds

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Calling endOfStream on a MockStreamHandlerEventSink causes widget tests to hang

3 participants