feat: the test suite no longer ships, and dist upgrade deletes what a release stops shipping (#113)
dist export leaves out tools/chemenu/tests/, tools/pytest.ini and tools/.coveragerc by exact path - the suite tests the origin repository, and 257 of its tests failed in a fresh export. dist upgrade now deletes a no-longer-shipped file that is unchanged since install, with any directory that leaves empty, and blocks one changed since install like any local change (--take-release deletes it, --keep-local keeps it). --prune is accepted and ignored. Files changed: - CHANGES.md - EVALS.md - VERSION - instructions/mcp-read-server.md - instructions/upgrade-instance.md - tools/CONTRACT.md - tools/README.md - tools/chemenu/commands/dist_cmd.py - tools/chemenu/tests/test_dist_cmd.py - tools/chemenu/tests/test_dist_upgrade.py - tools/requirements-mcp.txt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
This commit is contained in:
1 parent
dd565d249f
commit
0d3499ab04
11 files changed
+479
-87
No files matched your search
@@ -281,8 +281,34 @@ def test_plan_excludes_coverage_output(repo):
|
||||
assert "tools/.coverage.host.4242" not in plan
|
||||
assert "tools/coverage.xml" not in plan
|
||||
assert not any(relative.startswith("tools/htmlcov/") for relative in plan)
|
||||
# Configuration is machinery and ships, the way pytest.ini does.
|
||||
assert "tools/.coveragerc" in plan
|
||||
# Its configuration is dev-only with the suite it measures - see the next test.
|
||||
assert "tools/.coveragerc" not in plan
|
||||
|
||||
|
||||
def test_plan_excludes_the_test_suite_and_its_configuration_by_exact_path(repo):
|
||||
"""The suite tests this repository, not an instance: in an exported tree it
|
||||
reads templates where it expects this repo's own type-specs and conventions,
|
||||
and a quarter of it fails. It goes by exact path - a directory that merely
|
||||
shares the name `tests` elsewhere under tools/ is machinery and still ships."""
|
||||
tools_dir = repo / "tools"
|
||||
suite = tools_dir / "chemenu" / "tests"
|
||||
(suite / "fixtures" / "sp").mkdir(parents=True)
|
||||
(suite / "__init__.py").write_text("", encoding="utf-8")
|
||||
(suite / "test_something.py").write_text("def test_x():\n pass\n", encoding="utf-8")
|
||||
(suite / "fixtures" / "sp" / "tasks.json").write_text("[]\n", encoding="utf-8")
|
||||
(tools_dir / "pytest.ini").write_text("[pytest]\n", encoding="utf-8")
|
||||
(tools_dir / ".coveragerc").write_text("[run]\n", encoding="utf-8")
|
||||
(tools_dir / "chemenu" / "evals" / "tests").mkdir(parents=True)
|
||||
(tools_dir / "chemenu" / "evals" / "tests" / "keep.py").write_text("", encoding="utf-8")
|
||||
(tools_dir / "chemenu" / "tests.py").write_text("", encoding="utf-8")
|
||||
|
||||
plan = dist_cmd.build_plan()
|
||||
|
||||
assert not any(relative.startswith("tools/chemenu/tests/") for relative in plan)
|
||||
assert "tools/pytest.ini" not in plan
|
||||
assert "tools/.coveragerc" not in plan
|
||||
assert "tools/chemenu/evals/tests/keep.py" in plan
|
||||
assert "tools/chemenu/tests.py" in plan
|
||||
|
||||
|
||||
def test_plan_ships_the_environment_template_but_not_the_filled_file(repo):
|
||||
|
||||
@@ -130,11 +130,17 @@ def test_dry_run_classifies_every_case_and_writes_nothing(instance, tmp_path):
|
||||
assert json.loads((instance / version_mod.RELEASE_STAMP_FILENAME).read_text())["version"] == "1.0.0"
|
||||
|
||||
|
||||
def test_removed_file_is_reported_and_left_alone_without_prune(instance, tmp_path):
|
||||
def test_dry_run_reports_a_retired_file_and_its_emptied_directory_and_keeps_both(
|
||||
instance, tmp_path, capsys
|
||||
):
|
||||
release = _release(tmp_path, "release", "1.1.0", {"AGENTS.md": "core\n"})
|
||||
# tools/wikitool is in the old stamp but absent from the new one.
|
||||
dist_cmd.run_upgrade(release, dry_run=True)
|
||||
|
||||
assert (instance / "tools" / "wikitool").is_file()
|
||||
out = capsys.readouterr().out
|
||||
assert "tools/wikitool" in out
|
||||
assert "tools/" in out.split("Directories left empty")[1]
|
||||
|
||||
|
||||
# --- local changes are never silently overwritten ---------------------------
|
||||
@@ -520,27 +526,182 @@ def test_source_that_does_not_exist_is_refused(instance, tmp_path):
|
||||
dist_cmd.run_upgrade(tmp_path / "nowhere")
|
||||
|
||||
|
||||
# --- prune -------------------------------------------------------------------
|
||||
# --- files the release no longer ships ---------------------------------------
|
||||
#
|
||||
# Decided in the run that sees them go: the new stamp no longer names such a
|
||||
# path, so no later run could tell it from a file the instance wrote itself.
|
||||
|
||||
|
||||
def test_prune_removes_only_removed_files_still_unchanged_since_install(instance, tmp_path):
|
||||
(instance / "extra.txt").write_text("shipped once, edited since\n", encoding="utf-8")
|
||||
old_stamp = json.loads((instance / version_mod.RELEASE_STAMP_FILENAME).read_text())
|
||||
old_stamp["files"]["extra.txt"] = _digest("shipped once, unedited\n") # deliberately stale
|
||||
old_stamp["files"]["gone.txt"] = _digest("also shipped once\n")
|
||||
(instance / version_mod.RELEASE_STAMP_FILENAME).write_text(json.dumps(old_stamp), encoding="utf-8")
|
||||
(instance / "gone.txt").write_text("also shipped once\n", encoding="utf-8")
|
||||
def _record_in_stamp(root: Path, files: dict[str, str]) -> None:
|
||||
"""Add `files` (relative -> content as installed) to the local stamp, and
|
||||
put each on disk with that content."""
|
||||
stamp_path = root / version_mod.RELEASE_STAMP_FILENAME
|
||||
stamp = json.loads(stamp_path.read_text())
|
||||
for relative, content in files.items():
|
||||
path = root / relative
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
stamp["files"][relative] = _digest(content)
|
||||
stamp_path.write_text(json.dumps(stamp), encoding="utf-8")
|
||||
|
||||
|
||||
_CURRENT = {"AGENTS.md": "core\n", "tools/wikitool": "#!/bin/sh\n"}
|
||||
|
||||
|
||||
def test_retired_file_unchanged_since_install_is_deleted_without_any_flag(instance, tmp_path):
|
||||
_record_in_stamp(instance, {"gone.txt": "shipped once\n"})
|
||||
release = _release(tmp_path, "release", "1.1.0", _CURRENT)
|
||||
|
||||
dist_cmd.run_upgrade(release)
|
||||
|
||||
assert not (instance / "gone.txt").exists()
|
||||
assert (instance / "tools" / "wikitool").is_file()
|
||||
|
||||
|
||||
def test_prune_is_still_accepted_and_changes_nothing(instance, tmp_path):
|
||||
_record_in_stamp(instance, {"gone.txt": "shipped once\n"})
|
||||
release = _release(tmp_path, "release", "1.1.0", _CURRENT)
|
||||
|
||||
release = _release(
|
||||
tmp_path, "release", "1.1.0",
|
||||
{"AGENTS.md": "core\n", "tools/wikitool": "#!/bin/sh\n"},
|
||||
)
|
||||
dist_cmd.run_upgrade(release, prune=True)
|
||||
|
||||
# gone.txt matched its recorded digest -> pruned.
|
||||
assert not (instance / "gone.txt").exists()
|
||||
# extra.txt was locally edited relative to its recorded digest -> kept.
|
||||
assert (instance / "extra.txt").read_text(encoding="utf-8") == "shipped once, edited since\n"
|
||||
assert (instance / "AGENTS.md").read_text(encoding="utf-8") == "core\n"
|
||||
|
||||
|
||||
def test_directory_emptied_by_a_retired_file_goes_with_its_bytecode_cache(instance, tmp_path):
|
||||
_record_in_stamp(instance, {
|
||||
"tools/chemenu/tests/__init__.py": "",
|
||||
"tools/chemenu/tests/fixtures/sp/api/tasks.json": "[]\n",
|
||||
})
|
||||
cache = instance / "tools" / "chemenu" / "tests" / "__pycache__"
|
||||
cache.mkdir()
|
||||
(cache / "__init__.cpython-313.pyc").write_bytes(b"\x00bytecode")
|
||||
release = _release(tmp_path, "release", "1.1.0", _CURRENT)
|
||||
|
||||
dist_cmd.run_upgrade(release)
|
||||
|
||||
assert not (instance / "tools" / "chemenu" / "tests").exists()
|
||||
# tools/chemenu held nothing else, so it went too; tools/ still holds wikitool.
|
||||
assert not (instance / "tools" / "chemenu").exists()
|
||||
assert (instance / "tools" / "wikitool").is_file()
|
||||
|
||||
|
||||
def test_a_file_neither_stamp_names_keeps_itself_and_its_directory(instance, tmp_path):
|
||||
_record_in_stamp(instance, {"instructions/retired.md": "old\n"})
|
||||
own = instance / "instructions" / "my-own.md"
|
||||
own.write_text("the instance wrote this\n", encoding="utf-8")
|
||||
stray_cache = instance / "instructions" / "__pycache__"
|
||||
stray_cache.mkdir()
|
||||
(stray_cache / "notes.txt").write_text("not bytecode\n", encoding="utf-8")
|
||||
release = _release(tmp_path, "release", "1.1.0", _CURRENT)
|
||||
|
||||
dist_cmd.run_upgrade(release)
|
||||
|
||||
assert not (instance / "instructions" / "retired.md").exists()
|
||||
assert own.read_text(encoding="utf-8") == "the instance wrote this\n"
|
||||
assert (stray_cache / "notes.txt").is_file()
|
||||
|
||||
|
||||
def test_a_directory_with_a_non_bytecode_cache_entry_is_kept(instance, tmp_path):
|
||||
_record_in_stamp(instance, {"lib/old.py": "x = 1\n"})
|
||||
cache = instance / "lib" / "__pycache__"
|
||||
cache.mkdir()
|
||||
(cache / "old.cpython-313.pyc").write_bytes(b"\x00")
|
||||
(cache / "keep.txt").write_text("someone put this here\n", encoding="utf-8")
|
||||
release = _release(tmp_path, "release", "1.1.0", _CURRENT)
|
||||
|
||||
dist_cmd.run_upgrade(release)
|
||||
|
||||
assert not (instance / "lib" / "old.py").exists()
|
||||
assert (cache / "keep.txt").is_file()
|
||||
|
||||
|
||||
def test_instance_owned_paths_are_never_retired(instance, tmp_path):
|
||||
"""`CHANGES.md` and `kb/log.md` sit in the old stamp because an export seeds
|
||||
them - and belong to the instance from then on, shipped again or not."""
|
||||
_record_in_stamp(instance, {"CHANGES.md": "seeded\n", "kb/log.md": "seeded\n"})
|
||||
release = _release(tmp_path, "release", "1.1.0", _CURRENT)
|
||||
|
||||
dist_cmd.run_upgrade(release)
|
||||
|
||||
assert (instance / "CHANGES.md").read_text(encoding="utf-8") == "seeded\n"
|
||||
assert (instance / "kb" / "log.md").read_text(encoding="utf-8") == "seeded\n"
|
||||
|
||||
|
||||
def test_retired_file_changed_since_install_blocks_and_nothing_is_written(instance, tmp_path):
|
||||
_record_in_stamp(instance, {"gone.txt": "shipped once\n", "also-gone.txt": "same\n"})
|
||||
(instance / "gone.txt").write_text("edited since\n", encoding="utf-8")
|
||||
release = _release(
|
||||
tmp_path, "release", "1.1.0", {**_CURRENT, "types/new.md": "new\n"}
|
||||
)
|
||||
|
||||
with pytest.raises(typer.Exit):
|
||||
dist_cmd.run_upgrade(release)
|
||||
|
||||
assert (instance / "gone.txt").read_text(encoding="utf-8") == "edited since\n"
|
||||
# Neither the unchanged retired file nor a new one was touched by the refusal.
|
||||
assert (instance / "also-gone.txt").is_file()
|
||||
assert not (instance / "types").exists()
|
||||
assert json.loads((instance / version_mod.RELEASE_STAMP_FILENAME).read_text())["version"] == "1.0.0"
|
||||
|
||||
|
||||
def test_keep_local_keeps_a_changed_retired_file(instance, tmp_path):
|
||||
_record_in_stamp(instance, {"gone.txt": "shipped once\n"})
|
||||
(instance / "gone.txt").write_text("edited since\n", encoding="utf-8")
|
||||
release = _release(tmp_path, "release", "1.1.0", _CURRENT)
|
||||
|
||||
dist_cmd.run_upgrade(release, keep_local=True)
|
||||
|
||||
assert (instance / "gone.txt").read_text(encoding="utf-8") == "edited since\n"
|
||||
assert "gone.txt" not in json.loads(
|
||||
(instance / version_mod.RELEASE_STAMP_FILENAME).read_text()
|
||||
)["files"]
|
||||
|
||||
|
||||
def test_take_release_deletes_a_changed_retired_file(instance, tmp_path):
|
||||
_record_in_stamp(instance, {"sub/gone.txt": "shipped once\n"})
|
||||
(instance / "sub" / "gone.txt").write_text("edited since\n", encoding="utf-8")
|
||||
release = _release(tmp_path, "release", "1.1.0", _CURRENT)
|
||||
|
||||
dist_cmd.run_upgrade(release, take_release=["sub/gone.txt"])
|
||||
|
||||
assert not (instance / "sub").exists()
|
||||
|
||||
|
||||
def test_refusal_says_a_kept_retired_file_becomes_the_instances_own(instance, tmp_path, capsys):
|
||||
_record_in_stamp(instance, {"gone.txt": "shipped once\n"})
|
||||
(instance / "gone.txt").write_text("edited since\n", encoding="utf-8")
|
||||
release = _release(tmp_path, "release", "1.1.0", _CURRENT)
|
||||
|
||||
with pytest.raises(typer.Exit):
|
||||
dist_cmd.run_upgrade(release)
|
||||
|
||||
out = " ".join(capsys.readouterr().out.split())
|
||||
assert "no longer shipped" in out
|
||||
assert "taking it deletes it" in out
|
||||
|
||||
|
||||
def test_dry_run_leaves_the_tree_byte_identical(instance, tmp_path):
|
||||
_record_in_stamp(instance, {
|
||||
"gone.txt": "shipped once\n",
|
||||
"deep/er/gone.txt": "also\n",
|
||||
"edited.txt": "shipped\n",
|
||||
})
|
||||
(instance / "edited.txt").write_text("edited\n", encoding="utf-8")
|
||||
before = {
|
||||
p.relative_to(instance).as_posix(): p.read_bytes()
|
||||
for p in sorted(instance.rglob("*")) if p.is_file()
|
||||
}
|
||||
release = _release(tmp_path, "release", "1.1.0", {"AGENTS.md": "core v2\n"})
|
||||
|
||||
dist_cmd.run_upgrade(release, dry_run=True)
|
||||
|
||||
after = {
|
||||
p.relative_to(instance).as_posix(): p.read_bytes()
|
||||
for p in sorted(instance.rglob("*")) if p.is_file()
|
||||
}
|
||||
assert after == before
|
||||
assert (instance / "deep" / "er").is_dir()
|
||||
|
||||
|
||||
# --- tarball sources ----------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user