Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions tests/fixtures/wire_contract/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,12 @@ above it: the fixture was generated under `1.0.0`, CI has reproduced it continuo
`1.6.0`, and `1.10.2` was verified to regenerate **all five artifacts byte-identically**
before the pin was raised (2026-08-02). Raising it again does not require regenerating the
fixture — but it does require proving that, the same way.

**Confirmed across a `views_frames` MAJOR (2026-08-21).** The shard emitted through
`views_frames.io.arrow` under **1.10.2** and under **2.0.0** hashes identically, and both
equal the committed fixture (`203650fd…12c54`) — measured in an isolated environment at the
pinned toolchain (pyarrow 16.1.0, numpy 1.26.4), where all 61 frames-dependent tests also
pass at 2.0.0. So the views-frames 2.0.0 adoption (#286) is **not** a fixture re-vendor. It
is blocked only by views-pipeline-core, every published release of which (through 3.1.1)
pins `views-frames <2.0.0`. Recorded so the byte question is not re-opened when that
constraint widens.
99 changes: 99 additions & 0 deletions tests/test_clone_readiness.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

from __future__ import annotations

import ast
import subprocess
import sys
import textwrap
Expand Down Expand Up @@ -103,6 +104,40 @@ def _partner_prefixes() -> tuple[str, ...]:
return tuple(f"views_postprocessing.{name}" for name in _PARTNER_PACKAGES)


def _imported_modules(path: Path, package: str) -> set[str]:
"""Every absolute module name ``path`` imports, relative forms resolved.

Three forms have to survive this, and the module docstring above names two of them
as the misses that made the earlier regex insufficient:

import views_postprocessing.unfao.product
from views_postprocessing.unfao import product
from views_postprocessing import unfao <- the name is on the alias
from ..unfao import product <- the name is in `level`

The last two are why this resolves `level` against the file's own package and joins
each alias onto the module. A first pass at this skipped both and would have passed
a manager importing its sibling relatively — the exact shape `contract/enrichment.py`
once used to demonstrate a real gap.
"""
parts = package.split(".")
found: set[str] = set()
for node in ast.walk(ast.parse(path.read_text())):
if isinstance(node, ast.Import):
found.update(alias.name for alias in node.names)
elif isinstance(node, ast.ImportFrom):
if node.level:
base = parts[: len(parts) - (node.level - 1)]
prefix = ".".join(base + ([node.module] if node.module else []))
else:
prefix = node.module or ""
if not prefix:
continue
found.add(prefix)
found.update(f"{prefix}.{alias.name}" for alias in node.names)
return found


def _modules_on_disk(package: str) -> set[str]:
return {
"views_postprocessing." + f.relative_to(_PKG).with_suffix("").as_posix().replace("/", ".")
Expand Down Expand Up @@ -274,6 +309,70 @@ def test_the_machinery_does_not_pull_in_pipeline_core():
)


@pytest.mark.parametrize("partner", _PARTNER_PACKAGES)
def test_a_partner_does_not_import_its_sibling(partner):
"""The partners are independent, not merely both below the machinery.

Everything else in this file proves the *vertical* arrows of ADR-002 — machinery
imports no partner, invariants import no machinery. Nothing proved the horizontal
one, and it is the arrow that keeps a partner liftable: `crafd/` and `unfao/` are
deliberate clones (register **C-33**), so the realistic violation is a copy-paste
that leaves a sibling's import behind. `test_the_machinery_imports_without_any_partner`
cannot see it — that test imports the machinery, and this would be partner-to-partner.

Two halves, for the reason the module docstring already gives about regexes: the
subprocess is load-bearing and sees transitive arrivals; the source scan is the
supplement, and covers `managers/` — which the subprocess deliberately skips because
importing a manager needs views-pipeline-core, and a purity check should not be
contingent on a heavy framework being installed (C-40 (a)).
"""
siblings = tuple(f"views_postprocessing.{p}" for p in _PARTNER_PACKAGES if p != partner)
if not siblings:
pytest.skip("independence needs a sibling; only one partner is declared")

# "managers" as a package SEGMENT, not a substring: a partner module named
# `managers_shared.py` would otherwise be dropped from this half while also sitting
# outside the AST half below, exempting it from the guard entirely with no signal.
# (`_modules_on_disk` already excludes `__init__.py`, so those arrive via the glob.)
importable = sorted(
m for m in _modules_on_disk(partner) if "managers" not in m.split(".")
)
assert importable, f"no importable modules found for {partner}"

result = _import_in_subprocess(tuple(importable), siblings)
assert result.returncode == 0, (
f"{partner}'s own modules failed to import:\n{result.stderr}"
)
leaked = [m for m in result.stdout.split("LEAKED:")[-1].strip().split(",") if m]
assert not leaked, (
f"{partner} pulled in a sibling partner: {leaked}. The two are deliberate "
"clones (C-33) and must stay liftable one at a time — an import between them "
"means neither can be taken without the other, and no other test here sees it."
)

# IMPORTS only, via the AST — not a substring scan of the file. This repository's
# comments cite module paths constantly (C-33's own text points at
# `unfao/product.py`), so scanning the text would fail on documentation and get
# deleted for crying wolf, which is ADR-014 §3's whole point.
#
# EVERY file under `managers/`, not just `<partner>.py`: `managers/__init__.py`
# carries a real import today, and the subprocess half skips the whole package.
managers = sorted((_PKG / partner / "managers").rglob("*.py"))
assert managers, f"{partner} has no managers/ directory to scan"
for source in managers:
module = "views_postprocessing." + source.relative_to(_PKG).with_suffix("").as_posix().replace("/", ".")
package = module.rsplit(".", 1)[0]
offending = sorted(
name for name in _imported_modules(source, package)
if any(name == sib or name.startswith(sib + ".") for sib in siblings)
)
assert not offending, (
f"{source.relative_to(_REPO)} imports {offending}. These files are copies of "
"each other, so this is the shape a careless clone leaves behind — and it is "
"outside the subprocess half above, which skips managers/."
)


@pytest.mark.parametrize("partner", _PARTNER_PACKAGES)
def test_the_guard_would_actually_catch_a_violation(partner):
"""A purity test that cannot fail is decoration.
Expand Down
Loading