# CI-01: this repository's FIRST automated check. Before this file, admin/goodwe-addon # had no `.gitea/workflows/` directory at all - eight runnable test files, ~100 s # of real coverage including a wall-clock cadence test, and not one machine ever # ran them. Every merge here was a human reading a diff. # # ⚠️ NO `paths:` FILTER, AND ITS ABSENCE IS LOAD-BEARING. Branch protection on # release/1.0 requires the context `checks / unit (pull_request)`. Gitea leaves a # required context it never receives PENDING forever, so the moment this workflow # learns to sit a PR out, that PR becomes unmergeable. Condition a STEP if you # must; never the workflow. # # ⚠️ THE RUNNER IS SHARED WITH THE OTHER REPO'S RIG. `goodwe-runner` on # 192.168.2.147 carries the labels `self-hosted` and `goodwe-rig` and is # registered `:host`, and it is the only runner there is - so this job wears the # rig label without wanting a rig. Nothing below starts, stops, or reads the # `goodwe-sim` compose project or takes `sim/riglock.py`; it runs one short # container and exits. It does occupy the runner's single slot (`capacity: 1`), # so it QUEUES behind a rig campaign - a delayed verdict, never a destroyed one. # # NO JS ACTIONS: the runner image has no node, so `actions/checkout` cannot run. # Checkout is seven lines of git below. name: checks on: pull_request: branches: [release/1.0, master] workflow_dispatch: concurrency: # ⚠️ PER PULL REQUEST, NOT A BARE NAME. A shared group holds one running and # one pending run, so a bare name lets a second PR supersede a first PR's run # before any runner has picked it up - which is exactly what happened to the # other repo's run 521 (four jobs, `started_at` at the Unix epoch, # `runner_id: 0`, concluded `cancelled`, and the PR merged anyway). Do not # replace this with a constant. group: addon-checks-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: unit: runs-on: [self-hosted, goodwe-rig] timeout-minutes: 70 # ~4 to run; the rest is queueing for the single slot env: # ⚠️ UNDER /home/homelab, NOT $GITHUB_WORKSPACE (ENV-01, and re-measured # during CI-01). act's workspace is `/root/.cache/act//hostexecutor`, a # path that exists only INSIDE the runner container. Hand it to the host's # snap-confined dockerd and the build fails with "failed to read # dockerfile", or - worse, and this is the documented case - a bind mount # resolves to an EMPTY DIRECTORY with no error at all. /home/homelab is # mirrored 1:1 into the runner and is under $HOME, where snap confinement # accepts it. The same probe under /tmp failed and under $HOME succeeded. WS: /home/homelab/ci/addon-checks-${{ github.run_number }} steps: # ⚠️ THE CHECKOUT IS TWO LEVELS BELOW THE MOUNT ROOT, DELIBERATELY. # test_freeze.py's find_hwsim() reads `HERE.parents[1] / "goodwe" / "sim"`, # looking for the infra repo as a SIBLING of this one. Check out flat - # goodwe_controller directly at the mount root - and `parents[1]` does not # exist, so the test dies on `IndexError: 1` before its first assertion. # Measured during CI-01; the flat layout was tried first and did exactly # that. $WS/goodwe-addon/goodwe_controller gives parents[1] == $WS. - name: checkout into a path the host shares (git; no node for actions/checkout) env: URL: ${{ github.server_url }}/${{ github.repository }}.git TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eu rm -rf "$WS" && mkdir -p "$WS/goodwe-addon" && cd "$WS/goodwe-addon" git init -q . git remote add origin "$(echo "$URL" | sed "s#://#://x-access-token:${TOKEN}@#")" git fetch -q --depth 1 origin "$GITHUB_REF" || git fetch -q --depth 1 origin "$GITHUB_SHA" git checkout -qf FETCH_HEAD echo "checked out $(git log --oneline -1)" - name: build the test image (shipped deps, from apk - see .gitea/ci/Dockerfile) run: | set -eu docker build -q -t goodwe-addon-checks:1 "$WS/goodwe-addon/.gitea/ci" >/dev/null docker run --rm goodwe-addon-checks:1 python3 -c \ 'import aiohttp, paho.mqtt.client as m, sys; print("python", sys.version.split()[0], "aiohttp", aiohttp.__version__)' # ⚠️ THE FILE LIST IS DERIVED FROM THE TREE, NOT TYPED HERE. A hand-written # list is how a new test file joins the repo and is never run - the exact # shape of failure this ticket exists to close. The step globs `test_*.py`, # prints the count, and REFUSES A COUNT OF ZERO: a loop over an empty glob # exits 0 and would report a green gate that examined nothing. # # ⚠️ test_loop.py TAKES ~93 SECONDS OF WALL CLOCK ON PURPOSE (its own # docstring says so, and says why: it drives the real `run_control()` # against a real wall-clock floor, and mocking the clock would leave the # assertion testing the mock). It is not hung. Do not "fix" it with a # shorter timeout - a 60 s limit was tried during CI-01 and reported # exactly the false hang the docstring predicts. # # ⚠️ WHAT THIS RUN DOES NOT COVER, stated because a pass here would # otherwise over-claim. test_freeze.py's third section wants the infra # repo's `sim/hwsim.py` to drive a real HTTP meter through the real # transport. admin/goodwe is PRIVATE (checked via the API) and this # workflow's GITHUB_TOKEN is minted for THIS repository only, so hwsim is # absent here and `find_hwsim()` returns None. The rest of the file runs # and passes. It does so SILENTLY - the file prints nothing to say the # hwsim section was skipped - so "test_freeze.py: PASS" below is a weaker # claim than it looks. That belongs to whoever owns test_freeze.py next; # naming it here is the least this gate can do about it. - name: the add-on's own tests run: | set -eu docker run --rm -v "$WS:/w:ro" goodwe-addon-checks:1 sh -eu -c ' cd /w/goodwe-addon/goodwe_controller set -- test_*.py if [ "$1" = "test_*.py" ]; then echo "NO test_*.py FOUND - the mount is empty or the layout moved. NOTHING has been checked." exit 1 fi echo "$# test file(s) to run" rc=0 for f in "$@"; do start=$(date +%s) if python3 "$f" > /tmp/out 2>&1; then echo "PASS $f ($(( $(date +%s) - start ))s)" else echo "FAIL $f ($(( $(date +%s) - start ))s)" tail -40 /tmp/out rc=1 fi done exit $rc ' - name: clean up if: always() run: | rm -rf "$WS" || true # Root-owned by this runner inside a directory the `homelab` user owns, # and sudo on that box is interactive-only, so the user cannot clean # this up and the job that made it has to. ls -1dt /home/homelab/ci/addon-checks-*/ 2>/dev/null | tail -n +6 | xargs -r rm -rf || true