246 lines
7.9 KiB
Markdown
246 lines
7.9 KiB
Markdown
# Clean Install Test Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Add an opt-in pytest test that clones the Manual Slop repo to a temp dir, runs `uv sync`, launches `sloppy.py --enable-test-hooks`, and verifies the Hook API responds. Catches "works on my machine" failures by exercising the full install-and-launch path in an isolated environment.
|
|
|
|
**Architecture:** Standard subprocess-based integration test. `git clone` from the user's Gitea server to `tmp_path`. `uv sync` in the cloned dir. Launch the app as a background subprocess. Poll the Hook API endpoint with `requests` until ready. Test a write hook. Clean up the process tree.
|
|
|
|
**Tech Stack:** Python 3.11+, pytest, subprocess, requests, git CLI
|
|
|
|
**Spec:** `docs/superpowers/specs/2026-06-02-clean-install-test-design.md`
|
|
|
|
---
|
|
|
|
## Execution Constraints
|
|
|
|
- **No subagents.** Execute as a single agent.
|
|
- **Pre-edit checkpoint:** `git add .` before any file edit.
|
|
- **Per-file atomic commits.**
|
|
- **Commit message format:** `<type>(<scope>): <imperative description>`.
|
|
- **Git note format:** 3-8 line rationale per commit.
|
|
- **Style baseline:** 1-space indent, no comments, type hints.
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
| File | Action | Responsibility |
|
|
|---|---|---|
|
|
| `tests/test_clean_install.py` | Create | Opt-in test (RUN_CLEAN_INSTALL_TEST=1) |
|
|
| `pyproject.toml` | Modify | Add `clean_install` marker |
|
|
|
|
---
|
|
|
|
## Task 1: Add the `clean_install` marker to `pyproject.toml`
|
|
|
|
**Files:**
|
|
- Modify: `pyproject.toml`
|
|
|
|
- [ ] **Step 1.1: Pre-edit checkpoint**
|
|
|
|
```powershell
|
|
git -C C:\projects\manual_slop add .
|
|
```
|
|
|
|
- [ ] **Step 1.2: Read current markers section**
|
|
|
|
Use `manual-slop_py_get_code_outline` or grep for `markers` in `pyproject.toml`.
|
|
|
|
- [ ] **Step 1.3: Add the `clean_install` marker**
|
|
|
|
Find the existing markers list (e.g., under `[tool.pytest.ini_options]`) and add:
|
|
|
|
```toml
|
|
markers = [
|
|
"integration: integration tests requiring live GUI",
|
|
"strict: tests that require strict mode",
|
|
"clean_install: clean install verification (opt-in via RUN_CLEAN_INSTALL_TEST=1)",
|
|
]
|
|
```
|
|
|
|
If markers aren't already in `pyproject.toml`, add them. If they are, append the new entry.
|
|
|
|
- [ ] **Step 1.4: Commit**
|
|
|
|
```bash
|
|
git -C C:\projects\manual_slop add pyproject.toml
|
|
git -C C:\projects\manual_slop commit -m "test(pytest): add clean_install marker"
|
|
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Registers the clean_install marker so tests can be selected with pytest -m clean_install or filtered with -m 'not clean_install'." $_ }
|
|
```
|
|
|
|
---
|
|
|
|
## Task 2: Write the clean install test
|
|
|
|
**Files:**
|
|
- Create: `tests/test_clean_install.py`
|
|
|
|
- [ ] **Step 2.1: Pre-edit checkpoint**
|
|
|
|
```powershell
|
|
git -C C:\projects\manual_slop add .
|
|
```
|
|
|
|
- [ ] **Step 2.2: Create the test file**
|
|
|
|
```python
|
|
# tests/test_clean_install.py
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import requests
|
|
|
|
|
|
REPO_URL = "https://git.cozyair.dev/ed/manual_slop"
|
|
STARTUP_TIMEOUT_SECONDS = 30
|
|
READINESS_POLL_INTERVAL = 0.5
|
|
HOOK_PORT = 8999
|
|
|
|
|
|
@pytest.mark.clean_install
|
|
def test_clean_install_runs_with_hooks(tmp_path):
|
|
"""Clone the repo, install deps, launch sloppy.py, verify Hook API.
|
|
|
|
Opt-in: set RUN_CLEAN_INSTALL_TEST=1 to enable. Otherwise skipped.
|
|
Requires network access to the configured REPO_URL.
|
|
"""
|
|
if os.environ.get("RUN_CLEAN_INSTALL_TEST") != "1":
|
|
pytest.skip("Set RUN_CLEAN_INSTALL_TEST=1 to enable")
|
|
|
|
clone_dir = tmp_path / "manual_slop"
|
|
project_root = Path(__file__).resolve().parent.parent
|
|
|
|
subprocess.run(
|
|
["git", "clone", REPO_URL, str(clone_dir)],
|
|
capture_output=True, text=True, timeout=60,
|
|
check=True,
|
|
)
|
|
|
|
subprocess.run(
|
|
["uv", "sync"],
|
|
cwd=str(clone_dir),
|
|
capture_output=True, text=True, timeout=180,
|
|
check=True,
|
|
)
|
|
|
|
creationflags = 0
|
|
if os.name == "nt":
|
|
creationflags = subprocess.CREATE_NEW_PROCESS_GROUP
|
|
|
|
process = subprocess.Popen(
|
|
["uv", "run", "sloppy.py", "--enable-test-hooks"],
|
|
cwd=str(clone_dir),
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
creationflags=creationflags,
|
|
)
|
|
|
|
try:
|
|
start = time.time()
|
|
ready = False
|
|
while time.time() - start < STARTUP_TIMEOUT_SECONDS:
|
|
if process.poll() is not None:
|
|
stderr_out = process.stderr.read(2000) if process.stderr else b""
|
|
pytest.fail(f"Process exited early. stderr: {stderr_out.decode('utf-8', errors='replace')}")
|
|
try:
|
|
response = requests.get(
|
|
f"http://127.0.0.1:{HOOK_PORT}/status",
|
|
timeout=1.0,
|
|
)
|
|
if response.status_code == 200:
|
|
payload = response.json()
|
|
if payload.get("status") in ("running", "ready", "ok"):
|
|
ready = True
|
|
break
|
|
except (requests.ConnectionError, requests.Timeout):
|
|
pass
|
|
time.sleep(READINESS_POLL_INTERVAL)
|
|
|
|
assert ready, (
|
|
f"Hook server did not respond within {STARTUP_TIMEOUT_SECONDS}s. "
|
|
f"stderr: {process.stderr.read(2000).decode('utf-8', errors='replace') if process.stderr else 'N/A'}"
|
|
)
|
|
|
|
response = requests.get(
|
|
f"http://127.0.0.1:{HOOK_PORT}/api/gui/mma_status",
|
|
timeout=5.0,
|
|
)
|
|
assert response.status_code == 200, f"mma_status returned {response.status_code}"
|
|
data = response.json()
|
|
assert isinstance(data, dict), f"mma_status returned non-dict: {data!r}"
|
|
|
|
finally:
|
|
_cleanup_process(process)
|
|
|
|
|
|
def _cleanup_process(process: subprocess.Popen) -> None:
|
|
if os.name == "nt":
|
|
subprocess.run(
|
|
["taskkill", "/F", "/T", "/PID", str(process.pid)],
|
|
capture_output=True,
|
|
)
|
|
else:
|
|
process.terminate()
|
|
try:
|
|
process.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
process.kill()
|
|
```
|
|
|
|
- [ ] **Step 2.3: Run the test in skip mode**
|
|
|
|
```powershell
|
|
uv run pytest tests/test_clean_install.py -v
|
|
```
|
|
|
|
Expected: SKIPPED (RUN_CLEAN_INSTALL_TEST not set).
|
|
|
|
- [ ] **Step 2.4: Commit**
|
|
|
|
```bash
|
|
git -C C:\projects\manual_slop add tests/test_clean_install.py
|
|
git -C C:\projects\manual_slop commit -m "test(clean-install): add opt-in clone-and-verify pytest test"
|
|
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Clones the Gitea repo to tmp_path, runs uv sync, launches sloppy.py --enable-test-hooks, polls :8999/status until ready, then tests /api/gui/mma_status write hook. Robust Windows/Unix process cleanup. Skipped unless RUN_CLEAN_INSTALL_TEST=1." $_ }
|
|
```
|
|
|
|
---
|
|
|
|
## Task 3: Phase Completion Verification
|
|
|
|
- [ ] **Step 3.1: Confirm test is properly gated**
|
|
|
|
```powershell
|
|
uv run pytest tests/test_clean_install.py -v
|
|
```
|
|
|
|
Expected: 1 skipped.
|
|
|
|
- [ ] **Step 3.2: Manual opt-in run (if network is available)**
|
|
|
|
```powershell
|
|
RUN_CLEAN_INSTALL_TEST=1 uv run pytest tests/test_clean_install.py -v
|
|
```
|
|
|
|
Expected: 1 passed (or 1 failed with clear diagnostic if the clone target is unreachable).
|
|
|
|
- [ ] **Step 3.3: Create the checkpoint commit**
|
|
|
|
```bash
|
|
git -C C:\projects\manual_slop commit --allow-empty -m "conductor(checkpoint): Clean install test complete"
|
|
git -C C:\projects\manual_slop log -1 --format='%H' | ForEach-Object { git -C C:\projects\manual_slop notes add -m "Track complete. Opt-in test (RUN_CLEAN_INSTALL_TEST=1) added. Verifies clone + uv sync + launch + hook API. Marked with @pytest.mark.clean_install." $_ }
|
|
```
|
|
|
|
---
|
|
|
|
## Self-Review
|
|
|
|
- **Spec coverage:** All design tasks have a plan task. ✓
|
|
- **Placeholder scan:** Test code is complete. ✓
|
|
- **Type consistency:** `tmp_path`, `process`, `response` used consistently. ✓
|
|
- **Robust cleanup:** `_cleanup_process` handles Windows + Unix. ✓
|