Fuzz the fast renderer against the default one, and fix the KeyError it finds - #63
Merged
Merged
Conversation
The fast renderer was tested by running the test suite with REACTON_FAST=1, but those tests were written for the default renderer and rarely hit the cases the fast renderer skips: an element that is the same object in the next render, in a subtree that did not change. This test (from #58) renders random component trees that change shape with their state, sets random state, and checks that both renderers give the same widgets and run the same effects in the same order after every step. Seed 9 fails on master: the fast renderer raises a KeyError, fixed in the next commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In the fast renderer, a child element that is the same object in every render (it comes from outside, e.g. a prop, or it is the root element of a component that did not render again) keeps its previous subtree when nothing in it changed. But when the widget that holds it is replaced by a widget of another type (a VBox becomes an HBox), reconciliation first removes the old subtree, including the child's component contexts. Keeping the child as it was then made the render fail with a KeyError. The default renderer does not have this problem. The fuzz test from the previous commit finds it: on master, 62 of 300 seeds (25 steps each) fail with this KeyError; with this fix all pass. While the render phase walks the children of a widget that replaces a widget of another type, the fast path for unchanged children is now off, so the child is rendered again. Taken from #58 (part of "Skip a child component with equal arguments without walking it"), without the skip itself. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review found two holes: the leak check (the autouse cleanup_guard fixture) only ran for core_test.py, and the widget signature reads traits that a closed widget still has, so a renderer that returned a closed widget would match an open one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The fast renderer (
REACTON_FAST=1) is tested by running the test suite withREACTON_FAST=1. Those tests were written for the default renderer, so they rarely hit the cases the fast renderer skips: an element that is the same object in the next render, in a subtree that did not change. Bugs in those skips went unnoticed.What
Two commits:
reacton/fuzz_test.py, from perf: make fast-renderer updates 10-60x cheaper and fix a state memory leak #58). It renders random component trees that change shape with their state (container type flips, keys, shuffles, fragments, wrappers, effects that set state, caught exceptions) and sets random state. After every step it checks that both renderers give the same widgets and run the same effects in the same order. On master, seed 9 of the 12 fails with aKeyErrorin the fast renderer.KeyError. The default renderer does not.A child element is the same object in two renders when it comes from outside (a prop), or when it is the root element of a component that did not render again.
Tests
KeyError. With the fix, all 300 pass.test_replace_parent_same_child_element: the small case, which fails on master in the fast renderer.pytest reacton/withREACTON_FAST=0and=1: all pass.Review
Crossreview by three reviewers (astra, opus, glm): all three approve the fix.
finally..shared()child, and a replacement in a second render pass.cleanup_guardfixture only ran forcore_test.py), and it compares whether widgets are closed..key()that moves out of a container whose type changes, into a sibling container, in the same render, still gives thisKeyErrorin the fast renderer. It fails the same way on master. The default renderer handles it. The fuzz test cannot find it, because its children never move between containers. This is a follow-up.🤖 Generated with Claude Code