test_which_muse_script.py
python
sha256:133f9bcf57a62ec7ebf0cd71138b54a200b15989aea2fb2a6912497e2926aa8a
feat(dev-safety): Phase 1 of #185 — audit tool for ambiguou…
Sonnet 5
patch
2 days ago
| 1 | """Tests for scripts/dev/which_muse.py — the Phase 1 binary-classification tool for #185. |
| 2 | |
| 3 | Classifies a resolved ``muse`` executable as editable (source loaded live from a |
| 4 | checkout, e.g. ``pip install -e``) or installed (copied into a venv's |
| 5 | ``site-packages``), without requiring a real pip install for either case in tests. |
| 6 | """ |
| 7 | import json |
| 8 | import sys |
| 9 | import textwrap |
| 10 | from pathlib import Path |
| 11 | |
| 12 | import pytest |
| 13 | |
| 14 | sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts" / "dev")) |
| 15 | from which_muse import classify_muse_binary # noqa: E402 |
| 16 | |
| 17 | # A clean system interpreter with no pre-existing `muse` install of its own — |
| 18 | # using `_CLEAN_INTERPRETER` here would pick up this very checkout's real editable |
| 19 | # install via its PEP 660 meta-path finder (which runs before PYTHONPATH), |
| 20 | # silently defeating the fixture. /usr/bin/python3 has no muse install at all. |
| 21 | _CLEAN_INTERPRETER = "/usr/bin/python3" |
| 22 | |
| 23 | |
| 24 | def _write_fake_package(root: Path, version: str) -> None: |
| 25 | pkg_dir = root / "muse" |
| 26 | pkg_dir.mkdir(parents=True) |
| 27 | (pkg_dir / "__init__.py").write_text(f'__version__ = "{version}"\n') |
| 28 | |
| 29 | |
| 30 | def _write_shim(path: Path, interpreter: str) -> Path: |
| 31 | path.write_text(textwrap.dedent(f"""\ |
| 32 | #!{interpreter} |
| 33 | from muse.cli.app import main |
| 34 | if __name__ == '__main__': |
| 35 | main() |
| 36 | """)) |
| 37 | path.chmod(0o755) |
| 38 | return path |
| 39 | |
| 40 | |
| 41 | class TestClassifyMuseBinary: |
| 42 | def test_editable_source_checkout_is_classified_editable(self, tmp_path: Path) -> None: |
| 43 | repo_root = tmp_path / "ecosystem" / "muse" |
| 44 | _write_fake_package(repo_root, "9.9.9-editable-fixture") |
| 45 | shim = _write_shim(tmp_path / "editable-shim", _CLEAN_INTERPRETER) |
| 46 | |
| 47 | result = classify_muse_binary(str(shim), extra_pythonpath=str(repo_root)) |
| 48 | |
| 49 | assert result["editable"] is True |
| 50 | assert result["version"] == "9.9.9-editable-fixture" |
| 51 | assert str(repo_root) in result["source_root"] |
| 52 | assert "site-packages" not in result["source_root"] |
| 53 | |
| 54 | def test_installed_venv_copy_is_classified_not_editable(self, tmp_path: Path) -> None: |
| 55 | site_packages = tmp_path / "some-venv" / "lib" / "python3.14" / "site-packages" |
| 56 | _write_fake_package(site_packages, "9.9.9-installed-fixture") |
| 57 | shim = _write_shim(tmp_path / "installed-shim", _CLEAN_INTERPRETER) |
| 58 | |
| 59 | result = classify_muse_binary(str(shim), extra_pythonpath=str(site_packages)) |
| 60 | |
| 61 | assert result["editable"] is False |
| 62 | assert result["version"] == "9.9.9-installed-fixture" |
| 63 | assert "site-packages" in result["source_root"] |
| 64 | |
| 65 | def test_resolved_path_follows_symlinks(self, tmp_path: Path) -> None: |
| 66 | repo_root = tmp_path / "ecosystem" / "muse" |
| 67 | _write_fake_package(repo_root, "1.0.0") |
| 68 | real_shim = _write_shim(tmp_path / "real-shim", _CLEAN_INTERPRETER) |
| 69 | symlink = tmp_path / "bin" / "muse" |
| 70 | symlink.parent.mkdir(parents=True) |
| 71 | symlink.symlink_to(real_shim) |
| 72 | |
| 73 | result = classify_muse_binary(str(symlink), extra_pythonpath=str(repo_root)) |
| 74 | |
| 75 | assert result["resolved_path"] == str(real_shim.resolve()) |
| 76 | assert result["path"] == str(symlink) |
| 77 | |
| 78 | def test_missing_muse_package_raises_clear_error(self, tmp_path: Path) -> None: |
| 79 | shim = _write_shim(tmp_path / "broken-shim", _CLEAN_INTERPRETER) |
| 80 | |
| 81 | with pytest.raises(RuntimeError, match="Could not import"): |
| 82 | classify_muse_binary(str(shim), extra_pythonpath=str(tmp_path / "nowhere")) |
| 83 | |
| 84 | def test_env_indirect_shebang_is_resolved(self, tmp_path: Path) -> None: |
| 85 | # `#!/usr/bin/env python3 ...` is a common shim form (e.g. pip-generated |
| 86 | # console scripts) — the interpreter is a token to resolve via PATH, |
| 87 | # not a literal path to exec directly. |
| 88 | repo_root = tmp_path / "ecosystem" / "muse" |
| 89 | _write_fake_package(repo_root, "3.0.0-env-fixture") |
| 90 | shim = tmp_path / "env-shim" |
| 91 | shim.write_text(f"#!/usr/bin/env -S {_CLEAN_INTERPRETER}\nfrom muse.cli.app import main\n") |
| 92 | shim.chmod(0o755) |
| 93 | |
| 94 | result = classify_muse_binary(str(shim), extra_pythonpath=str(repo_root)) |
| 95 | |
| 96 | assert result["editable"] is True |
| 97 | assert result["version"] == "3.0.0-env-fixture" |
| 98 | |
| 99 | def test_non_python_dispatcher_shebang_fails_clearly(self, tmp_path: Path) -> None: |
| 100 | # e.g. pyenv shims are `#!/usr/bin/env bash` dispatchers — not a |
| 101 | # Python interpreter at all. Must fail with a clear, specific error |
| 102 | # rather than an opaque FileNotFoundError from trying to run |
| 103 | # "/usr/bin/env bash" as a single literal path. |
| 104 | shim = tmp_path / "pyenv-style-shim" |
| 105 | shim.write_text("#!/usr/bin/env bash\necho dispatcher\n") |
| 106 | shim.chmod(0o755) |
| 107 | |
| 108 | with pytest.raises(RuntimeError, match="not a Python interpreter"): |
| 109 | classify_muse_binary(str(shim)) |
| 110 | |
| 111 | |
| 112 | class TestWhichMuseCli: |
| 113 | def test_json_output_is_valid_json_with_expected_keys(self, tmp_path: Path, capsys: pytest.CaptureFixture) -> None: |
| 114 | from which_muse import main as which_muse_main |
| 115 | |
| 116 | repo_root = tmp_path / "ecosystem" / "muse" |
| 117 | _write_fake_package(repo_root, "2.0.0-cli-fixture") |
| 118 | shim = _write_shim(tmp_path / "cli-shim", _CLEAN_INTERPRETER) |
| 119 | |
| 120 | exit_code = which_muse_main([ |
| 121 | "--target", str(shim), |
| 122 | "--pythonpath", str(repo_root), |
| 123 | "--json", |
| 124 | ]) |
| 125 | |
| 126 | assert exit_code == 0 |
| 127 | out = json.loads(capsys.readouterr().out) |
| 128 | assert set(out.keys()) >= {"path", "resolved_path", "editable", "version", "source_root"} |
| 129 | assert out["editable"] is True |
| 130 | assert out["version"] == "2.0.0-cli-fixture" |
| 131 | |
| 132 | def test_nonexistent_target_exits_nonzero(self, tmp_path: Path) -> None: |
| 133 | from which_muse import main as which_muse_main |
| 134 | |
| 135 | exit_code = which_muse_main(["--target", str(tmp_path / "does-not-exist"), "--json"]) |
| 136 | |
| 137 | assert exit_code != 0 |
File History
1 commit
sha256:133f9bcf57a62ec7ebf0cd71138b54a200b15989aea2fb2a6912497e2926aa8a
feat(dev-safety): Phase 1 of #185 — audit tool for ambiguou…
Sonnet 5
patch
2 days ago