From 7a0152e6b5c99f75e0efc1d5ed578a20e776388b Mon Sep 17 00:00:00 2001 From: James Mackay Date: Sat, 10 Oct 2026 21:04:34 +0100 Subject: [PATCH] =?UTF-8?q?fix(tests):=20F2=20=E2=80=94=20the=20voluptuous?= =?UTF-8?q?=20mirror=20compares=20fields,=20not=20marker=20classes=20(t35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit real voluptuous 0.16.0 implements ``Marker.__eq__`` as ``self.schema == other`` — a field-name comparison with no class check, so ``Optional("x") == Required("x")`` is True (verified against the library source, not just the audit note). The mirror compared class + field, which kept two dict entries for one field: a ``Required(field, default=...)`` after ``Optional(field)`` LOOKED like it applied the default, while real voluptuous keeps the first (defaultless) key object and silently drops it — the exact t33 F-A failure mode the mirror exists to catch, i.e. the harness could mask the bug it was built to see. ``Marker.__eq__`` is now the library's comparison and the contract tests gained both pins: field-only equality (with equal hashes) and the mixed-marker re-declaration that used to be masked. Reverting only ``__eq__`` makes those two fail, which is the red proof recorded for t35. Verified: uvx --with pytest-asyncio pytest tests -q → 276 passed, 1 skipped; uvx ruff check custom_components tests → clean. --- tests/test_config_flow_ux.py | 31 +++++++++++++++++++++++++++++++ tests/voluptuous_mirror.py | 17 +++++++++++------ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/tests/test_config_flow_ux.py b/tests/test_config_flow_ux.py index 8d94859..11e8c26 100644 --- a/tests/test_config_flow_ux.py +++ b/tests/test_config_flow_ux.py @@ -424,6 +424,37 @@ class TestVoluptuousMirrorContract: vol_mirror.declared_fields({CONF_ADB_HOST: str}) + def test_marker_equality_is_field_only_like_voluptuous(self): + """t35 F2: real ``Marker.__eq__`` is ``self.schema == other``. + + No class check: ``Optional("x") == Required("x")`` on voluptuous 0.16.0. + """ + vol = sys.modules["voluptuous"] + + assert vol.Optional(CONF_ADB_HOST) == vol.Required(CONF_ADB_HOST) + assert hash(vol.Optional(CONF_ADB_HOST)) == hash( + vol.Required(CONF_ADB_HOST) + ) + # the real implementation compares the field name to the other value + assert vol.Optional(CONF_ADB_HOST) == CONF_ADB_HOST + + def test_a_mixed_marker_redeclaration_drops_the_default(self): + """The case a class-comparing mirror masks (t35 F2). + + ``Required(field, default=...)`` after ``Optional(field)`` collides on + the field name, so the FIRST key object (no default) survives — the + schema silently loses the default, exactly like the Optional case. + """ + vol = sys.modules["voluptuous"] + schema = {vol.Optional(CONF_ADB_HOST): str} + schema[vol.Required(CONF_ADB_HOST, default=DISCOVERED_HOST)] = str + + assert ( + vol_mirror.defaults_by_key(schema)[CONF_ADB_HOST] + is vol_mirror.UNDEFINED + ) + + class TestDiscoveryPrefill: """F-A / F-A2 / F-D: the discovered address reaches the rendered FORM.""" diff --git a/tests/voluptuous_mirror.py b/tests/voluptuous_mirror.py index 362cc9b..824f8c9 100644 --- a/tests/voluptuous_mirror.py +++ b/tests/voluptuous_mirror.py @@ -6,10 +6,12 @@ behaviours the flow schemas depend on: * ``Optional(field, default=value)`` / ``Required(field, default=value)`` carry that default on the KEY object; -* keys compare by FIELD NAME only — real ``voluptuous.Marker`` implements - ``__hash__ = hash(self.schema)`` and ``__eq__`` on class + schema. A later - re-declaration of the same field therefore keeps the FIRST key object, so a - default added by a second declaration is silently dropped. +* keys compare by FIELD NAME only — real ``voluptuous.Marker.__eq__`` is + ``self.schema == other`` (no class check at all), so + ``Optional("x") == Required("x")`` is True, and ``__hash__`` is + ``hash(self.schema)``. A later re-declaration of the same field therefore + collides with the FIRST key object, so a default added by a second + declaration is silently dropped. That second property is exactly how the t33 F-A discovery-prefill bug hid from the suite: the old stubs reduced every key to its bare string, so a schema @@ -37,8 +39,11 @@ class Marker: return hash(self.schema) def __eq__(self, other): - # real Marker compares class + field name, never the default - return type(other) is type(self) and other.schema == self.schema + # real Marker.__eq__ is `self.schema == other`: the FIELD is the whole + # comparison — no class check, so Optional("x") == Required("x") (and + # even Marker("x") == "x") is True. That is what makes a re-declaration + # of a field collide with the first key object and drop its default. + return self.schema == other def __repr__(self): return f"<{type(self).__name__} {self.schema!r} default={self.default!r}>"