Migrate from pip-install-e + setuptools to a uv workspace with a
committed uv.lock for deterministic deps. Switch both members to
hatchling, and move package sources into nested standard layout
(l4d2host/l4d2host/, l4d2web/l4d2web/) so builds work from a
read-only source tree — setuptools wrote egg-info to source under
the old layout, which broke uv sync on the root-owned /opt/left4me/src.
Local dev install: `pip install -e ./l4d2host -e ./l4d2web` -> `uv sync`.
.envrc switches from `layout python python3.13` to `use uv`. Python
pinned to 3.13 via .python-version.
l4d2web now declares its cross-dep on l4d2host explicitly via
[tool.uv.sources] (workspace = true). l4d2web/alembic.ini and
l4d2web/alembic/ stay at the project root (standard alembic layout).
Test fixes:
- tests/__init__.py added to both test dirs so pytest doesn't shadow
l4d2host as a namespace package via outer-dir walk.
- 3 CWD-relative paths in tests (l4d2web/static/css/{tokens,layout}.css
and js/sse.js) anchored to Path(__file__) so they survive layout
changes.
- Two test_install.py tests now monkeypatch HOME to tmp_path so they
stop silently mutating ~/.steam/sdk32 on every run.
628 tests pass under sandboxed `uv run pytest`.
Per docs/superpowers/plans/2026-05-15-uv-workspace-execution.md;
prereq for the ckn-bw bundle's uv-sync action (queued).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
142 lines
4.9 KiB
Python
142 lines
4.9 KiB
Python
import subprocess
|
|
|
|
import pytest
|
|
|
|
from l4d2host.steam_install import SteamInstaller
|
|
|
|
|
|
def test_windows_then_linux(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("HOME", str(tmp_path / "home"))
|
|
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
|
|
calls: list[list[str]] = []
|
|
|
|
def fake_run_command(cmd, **kwargs):
|
|
del kwargs
|
|
calls.append(list(cmd))
|
|
|
|
monkeypatch.setattr("l4d2host.steam_install.run_command", fake_run_command)
|
|
SteamInstaller().install_or_update()
|
|
assert "windows" in calls[0]
|
|
assert "linux" in calls[1]
|
|
|
|
|
|
def test_fail_fast_on_first_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
calls: list[list[str]] = []
|
|
|
|
def fake_run_command(cmd, **kwargs):
|
|
del kwargs
|
|
calls.append(list(cmd))
|
|
if len(calls) == 1:
|
|
raise subprocess.CalledProcessError(returncode=1, cmd=cmd)
|
|
|
|
monkeypatch.setattr("l4d2host.steam_install.run_command", fake_run_command)
|
|
|
|
with pytest.raises(subprocess.CalledProcessError):
|
|
SteamInstaller().install_or_update()
|
|
|
|
assert len(calls) == 1
|
|
|
|
|
|
def test_default_install_dir_uses_left4me_root(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HOME", str(tmp_path / "home"))
|
|
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
|
|
calls = []
|
|
monkeypatch.setattr("l4d2host.steam_install.run_command", lambda cmd, **kwargs: calls.append(cmd))
|
|
|
|
SteamInstaller().install_or_update()
|
|
|
|
assert str(tmp_path / "installation") in calls[0]
|
|
|
|
|
|
def test_steam_installer_emits_steps(tmp_path, monkeypatch) -> None:
|
|
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
|
|
monkeypatch.setenv("HOME", str(tmp_path / "home"))
|
|
monkeypatch.setattr("l4d2host.steam_install.run_command", lambda cmd, **kwargs: None)
|
|
|
|
steps: list[str] = []
|
|
|
|
from l4d2host.steam_install import SteamInstaller
|
|
SteamInstaller().install_or_update(on_stdout=steps.append)
|
|
|
|
assert steps == [
|
|
"Step: downloading windows platform payload...",
|
|
"Step: downloading linux platform payload...",
|
|
"Step: ensuring steam sdk symlinks...",
|
|
"Step: installation complete."
|
|
]
|
|
|
|
|
|
def test_install_creates_steam_sdk_symlinks_to_steamcmd_siblings(tmp_path, monkeypatch) -> None:
|
|
home = tmp_path / "home"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HOME", str(home))
|
|
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
|
|
|
|
steamcmd_dir = tmp_path / "opt" / "steamcmd"
|
|
(steamcmd_dir / "linux32").mkdir(parents=True)
|
|
(steamcmd_dir / "linux64").mkdir(parents=True)
|
|
fake_steamcmd = steamcmd_dir / "steamcmd.sh"
|
|
fake_steamcmd.write_text("#!/bin/sh\nexit 0\n")
|
|
fake_steamcmd.chmod(0o755)
|
|
|
|
monkeypatch.setattr("l4d2host.steam_install.run_command", lambda cmd, **kwargs: None)
|
|
|
|
from l4d2host.steam_install import SteamInstaller
|
|
SteamInstaller(steamcmd=str(fake_steamcmd)).install_or_update()
|
|
|
|
sdk32 = home / ".steam" / "sdk32"
|
|
sdk64 = home / ".steam" / "sdk64"
|
|
assert sdk32.is_symlink()
|
|
assert sdk64.is_symlink()
|
|
assert sdk32.resolve() == (steamcmd_dir / "linux32").resolve()
|
|
assert sdk64.resolve() == (steamcmd_dir / "linux64").resolve()
|
|
|
|
|
|
def test_install_creates_steam_sdk_symlinks_falls_back_to_install_bin(tmp_path, monkeypatch) -> None:
|
|
home = tmp_path / "home"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HOME", str(home))
|
|
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
|
|
|
|
install_bin = tmp_path / "installation" / "bin"
|
|
install_bin.mkdir(parents=True)
|
|
|
|
isolated_dir = tmp_path / "no-siblings"
|
|
isolated_dir.mkdir()
|
|
fake_steamcmd = isolated_dir / "steamcmd.sh"
|
|
fake_steamcmd.write_text("#!/bin/sh\nexit 0\n")
|
|
fake_steamcmd.chmod(0o755)
|
|
|
|
monkeypatch.setattr("l4d2host.steam_install.run_command", lambda cmd, **kwargs: None)
|
|
|
|
from l4d2host.steam_install import SteamInstaller
|
|
SteamInstaller(steamcmd=str(fake_steamcmd)).install_or_update()
|
|
|
|
sdk32 = home / ".steam" / "sdk32"
|
|
sdk64 = home / ".steam" / "sdk64"
|
|
assert sdk32.resolve() == install_bin.resolve()
|
|
assert sdk64.resolve() == install_bin.resolve()
|
|
|
|
|
|
def test_install_steam_sdk_symlinks_is_idempotent(tmp_path, monkeypatch) -> None:
|
|
home = tmp_path / "home"
|
|
home.mkdir()
|
|
monkeypatch.setenv("HOME", str(home))
|
|
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
|
|
|
|
steamcmd_dir = tmp_path / "opt" / "steamcmd"
|
|
(steamcmd_dir / "linux32").mkdir(parents=True)
|
|
(steamcmd_dir / "linux64").mkdir(parents=True)
|
|
fake_steamcmd = steamcmd_dir / "steamcmd.sh"
|
|
fake_steamcmd.write_text("#!/bin/sh\nexit 0\n")
|
|
fake_steamcmd.chmod(0o755)
|
|
|
|
monkeypatch.setattr("l4d2host.steam_install.run_command", lambda cmd, **kwargs: None)
|
|
|
|
from l4d2host.steam_install import SteamInstaller
|
|
installer = SteamInstaller(steamcmd=str(fake_steamcmd))
|
|
installer.install_or_update()
|
|
installer.install_or_update()
|
|
|
|
sdk32 = home / ".steam" / "sdk32"
|
|
assert sdk32.resolve() == (steamcmd_dir / "linux32").resolve()
|