Skip to content

Commit b96fd5e

Browse files
committed
ci: preserve runtime live test artifacts
1 parent 01c0b3c commit b96fd5e

7 files changed

Lines changed: 190 additions & 3 deletions

.github/workflows/runtime-live-e2e.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
environment: CI-E2E
2020
env:
2121
DISABLE_AUTOUPDATER: "1"
22+
KEEP_TEST_DIR: "1"
23+
SPACEDOCK_TEST_TMP_ROOT: ${{ runner.temp }}/spacedock-live/${{ github.job }}
2224
TRIGGER_SOURCE: ${{ github.event_name }}
2325
PR_NUMBER: ${{ github.event.pull_request.number }}
2426
DISPATCH_PR_NUMBER: ${{ inputs.pr_number }}
@@ -122,10 +124,20 @@ jobs:
122124
unset CLAUDECODE && uv run tests/test_push_main_before_pr.py
123125
unset CLAUDECODE && uv run tests/test_rebase_branch_before_push.py
124126
127+
- name: Upload Claude live artifacts
128+
if: always()
129+
uses: actions/upload-artifact@v4
130+
with:
131+
name: runtime-live-e2e-claude-live
132+
path: ${{ env.SPACEDOCK_TEST_TMP_ROOT }}
133+
if-no-files-found: warn
134+
125135
codex-live:
126136
runs-on: ubuntu-latest
127137
environment: CI-E2E
128138
env:
139+
KEEP_TEST_DIR: "1"
140+
SPACEDOCK_TEST_TMP_ROOT: ${{ runner.temp }}/spacedock-live/${{ github.job }}
129141
TRIGGER_SOURCE: ${{ github.event_name }}
130142
PR_NUMBER: ${{ github.event.pull_request.number }}
131143
DISPATCH_PR_NUMBER: ${{ inputs.pr_number }}
@@ -221,3 +233,11 @@ jobs:
221233
uv run tests/test_gate_guardrail.py --runtime codex
222234
uv run tests/test_rejection_flow.py --runtime codex
223235
uv run tests/test_merge_hook_guardrail.py --runtime codex
236+
237+
- name: Upload Codex live artifacts
238+
if: always()
239+
uses: actions/upload-artifact@v4
240+
with:
241+
name: runtime-live-e2e-codex-live
242+
path: ${{ env.SPACEDOCK_TEST_TMP_ROOT }}
243+
if-no-files-found: warn
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Runtime Live E2E Artifact Preservation Implementation Plan
2+
3+
> **For Claude:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
4+
5+
**Goal:** Preserve and upload live-test temp directories as GitHub Actions artifacts on every runtime live E2E run.
6+
7+
**Architecture:** Add a deterministic temp-root hook to the shared live-test harness, then point both live workflow jobs at job-specific temp roots and upload those directories with `actions/upload-artifact`. Keep default local behavior unchanged when the CI env hook is absent.
8+
9+
**Tech Stack:** Python test harness, GitHub Actions workflow YAML, pytest-style offline workflow checks
10+
11+
---
12+
13+
### Task 1: Add a failing helper test for deterministic temp roots
14+
15+
**Files:**
16+
- Modify: `tests/test_test_lib_helpers.py`
17+
- Modify: `scripts/test_lib.py`
18+
19+
- [ ] **Step 1: Write the failing test**
20+
21+
Add a unit test asserting `TestRunner` creates `test_dir` under `SPACEDOCK_TEST_TMP_ROOT` when that env var is set.
22+
23+
- [ ] **Step 2: Run test to verify it fails**
24+
25+
Run: `uv run --with pytest python -m pytest tests/test_test_lib_helpers.py -q`
26+
Expected: FAIL because `TestRunner` ignores `SPACEDOCK_TEST_TMP_ROOT`.
27+
28+
- [ ] **Step 3: Write minimal implementation**
29+
30+
Update `TestRunner` to use `tempfile.mkdtemp(dir=..., prefix=...)` when the env var is present.
31+
32+
- [ ] **Step 4: Run test to verify it passes**
33+
34+
Run: `uv run --with pytest python -m pytest tests/test_test_lib_helpers.py -q`
35+
Expected: PASS.
36+
37+
- [ ] **Step 5: Commit**
38+
39+
```bash
40+
git add tests/test_test_lib_helpers.py scripts/test_lib.py
41+
git commit -m "test: preserve live test dirs under a configured root"
42+
```
43+
44+
### Task 2: Add a failing workflow test for artifact preservation
45+
46+
**Files:**
47+
- Modify: `tests/test_runtime_live_e2e_workflow.py`
48+
- Modify: `.github/workflows/runtime-live-e2e.yml`
49+
50+
- [ ] **Step 1: Write the failing test**
51+
52+
Add assertions that each live job sets `KEEP_TEST_DIR`, sets `SPACEDOCK_TEST_TMP_ROOT`, and uploads artifacts on `if: always()`.
53+
54+
- [ ] **Step 2: Run test to verify it fails**
55+
56+
Run: `unset CLAUDECODE && uv run tests/test_runtime_live_e2e_workflow.py`
57+
Expected: FAIL because the workflow does not yet preserve or upload live temp dirs.
58+
59+
- [ ] **Step 3: Write minimal implementation**
60+
61+
Set the env vars in both live jobs and add upload-artifact steps after the live suite commands.
62+
63+
- [ ] **Step 4: Run test to verify it passes**
64+
65+
Run: `unset CLAUDECODE && uv run tests/test_runtime_live_e2e_workflow.py`
66+
Expected: PASS.
67+
68+
- [ ] **Step 5: Commit**
69+
70+
```bash
71+
git add tests/test_runtime_live_e2e_workflow.py .github/workflows/runtime-live-e2e.yml
72+
git commit -m "ci: upload preserved live test dirs"
73+
```
74+
75+
### Task 3: Run focused verification
76+
77+
**Files:**
78+
- Modify: `tests/README.md`
79+
80+
- [ ] **Step 1: Update docs**
81+
82+
Document that live workflow runs preserve test dirs as artifacts.
83+
84+
- [ ] **Step 2: Run focused verification**
85+
86+
Run:
87+
- `uv run --with pytest python -m pytest tests/test_test_lib_helpers.py -q`
88+
- `unset CLAUDECODE && uv run tests/test_runtime_live_e2e_workflow.py`
89+
90+
Expected: both pass.
91+
92+
- [ ] **Step 3: Commit**
93+
94+
```bash
95+
git add tests/README.md
96+
git commit -m "docs: document live test artifact preservation"
97+
```
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Runtime Live E2E Artifact Preservation Design
2+
3+
**Goal:** Preserve each live test's temp directory in CI and upload it as a GitHub Actions artifact on every `Runtime Live E2E` run.
4+
5+
**Why:** The current live workflow only exposes the outer job log. When a live test fails in CI, the per-test temp directory printed by `TestRunner` is lost with the runner filesystem, which blocks diagnosis of CI-only failures.
6+
7+
## Scope
8+
9+
- Preserve temp directories for both `claude-live` and `codex-live`.
10+
- Store preserved test dirs under a deterministic per-job root instead of anonymous `mkdtemp()` paths.
11+
- Upload those preserved directories as workflow artifacts on every run, not only on failure.
12+
13+
## Design
14+
15+
1. `scripts/test_lib.py` will honor a new environment variable, `SPACEDOCK_TEST_TMP_ROOT`.
16+
- When set, `TestRunner` will create its temp dir under that root with a stable prefix.
17+
- Existing behavior remains unchanged when the variable is absent.
18+
19+
2. `.github/workflows/runtime-live-e2e.yml` will set:
20+
- `KEEP_TEST_DIR=1`
21+
- `SPACEDOCK_TEST_TMP_ROOT` to a per-job path under `${{ runner.temp }}/spacedock-live/${{ github.job }}`
22+
23+
3. The workflow will upload `${{ env.SPACEDOCK_TEST_TMP_ROOT }}` with `actions/upload-artifact` using `if: always()`.
24+
- Artifact names should distinguish `claude-live` and `codex-live`.
25+
26+
## Validation
27+
28+
- Offline workflow test should assert `KEEP_TEST_DIR`, `SPACEDOCK_TEST_TMP_ROOT`, and upload-artifact steps are present for both live jobs.
29+
- Helper unit test should assert `TestRunner` creates temp dirs under the configured root.

scripts/test_lib.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,15 @@ def __init__(self, test_name: str, keep_test_dir: bool = False):
385385
self.passes = 0
386386
self.failures = 0
387387
self.repo_root = Path(__file__).resolve().parent.parent
388-
self.test_dir = Path(tempfile.mkdtemp())
388+
temp_root = os.environ.get("SPACEDOCK_TEST_TMP_ROOT")
389+
if temp_root:
390+
temp_root_path = Path(temp_root)
391+
temp_root_path.mkdir(parents=True, exist_ok=True)
392+
self.test_dir = Path(
393+
tempfile.mkdtemp(prefix="spacedock-test-", dir=str(temp_root_path))
394+
)
395+
else:
396+
self.test_dir = Path(tempfile.mkdtemp())
389397
self.log_dir = self.test_dir
390398
self.keep_test_dir = keep_test_dir or bool(os.environ.get("KEEP_TEST_DIR"))
391399
self.test_project_dir: Path | None = None

tests/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,12 @@ Operators should expect each job summary to show the run provenance explicitly:
226226
- same-repo vs fork status
227227
- approval/reviewer context
228228

229-
Set `KEEP_TEST_DIR=1` to preserve temp directories after test runs for debugging.
229+
The live workflow sets `KEEP_TEST_DIR=1` automatically and uploads each job's preserved temp dirs as GitHub Actions artifacts:
230+
231+
- `runtime-live-e2e-claude-live`
232+
- `runtime-live-e2e-codex-live`
233+
234+
For local debugging, set `KEEP_TEST_DIR=1` to preserve temp directories after test runs. Set `SPACEDOCK_TEST_TMP_ROOT=/path/to/root` to force `TestRunner` to create preserved dirs under a predictable parent directory.
230235

231236
## File Requirements
232237

tests/test_runtime_live_e2e_workflow.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ def test_runtime_live_e2e_workflow_has_exactly_two_runtime_jobs():
5757
assert "matrix:" not in text
5858

5959

60+
def test_runtime_live_e2e_workflow_preserves_and_uploads_live_test_dirs():
61+
text = read_workflow()
62+
claude_section = section(text, " claude-live")
63+
codex_section = section(text, " codex-live")
64+
65+
for job_section, artifact_name in (
66+
(claude_section, "runtime-live-e2e-claude-live"),
67+
(codex_section, "runtime-live-e2e-codex-live"),
68+
):
69+
assert 'KEEP_TEST_DIR: "1"' in job_section
70+
assert "SPACEDOCK_TEST_TMP_ROOT:" in job_section
71+
assert "${{ runner.temp }}/spacedock-live/${{ github.job }}" in job_section
72+
assert "uses: actions/upload-artifact@v4" in job_section
73+
assert "if: always()" in job_section
74+
assert f"name: {artifact_name}" in job_section
75+
assert "path: ${{ env.SPACEDOCK_TEST_TMP_ROOT }}" in job_section
76+
77+
6078
def test_runtime_live_e2e_workflow_scopes_secrets_to_the_matching_job():
6179
text = read_workflow()
6280
claude_section = section(text, " claude-live")

tests/test_test_lib_helpers.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import pytest
1515

1616
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
17-
from test_lib import bash_command_targets_write, emit_skip_result, probe_claude_runtime
17+
from test_lib import TestRunner, bash_command_targets_write, emit_skip_result, probe_claude_runtime
1818

1919

2020
TARGETS = ("skills/", "agents/", "references/", "plugin.json")
@@ -112,3 +112,13 @@ def test_emit_skip_result_prints_standardized_skip_output(capsys):
112112
assert "SKIP: runtime unavailable" in captured
113113
assert "RESULT: SKIP" in captured
114114
assert excinfo.value.code == 0
115+
116+
117+
def test_test_runner_uses_configured_temp_root(monkeypatch, tmp_path):
118+
configured_root = tmp_path / "live-artifacts"
119+
monkeypatch.setenv("SPACEDOCK_TEST_TMP_ROOT", str(configured_root))
120+
121+
runner = TestRunner("helper temp root", keep_test_dir=True)
122+
123+
assert runner.test_dir.parent == configured_root
124+
assert runner.test_dir.name.startswith("spacedock-test-")

0 commit comments

Comments
 (0)