"""Tests for scripts/dev/which_muse.py — the Phase 1 binary-classification tool for #185. Classifies a resolved ``muse`` executable as editable (source loaded live from a checkout, e.g. ``pip install -e``) or installed (copied into a venv's ``site-packages``), without requiring a real pip install for either case in tests. """ import json import sys import textwrap from pathlib import Path import pytest sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts" / "dev")) from which_muse import classify_muse_binary # noqa: E402 # A clean system interpreter with no pre-existing `muse` install of its own — # using `_CLEAN_INTERPRETER` here would pick up this very checkout's real editable # install via its PEP 660 meta-path finder (which runs before PYTHONPATH), # silently defeating the fixture. /usr/bin/python3 has no muse install at all. _CLEAN_INTERPRETER = "/usr/bin/python3" def _write_fake_package(root: Path, version: str) -> None: pkg_dir = root / "muse" pkg_dir.mkdir(parents=True) (pkg_dir / "__init__.py").write_text(f'__version__ = "{version}"\n') def _write_shim(path: Path, interpreter: str) -> Path: path.write_text(textwrap.dedent(f"""\ #!{interpreter} from muse.cli.app import main if __name__ == '__main__': main() """)) path.chmod(0o755) return path class TestClassifyMuseBinary: def test_editable_source_checkout_is_classified_editable(self, tmp_path: Path) -> None: repo_root = tmp_path / "ecosystem" / "muse" _write_fake_package(repo_root, "9.9.9-editable-fixture") shim = _write_shim(tmp_path / "editable-shim", _CLEAN_INTERPRETER) result = classify_muse_binary(str(shim), extra_pythonpath=str(repo_root)) assert result["editable"] is True assert result["version"] == "9.9.9-editable-fixture" assert str(repo_root) in result["source_root"] assert "site-packages" not in result["source_root"] def test_installed_venv_copy_is_classified_not_editable(self, tmp_path: Path) -> None: site_packages = tmp_path / "some-venv" / "lib" / "python3.14" / "site-packages" _write_fake_package(site_packages, "9.9.9-installed-fixture") shim = _write_shim(tmp_path / "installed-shim", _CLEAN_INTERPRETER) result = classify_muse_binary(str(shim), extra_pythonpath=str(site_packages)) assert result["editable"] is False assert result["version"] == "9.9.9-installed-fixture" assert "site-packages" in result["source_root"] def test_resolved_path_follows_symlinks(self, tmp_path: Path) -> None: repo_root = tmp_path / "ecosystem" / "muse" _write_fake_package(repo_root, "1.0.0") real_shim = _write_shim(tmp_path / "real-shim", _CLEAN_INTERPRETER) symlink = tmp_path / "bin" / "muse" symlink.parent.mkdir(parents=True) symlink.symlink_to(real_shim) result = classify_muse_binary(str(symlink), extra_pythonpath=str(repo_root)) assert result["resolved_path"] == str(real_shim.resolve()) assert result["path"] == str(symlink) def test_missing_muse_package_raises_clear_error(self, tmp_path: Path) -> None: shim = _write_shim(tmp_path / "broken-shim", _CLEAN_INTERPRETER) with pytest.raises(RuntimeError, match="Could not import"): classify_muse_binary(str(shim), extra_pythonpath=str(tmp_path / "nowhere")) def test_env_indirect_shebang_is_resolved(self, tmp_path: Path) -> None: # `#!/usr/bin/env python3 ...` is a common shim form (e.g. pip-generated # console scripts) — the interpreter is a token to resolve via PATH, # not a literal path to exec directly. repo_root = tmp_path / "ecosystem" / "muse" _write_fake_package(repo_root, "3.0.0-env-fixture") shim = tmp_path / "env-shim" shim.write_text(f"#!/usr/bin/env -S {_CLEAN_INTERPRETER}\nfrom muse.cli.app import main\n") shim.chmod(0o755) result = classify_muse_binary(str(shim), extra_pythonpath=str(repo_root)) assert result["editable"] is True assert result["version"] == "3.0.0-env-fixture" def test_non_python_dispatcher_shebang_fails_clearly(self, tmp_path: Path) -> None: # e.g. pyenv shims are `#!/usr/bin/env bash` dispatchers — not a # Python interpreter at all. Must fail with a clear, specific error # rather than an opaque FileNotFoundError from trying to run # "/usr/bin/env bash" as a single literal path. shim = tmp_path / "pyenv-style-shim" shim.write_text("#!/usr/bin/env bash\necho dispatcher\n") shim.chmod(0o755) with pytest.raises(RuntimeError, match="not a Python interpreter"): classify_muse_binary(str(shim)) class TestWhichMuseCli: def test_json_output_is_valid_json_with_expected_keys(self, tmp_path: Path, capsys: pytest.CaptureFixture) -> None: from which_muse import main as which_muse_main repo_root = tmp_path / "ecosystem" / "muse" _write_fake_package(repo_root, "2.0.0-cli-fixture") shim = _write_shim(tmp_path / "cli-shim", _CLEAN_INTERPRETER) exit_code = which_muse_main([ "--target", str(shim), "--pythonpath", str(repo_root), "--json", ]) assert exit_code == 0 out = json.loads(capsys.readouterr().out) assert set(out.keys()) >= {"path", "resolved_path", "editable", "version", "source_root"} assert out["editable"] is True assert out["version"] == "2.0.0-cli-fixture" def test_nonexistent_target_exits_nonzero(self, tmp_path: Path) -> None: from which_muse import main as which_muse_main exit_code = which_muse_main(["--target", str(tmp_path / "does-not-exist"), "--json"]) assert exit_code != 0