fix(tests): F2 — the voluptuous mirror compares fields, not marker classes (t35)
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.
This commit is contained in:
1 parent
186e5c1935
commit
7a0152e6b5
2 files changed
+42
-6
No files matched your search
@@ -424,6 +424,37 @@ class TestVoluptuousMirrorContract:
|
|||||||
vol_mirror.declared_fields({CONF_ADB_HOST: str})
|
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:
|
class TestDiscoveryPrefill:
|
||||||
"""F-A / F-A2 / F-D: the discovered address reaches the rendered FORM."""
|
"""F-A / F-A2 / F-D: the discovered address reaches the rendered FORM."""
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ behaviours the flow schemas depend on:
|
|||||||
|
|
||||||
* ``Optional(field, default=value)`` / ``Required(field, default=value)`` carry
|
* ``Optional(field, default=value)`` / ``Required(field, default=value)`` carry
|
||||||
that default on the KEY object;
|
that default on the KEY object;
|
||||||
* keys compare by FIELD NAME only — real ``voluptuous.Marker`` implements
|
* keys compare by FIELD NAME only — real ``voluptuous.Marker.__eq__`` is
|
||||||
``__hash__ = hash(self.schema)`` and ``__eq__`` on class + schema. A later
|
``self.schema == other`` (no class check at all), so
|
||||||
re-declaration of the same field therefore keeps the FIRST key object, so a
|
``Optional("x") == Required("x")`` is True, and ``__hash__`` is
|
||||||
default added by a second declaration is silently dropped.
|
``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
|
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
|
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)
|
return hash(self.schema)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
# real Marker compares class + field name, never the default
|
# real Marker.__eq__ is `self.schema == other`: the FIELD is the whole
|
||||||
return type(other) is type(self) and other.schema == self.schema
|
# 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):
|
def __repr__(self):
|
||||||
return f"<{type(self).__name__} {self.schema!r} default={self.default!r}>"
|
return f"<{type(self).__name__} {self.schema!r} default={self.default!r}>"
|
||||||
|
|||||||
Reference in new issue
Block a user