Skip to content

Refactor TestPlatformDispatcher - #192747

Merged
auto-submit[bot] merged 18 commits into
flutter:masterfrom
victorsanni:test-platform-dispatcher
Oct 13, 2026
Merged

auto-submit[bot] merged 18 commits into
flutter:masterfrom
victorsanni:test-platform-dispatcher

Conversation

@victorsanni

@victorsanni victorsanni commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Refactor TestPlatformDispatcher to properly manage custom view lifecycles.

Fixes a longstanding getter/setter asymmetry bug on TestPlatformDispatcher.onMetricsChanged and onViewFocusChange (where saving/restoring callbacks caused infinite recursion because the getter returned _platformDispatcher's internal handler rather than _onMetricsChanged).

@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 Sep 14, 2026
@victorsanni
victorsanni force-pushed the test-platform-dispatcher branch from 70dd8a4 to dfcdd60 Compare September 14, 2026 17:12
@victorsanni victorsanni added the CICD Run CI/CD label Sep 14, 2026
…n tests

Remove the per-view TestPlatformDispatcher indirection (_forView,
_testValuesOwner, _testValues, _sharingTestValues). With only this PR
applied there is one dispatcher per binding, so _testValues resolved to
"this" everywhere. The indirection exists to serve a later PR and
belongs there.

Keep a custom view that shadows an engine view from rebuilding its
TestFlutterView on every refresh, which discarded the test values set
on it.

Route the teardown unfocus through notifyViewFocusChanged rather than
the wrapped dispatcher's callback field.

Tests: the custom-view test now refreshes the registry through
notifyMetricsChanged instead of calling back the callback it just
registered, which refreshed nothing. The chaining tests read the
previous callback back through the getter, which is what the symmetry
is for. New tests cover the focus bookkeeping, the refresh-before-notify
ordering, and shadowing.

Docs: drop a dangling [debugViewMetricsOverrides] reference and correct
resetWindowingOwner, which does not destroy the previous owner's
windows.
BEHAVIOUR CHANGE: fixes a reproduced view leak in removeTestView.

Window controllers hold the TestFlutterView wrapper that addTestView
returns, and _destroyTestWindowController passes that wrapper to
removeTestView. The staleness check guarding the fallback refresh only
compared against the backing view, so it was always false for that
caller. Normally harmless, because the metrics change refreshes the
registry anyway. But destroying a controller from inside a
metrics-changed notification makes that refresh reentrant, and the
guard drops it by design, so the fallback was the only thing left and
it never fired. The destroyed view then stayed in views and view(id:)
permanently.

Reproduced with a throwaway test before fixing: after a reentrant
destroy the registry still reported the destroyed view. removeTestView
now accepts the wrapper as well as the backing view. A regression test
covers it, mutation-verified in both directions.

The fix is in removeTestView rather than at the call site because the
backing view is not reachable from the controller and _view is private
to window.dart.

Boundary correction, per the repository owner's decision:

TestFlutterView.render and the physicalConstraints getter are reverted
to master's form. Both carried fallbacks that belong to the piece that
introduces debug view metric overrides:

- render used `size ?? _physicalSize`. Provably inert here: the only
  caller, RenderView.compositeFrame at rendering/view.dart:360, always
  passes a non-null size, and no other caller omits it. Its comment
  also forward-referenced debug overrides, which do not exist in this
  piece.
- physicalConstraints fell back through _physicalSize. Reachable only
  by calling resetPhysicalConstraints() without resetPhysicalSize(),
  where it changed behaviour versus master and left
  resetPhysicalConstraints not fully resetting. No test covered it.

Test coverage, both gaps confirmed by mutation first:

- clearAllTestValues dropping custom views was unpinned. Deleting
  _customViews.clear() and the refresh left all 714 tests green.
- The notify: false argument in _TestFlutterView.register was
  unpinned. Removing it left all 94 windowing tests green.

Test hygiene: three tests replaced the shared dispatcher's
onMetricsChanged or onViewFocusChange without restoring it. One never
restored it at all; two restored it mid-test, which an earlier failing
expect would skip. All three now restore through addTearDown.

Gate: dart analyze --fatal-infos clean, dart format 0 changed,
flutter_test 715 passing, flutter test/widgets 7327 passing.

Findings rejected with evidence, for the record:

- "removeTestView leaves a dangling _currentlyFocusedViewId, and
  clearAllTestValues does not clear focus state." Reproduced with a
  probe: nothing throws and the state converges to null. It matches the
  documented contract that currentlyFocusedViewIdTestValue records the
  last focus event rather than querying a live view. The
  clearAllTestValues half is pre-existing master behaviour, untouched
  by this PR.

- "Add @VisibleForTesting to createWindowingOwner." Rejected: the
  method is invoked by WidgetsBinding.initInstances on every production
  app start, so it is not test-only. Adding it was also tried and does
  not produce the diagnostic that was claimed to justify it.

- "The constraints != null fallback in _TestFlutterView is inert
  scaffolding." It is verbatim master code, untouched by this PR.

Noted but deliberately NOT fixed here, as unrelated to this split:
TestPlatformDispatcher's `set onError` is a no-op. It evaluates
_platformDispatcher.onError as a statement and discards it, never
assigning value. That is pre-existing on master and untouched by this
PR; it wants its own change.
@victorsanni
victorsanni marked this pull request as ready for review September 17, 2026 21:15

@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 refactors the initialization and destruction of test window controllers and views within the Flutter test framework. It introduces a createWindowingOwner method to safely initialize windowing owners, and updates TestPlatformDispatcher to better manage custom views, handle reentrant metrics, and track view focus changes. Additionally, a compile-time error was identified in packages/flutter_test/lib/src/binding.dart where _TestFlutterView is instantiated with an incorrect parameter name platformDispatcher instead of _platformDispatcher.

justinmc
justinmc previously approved these changes Sep 28, 2026

@justinmc justinmc 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.

LGTM 👍 . I'm encouraged by the fact that CI is passing. CC @mattkae as this brushes up against windowing.

@victorsanni
victorsanni requested a review from mattkae September 28, 2026 19:33

@mattkae mattkae 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.

A few things, but this looks largely ok!

…eWindowingOwner

- Mark WidgetsBinding.createWindowingOwner @internal and use the shared
  windowing experimental macro, like the rest of the windowing surface.
  @Protected stays: it is an override hook.
- Doc nit: "Only state that a constructor established".
- Move the six tests that exercise flutter_test's own window controller
  doubles into a 'test window controllers' group whose setUp installs a
  test windowing owner and restores the previous one, instead of each
  test repeating that. The owner has to be installed there because the
  binding is constructed (and picks its owner) when the first testWidgets
  is declared, before this file's setUp enables windowing; the stub
  controller tests never notice because they bypass the owner.
- Document that on resetWindowingOwner, and that it is part of the
  experimental windowing support.
…patcher

# Conflicts:
#	packages/flutter/test/widgets/windowing_test.dart
…ists

The binding chooses its windowing owner when it is constructed, which
happens when the first testWidgets in a file is declared, before any
setUp runs. windowing_test.dart enabled windowing in setUp, so the
binding had installed the unsupported owner and the test window
controller tests needed resetWindowingOwner() to swap a test owner in.

Instead, enable windowing at the top of main(), so the binding installs
its test owner itself and the whole file shares it. Remove
resetWindowingOwner() from flutter_test. Because the owner is shared,
every test in the group destroys the windows it creates at teardown
(destroy is idempotent). Document the timing on
registerTestWindowingOwner.
@victorsanni
victorsanni requested a review from mattkae October 13, 2026 00:41
@victorsanni
victorsanni requested a review from justinmc October 13, 2026 00:41

@mattkae mattkae 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.

Cool by me now, thanks for fixing this!

@victorsanni

Copy link
Copy Markdown
Contributor Author

Google testing failures are text goldens failing because of a stacktrace that has different line numbers as a result of the diff in flutter_test/lib/src/binding.dart.

@victorsanni victorsanni added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 13, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Oct 13, 2026
Merged via the queue into flutter:master with commit dd5c509 Oct 13, 2026
23 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 13, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Oct 14, 2026
Roll Flutter from 4249ccbc18a3 to 426be968b5eb (44 revisions)

flutter/flutter@4249ccb...426be96

2026-10-14 [email protected] Allow color delta tolerance in in-memory fuzzy image comparator (flutter/flutter#193937)
2026-10-14 [email protected] [web] Drop stale composing range after text is replaced mid-composition (flutter/flutter#194096)
2026-10-14 [email protected] Roll Skia from 2a2bbea66844 to acde3755d2e2 (1 revision) (flutter/flutter#194376)
2026-10-14 [email protected] Roll Skia from 128c983b12de to 2a2bbea66844 (1 revision) (flutter/flutter#194371)
2026-10-14 [email protected] Roll Skia from 48a294ed4955 to 128c983b12de (3 revisions) (flutter/flutter#194353)
2026-10-14 [email protected] feat(flutter_tools): extract WebDebugConfig from DebuggingOptions (flutter/flutter#194312)
2026-10-14 [email protected] Roll Skia from ea6781c5b6d1 to 48a294ed4955 (2 revisions) (flutter/flutter#194349)
2026-10-14 [email protected] Include <vector> in Android proc_table.h. (flutter/flutter#194325)
2026-10-14 [email protected] Roll Dart SDK from 5933cb486b4a to 6b50116630d0 (28 revisions) (flutter/flutter#194341)
2026-10-14 [email protected] Move GLES3 instancing API entry points to FOR_EACH_IMPELLER_GLES3_PROC (flutter/flutter#194319)
2026-10-14 [email protected] feat(flutter_tools): extract AndroidGradleConfig and move toGradleConfig to AndroidBuildInfo (flutter/flutter#194016)
2026-10-14 [email protected] Roll Skia from ae297bec6012 to ea6781c5b6d1 (8 revisions) (flutter/flutter#194331)
2026-10-14 [email protected] [flutter_tools] Migrate gradle_errors and deferred_components_prebuild_validator away from globals (flutter/flutter#193649)
2026-10-14 [email protected] [Impeller] Cache GLES sampler configuration in RenderPassStateCache (flutter/flutter#193922)
2026-10-14 [email protected] Roll pub packages (flutter/flutter#194332)
2026-10-13 [email protected] [tool] Migrate RunCommand to modular dependency injection (flutter/flutter#190791)
2026-10-13 [email protected] Include `engine/src/flutter/.ci.yaml` in quarterly `goldctl` checklist (flutter/flutter#194316)
2026-10-13 [email protected] [flutter_tools] Remove the last AMD module system support (flutter/flutter#193607)
2026-10-13 [email protected] Roll Skia from 8b3394a60654 to ae297bec6012 (5 revisions) (flutter/flutter#194310)
2026-10-13 [email protected] [web] Remove the a11y placeholder only after the gesture ends (flutter/flutter#194109)
2026-10-13 [email protected] Upgrade goldctl to the latest version (flutter/flutter#193932)
2026-10-13 [email protected] [flutter_tools] Lazily initialize AndroidStudio validation and optimize candidate selection (flutter/flutter#191968)
2026-10-13 [email protected] [flutter_tools] Clean up outdated widget preview TODOs and refactor static state (flutter/flutter#189437)
2026-10-13 [email protected] Roll Skia from e5b5db6d0d6f to 8b3394a60654 (2 revisions) (flutter/flutter#194306)
2026-10-13 [email protected] Redistribute TESTOWNERS for Android team (flutter/flutter#193861)
2026-10-13 [email protected] feat(flutter_tools): introduce Test and Drive specification objects (flutter/flutter#194017)
2026-10-13 [email protected] [Impeller] Keep the aspect ratio when shrinking images to the max texture size (flutter/flutter#194010)
2026-10-13 [email protected] [flutter tool] preserve inherited `DASH__SUPPRESS_ANALYTICS=true` for sub-tools (flutter/flutter#194261)
2026-10-13 [email protected] Roll Packages from d8d9e97 to a1ed23c (9 revisions) (flutter/flutter#194302)
2026-10-13 [email protected] Use PE/COFF AOT snapshots for Windows x64 (flutter/flutter#187594)
2026-10-13 [email protected] Fix Expansible crash when PageStorage holds a non-bool value (flutter/flutter#192195)
2026-10-13 [email protected] Roll Skia from ffeccb3ca58a to e5b5db6d0d6f (1 revision) (flutter/flutter#194297)
2026-10-13 [email protected] Explain why initState() is too early to depend on an inherited widget (flutter/flutter#192727)
2026-10-13 [email protected] Refactor TestPlatformDispatcher (flutter/flutter#192747)
2026-10-13 [email protected] Roll Skia from 3de0984208ec to ffeccb3ca58a (2 revisions) (flutter/flutter#194291)
2026-10-13 [email protected] Roll Skia from 0580c2b780f6 to 3de0984208ec (2 revisions) (flutter/flutter#194281)
2026-10-13 [email protected] Roll Skia from f641d55e07a8 to 0580c2b780f6 (3 revisions) (flutter/flutter#194275)
2026-10-13 [email protected] Roll Skia from 0e5d8d1be81b to f641d55e07a8 (1 revision) (flutter/flutter#194266)
2026-10-13 [email protected] Scaffold view metrics overrides (flutter/flutter#192764)
2026-10-13 [email protected] [web] Add SemanticsRole.listBox and SemanticsRole.option (flutter/flutter#192971)
2026-10-13 [email protected] Pin mac_unopt to macOS 15 (flutter/flutter#194254)
2026-10-13 [email protected] Roll pub packages (flutter/flutter#194257)
2026-10-12 [email protected] Roll Skia from 17f6986c226c to 0e5d8d1be81b (4 revisions) (flutter/flutter#194249)
2026-10-12 [email protected] Make DisplayLists' use of DlPaint, SkPaint, and impeller::Paint agree on the default isAntiAlias (flutter/flutter#193939)

If this roll has caused a breakage, revert this CL and set the roller
...
suryaKommana2662 pushed a commit to suryaKommana2662/flutter that referenced this pull request Oct 29, 2026
Refactor `TestPlatformDispatcher` to properly manage custom view
lifecycles.

Fixes a longstanding getter/setter asymmetry bug on
`TestPlatformDispatcher.onMetricsChanged` and `onViewFocusChange` (where
saving/restoring callbacks caused infinite recursion because the getter
returned `_platformDispatcher`'s internal handler rather than
`_onMetricsChanged`).
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 CICD Run CI/CD framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants