Files
strukto-ai--mirage/python/tests/test_tests_mirror_sources.py
T
bytecii ca89dc5970 fix(parity): github ref default, NAME_MAX byte budget, browser registry onto node's config shape
Two py<->ts divergences, one structural alignment, one swept-and-gated class
of config noise.

1. GitHub `ref` defaulted to the literal "main" in python where TypeScript
   resolves `config.ref ?? repoInfo.default_branch`, so every repository whose
   default branch is something else 404d on the one tree fetch the mount is
   built on and read as empty. `GitHubConfig.ref` is now optional, and
   `ensure_ref` resolves it lazily beside the existing `ensure_default_branch`
   -- python's constructor deliberately does no network. Every reader that
   needs a concrete ref goes through it. `mirage.accessor.github` is fully
   annotated and leaves mypy's untyped-defs allowlist.

2. `sanitize_label` capped characters where NAME_MAX counts bytes, which the
   constant's own comment already spelled out. gdocs/gsheets/gslides rendered
   ~367-byte filenames for a CJK title and gmail/email ~269; ext4 and APFS
   reject those. `sanitize_label`/`sanitizeLabel` grew a byte budget and every
   compose site passes what the date, id and suffix leave. Found alongside: TS
   email's hand-rolled copy of the sanitizer used JS's ASCII-only `\w` (so
   non-ASCII subjects became underscores there and stayed intact in python),
   and three sites re-composed `<subject>__<id>.<ext>` independently of
   readdir, so a search hit could name a path that does not exist.

3. The browser registry hand-rolled `normalizeFields` per entry and cast
   through 7 `*BrowserCtorConfig` interfaces -- hand-written twins of the real
   config types. It now delegates to a `normalizeXConfig` beside each config,
   as node's registry does; the interfaces and 22 double casts are gone.

4. 95 rename entries that only restated `snakeToCamel` deleted across 24
   files, with `scripts/check_normalize_renames.py` and a CI step to keep them
   from returning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 02:34:03 -07:00

136 lines
5.7 KiB
Python

# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import pathlib
TESTS = pathlib.Path(__file__).resolve().parent
SOURCE = TESTS.parent / "mirage"
IGNORED = {"__pycache__", ".pytest_cache"}
# A test directory earns its place by mirroring a source package, so that a
# reader can go from `mirage/<pkg>/` to `tests/<pkg>/` without guessing. These
# eight mirror nothing on purpose: they hold harnesses and fixture data rather
# than a suite for one source package. Every other test directory must have a
# twin -- `tests/resource/object_storage/` is what this rule is for, because it
# collected 24 suites for 19 sibling packages and made `mirage/resource/s3/`
# look untested.
UNMIRRORED_DIRS = {
"commands/builtin/jq":
"the embedded jq engine spans generic/jq.py and its builder; the "
"suite is grouped per feature, not per module",
"commands/custom":
"exercises the public command-registration API, not a package",
"commands/native":
"the native-binary harness: each case runs a real coreutils binary "
"and diffs mirage against it",
"config":
"loader tests plus the bad-config YAML shared with the TypeScript tree",
"config/fixtures":
"the YAML files themselves",
"conformance":
"runs the cross-language cases under the top-level conformance/ tree",
"e2e":
"end-to-end suites that span the package, so no one source dir owns "
"them",
"fixtures":
"shared test doubles imported by suites elsewhere",
}
# A ratchet, not a target. It counts source modules with no `test_<name>.py`
# anywhere under tests/ -- the weaker of the two readings, because CLAUDE.md
# asks for a mirror only "where reasonable" and several suites deliberately
# cover a family in one file. Moving it in either direction is a deliberate
# act: adding a module with no test raises it, and closing a gap has to be
# locked in by lowering it.
#
# Known blind spot, and the reason this number is a floor rather than a
# measure of coverage: the match is by basename, so seven unrelated
# `test_history.py` files make every `history.py` in the tree look
# mirrored. Reading it strictly -- a test beside its own source twin --
# would count 816 today. What the ratchet buys is narrower than it looks:
# a module whose name appears nowhere in the suite cannot be added
# silently.
MIRROR_BASELINE = 184
def _test_dirs() -> list[pathlib.Path]:
return [
d for d in sorted(TESTS.rglob("*"))
if d.is_dir() and not IGNORED & set(d.parts)
]
def _source_modules() -> list[pathlib.Path]:
return [
p.relative_to(SOURCE) for p in sorted(SOURCE.rglob("*.py"))
if not IGNORED & set(p.parts) and p.name != "__init__.py"
]
def test_tests_declare_no_packages():
"""`tests/` is a namespace tree; pytest imports it without __init__.py.
`pyproject.toml` sets `--import-mode=importlib`, which already resolves
the same-basename collisions (34 different `test_find.py`) that an
`__init__.py` would otherwise be needed for. CLAUDE.md forbids them
outright, and without a check the count reached 87.
"""
found = sorted(
str(p.relative_to(TESTS)) for p in TESTS.rglob("__init__.py")
if not IGNORED & set(p.parts))
assert not found, ("tests/ is a namespace tree -- delete these:\n" +
"\n".join(found))
def test_every_test_directory_mirrors_a_source_package():
"""Each test directory maps to a `mirage/` package, or is exempt above."""
missing = [
rel for rel in (str(d.relative_to(TESTS)) for d in _test_dirs())
if not (SOURCE / rel).is_dir() and rel not in UNMIRRORED_DIRS
]
assert not missing, (
"these test directories mirror no mirage/ package -- move them onto "
"the source layout, or add them to UNMIRRORED_DIRS with a reason:\n" +
"\n".join(f" tests/{rel}" for rel in sorted(missing)))
def test_no_stale_directory_exemption():
"""An exemption outlives its reason silently; make it fail instead."""
stale = []
for rel, reason in UNMIRRORED_DIRS.items():
if not (TESTS / rel).is_dir():
stale.append(f" {rel}: gone ({reason})")
elif (SOURCE / rel).is_dir():
stale.append(f" {rel}: mirage/{rel} exists now ({reason})")
assert not stale, ("drop these entries from UNMIRRORED_DIRS:\n" +
"\n".join(stale))
def test_module_mirror_coverage_holds_the_baseline():
"""Ratchet the number of source modules that no test file is named for."""
named = {p.name for p in TESTS.rglob("test_*.py")}
unmirrored = [
m for m in _source_modules() if f"test_{m.stem}.py" not in named
]
count = len(unmirrored)
if count > MIRROR_BASELINE:
added = "\n".join(f" mirage/{m}" for m in unmirrored[:20])
raise AssertionError(
f"{count} source modules have no test named for them, above the "
f"baseline of {MIRROR_BASELINE}. Add `test_<module>.py` beside "
f"its source twin. Some of the modules counted:\n{added}")
assert count == MIRROR_BASELINE, (
f"mirror coverage improved to {count}; lock it in by setting "
f"MIRROR_BASELINE = {count}")