Files
SnapOtter/packages/ai/python/tests/test_install_feature_move.py
T
SnapOtterandGitHub a731c3d1fe fix: reliable, self-healing AI feature-bundle installs (#472)
Make on-demand AI feature-bundle installs reliable and self-healing, closing
the failure modes behind most "some tool doesn't work" reports.

Multi-bundle installs: tools needing more than one bundle (Passport Photo,
Enhance Faces) install every required bundle from one action and stay
not-installed until all are present. Verified across all 19 AI tools.

Downloads: self-heal the accelerated Hugging Face (Xet) client so an upgraded
venv no longer silently falls back to slow urllib; restart instead of
corrupting a resumed partial when a proxy ignores Range and returns 200;
verify the completed size; fail fast on disk-full and HTTP 4xx; retry
transient errors five times; add hf_transfer fallback and document Xet egress.

Install integrity: crash-atomic venv writes so a killed or out-of-space
install can no longer tear the shared venv and break other tools; a boot
breadcrumb reseeds a torn venv to a clean state automatically; a post-install
smoke import test refuses to record a bundle whose libraries cannot load; an
install watchdog stops a wedged installer that would otherwise hold the venv
writer lock forever.

Adds unit and end-to-end tests for every failure mode above.
2026-07-10 07:32:48 +00:00

135 lines
4.2 KiB
Python

"""Crash-atomicity tests for move_tree, which writes into the SHARED venv.
The invariant under test: a crash (or ENOSPC) partway through move_tree must
never leave a destination entry missing. Because the venv is shared by every AI
tool, a half-replaced package is what tears the venv and breaks unrelated tools.
"""
import errno
import importlib.util
import os
import pytest
def load_installer():
script_path = os.path.join(os.path.dirname(__file__), "..", "install_feature.py")
spec = importlib.util.spec_from_file_location("install_feature_move_under_test", script_path)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module
def test_file_over_file_is_replaced_without_a_delete_window(monkeypatch, tmp_path):
installer = load_installer()
src = tmp_path / "src"
dst = tmp_path / "dst"
src.mkdir()
dst.mkdir()
(dst / "pkg.py").write_text("OLD")
(src / "pkg.py").write_text("NEW")
removed = []
real_remove = os.remove
monkeypatch.setattr(installer.os, "remove", lambda p: removed.append(p) or real_remove(p))
installer.move_tree(str(src), str(dst))
assert (dst / "pkg.py").read_text() == "NEW"
# The old file was atomically replaced, never deleted-then-rewritten.
assert str(dst / "pkg.py") not in removed
def test_crash_mid_move_leaves_every_dest_old_or_new_never_missing(monkeypatch, tmp_path):
installer = load_installer()
src = tmp_path / "src"
dst = tmp_path / "dst"
src.mkdir()
dst.mkdir()
for name in ("a.py", "b.py", "c.py"):
(dst / name).write_text(f"OLD_{name}")
(src / name).write_text(f"NEW_{name}")
real_replace = os.replace
state = {"n": 0}
def flaky_replace(s, d):
state["n"] += 1
if state["n"] == 2:
raise OSError("simulated crash mid-move")
return real_replace(s, d)
monkeypatch.setattr(installer.os, "replace", flaky_replace)
with pytest.raises(OSError):
installer.move_tree(str(src), str(dst))
# Regardless of listdir order, every destination file must still exist and
# hold either its old or its new content -- never a torn/missing entry.
for name in ("a.py", "b.py", "c.py"):
assert (dst / name).exists()
assert (dst / name).read_text() in (f"OLD_{name}", f"NEW_{name}")
def test_merges_directories_and_overwrites_files(tmp_path):
installer = load_installer()
src = tmp_path / "src"
dst = tmp_path / "dst"
(src / "pkg").mkdir(parents=True)
(dst / "pkg").mkdir(parents=True)
(dst / "pkg" / "keep.py").write_text("KEEP")
(src / "pkg" / "keep.py").write_text("UPDATED")
(src / "pkg" / "new.py").write_text("NEW")
installer.move_tree(str(src), str(dst))
assert (dst / "pkg" / "keep.py").read_text() == "UPDATED"
assert (dst / "pkg" / "new.py").read_text() == "NEW"
def test_type_mismatch_dir_replaces_file(tmp_path):
installer = load_installer()
src = tmp_path / "src"
dst = tmp_path / "dst"
src.mkdir()
dst.mkdir()
(dst / "x").write_text("i-am-a-file")
(src / "x").mkdir()
(src / "x" / "inner.py").write_text("dir-content")
installer.move_tree(str(src), str(dst))
assert (dst / "x").is_dir()
assert (dst / "x" / "inner.py").read_text() == "dir-content"
def test_exdev_file_copies_via_temp_then_atomic_replace(monkeypatch, tmp_path):
installer = load_installer()
src = tmp_path / "src"
dst = tmp_path / "dst"
src.mkdir()
dst.mkdir()
(dst / "f.py").write_text("OLD")
(src / "f.py").write_text("NEW")
real_replace = os.replace
seen = {"exdev": False, "part": False}
def exdev_for_direct_move(s, d):
if s == str(src / "f.py"):
seen["exdev"] = True
raise OSError(errno.EXDEV, "cross-device link")
if s.endswith(".part"):
seen["part"] = True
return real_replace(s, d)
monkeypatch.setattr(installer.os, "replace", exdev_for_direct_move)
installer.move_tree(str(src), str(dst))
assert (dst / "f.py").read_text() == "NEW"
assert seen["exdev"] and seen["part"]
# No leftover temp file.
assert not (dst / "f.py.part").exists()