160 lines
5.2 KiB
Markdown
160 lines
5.2 KiB
Markdown
# SteamCMD Install Logging & Buffering Fix Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Improve live feedback during the `install` operation by adding step markers to `SteamInstaller` and fixing Python subprocess buffering so output streams immediately.
|
|
|
|
**Architecture:** Modifies `l4d2host/process.py` and `l4d2web/services/host_commands.py` to add `flush=True` to `print()` statements for immediate pipeline throughput. Modifies `l4d2host/steam_install.py` to use `_emit_step` to log platform payload downloads.
|
|
|
|
**Tech Stack:** Python, subprocess
|
|
|
|
---
|
|
|
|
### Task 1: Fix process output buffering
|
|
|
|
**Files:**
|
|
- Modify: `l4d2host/process.py`
|
|
- Modify: `l4d2web/services/host_commands.py`
|
|
|
|
- [ ] **Step 1: Check existing test suite**
|
|
No failing test is required here because we are only modifying the `flush` parameter of `print()` inside existing pass-through functions, which are thoroughly covered by integration tests but unit-testing the buffering of `print` is notoriously flaky across OSes. We will just modify the code and run the existing suite.
|
|
|
|
- [ ] **Step 2: Add flush to l4d2host process**
|
|
|
|
Modify `emit_stderr_message` and `pump` inside `run_command` in `l4d2host/process.py`:
|
|
|
|
```python
|
|
def emit_stderr_message(line: str) -> None:
|
|
stderr_lines.append(line)
|
|
if on_stderr is not None:
|
|
on_stderr(line)
|
|
if passthrough:
|
|
print(line, file=sys.stderr, flush=True)
|
|
|
|
# ... inside pump ...
|
|
if passthrough:
|
|
print(line, file=output_stream, flush=True)
|
|
```
|
|
|
|
- [ ] **Step 3: Add flush to l4d2web host_commands**
|
|
|
|
Modify `emit_stderr_message` and `pump` inside `run_command` in `l4d2web/services/host_commands.py`:
|
|
|
|
```python
|
|
def emit_stderr_message(line: str) -> None:
|
|
stderr_lines.append(line)
|
|
if on_stderr is not None:
|
|
on_stderr(line)
|
|
if passthrough:
|
|
print(line, file=sys.stderr, flush=True)
|
|
|
|
# ... inside pump ...
|
|
if passthrough:
|
|
print(line, file=output_stream, flush=True)
|
|
```
|
|
|
|
- [ ] **Step 4: Run test to verify it passes**
|
|
|
|
Run: `pytest l4d2host/tests l4d2web/tests -q`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add l4d2host/process.py l4d2web/services/host_commands.py
|
|
git commit -m "fix(host): enforce flush=True to prevent pipeline block buffering"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: Add step logging to SteamInstaller
|
|
|
|
**Files:**
|
|
- Modify: `l4d2host/steam_install.py`
|
|
- Modify: `l4d2host/tests/test_install.py`
|
|
|
|
- [ ] **Step 1: Write failing test**
|
|
|
|
In `l4d2host/tests/test_install.py`, add a test to verify `SteamInstaller` logs steps:
|
|
|
|
```python
|
|
def test_steam_installer_emits_steps(tmp_path: Path, monkeypatch) -> None:
|
|
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
|
|
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: installation complete."
|
|
]
|
|
```
|
|
|
|
- [ ] **Step 2: Run test to verify it fails**
|
|
|
|
Run: `pytest l4d2host/tests/test_install.py -k test_steam_installer_emits_steps -q`
|
|
Expected: FAIL because `steps` is empty.
|
|
|
|
- [ ] **Step 3: Add step logs to SteamInstaller**
|
|
|
|
In `l4d2host/steam_install.py`, import `_emit_step`:
|
|
|
|
```python
|
|
from l4d2host.instances import _emit_step
|
|
```
|
|
|
|
Modify `install_or_update`:
|
|
|
|
```python
|
|
def install_or_update(
|
|
self,
|
|
*,
|
|
on_stdout: Callable[[str], None] | None = None,
|
|
on_stderr: Callable[[str], None] | None = None,
|
|
passthrough: bool = False,
|
|
should_cancel: Callable[[], bool] | None = None,
|
|
) -> None:
|
|
for platform in ("windows", "linux"):
|
|
_emit_step(f"downloading {platform} platform payload...", on_stdout, passthrough)
|
|
run_command(
|
|
[
|
|
self.steamcmd,
|
|
"+force_install_dir",
|
|
str(self.install_dir),
|
|
"+login",
|
|
"anonymous",
|
|
"+@sSteamCmdForcePlatformType",
|
|
platform,
|
|
"+app_update",
|
|
"222860",
|
|
"validate",
|
|
"+quit",
|
|
],
|
|
on_stdout=on_stdout,
|
|
on_stderr=on_stderr,
|
|
passthrough=passthrough,
|
|
should_cancel=should_cancel,
|
|
)
|
|
_emit_step("installation complete.", on_stdout, passthrough)
|
|
```
|
|
|
|
- [ ] **Step 4: Run test to verify it passes**
|
|
|
|
Run: `pytest l4d2host/tests/test_install.py -k test_steam_installer_emits_steps -q`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Run full suite**
|
|
|
|
Run: `pytest l4d2host/tests l4d2web/tests -q`
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
```bash
|
|
git add l4d2host/steam_install.py l4d2host/tests/test_install.py
|
|
git commit -m "feat(host): add step logging to steam_install"
|
|
```
|