gabriel / muse public
test_which_muse_script.py python
137 lines 5.7 KB
Raw
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