Files
Artemis-Eapil/tests/unit/test_windows_bootstrap.py

186 lines
6.7 KiB
Python

"""Exercise the Windows bootstrap in disposable projects without installing host tools."""
import os
from pathlib import Path
import shutil
import subprocess
import sys
import pytest
ROOT = Path(__file__).resolve().parents[2]
pytestmark = pytest.mark.skipif(
sys.platform != "win32" or not shutil.which("uv"), reason="Windows and uv required"
)
@pytest.fixture
def project(tmp_path):
# Import fixtures let the final smoke check run without downloading project dependencies.
path = tmp_path / "source with spaces!"
path.mkdir()
(path / "pyproject.toml").write_text(
'[project]\nname="bootstrap-fixture"\nversion="0.1.0"\nrequires-python=">=3.12"\ndependencies=[]\n'
)
for name in ("artemis", "uvicorn", "fastapi", "langchain_openai"):
(path / f"{name}.py").write_text("")
subprocess.run(["uv", "lock", "--offline"], cwd=path, check=True, capture_output=True)
return path
def run_bootstrap(
project, *, missing_uv=False, installer_failure=False, sync_failure=False, mirror_index=""
):
script = project / "check.ps1"
script.write_text(
"""
$ErrorActionPreference = 'Continue'
$realUv = (Get-Command uv).Source
. $env:BOOTSTRAP_SCRIPT
. (Join-Path (Split-Path $env:BOOTSTRAP_SCRIPT -Parent) 'download_config.ps1')
if ($env:TEST_MIRROR) { $script:ArtemisDownloads = @{ python_index = $env:TEST_MIRROR } }
$script:uvInstalled = $env:SIMULATE_MISSING_UV -ne '1'
function Test-CommandExists { param($Command); return $script:uvInstalled }
function Update-EnvironmentPath {}
function Invoke-DownloadFile {
param($Uri, $OutFile, $TimeoutSec)
if ($Uri -ne 'https://astral.sh/uv/install.ps1') { throw 'Unexpected installer source' }
Set-Content -LiteralPath $OutFile -Value '# fixture installer'
return $true
}
function powershell.exe {
Write-Host 'INSTALLER_CALLED'
if ($env:SIMULATE_INSTALL_FAILURE -eq '1') { $global:LASTEXITCODE = 25; return }
$script:uvInstalled = $true
$global:LASTEXITCODE = 0
}
function uv {
if ($env:SIMULATE_SYNC_FAILURE -eq '1' -and $args[0] -eq 'sync') {
$global:LASTEXITCODE = 9
return
}
& $realUv @args
}
try {
$python = Initialize-ArtemisPython -ProjectRoot $PSScriptRoot
Write-Host "PYTHON_READY=$python"
exit 0
} catch { Write-Host "EXPECTED_ERROR=$_"; exit 1 }
""",
encoding="ascii",
)
env = dict(os.environ)
env.update(
BOOTSTRAP_SCRIPT=str(ROOT / "scripts/bootstrap_python.ps1"),
SIMULATE_MISSING_UV=str(int(missing_uv)),
SIMULATE_INSTALL_FAILURE=str(int(installer_failure)),
SIMULATE_SYNC_FAILURE=str(int(sync_failure)),
UV_OFFLINE="0" if mirror_index else "1",
TEST_MIRROR=mirror_index,
)
return subprocess.run(
["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(script)],
env=env,
capture_output=True,
text=True,
timeout=60,
)
def test_first_setup_and_repeat_reuse_environment(project):
first = run_bootstrap(project, missing_uv=True)
assert first.returncode == 0, first.stdout + first.stderr
assert "INSTALLER_CALLED" in first.stdout
assert "Creating .venv" in first.stdout
config = project / ".venv/pyvenv.cfg"
before = config.stat().st_mtime_ns
second = run_bootstrap(project)
assert second.returncode == 0, second.stdout + second.stderr
assert "INSTALLER_CALLED" not in second.stdout
assert "Reusing the existing" in second.stdout
assert "dependencies are already ready" in second.stdout
assert config.stat().st_mtime_ns == before
def test_installer_failure_stops_before_environment_creation(project):
result = run_bootstrap(project, missing_uv=True, installer_failure=True)
assert result.returncode == 1
assert "uv installation failed" in result.stdout
assert not (project / ".venv").exists()
def test_dependency_failure_stops_before_launch(project):
result = run_bootstrap(project, sync_failure=True)
assert result.returncode == 1
assert "Dependency installation failed" in result.stdout
assert "PYTHON_READY=" not in result.stdout
def test_incomplete_environment_is_preserved(project):
(project / ".venv").mkdir()
sentinel = project / ".venv/keep.txt"
sentinel.write_text("existing data")
result = run_bootstrap(project)
assert result.returncode == 1
assert "incomplete" in result.stdout
assert sentinel.read_text() == "existing data"
@pytest.mark.parametrize("exit_code", [0, 7])
def test_batch_preserves_exit_code_and_handles_space_and_exclamation_paths(tmp_path, exit_code):
root = tmp_path / "source with spaces!"
(root / "scripts").mkdir(parents=True)
shutil.copy2(ROOT / "start.bat", root / "start.bat")
(root / "scripts/start.ps1").write_text(
"param([switch]$SetupOnly)\n"
"if (-not $SetupOnly) { exit 19 }\n"
"if ((Get-Location).Path -ne (Split-Path $PSScriptRoot -Parent)) { exit 20 }\n"
f"exit {exit_code}\n",
encoding="ascii",
)
result = subprocess.run(
["cmd.exe", "/d", "/c", "call", str(root / "start.bat"), "-SetupOnly"],
cwd=tmp_path,
env={**os.environ, "ARTEMIS_NO_PAUSE": "1"},
capture_output=True,
timeout=30,
)
assert result.returncode == exit_code, result.stdout + result.stderr
@pytest.mark.parametrize("exit_code", [0, 7])
def test_chinese_batch_prints_complete_lines_and_preserves_exit_code(tmp_path, exit_code):
root = tmp_path / "source with spaces!"
root.mkdir()
shutil.copy2(ROOT / "start-cn.bat", root / "start-cn.bat")
shutil.copy2(ROOT / "start.bat", root / "start.bat")
(root / "scripts").mkdir()
# Exercise the real Chinese banner/help prologue, stopping before installs,
# update checks or downloads. Chinese now belongs to PowerShell, not CMD.
prologue = (
(ROOT / "scripts/start.ps1")
.read_text(encoding="utf-8-sig")
.split("# Ensure modern TLS", 1)[0]
)
(root / "scripts/start.ps1").write_text(
prologue + f"\nif ($Mirror -ne 'China') {{ exit 20 }}\nexit {exit_code}\n",
encoding="utf-8-sig",
)
result = subprocess.run(
["cmd.exe", "/d", "/c", "call", str(root / "start-cn.bat")],
env={**os.environ, "ARTEMIS_NO_PAUSE": "1"},
capture_output=True,
timeout=15,
)
assert result.returncode == exit_code, result.stdout + result.stderr
assert not result.stderr, result.stderr
assert "镜像地址和下载超时可在" in result.stdout.decode("utf-8")
help_result = subprocess.run(
["cmd.exe", "/d", "/c", "call", str(root / "start-cn.bat"), "-Help"],
capture_output=True,
timeout=15,
)
assert help_result.returncode == 0
assert not help_result.stderr
assert "Codex MCP" in help_result.stdout.decode("utf-8")