shin / tests /scripts /test_installers.py
LastNoob's picture
Simplify README around customer workflows (#1345)
58059be unverified
Raw
History Blame Contribute Delete
59.8 kB
import os
import shutil
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
import pytest
FCC_COMMANDS = (
"fcc-desktop",
"fcc-server",
"fcc-claude",
"fcc-codex",
"fcc-pi",
"fcc-init",
"free-claude-code",
)
def _repo_root() -> Path:
return Path(__file__).resolve().parents[2]
def _write_executable(path: Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
path.chmod(0o755)
def _braced_body(text: str, declaration: str) -> str:
start = text.index(declaration)
brace_start = text.index("{", start)
depth = 0
for index, char in enumerate(text[brace_start:], start=brace_start):
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return text[brace_start + 1 : index]
raise AssertionError(f"Unclosed function body for {declaration}")
def _posix_command(name: str) -> str:
help_output = (
' echo " --extension, -e <path> Load an extension"\n'
' echo " --models <patterns> Scope models"'
if name == "pi"
else " :"
)
return f"""#!/bin/sh
echo "{name}:$*" >> "$CALL_LOG"
if [ "$FAIL_STEP" = "{name}-verify" ]; then
exit 31
fi
if [ "${{1:-}}" = "--version" ]; then
echo "{name} 1.0.0"
fi
if [ "${{1:-}}" = "--help" ]; then
{help_output}
fi
"""
def _posix_npm_command() -> str:
return """#!/bin/sh
echo "npm:$*" >> "$CALL_LOG"
if [ "${1:-}" = "prefix" ] && [ "${2:-}" = "-g" ]; then
printf '%s\n' "$FAKE_NPM_PREFIX"
exit 0
fi
if [ "${1:-}" = "config" ] && [ "${2:-}" = "get" ] && [ "${3:-}" = "prefix" ]; then
printf '%s\n' "$FAKE_NPM_PREFIX"
exit 0
fi
exit 71
"""
def _posix_uv_command(version: str) -> str:
return f"""#!/bin/sh
echo "uv:$*" >> "$CALL_LOG"
if [ "${{1:-}}" = "--version" ]; then
if [ "${{FCC_RUNNING_PHASE:-}}" = "late" ]; then
: > "$FCC_PROCESS_MARKER"
fi
if [ "$FAIL_STEP" = "uv-verify" ]; then
exit 32
fi
echo "uv {version}"
exit 0
fi
if [ "${{1:-}}" = "tool" ] && [ "${{2:-}}" = "install" ]; then
if [ "$FAIL_STEP" = "fcc-install" ]; then
exit 33
fi
mkdir -p "$FAKE_TOOL_BIN"
cp "$FAKE_FIXTURES/fcc-command.sh" "$FAKE_TOOL_BIN/fcc-server"
cp "$FAKE_FIXTURES/fcc-command.sh" "$FAKE_TOOL_BIN/fcc-desktop"
cp "$FAKE_FIXTURES/fcc-command.sh" "$FAKE_TOOL_BIN/fcc-claude"
cp "$FAKE_FIXTURES/fcc-command.sh" "$FAKE_TOOL_BIN/fcc-pi"
if [ "$FAIL_STEP" != "fcc-missing" ]; then
cp "$FAKE_FIXTURES/fcc-command.sh" "$FAKE_TOOL_BIN/fcc-codex"
fi
chmod +x "$FAKE_TOOL_BIN"/fcc-*
exit 0
fi
if [ "${{1:-}}" = "tool" ] && [ "${{2:-}}" = "update-shell" ]; then
if [ "$FAIL_STEP" = "path-update" ]; then
exit 34
fi
exit 0
fi
if [ "${{1:-}}" = "tool" ] && [ "${{2:-}}" = "dir" ] && [ "${{3:-}}" = "--bin" ]; then
printf '%s\n' "$FAKE_TOOL_BIN"
exit 0
fi
exit 35
"""
@dataclass
class PosixHarness:
root: Path
bin_dir: Path
fixtures: Path
tool_bin: Path
log: Path
env: dict[str, str]
def add_client(self, name: str) -> None:
_write_executable(self.bin_dir / name, _posix_command(name))
def add_unrelated_pi(self) -> None:
_write_executable(self.bin_dir / "pi", _posix_command("unrelated-pi"))
def add_npm_prefix(self, prefix: Path) -> None:
prefix.mkdir(parents=True)
self.env["FAKE_NPM_PREFIX"] = str(prefix)
_write_executable(self.bin_dir / "npm", _posix_npm_command())
def add_uv(self, version: str) -> None:
_write_executable(self.bin_dir / "uv", _posix_uv_command(version))
def use_process_list_fallback(self, process_line: str) -> None:
fallback_bin = self.root / "fallback-bin"
fallback_bin.mkdir()
_write_executable(
fallback_bin / "ps",
"""#!/bin/sh
printf '%s\n' "$FCC_PS_OUTPUT"
""",
)
awk = shutil.which("awk", path=self.env["PATH"])
if awk is None:
pytest.skip("awk is required for the POSIX process fallback scenario")
shutil.copy2(awk, fallback_bin / "awk")
self.env["FCC_PS_OUTPUT"] = process_line
self.env["PATH"] = str(fallback_bin)
def run(self, *args: str, fail_step: str = "") -> subprocess.CompletedProcess[str]:
env = self.env | {"FAIL_STEP": fail_step}
return subprocess.run(
["/bin/sh", str(_repo_root() / "scripts" / "install.sh"), *args],
check=False,
capture_output=True,
text=True,
env=env,
)
def run_interactive(
self,
answers: str,
*args: str,
fail_step: str = "",
) -> subprocess.CompletedProcess[str]:
import errno
import pty
import select
import time
env = self.env | {
"FAIL_STEP": fail_step,
"FCC_INSTALLER": str(_repo_root() / "scripts" / "install.sh"),
}
command = [
"/bin/sh",
"-c",
'cat "$FCC_INSTALLER" | /bin/sh -s -- "$@"',
"fcc-installer",
*args,
]
fork = vars(pty)["fork"]
wait_without_blocking = vars(os)["WNOHANG"]
child_pid, master_fd = fork()
if child_pid == 0:
os.execve(command[0], command, env)
output = bytearray()
status: int | None = None
deadline = time.monotonic() + 30
try:
os.write(master_fd, answers.encode())
while status is None:
readable, _, _ = select.select([master_fd], [], [], 0.1)
if readable:
try:
output.extend(os.read(master_fd, 65536))
except OSError as error:
if error.errno != errno.EIO:
raise
waited_pid, wait_status = os.waitpid(child_pid, wait_without_blocking)
if waited_pid == child_pid:
status = wait_status
elif time.monotonic() >= deadline:
os.kill(child_pid, 9)
os.waitpid(child_pid, 0)
raise AssertionError("Interactive installer did not finish")
while True:
readable, _, _ = select.select([master_fd], [], [], 0)
if not readable:
break
try:
output.extend(os.read(master_fd, 65536))
except OSError as error:
if error.errno == errno.EIO:
break
raise
finally:
os.close(master_fd)
assert status is not None
return subprocess.CompletedProcess(
command,
os.waitstatus_to_exitcode(status),
output.decode(errors="replace"),
"",
)
def calls(self) -> list[str]:
if not self.log.exists():
return []
return self.log.read_text(encoding="utf-8").splitlines()
@pytest.fixture
def posix_harness(tmp_path: Path) -> PosixHarness:
if os.name == "nt":
pytest.skip("POSIX installer scenarios run on POSIX hosts")
bin_dir = tmp_path / "bin"
fixtures = tmp_path / "fixtures"
tool_bin = tmp_path / "tool-bin"
home = tmp_path / "home"
log = tmp_path / "calls.log"
for path in (bin_dir, fixtures, tool_bin, home):
path.mkdir(parents=True)
_write_executable(
bin_dir / "pgrep",
"""#!/bin/sh
[ -n "${FCC_RUNNING_COMMAND:-}" ] || exit 1
if [ "${FCC_RUNNING_PHASE:-early}" = "late" ] && [ ! -e "$FCC_PROCESS_MARKER" ]; then
exit 1
fi
case "$*" in
*"$FCC_RUNNING_COMMAND"*) printf '4242\n'; exit 0 ;;
*) exit 1 ;;
esac
""",
)
_write_executable(
bin_dir / "curl",
"""#!/bin/sh
url=""
output=""
while [ "$#" -gt 0 ]; do
case "$1" in
-o)
shift
output=$1
;;
http*)
url=$1
;;
esac
shift
done
echo "download:$url" >> "$CALL_LOG"
case "$url:$FAIL_STEP" in
*claude.ai*:claude-download|*chatgpt.com*:codex-download|*pi.dev*:pi-download|*astral.sh*:uv-download)
exit 41
;;
esac
case "$url" in
*claude.ai*) source="$FAKE_FIXTURES/claude-installer.sh" ;;
*chatgpt.com*) source="$FAKE_FIXTURES/codex-installer.sh" ;;
*pi.dev*) source="$FAKE_FIXTURES/pi-installer.sh" ;;
*astral.sh*) source="$FAKE_FIXTURES/uv-installer.sh" ;;
*) exit 42 ;;
esac
cp "$source" "$output"
""",
)
_write_executable(
fixtures / "claude-installer.sh",
"""#!/bin/sh
echo "claude-install" >> "$CALL_LOG"
[ "$FAIL_STEP" = "claude-install" ] && exit 21
mkdir -p "$HOME/.local/bin"
cp "$FAKE_FIXTURES/claude-command.sh" "$HOME/.local/bin/claude"
chmod +x "$HOME/.local/bin/claude"
""",
)
_write_executable(
fixtures / "codex-installer.sh",
"""#!/bin/sh
echo "codex-install:$CODEX_NON_INTERACTIVE" >> "$CALL_LOG"
[ "$FAIL_STEP" = "codex-install" ] && exit 22
mkdir -p "$HOME/.local/bin"
cp "$FAKE_FIXTURES/codex-command.sh" "$HOME/.local/bin/codex"
chmod +x "$HOME/.local/bin/codex"
""",
)
_write_executable(
fixtures / "pi-installer.sh",
"""#!/bin/sh
echo "pi-install" >> "$CALL_LOG"
[ "$FAIL_STEP" = "pi-install" ] && exit 24
[ "$FAIL_STEP" = "pi-skip" ] && exit 0
if [ -n "${FAKE_NPM_PREFIX:-}" ]; then
pi_bin="$FAKE_NPM_PREFIX/bin"
else
pi_bin="$HOME/.local/bin"
fi
mkdir -p "$pi_bin"
cp "$FAKE_FIXTURES/pi-command.sh" "$pi_bin/pi"
chmod +x "$pi_bin/pi"
""",
)
_write_executable(
fixtures / "uv-installer.sh",
"""#!/bin/sh
echo "uv-install" >> "$CALL_LOG"
[ "$FAIL_STEP" = "uv-install" ] && exit 23
mkdir -p "$HOME/.local/bin"
cp "$FAKE_FIXTURES/uv-command.sh" "$HOME/.local/bin/uv"
chmod +x "$HOME/.local/bin/uv"
""",
)
_write_executable(fixtures / "claude-command.sh", _posix_command("claude"))
_write_executable(fixtures / "codex-command.sh", _posix_command("codex"))
_write_executable(fixtures / "pi-command.sh", _posix_command("pi"))
_write_executable(fixtures / "uv-command.sh", _posix_uv_command("0.11.28"))
_write_executable(
fixtures / "fcc-command.sh",
"""#!/bin/sh
name=${0##*/}
echo "$name:$*" >> "$CALL_LOG"
if [ "$FAIL_STEP" = "fcc-verify" ]; then
exit 36
fi
if [ "$name" = "fcc-desktop" ] && [ "${1:-}" = "--export-icon" ]; then
[ "$FAIL_STEP" = "desktop-icon-export" ] && exit 37
mkdir -p "$(dirname "$2")"
printf 'fake icon\n' > "$2"
fi
if [ "$name" = "fcc-server" ] && [ "${1:-}" = "--version" ]; then
echo "free-claude-code 3.5.18"
fi
""",
)
_write_executable(
bin_dir / "uname",
"""#!/bin/sh
printf '%s\n' "${FAKE_UNAME:-Linux}"
""",
)
env = os.environ.copy()
env.update(
{
"PATH": f"{bin_dir}:/usr/bin:/bin",
"HOME": str(home),
"CALL_LOG": str(log),
"FAKE_FIXTURES": str(fixtures),
"FAKE_TOOL_BIN": str(tool_bin),
"FCC_PROCESS_MARKER": str(tmp_path / "fcc-process-ready"),
"FCC_RUNNING_COMMAND": "",
"FCC_RUNNING_PHASE": "early",
"FAKE_UNAME": "Linux",
"FAIL_STEP": "",
}
)
env.pop("XDG_BIN_HOME", None)
return PosixHarness(tmp_path, bin_dir, fixtures, tool_bin, log, env)
def test_install_sh_fresh_install_is_verified(posix_harness: PosixHarness) -> None:
result = posix_harness.run()
assert result.returncode == 0, result.stderr
assert "Free Claude Code is installed and verified." in result.stdout
calls = posix_harness.calls()
assert calls.index("claude-install") < calls.index("claude:--version")
assert calls.index("codex-install:1") < calls.index("codex:--version")
assert calls.index("pi-install") < calls.index("pi:--version")
assert calls.index("uv-install") < calls.index("uv:--version")
assert any(
call.startswith(
"uv:tool install --force --refresh-package free-claude-code "
"--python 3.14.0 free-claude-code @ "
"https://github.com/Alishahryar1/free-claude-code/archive/refs/heads/main.zip"
)
for call in calls
)
assert not any(call.startswith("git:") for call in calls)
assert calls[-3:] == [
"uv:tool update-shell",
"uv:tool dir --bin",
"fcc-server:--version",
]
def test_install_sh_reprompts_then_installs_only_selected_agent(
posix_harness: PosixHarness,
) -> None:
result = posix_harness.run_interactive("n\nn\nn\nn\ny\nn\n")
assert result.returncode == 0, result.stdout
assert "Select at least one coding agent." in result.stdout
assert "Run Codex with: fcc-codex" in result.stdout
assert "Run Claude Code with: fcc-claude" not in result.stdout
assert "Run Pi with: fcc-pi" not in result.stdout
calls = posix_harness.calls()
assert "codex-install:1" in calls
assert not any("claude.ai" in call for call in calls)
assert not any("pi.dev" in call for call in calls)
def test_install_sh_rejects_uninstalled_only_selection(
posix_harness: PosixHarness,
) -> None:
result = posix_harness.run_interactive("n\nn\ny\n", fail_step="pi-skip")
assert result.returncode != 0
assert "No selected coding agent was installed." in result.stdout
assert not any("astral.sh" in call for call in posix_harness.calls())
def test_install_sh_creates_native_macos_app_and_desktop_link(
posix_harness: PosixHarness,
) -> None:
posix_harness.env["FAKE_UNAME"] = "Darwin"
tool_bin = posix_harness.root / "tool's bin"
posix_harness.env["FAKE_TOOL_BIN"] = str(tool_bin)
result = posix_harness.run()
assert result.returncode == 0, result.stderr
app = posix_harness.root / "home" / "Applications" / "Free Claude Code.app"
plist = app / "Contents" / "Info.plist"
owner_file = app / "Contents" / ".free-claude-code-owner"
launcher = app / "Contents" / "MacOS" / "fcc-desktop"
icon = app / "Contents" / "Resources" / "AppIcon.icns"
desktop_link = posix_harness.root / "home" / "Desktop" / "Free Claude Code.app"
assert owner_file.read_text(encoding="utf-8").strip() == (
"io.github.alishahryar1.free-claude-code"
)
plist_text = plist.read_text(encoding="utf-8")
assert "<key>CFBundleIconFile</key>" in plist_text
assert "<string>AppIcon</string>" in plist_text
assert "<key>LSUIElement</key>" in plist_text
assert "<key>LSMultipleInstancesProhibited</key>" in plist_text
assert icon.read_bytes() == b"fake icon\n"
assert launcher.stat().st_mode & 0o111
expected_command = str(tool_bin / "fcc-desktop").replace("'", "'\\''")
assert f"exec '{expected_command}'" in launcher.read_text(encoding="utf-8")
assert desktop_link.is_symlink()
assert desktop_link.readlink() == app
assert any(
call == f"fcc-desktop:--export-icon {icon}" for call in posix_harness.calls()
)
def test_install_sh_stops_if_macos_icon_export_fails(
posix_harness: PosixHarness,
) -> None:
posix_harness.env["FAKE_UNAME"] = "Darwin"
result = posix_harness.run(fail_step="desktop-icon-export")
assert result.returncode != 0
assert "Command failed with exit code 37" in result.stderr
assert not (
posix_harness.root / "home" / "Desktop" / "Free Claude Code.app"
).exists()
def test_install_sh_rejects_unowned_macos_app_bundle(
posix_harness: PosixHarness,
) -> None:
posix_harness.env["FAKE_UNAME"] = "Darwin"
app = posix_harness.root / "home" / "Applications" / "Free Claude Code.app"
contents = app / "Contents"
contents.mkdir(parents=True)
plist = contents / "Info.plist"
plist.write_text("foreign app", encoding="utf-8")
result = posix_harness.run()
assert result.returncode != 0
assert "not managed by Free Claude Code" in result.stderr
assert plist.read_text(encoding="utf-8") == "foreign app"
assert not (contents / ".free-claude-code-owner").exists()
def test_install_sh_preserves_unrelated_macos_desktop_link(
posix_harness: PosixHarness,
) -> None:
posix_harness.env["FAKE_UNAME"] = "Darwin"
desktop = posix_harness.root / "home" / "Desktop"
desktop.mkdir()
unrelated = posix_harness.root / "Unrelated.app"
unrelated.mkdir()
desktop_link = desktop / "Free Claude Code.app"
desktop_link.symlink_to(unrelated, target_is_directory=True)
result = posix_harness.run()
assert result.returncode == 0, result.stderr
assert "non-FCC link" in result.stdout
assert desktop_link.readlink() == unrelated
@pytest.mark.parametrize("uv_version", ("0.11.16", "0.11.16+build.1"))
def test_install_sh_preserves_valid_existing_tools(
posix_harness: PosixHarness,
uv_version: str,
) -> None:
posix_harness.add_client("claude")
posix_harness.add_client("codex")
posix_harness.add_client("pi")
posix_harness.add_uv(uv_version)
result = posix_harness.run()
assert result.returncode == 0, result.stderr
assert not any(call.startswith("download:") for call in posix_harness.calls())
assert "leaving it unchanged" in result.stdout
def test_install_sh_replaces_unrelated_pi_command(
posix_harness: PosixHarness,
) -> None:
posix_harness.add_client("claude")
posix_harness.add_client("codex")
posix_harness.add_unrelated_pi()
posix_harness.add_uv("0.11.16")
result = posix_harness.run()
assert result.returncode == 0, result.stderr
assert "is not Pi Coding Agent; installing Pi" in result.stdout
assert "pi-install" in posix_harness.calls()
def test_install_sh_discovers_custom_pi_npm_prefix(
posix_harness: PosixHarness,
) -> None:
posix_harness.add_client("claude")
posix_harness.add_client("codex")
posix_harness.add_npm_prefix(posix_harness.root / "custom-npm")
posix_harness.add_uv("0.11.16")
result = posix_harness.run()
assert result.returncode == 0, result.stderr
calls = posix_harness.calls()
assert "npm:prefix -g" in calls
assert "pi:--help" in calls
assert "pi:--version" in calls
def test_install_sh_continues_when_pi_is_not_installed(
posix_harness: PosixHarness,
) -> None:
result = posix_harness.run(fail_step="pi-skip")
assert result.returncode == 0, result.stderr
assert "Pi was not installed; continuing without it." in result.stdout
assert "Run Pi with: fcc-pi" not in result.stdout
calls = posix_harness.calls()
assert "pi-install" in calls
assert not any(call.startswith("pi:") for call in calls)
assert "uv-install" in calls
assert "fcc-server:--version" in calls
def test_install_sh_continues_when_unrelated_pi_is_unchanged(
posix_harness: PosixHarness,
) -> None:
posix_harness.add_unrelated_pi()
result = posix_harness.run(fail_step="pi-skip")
assert result.returncode == 0, result.stderr
assert "Pi was not installed; continuing without it." in result.stdout
assert "Run Pi with: fcc-pi" not in result.stdout
calls = posix_harness.calls()
assert "unrelated-pi:--help" in calls
assert "unrelated-pi:--version" not in calls
assert "fcc-server:--version" in calls
def test_install_sh_continues_when_pi_resolution_changes_to_unrelated_command(
posix_harness: PosixHarness,
) -> None:
posix_harness.add_unrelated_pi()
npm_prefix = posix_harness.root / "custom-npm"
posix_harness.add_npm_prefix(npm_prefix)
_write_executable(
npm_prefix / "bin" / "pi",
_posix_command("other-unrelated-pi"),
)
result = posix_harness.run(fail_step="pi-skip")
assert result.returncode == 0, result.stderr
assert "Pi was not installed; continuing without it." in result.stdout
assert "Run Pi with: fcc-pi" not in result.stdout
calls = posix_harness.calls()
assert "other-unrelated-pi:--help" in calls
assert "other-unrelated-pi:--version" not in calls
assert "fcc-server:--version" in calls
def test_install_sh_replaces_obsolete_uv(posix_harness: PosixHarness) -> None:
posix_harness.add_client("claude")
posix_harness.add_client("codex")
posix_harness.add_client("pi")
posix_harness.add_uv("0.5.9")
result = posix_harness.run()
assert result.returncode == 0, result.stderr
assert "uv 0.5.9 does not satisfy stable >=0.11.16" in result.stdout
assert "uv-install" in posix_harness.calls()
@pytest.mark.parametrize("version", ("0.11.16-alpha.1", "0.12.0-rc.1"))
def test_install_sh_replaces_prerelease_uv(
posix_harness: PosixHarness,
version: str,
) -> None:
posix_harness.add_client("claude")
posix_harness.add_client("codex")
posix_harness.add_client("pi")
posix_harness.add_uv(version)
result = posix_harness.run()
assert result.returncode == 0, result.stderr
assert f"uv {version} does not satisfy stable >=0.11.16" in result.stdout
assert "uv-install" in posix_harness.calls()
@pytest.mark.parametrize(
"failure",
[
"claude-download",
"claude-install",
"claude-verify",
"codex-download",
"codex-install",
"codex-verify",
"pi-download",
"pi-install",
"pi-verify",
"uv-download",
"uv-install",
"uv-verify",
"fcc-install",
"path-update",
"fcc-missing",
"fcc-verify",
],
)
def test_install_sh_stops_without_success_on_each_failure(
posix_harness: PosixHarness,
failure: str,
) -> None:
result = posix_harness.run(fail_step=failure)
assert result.returncode != 0
assert "Free Claude Code is installed and verified." not in result.stdout
forbidden = {
"claude-download": "claude-install",
"claude-install": "claude:--version",
"claude-verify": "chatgpt.com",
"codex-download": "codex-install",
"codex-install": "codex:--version",
"codex-verify": "pi.dev",
"pi-download": "pi-install",
"pi-install": "pi:--version",
"pi-verify": "astral.sh",
"uv-download": "uv-install",
"uv-install": "uv:--version",
"uv-verify": "uv:tool install",
"fcc-install": "uv:tool update-shell",
"path-update": "uv:tool dir --bin",
"fcc-missing": "fcc-server:--version",
}.get(failure)
if forbidden is not None:
assert not any(forbidden in call for call in posix_harness.calls())
def test_install_sh_dry_run_never_executes_commands(
posix_harness: PosixHarness,
) -> None:
result = posix_harness.run("--dry-run")
assert result.returncode == 0, result.stderr
assert posix_harness.calls() == []
assert "Dry run complete. No changes were made." in result.stdout
assert "Free Claude Code is installed and verified." not in result.stdout
def test_install_sh_rejects_broken_existing_client_without_replacing_it(
posix_harness: PosixHarness,
) -> None:
posix_harness.add_client("claude")
result = posix_harness.run(fail_step="claude-verify")
assert result.returncode != 0
assert not any(call.startswith("download:") for call in posix_harness.calls())
def test_install_sh_rejects_unparseable_existing_uv(
posix_harness: PosixHarness,
) -> None:
posix_harness.add_client("claude")
posix_harness.add_client("codex")
posix_harness.add_client("pi")
posix_harness.add_uv("not-a-version")
result = posix_harness.run()
assert result.returncode != 0
assert not any("astral.sh" in call for call in posix_harness.calls())
def test_install_sh_voice_flags_only_change_fcc_spec(
posix_harness: PosixHarness,
) -> None:
result = posix_harness.run("--voice-all", "--torch-backend", "cu130")
assert result.returncode == 0, result.stderr
assert any(
"--torch-backend cu130 free-claude-code[voice,voice_local] @ "
"https://github.com/Alishahryar1/free-claude-code/archive/refs/heads/main.zip"
in call
for call in posix_harness.calls()
)
def test_install_sh_rejects_invalid_options_before_mutation(
posix_harness: PosixHarness,
) -> None:
result = posix_harness.run("--torch-backend", "cu130")
assert result.returncode != 0
assert posix_harness.calls() == []
@pytest.mark.parametrize("command_name", FCC_COMMANDS)
def test_install_sh_rejects_running_fcc_before_mutation(
posix_harness: PosixHarness,
command_name: str,
) -> None:
posix_harness.env["FCC_RUNNING_COMMAND"] = command_name
result = posix_harness.run()
assert result.returncode != 0
assert posix_harness.calls() == []
assert f"{command_name} (PID 4242)" in result.stderr
def test_install_sh_rechecks_for_fcc_process_before_tool_replacement(
posix_harness: PosixHarness,
) -> None:
posix_harness.add_client("claude")
posix_harness.add_client("codex")
posix_harness.add_client("pi")
posix_harness.add_uv("0.11.16")
posix_harness.env["FCC_RUNNING_COMMAND"] = "fcc-server"
posix_harness.env["FCC_RUNNING_PHASE"] = "late"
result = posix_harness.run()
assert result.returncode != 0
assert "fcc-server (PID 4242)" in result.stderr
assert not any(call.startswith("uv:tool install") for call in posix_harness.calls())
def test_install_sh_ignores_similarly_named_process(
posix_harness: PosixHarness,
) -> None:
posix_harness.env["FCC_RUNNING_COMMAND"] = "fcc-server-helper"
result = posix_harness.run()
assert result.returncode == 0, result.stderr
@pytest.mark.parametrize(
("command_name", "process_args"),
(
("free-claude-code", "/home/user/.local/bin/free-claude-code"),
("fcc-server", "/usr/bin/python3 /home/user/.local/bin/fcc-server"),
),
)
def test_install_sh_process_fallback_reads_full_command_line(
posix_harness: PosixHarness,
command_name: str,
process_args: str,
) -> None:
posix_harness.use_process_list_fallback(f"4242 {process_args}")
result = posix_harness.run()
assert result.returncode != 0
assert posix_harness.calls() == []
assert f"{command_name} (PID 4242)" in result.stderr
def _powershells() -> tuple[str, ...]:
candidates = (shutil.which("pwsh"), shutil.which("powershell"))
return tuple(dict.fromkeys(path for path in candidates if path is not None))
def test_install_ps1_waits_for_gui_icon_export() -> None:
installer = (_repo_root() / "scripts" / "install.ps1").read_text(encoding="utf-8")
body = _braced_body(installer, "function Export-FccDesktopIcon")
assert "Start-Process" in body
assert "-WindowStyle Hidden" in body
assert "-Wait" in body
assert "-PassThru" in body
assert "$process.ExitCode" in body
@pytest.mark.parametrize(
"powershell",
_powershells() or (None,),
ids=lambda path: Path(path).name if path is not None else "unavailable",
)
def test_install_ps1_gui_icon_export_completes_before_returning(
powershell: str | None,
tmp_path: Path,
) -> None:
if powershell is None or os.name != "nt":
pytest.skip("PowerShell GUI process behavior runs on Windows hosts")
installer = (_repo_root() / "scripts" / "install.ps1").read_text(encoding="utf-8")
function_declarations = (
"function Format-Argument",
"function Format-Command",
"function Export-FccDesktopIcon",
)
functions = "\n".join(
f"{declaration} {{{_braced_body(installer, declaration)}}}"
for declaration in function_declarations
)
installed_desktop_command = Path(sys.executable).with_name("fcc-desktop.exe")
desktop_command = tmp_path / "icon-exporter.exe"
shutil.copy2(installed_desktop_command, desktop_command)
destination = tmp_path / "profile with spaces" / ".fcc" / "app-icon.ico"
env = os.environ | {
"FCC_TEST_DESKTOP_COMMAND": str(desktop_command),
"FCC_TEST_ICON_PATH": str(destination),
}
script = "\n".join(
(
'$ErrorActionPreference = "Stop"',
"$DryRun = $false",
functions,
(
"Export-FccDesktopIcon "
"-DesktopCommand $env:FCC_TEST_DESKTOP_COMMAND "
"-IconPath $env:FCC_TEST_ICON_PATH"
),
)
)
completed = subprocess.run(
[powershell, "-NoProfile", "-Command", script],
check=False,
capture_output=True,
text=True,
env=env,
)
assert completed.returncode == 0, completed.stderr
assert (
destination.read_bytes()
== (
_repo_root() / "src" / "free_claude_code" / "assets" / "app-icon.ico"
).read_bytes()
)
def _create_windows_shortcut(
powershell: str,
shortcut_path: Path,
target_path: Path,
) -> None:
shortcut_path.parent.mkdir(parents=True, exist_ok=True)
env = os.environ | {
"FCC_TEST_SHORTCUT": str(shortcut_path),
"FCC_TEST_TARGET": str(target_path),
}
subprocess.run(
[
powershell,
"-NoProfile",
"-Command",
(
"$shell = New-Object -ComObject WScript.Shell; "
"$shortcut = $shell.CreateShortcut($env:FCC_TEST_SHORTCUT); "
"$shortcut.TargetPath = $env:FCC_TEST_TARGET; "
"$shortcut.Save()"
),
],
check=True,
capture_output=True,
text=True,
env=env,
)
def _windows_shortcut_icon(powershell: str, shortcut_path: Path) -> str:
env = os.environ | {"FCC_TEST_SHORTCUT": str(shortcut_path)}
completed = subprocess.run(
[
powershell,
"-NoProfile",
"-Command",
(
"$shell = New-Object -ComObject WScript.Shell; "
"$shortcut = $shell.CreateShortcut($env:FCC_TEST_SHORTCUT); "
"[Console]::Out.Write($shortcut.IconLocation)"
),
],
check=True,
capture_output=True,
text=True,
env=env,
)
return completed.stdout
def _batch_client(name: str) -> str:
help_output = (
"echo --extension, -e ^<path^> Load an extension\n"
"echo --models ^<patterns^> Scope models"
if name == "pi"
else "rem no product help"
)
return f"""@echo off
echo {name}:%*>>"%CALL_LOG%"
if "%FAIL_STEP%"=="{name}-verify" exit /b 51
if "%1"=="--version" echo {name} 1.0.0
if "%1"=="--help" (
{help_output}
)
exit /b 0
"""
def _batch_npm() -> str:
return r"""@echo off
echo npm:%*>>"%CALL_LOG%"
if "%1"=="prefix" if "%2"=="-g" echo %FAKE_NPM_PREFIX%& exit /b 0
if "%1"=="config" if "%2"=="get" if "%3"=="prefix" echo %FAKE_NPM_PREFIX%& exit /b 0
exit /b 71
"""
def _batch_uv(version: str) -> str:
return rf"""@echo off
echo uv:%*>>"%CALL_LOG%"
if "%1"=="--version" goto version
if "%1"=="tool" if "%2"=="install" goto install
if "%1"=="tool" if "%2"=="update-shell" goto update_shell
if "%1"=="tool" if "%2"=="dir" if "%3"=="--bin" goto tool_bin
exit /b 59
:version
if "%FCC_RUNNING_PHASE%"=="late" type nul > "%FCC_PROCESS_MARKER%"
if "%FAIL_STEP%"=="uv-verify" exit /b 52
echo uv {version}
exit /b 0
:install
if "%FAIL_STEP%"=="fcc-install" exit /b 53
if not exist "%FAKE_TOOL_BIN%" mkdir "%FAKE_TOOL_BIN%"
copy /y "%FAKE_FIXTURES%\fcc-command.cmd" "%FAKE_TOOL_BIN%\fcc-server.cmd" >nul
copy /y "%FAKE_FIXTURES%\fcc-command.cmd" "%FAKE_TOOL_BIN%\fcc-desktop.cmd" >nul
copy /y "%FAKE_FIXTURES%\fcc-command.cmd" "%FAKE_TOOL_BIN%\fcc-claude.cmd" >nul
copy /y "%FAKE_FIXTURES%\fcc-command.cmd" "%FAKE_TOOL_BIN%\fcc-pi.cmd" >nul
if not "%FAIL_STEP%"=="fcc-missing" copy /y "%FAKE_FIXTURES%\fcc-command.cmd" "%FAKE_TOOL_BIN%\fcc-codex.cmd" >nul
exit /b 0
:update_shell
if "%FAIL_STEP%"=="path-update" exit /b 54
exit /b 0
:tool_bin
echo %FAKE_TOOL_BIN%
exit /b 0
"""
@dataclass
class PowerShellHarness:
root: Path
bin_dir: Path
fixtures: Path
tool_bin: Path
log: Path
env: dict[str, str]
powershell: str
wrapper: Path
def add_client(self, name: str) -> None:
_write_executable(self.bin_dir / f"{name}.cmd", _batch_client(name))
def add_unrelated_pi(self) -> None:
_write_executable(self.bin_dir / "pi.cmd", _batch_client("unrelated-pi"))
def add_npm_prefix(self, prefix: Path) -> None:
prefix.mkdir(parents=True)
self.env["FAKE_NPM_PREFIX"] = str(prefix)
_write_executable(self.bin_dir / "npm.cmd", _batch_npm())
def add_uv(self, version: str) -> None:
_write_executable(self.bin_dir / "uv.cmd", _batch_uv(version))
def run(self, *args: str, fail_step: str = "") -> subprocess.CompletedProcess[str]:
env = self.env | {"FAIL_STEP": fail_step}
return subprocess.run(
[
self.powershell,
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
str(self.wrapper),
*args,
],
check=False,
capture_output=True,
text=True,
env=env,
)
def calls(self) -> list[str]:
if not self.log.exists():
return []
return self.log.read_text(encoding="utf-8").splitlines()
@pytest.fixture(
params=_powershells() or (None,),
ids=lambda path: Path(path).name if path is not None else "unavailable",
)
def powershell_harness(
tmp_path: Path,
request: pytest.FixtureRequest,
) -> PowerShellHarness:
powershell = request.param
if powershell is None or os.name != "nt":
pytest.skip("PowerShell installer scenarios run on Windows hosts")
bin_dir = tmp_path / "bin"
fixtures = tmp_path / "fixtures"
tool_bin = tmp_path / "tool-bin"
home = tmp_path / "home"
local_app_data = tmp_path / "local-app-data"
app_data = tmp_path / "app-data"
log = tmp_path / "calls.log"
for path in (bin_dir, fixtures, tool_bin, home, local_app_data, app_data):
path.mkdir(parents=True)
(fixtures / "claude-command.cmd").write_text(
_batch_client("claude"), encoding="utf-8"
)
(fixtures / "codex-command.cmd").write_text(
_batch_client("codex"), encoding="utf-8"
)
(fixtures / "pi-command.cmd").write_text(_batch_client("pi"), encoding="utf-8")
(fixtures / "uv-command.cmd").write_text(_batch_uv("0.11.28"), encoding="utf-8")
(fixtures / "fcc-command.cmd").write_text(
"""@echo off
for %%I in ("%~f0") do set "FCC_NAME=%%~nI"
echo %FCC_NAME%:%*>>"%CALL_LOG%"
if "%FAIL_STEP%"=="fcc-verify" exit /b 55
if "%FCC_NAME%"=="fcc-desktop" if "%1"=="--export-icon" if "%FAIL_STEP%"=="desktop-icon-export" exit /b 56
if "%FCC_NAME%"=="fcc-desktop" if "%1"=="--export-icon" (
if not exist "%~dp2" mkdir "%~dp2"
echo fake icon>"%~2"
)
if "%FCC_NAME%"=="fcc-server" if "%1"=="--version" echo free-claude-code 3.5.18
exit /b 0
""",
encoding="utf-8",
)
(fixtures / "claude-installer.ps1").write_text(
r"""if ($env:FAIL_STEP -eq "claude-install") { exit 61 }
$bin = Join-Path $env:USERPROFILE ".local\bin"
New-Item -ItemType Directory -Force -Path $bin | Out-Null
Copy-Item (Join-Path $env:FAKE_FIXTURES "claude-command.cmd") (Join-Path $bin "claude.cmd") -Force
Add-Content -LiteralPath $env:CALL_LOG -Value "claude-install"
""",
encoding="utf-8",
)
(fixtures / "codex-installer.ps1").write_text(
r"""if ($env:FAIL_STEP -eq "codex-install") { exit 62 }
$bin = Join-Path $env:LOCALAPPDATA "Programs\OpenAI\Codex\bin"
New-Item -ItemType Directory -Force -Path $bin | Out-Null
Copy-Item (Join-Path $env:FAKE_FIXTURES "codex-command.cmd") (Join-Path $bin "codex.cmd") -Force
Add-Content -LiteralPath $env:CALL_LOG -Value "codex-install:$env:CODEX_NON_INTERACTIVE"
""",
encoding="utf-8",
)
(fixtures / "pi-installer.ps1").write_text(
r"""if ($env:FAIL_STEP -eq "pi-install") { exit 64 }
if ($env:FAIL_STEP -eq "pi-skip") {
Add-Content -LiteralPath $env:CALL_LOG -Value "pi-install"
return
}
$bin = if ($env:FAKE_NPM_PREFIX) { $env:FAKE_NPM_PREFIX } else { Join-Path $env:APPDATA "npm" }
New-Item -ItemType Directory -Force -Path $bin | Out-Null
Copy-Item (Join-Path $env:FAKE_FIXTURES "pi-command.cmd") (Join-Path $bin "pi.cmd") -Force
Add-Content -LiteralPath $env:CALL_LOG -Value "pi-install"
""",
encoding="utf-8",
)
(fixtures / "uv-installer.ps1").write_text(
r"""if ($env:FAIL_STEP -eq "uv-install") { exit 63 }
$bin = Join-Path $env:USERPROFILE ".local\bin"
New-Item -ItemType Directory -Force -Path $bin | Out-Null
Copy-Item (Join-Path $env:FAKE_FIXTURES "uv-command.cmd") (Join-Path $bin "uv.cmd") -Force
Add-Content -LiteralPath $env:CALL_LOG -Value "uv-install"
""",
encoding="utf-8",
)
wrapper = tmp_path / "run-installer.ps1"
wrapper.write_text(
"""Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
function Invoke-RestMethod {
[CmdletBinding()]
param([string] $Uri, [string] $OutFile)
Add-Content -LiteralPath $env:CALL_LOG -Value "download:$Uri"
if (
($env:FAIL_STEP -eq "claude-download" -and $Uri.Contains("claude.ai")) -or
($env:FAIL_STEP -eq "codex-download" -and $Uri.Contains("chatgpt.com")) -or
($env:FAIL_STEP -eq "pi-download" -and $Uri.Contains("pi.dev")) -or
($env:FAIL_STEP -eq "uv-download" -and $Uri.Contains("astral.sh"))
) {
throw "simulated download failure"
}
if ($Uri.Contains("claude.ai")) {
$source = Join-Path $env:FAKE_FIXTURES "claude-installer.ps1"
}
elseif ($Uri.Contains("chatgpt.com")) {
$source = Join-Path $env:FAKE_FIXTURES "codex-installer.ps1"
}
elseif ($Uri.Contains("pi.dev")) {
$source = Join-Path $env:FAKE_FIXTURES "pi-installer.ps1"
}
elseif ($Uri.Contains("astral.sh")) {
$source = Join-Path $env:FAKE_FIXTURES "uv-installer.ps1"
}
else {
throw "unexpected installer URL: $Uri"
}
Copy-Item -LiteralPath $source -Destination $OutFile -Force
}
function Get-Process {
[CmdletBinding()]
param([string[]] $Name)
if ([string]::IsNullOrWhiteSpace($env:FCC_RUNNING_COMMAND)) {
return
}
if (
$env:FCC_RUNNING_PHASE -eq "late" -and
-not (Test-Path -LiteralPath $env:FCC_PROCESS_MARKER)
) {
return
}
foreach ($requestedName in $Name) {
if ($requestedName -eq $env:FCC_RUNNING_COMMAND) {
[pscustomobject] @{ Id = 4242; ProcessName = $requestedName }
}
}
}
$installer = [scriptblock]::Create([IO.File]::ReadAllText($env:FCC_INSTALLER))
& $installer @args
""",
encoding="utf-8",
)
system_root = os.environ["SYSTEMROOT"]
env = os.environ.copy()
env.update(
{
"PATH": os.pathsep.join(
[str(bin_dir), str(Path(system_root) / "System32"), system_root]
),
"PATHEXT": ".COM;.EXE;.BAT;.CMD",
"USERPROFILE": str(home),
"LOCALAPPDATA": str(local_app_data),
"APPDATA": str(app_data),
"CALL_LOG": str(log),
"FAKE_FIXTURES": str(fixtures),
"FAKE_TOOL_BIN": str(tool_bin),
"FCC_INSTALLER": str(_repo_root() / "scripts" / "install.ps1"),
"FCC_PROCESS_MARKER": str(tmp_path / "fcc-process-ready"),
"FCC_RUNNING_COMMAND": "",
"FCC_RUNNING_PHASE": "early",
"FAIL_STEP": "",
}
)
return PowerShellHarness(
tmp_path, bin_dir, fixtures, tool_bin, log, env, powershell, wrapper
)
def test_install_ps1_fresh_install_is_verified(
powershell_harness: PowerShellHarness,
) -> None:
result = powershell_harness.run()
assert result.returncode == 0, result.stderr
assert "Free Claude Code is installed and verified." in result.stdout
calls = powershell_harness.calls()
assert calls.index("claude-install") < calls.index("claude:--version")
assert calls.index("codex-install:1") < calls.index("codex:--version")
assert calls.index("pi-install") < calls.index("pi:--version")
assert calls.index("uv-install") < calls.index("uv:--version")
assert any(
call.startswith(
"uv:tool install --force --refresh-package free-claude-code "
"--python cpython-3.14.0-windows-x86_64-none "
'"free-claude-code @ '
'https://github.com/Alishahryar1/free-claude-code/archive/refs/heads/main.zip"'
)
for call in calls
)
assert not any(call.startswith("git:") for call in calls)
assert calls[-4:-1] == [
"uv:tool update-shell",
"uv:tool dir --bin",
"fcc-server:--version",
]
home = Path(powershell_harness.env["USERPROFILE"])
app_data = Path(powershell_harness.env["APPDATA"])
icon = home / ".fcc" / "app-icon.ico"
assert icon.read_text(encoding="utf-8").strip() == "fake icon"
assert calls[-1] == f'fcc-desktop:--export-icon "{icon}"'
desktop_shortcut = home / "Desktop" / "Free Claude Code.lnk"
assert desktop_shortcut.is_file()
assert (
_windows_shortcut_icon(
powershell_harness.powershell,
desktop_shortcut,
)
== f"{icon},0"
)
assert (
app_data
/ "Microsoft"
/ "Windows"
/ "Start Menu"
/ "Programs"
/ "Free Claude Code.lnk"
).is_file()
def test_install_ps1_stops_if_windows_icon_export_fails(
powershell_harness: PowerShellHarness,
) -> None:
result = powershell_harness.run(fail_step="desktop-icon-export")
assert result.returncode != 0
assert "Command failed with exit code 56" in result.stderr
home = Path(powershell_harness.env["USERPROFILE"])
assert not (home / "Desktop" / "Free Claude Code.lnk").exists()
def test_install_ps1_preserves_unowned_desktop_shortcut(
powershell_harness: PowerShellHarness,
) -> None:
desktop_shortcut = (
Path(powershell_harness.env["USERPROFILE"]) / "Desktop" / "Free Claude Code.lnk"
)
unrelated_target = powershell_harness.root / "unrelated.cmd"
unrelated_target.write_text("@echo off\n", encoding="utf-8")
_create_windows_shortcut(
powershell_harness.powershell,
desktop_shortcut,
unrelated_target,
)
original_shortcut = desktop_shortcut.read_bytes()
result = powershell_harness.run()
assert result.returncode == 0, result.stderr
assert "not managed by Free Claude Code" in result.stdout
assert desktop_shortcut.read_bytes() == original_shortcut
@pytest.mark.parametrize("uv_version", ("0.11.16", "0.11.16+build.1"))
def test_install_ps1_preserves_valid_existing_tools(
powershell_harness: PowerShellHarness,
uv_version: str,
) -> None:
powershell_harness.add_client("claude")
powershell_harness.add_client("codex")
powershell_harness.add_client("pi")
powershell_harness.add_uv(uv_version)
result = powershell_harness.run()
assert result.returncode == 0, result.stderr
assert not any(call.startswith("download:") for call in powershell_harness.calls())
assert "leaving it unchanged" in result.stdout
def test_install_ps1_replaces_unrelated_pi_command(
powershell_harness: PowerShellHarness,
) -> None:
powershell_harness.add_client("claude")
powershell_harness.add_client("codex")
powershell_harness.add_unrelated_pi()
powershell_harness.add_uv("0.11.16")
result = powershell_harness.run()
assert result.returncode == 0, result.stderr
assert "is not Pi Coding Agent; installing Pi" in result.stdout
assert "pi-install" in powershell_harness.calls()
def test_install_ps1_discovers_custom_pi_npm_prefix(
powershell_harness: PowerShellHarness,
) -> None:
powershell_harness.add_client("claude")
powershell_harness.add_client("codex")
powershell_harness.add_npm_prefix(powershell_harness.root / "custom-npm")
powershell_harness.add_uv("0.11.16")
result = powershell_harness.run()
assert result.returncode == 0, result.stderr
calls = powershell_harness.calls()
assert "npm:prefix -g" in calls
assert "pi:--help" in calls
assert "pi:--version" in calls
def test_install_ps1_continues_when_pi_is_not_installed(
powershell_harness: PowerShellHarness,
) -> None:
result = powershell_harness.run(fail_step="pi-skip")
assert result.returncode == 0, result.stderr
assert "Pi was not installed; continuing without it." in result.stdout
assert "Run Pi with: fcc-pi" not in result.stdout
calls = powershell_harness.calls()
assert "pi-install" in calls
assert not any(call.startswith("pi:") for call in calls)
assert "uv-install" in calls
assert "fcc-server:--version" in calls
def test_install_ps1_continues_when_unrelated_pi_is_unchanged(
powershell_harness: PowerShellHarness,
) -> None:
powershell_harness.add_unrelated_pi()
result = powershell_harness.run(fail_step="pi-skip")
assert result.returncode == 0, result.stderr
assert "Pi was not installed; continuing without it." in result.stdout
assert "Run Pi with: fcc-pi" not in result.stdout
calls = powershell_harness.calls()
assert "unrelated-pi:--help" in calls
assert "unrelated-pi:--version" not in calls
assert "fcc-server:--version" in calls
def test_install_ps1_continues_when_pi_resolution_changes_to_unrelated_command(
powershell_harness: PowerShellHarness,
) -> None:
powershell_harness.add_unrelated_pi()
npm_prefix = powershell_harness.root / "custom-npm"
powershell_harness.add_npm_prefix(npm_prefix)
_write_executable(
npm_prefix / "pi.cmd",
_batch_client("other-unrelated-pi"),
)
result = powershell_harness.run(fail_step="pi-skip")
assert result.returncode == 0, result.stderr
assert "Pi was not installed; continuing without it." in result.stdout
assert "Run Pi with: fcc-pi" not in result.stdout
calls = powershell_harness.calls()
assert "other-unrelated-pi:--help" in calls
assert "other-unrelated-pi:--version" not in calls
assert "fcc-server:--version" in calls
def test_install_ps1_replaces_obsolete_uv(
powershell_harness: PowerShellHarness,
) -> None:
powershell_harness.add_client("claude")
powershell_harness.add_client("codex")
powershell_harness.add_client("pi")
powershell_harness.add_uv("0.5.9")
result = powershell_harness.run()
assert result.returncode == 0, result.stderr
assert "uv 0.5.9 does not satisfy stable >=0.11.16" in result.stdout
assert "uv-install" in powershell_harness.calls()
@pytest.mark.parametrize("version", ("0.11.16-alpha.1", "0.12.0-rc.1"))
def test_install_ps1_replaces_prerelease_uv(
powershell_harness: PowerShellHarness,
version: str,
) -> None:
powershell_harness.add_client("claude")
powershell_harness.add_client("codex")
powershell_harness.add_client("pi")
powershell_harness.add_uv(version)
result = powershell_harness.run()
assert result.returncode == 0, result.stderr
assert f"uv {version} does not satisfy stable >=0.11.16" in result.stdout
assert "uv-install" in powershell_harness.calls()
@pytest.mark.parametrize(
"failure",
[
"claude-download",
"claude-install",
"claude-verify",
"codex-download",
"codex-install",
"codex-verify",
"pi-download",
"pi-install",
"pi-verify",
"uv-download",
"uv-install",
"uv-verify",
"fcc-install",
"path-update",
"fcc-missing",
"fcc-verify",
],
)
def test_install_ps1_stops_without_success_on_each_failure(
powershell_harness: PowerShellHarness,
failure: str,
) -> None:
result = powershell_harness.run(fail_step=failure)
assert result.returncode != 0
assert "Free Claude Code is installed and verified." not in result.stdout
forbidden = {
"claude-download": "claude-install",
"claude-install": "claude:--version",
"claude-verify": "chatgpt.com",
"codex-download": "codex-install",
"codex-install": "codex:--version",
"codex-verify": "pi.dev",
"pi-download": "pi-install",
"pi-install": "pi:--version",
"pi-verify": "astral.sh",
"uv-download": "uv-install",
"uv-install": "uv:--version",
"uv-verify": "uv:tool install",
"fcc-install": "uv:tool update-shell",
"path-update": "uv:tool dir --bin",
"fcc-missing": "fcc-server:--version",
}.get(failure)
if forbidden is not None:
assert not any(forbidden in call for call in powershell_harness.calls())
def test_install_ps1_dry_run_never_executes_commands(
powershell_harness: PowerShellHarness,
) -> None:
result = subprocess.run(
[
powershell_harness.powershell,
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
str(_repo_root() / "scripts" / "install.ps1"),
"-DryRun",
],
check=False,
capture_output=True,
text=True,
env=powershell_harness.env,
)
assert result.returncode == 0, result.stderr
assert powershell_harness.calls() == []
assert "Dry run complete. No changes were made." in result.stdout
assert "Free Claude Code is installed and verified." not in result.stdout
def test_install_ps1_rejects_broken_existing_client_without_replacing_it(
powershell_harness: PowerShellHarness,
) -> None:
powershell_harness.add_client("claude")
result = powershell_harness.run(fail_step="claude-verify")
assert result.returncode != 0
assert not any(call.startswith("download:") for call in powershell_harness.calls())
def test_install_ps1_rejects_unparseable_existing_uv(
powershell_harness: PowerShellHarness,
) -> None:
powershell_harness.add_client("claude")
powershell_harness.add_client("codex")
powershell_harness.add_client("pi")
powershell_harness.add_uv("not-a-version")
result = powershell_harness.run()
assert result.returncode != 0
assert not any("astral.sh" in call for call in powershell_harness.calls())
def test_install_ps1_voice_flags_only_change_fcc_spec(
powershell_harness: PowerShellHarness,
) -> None:
result = powershell_harness.run("-VoiceAll", "-TorchBackend", "cu130")
assert result.returncode == 0, result.stderr
assert any(
'--torch-backend cu130 "free-claude-code[voice,voice_local] @ '
'https://github.com/Alishahryar1/free-claude-code/archive/refs/heads/main.zip"'
in call
for call in powershell_harness.calls()
)
@pytest.mark.parametrize("command_name", FCC_COMMANDS)
def test_install_ps1_rejects_running_fcc_before_mutation(
powershell_harness: PowerShellHarness,
command_name: str,
) -> None:
powershell_harness.env["FCC_RUNNING_COMMAND"] = command_name
result = powershell_harness.run()
assert result.returncode != 0
assert powershell_harness.calls() == []
assert f"{command_name} (PID 4242)" in result.stderr
def test_install_ps1_rechecks_for_fcc_process_before_tool_replacement(
powershell_harness: PowerShellHarness,
) -> None:
powershell_harness.add_client("claude")
powershell_harness.add_client("codex")
powershell_harness.add_client("pi")
powershell_harness.add_uv("0.11.16")
powershell_harness.env["FCC_RUNNING_COMMAND"] = "fcc-server"
powershell_harness.env["FCC_RUNNING_PHASE"] = "late"
result = powershell_harness.run()
assert result.returncode != 0
assert "fcc-server (PID 4242)" in result.stderr
assert not any(
call.startswith("uv:tool install") for call in powershell_harness.calls()
)
def test_install_ps1_ignores_similarly_named_process(
powershell_harness: PowerShellHarness,
) -> None:
powershell_harness.env["FCC_RUNNING_COMMAND"] = "fcc-server-helper"
result = powershell_harness.run()
assert result.returncode == 0, result.stderr
def test_installers_use_native_clients_and_single_python_selection() -> None:
shell = (_repo_root() / "scripts" / "install.sh").read_text(encoding="utf-8")
powershell = (_repo_root() / "scripts" / "install.ps1").read_text(encoding="utf-8")
for text in (shell, powershell):
for command_name in FCC_COMMANDS:
assert command_name in text
assert "@anthropic-ai/claude-code" not in text
assert "@openai/codex" not in text
assert "@earendil-works/pi-coding-agent" not in text
assert "git+" not in text
assert "git --version" not in text
assert (
"https://github.com/Alishahryar1/free-claude-code/archive/refs/heads/main.zip"
in text
)
assert "python install" not in text
assert "--refresh-package" in text
assert "tool update-shell" in text
assert "--python" in text
assert "https://pi.dev/install.sh" in shell
assert "https://pi.dev/install.ps1" in powershell
def test_install_ps1_uses_x64_python_for_windows_arm_compatibility() -> None:
powershell = (_repo_root() / "scripts" / "install.ps1").read_text(encoding="utf-8")
assert '$PythonRequest = "cpython-3.14.0-windows-x86_64-none"' in powershell
@pytest.mark.parametrize("powershell", _powershells())
def test_install_ps1_rejects_invalid_download_before_execution(
powershell: str,
) -> None:
text = (_repo_root() / "scripts" / "install.ps1").read_text(encoding="utf-8")
body = _braced_body(text, "function Invoke-DownloadedPowerShellInstaller")
script = f"""Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$DryRun = $false
function Format-Argument {{ param([string] $Value) return $Value }}
function Invoke-RestMethod {{
[CmdletBinding()]
param([string] $Uri, [string] $OutFile)
[IO.File]::WriteAllText($OutFile, "<style>div#box {{")
}}
function Get-PowerShellExecutable {{ throw "invalid installer reached execution" }}
function Invoke-DownloadedPowerShellInstaller {{{body}}}
Invoke-DownloadedPowerShellInstaller `
-Url "https://example.test/install.ps1" `
-Name "Example"
"""
result = subprocess.run(
[powershell, "-NoProfile", "-Command", script],
check=False,
capture_output=True,
text=True,
)
assert result.returncode != 0
assert (
"Example installer from 'https://example.test/install.ps1' is not valid PowerShell"
in result.stderr
)
assert "network proxy or filter" in result.stderr
assert "invalid installer reached execution" not in result.stderr
@pytest.mark.parametrize("powershell", _powershells())
@pytest.mark.parametrize(
("answers", "expected", "expected_messages"),
[
(("", "", ""), "True,True,True", ()),
(
("maybe", "n", "n", "n", "n", "y", "n"),
"False,True,False",
("Please answer Y or N.", "Select at least one coding agent."),
),
],
)
def test_install_ps1_selects_at_least_one_coding_agent(
powershell: str,
answers: tuple[str, ...],
expected: str,
expected_messages: tuple[str, ...],
) -> None:
text = (_repo_root() / "scripts" / "install.ps1").read_text(encoding="utf-8")
read_yes_no = _braced_body(text, "function Read-YesNo")
select_agents = _braced_body(text, "function Select-CodingAgents")
answer_array = ", ".join(repr(answer) for answer in answers)
script = f"""Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$script:Answers = @({answer_array})
$script:AnswerIndex = 0
$script:InstallClaudeCode = $true
$script:InstallCodex = $true
$script:InstallPi = $true
function Read-Host {{
param([string] $Prompt)
$answer = $script:Answers[$script:AnswerIndex]
$script:AnswerIndex += 1
return $answer
}}
function Read-YesNo {{{read_yes_no}}}
function Select-CodingAgents {{{select_agents}}}
Select-CodingAgents
Write-Output "selection:$($script:InstallClaudeCode),$($script:InstallCodex),$($script:InstallPi)"
"""
result = subprocess.run(
[powershell, "-NoProfile", "-Command", script],
check=False,
capture_output=True,
text=True,
)
assert result.returncode == 0, result.stderr
assert f"selection:{expected}" in result.stdout
for message in expected_messages:
assert message in result.stdout
@pytest.mark.parametrize("powershell", _powershells())
def test_install_ps1_runs_only_selected_coding_agents(powershell: str) -> None:
text = (_repo_root() / "scripts" / "install.ps1").read_text(encoding="utf-8")
body = _braced_body(text, "function Ensure-SelectedCodingAgents")
script = f"""Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$script:InstallClaudeCode = $false
$script:InstallCodex = $true
$script:InstallPi = $false
$script:PiAvailable = $false
$script:Calls = @()
function Write-Step {{ param([string] $Message) }}
function Ensure-ClaudeCode {{ $script:Calls += "claude" }}
function Ensure-Codex {{ $script:Calls += "codex" }}
function Ensure-Pi {{ $script:Calls += "pi"; $script:PiAvailable = $true }}
function Ensure-SelectedCodingAgents {{{body}}}
Ensure-SelectedCodingAgents
Write-Output "calls:$($script:Calls -join ',')"
"""
result = subprocess.run(
[powershell, "-NoProfile", "-Command", script],
check=False,
capture_output=True,
text=True,
)
assert result.returncode == 0, result.stderr
assert "calls:codex" in result.stdout
@pytest.mark.parametrize("powershell", _powershells())
def test_install_ps1_rejects_uninstalled_only_selection(powershell: str) -> None:
text = (_repo_root() / "scripts" / "install.ps1").read_text(encoding="utf-8")
body = _braced_body(text, "function Ensure-SelectedCodingAgents")
script = f"""Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$script:InstallClaudeCode = $false
$script:InstallCodex = $false
$script:InstallPi = $true
$script:PiAvailable = $false
function Write-Step {{ param([string] $Message) }}
function Ensure-ClaudeCode {{ }}
function Ensure-Codex {{ }}
function Ensure-Pi {{ }}
function Ensure-SelectedCodingAgents {{{body}}}
Ensure-SelectedCodingAgents
"""
result = subprocess.run(
[powershell, "-NoProfile", "-Command", script],
check=False,
capture_output=True,
text=True,
)
assert result.returncode != 0
assert "No selected coding agent was installed." in result.stderr
@pytest.mark.parametrize("powershell", _powershells())
def test_install_ps1_falls_back_when_pshome_executable_is_unavailable(
tmp_path: Path,
powershell: str,
) -> None:
text = (_repo_root() / "scripts" / "install.ps1").read_text(encoding="utf-8")
body = _braced_body(text, "function Get-PowerShellExecutable")
fallback = tmp_path / "fallback" / "powershell.exe"
script = tmp_path / "test-powershell-resolution.ps1"
script.write_text(
f"""Set-StrictMode -Version Latest
function Get-ApplicationCommand {{
param([string] $Name)
return [pscustomobject] @{{ Source = {str(fallback)!r} }}
}}
function Get-PowerShellExecutable {{
{body}
}}
$resolved = Get-PowerShellExecutable -PowerShellHome {str(tmp_path / "missing")!r}
if ($resolved -ne {str(fallback)!r}) {{
throw "Unexpected fallback: $resolved"
}}
""",
encoding="utf-8",
)
result = subprocess.run(
[powershell, "-NoProfile", "-File", str(script)],
check=False,
capture_output=True,
text=True,
)
assert result.returncode == 0, result.stderr