Skip to content

No round-trip coverage: copy.deepcopy / pickle / dataclasses.replace on Vis #63

@gerchowl

Description

@gerchowl

Tier 2 — coverage for things we're correct-by-construction about

The adversarial review noted: `Vis` is currently safe to `copy.deepcopy`,
`pickle.dumps/loads`, and pass through `dataclasses.replace(vis, ...)` —
but none of these paths have test coverage, so a future refactor of
`setattr` / `getstate` / field order could silently break any of
them.

Scenarios to pin

  1. `copy.deepcopy(vis)` — the copy should own its own cache; mutating
    the copy's `source` should not wipe the original's cache.
  2. `pickle.dumps / loads(vis)` — round-tripping through pickle should
    preserve identity + finishes + scalars + cache state. (Pickle currently
    uses `dict.update`, so `setattr` isn't invoked per-field —
    but that's one `reduce` override away from being per-field.)
  3. `dataclasses.replace(vis, source="new")` — currently goes through
    a fresh `init`, so the replaced Vis starts with `_textures={}` /
    `_fetched=False`. Pin this; it's the safe behavior but a refactor
    could regress.

Test sketch

class TestRoundTrips:
    def _populated(self):
        v = Vis(source="ambientcg", material_id="Metal012", tier="1k",
                finishes={"brushed": {"source": "ambientcg", "id": "Metal012"}},
                roughness=0.3, metallic=1.0)
        v._textures = {"color": b"cached"}
        v._fetched = True
        return v

    def test_deepcopy_independent_cache(self):
        import copy
        v = self._populated()
        v2 = copy.deepcopy(v)
        v2.source = "polyhaven"  # triggers cache clear on v2 only
        assert v._textures == {"color": b"cached"}
        assert v2._textures == {}

    def test_pickle_roundtrip_preserves_state(self):
        import pickle
        v = self._populated()
        v2 = pickle.loads(pickle.dumps(v))
        assert v == v2  # equality ignores cache per compare=False
        assert v2._fetched == v._fetched  # but round-trip preserves flag

    def test_dataclasses_replace_starts_unfetched(self):
        import dataclasses
        v = self._populated()
        v2 = dataclasses.replace(v, source="polyhaven")
        assert v2.source == "polyhaven"
        assert v2.material_id == "Metal012"  # preserved
        assert v2._fetched is False
        assert v2._textures == {}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions