test_binary_separation.py
python
sha256:aed089a7a47a05fb34e98fe7156e80cb34c92a9c63ee5f1b81e75aa989098fe5
test(dev-safety): Phase 2 of #185 — verify muse/muse-dev bi…
Sonnet 5
2 days ago
| 1 | """Phase 2 of #185 (musehub staging): verify `muse` vs `muse-dev` are unambiguous. |
| 2 | |
| 3 | These tests inspect *live machine state* (PATH, installed venvs), not |
| 4 | fixtures — Phase 2 is literally about fixing this developer's environment, |
| 5 | so "red" here means the environment isn't fixed yet, and "green" means it |
| 6 | is. Deliberately does not mutate ~/ecosystem/muse's source to prove |
| 7 | "live-edit reflected" — doing that would touch the very canonical repo this |
| 8 | whole ticket exists to protect. Editable-install classification (via |
| 9 | which_muse.classify_muse_binary) is sufficient proof: an editable install by |
| 10 | definition loads straight from the source tree, so there's nothing further |
| 11 | to demonstrate without risking the object store. |
| 12 | """ |
| 13 | import subprocess |
| 14 | import sys |
| 15 | from pathlib import Path |
| 16 | |
| 17 | sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts" / "dev")) |
| 18 | from which_muse import classify_muse_binary # noqa: E402 |
| 19 | |
| 20 | HOME = Path.home() |
| 21 | |
| 22 | |
| 23 | def _resolve_in_login_shell(command: str) -> str | None: |
| 24 | """Resolve `command` in a fresh login shell, following symlinks. |
| 25 | |
| 26 | `command -v` reports the first PATH match verbatim — if that's a |
| 27 | symlink (as `~/.local/bin/muse` is, by design), the caller needs the |
| 28 | real target to classify, so resolve it here. |
| 29 | """ |
| 30 | proc = subprocess.run( |
| 31 | ["zsh", "-lc", f"command -v {command}"], |
| 32 | capture_output=True, text=True, |
| 33 | ) |
| 34 | out = proc.stdout.strip() |
| 35 | return str(Path(out).resolve()) if out else None |
| 36 | |
| 37 | |
| 38 | class TestStableMuseResolution: |
| 39 | def test_muse_resolves_under_installer_managed_venv(self) -> None: |
| 40 | resolved = _resolve_in_login_shell("muse") |
| 41 | assert resolved is not None, "`muse` does not resolve at all in a login shell" |
| 42 | assert str(HOME / ".local" / "share" / "muse" / "venv") in resolved, ( |
| 43 | f"`muse` resolved to {resolved!r}, expected it under " |
| 44 | f"~/.local/share/muse/venv (the installer-managed venv)" |
| 45 | ) |
| 46 | |
| 47 | def test_muse_never_resolves_under_the_live_source_checkout(self) -> None: |
| 48 | resolved = _resolve_in_login_shell("muse") |
| 49 | assert resolved is not None |
| 50 | assert str(HOME / "ecosystem" / "muse") not in resolved, ( |
| 51 | f"`muse` resolved to {resolved!r} — inside the live source checkout. " |
| 52 | "This is the exact hazard Phase 2 exists to close." |
| 53 | ) |
| 54 | |
| 55 | def test_muse_classifies_as_not_editable(self) -> None: |
| 56 | resolved = _resolve_in_login_shell("muse") |
| 57 | assert resolved is not None |
| 58 | result = classify_muse_binary(resolved) |
| 59 | assert result["editable"] is False |
| 60 | |
| 61 | |
| 62 | class TestDevMuseResolution: |
| 63 | def test_muse_dev_resolves_and_is_editable(self) -> None: |
| 64 | resolved = _resolve_in_login_shell("muse-dev") |
| 65 | assert resolved is not None, "`muse-dev` does not resolve — Phase 2 not yet applied" |
| 66 | result = classify_muse_binary(resolved) |
| 67 | assert result["editable"] is True |
| 68 | assert result["source_root"] == str(HOME / "ecosystem" / "muse") |
| 69 | |
| 70 | def test_muse_and_muse_dev_are_distinct_binaries(self) -> None: |
| 71 | stable = _resolve_in_login_shell("muse") |
| 72 | dev = _resolve_in_login_shell("muse-dev") |
| 73 | assert stable is not None and dev is not None |
| 74 | assert Path(stable).resolve() != Path(dev).resolve() |
| 75 | |
| 76 | |
| 77 | class TestRedundantResolutionsRemoved: |
| 78 | def test_homebrew_muse_shim_is_gone(self) -> None: |
| 79 | assert not Path("/opt/homebrew/bin/muse").exists(), ( |
| 80 | "/opt/homebrew/bin/muse still exists — was a duplicate editable " |
| 81 | "shim pointed at the same live checkout, confirmed unused by " |
| 82 | "brew (not a formula) or anything else before removal." |
| 83 | ) |
| 84 | |
| 85 | def test_pyenv_global_site_packages_no_longer_has_muse_installed(self) -> None: |
| 86 | proc = subprocess.run( |
| 87 | [str(HOME / ".pyenv" / "versions" / "3.14.4" / "bin" / "pip"), "show", "muse"], |
| 88 | capture_output=True, text=True, |
| 89 | ) |
| 90 | assert proc.returncode != 0, ( |
| 91 | "muse is still pip-installed into pyenv's global 3.14.4 site-packages — " |
| 92 | "confirmed unused (no Required-by, no .python-version pins) before removal." |
| 93 | ) |
File History
1 commit
sha256:aed089a7a47a05fb34e98fe7156e80cb34c92a9c63ee5f1b81e75aa989098fe5
test(dev-safety): Phase 2 of #185 — verify muse/muse-dev bi…
Sonnet 5
2 days ago