CI-02 probe A: ask the gate what ref it grades, on a conflicted PR
checks / unit (pull_request) Failing after 2m33s

This commit is contained in:
2026-09-01 03:18:19 +02:00
parent f6ecd5ff03
commit bfdf73d36b
2 changed files with 91 additions and 115 deletions
+86 -115
View File
@@ -1,24 +1,15 @@
# 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.
# CI-02 PROBE WORKFLOW - THROWAWAY. This branch is deleted after the run.
#
# ⚠️ 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.
# It replaces the real gate with a set of one-bit steps, because this Gitea
# 404s its Actions log endpoints and the ONLY machine-readable channel out of a
# job is /api/v1/repos/.../actions/runs/<id>/jobs, which carries a per-STEP
# conclusion. So each question this ticket has to answer is asked as its own
# step, and the answer is read as that step's success or failure.
#
# ⚠️ 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.
# There is deliberately NO `concurrency:` block: the point of pushes B and C is
# to find out whether a still-WAITING run is cancelled by the next push even
# when the workflow asks for no concurrency at all. If it is, the cancellation
# is the server's and no edit to this file can prevent it.
name: checks
on:
@@ -26,111 +17,91 @@ on:
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
timeout-minutes: 20
env:
# ⚠️ UNDER /home/homelab, NOT $GITHUB_WORKSPACE (ENV-01, and re-measured
# during CI-01). act's workspace is `/root/.cache/act/<id>/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 }}
WS: /home/homelab/ci/ci02probe-${{ github.run_number }}
PRNUM: ${{ github.event.pull_request.number }}
PRHEAD: ${{ github.event.pull_request.head.sha }}
BASEREF: ${{ github.base_ref }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
URL: ${{ github.server_url }}/${{ github.repository }}.git
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 }}
- name: P00 an empty clone to ask the server questions from
run: |
set -eu
rm -rf "$WS" && mkdir -p "$WS/goodwe-addon" && cd "$WS/goodwe-addon"
rm -rf "$WS" && mkdir -p "$WS" && cd "$WS"
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)"
echo "GITHUB_REF=$GITHUB_REF"
echo "GITHUB_SHA=$GITHUB_SHA"
echo "PRNUM=$PRNUM PRHEAD=$PRHEAD BASEREF=$BASEREF"
- 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
- name: P01 GITHUB_REF is refs-pull-N-head (the bare head)
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
set -eu
test "$GITHUB_REF" = "refs/pull/$PRNUM/head"
- name: P02 GITHUB_REF is refs-pull-N-merge (merged content)
if: always()
run: |
set -eu
test "$GITHUB_REF" = "refs/pull/$PRNUM/merge"
- name: P03 GITHUB_SHA equals the PR head sha
if: always()
run: |
set -eu
test "$GITHUB_SHA" = "$PRHEAD"
- name: P04 the first half of the shipped fallback fetches at all
if: always()
run: |
set -eu
cd "$WS"
git fetch -q --depth 1 origin "$GITHUB_REF"
- name: P05 ...and what it lands on IS the bare PR head
if: always()
run: |
set -eu
cd "$WS"
git fetch -q --depth 1 origin "$GITHUB_REF"
test "$(git rev-parse FETCH_HEAD)" = "$PRHEAD"
- name: P06 refs-pull-N-merge exists on the server for an OPEN pr
if: always()
run: |
set -eu
cd "$WS"
git fetch -q --depth 1 origin "refs/pull/$PRNUM/merge"
- name: P07 a real merge of this head into the base tip applies cleanly
if: always()
run: |
set -eu
cd "$WS"
git fetch -q origin "+refs/heads/$BASEREF:refs/remotes/origin/base"
git fetch -q origin "+refs/pull/$PRNUM/head:refs/remotes/origin/pr"
git checkout -qf refs/remotes/origin/base
git -c user.email=ci@local -c user.name=ci merge --no-edit --no-ff refs/remotes/origin/pr
- name: P08 the base tip is already an ancestor of the head
if: always()
run: |
set -eu
cd "$WS"
git fetch -q origin "+refs/heads/$BASEREF:refs/remotes/origin/base"
git fetch -q origin "+refs/pull/$PRNUM/head:refs/remotes/origin/pr"
git merge-base --is-ancestor refs/remotes/origin/base refs/remotes/origin/pr
- name: P09 hold the single runner slot so the next push has to wait
if: always()
run: sleep 150
- name: P99 clean up
if: always()
run: rm -rf "$WS" || true
+5
View File
@@ -2,6 +2,11 @@
## Unreleased
**CI-02 PROBE - THROWAWAY, DO NOT MERGE.** This paragraph exists only to
collide with the entries release/1.0 has gained since this branch point, so
that the pull request is genuinely unmergeable and the gate can be observed
reporting on it. The branch and its pull request are deleted afterwards.
**Thirty days of the real meter, and the acceptance criterion three cards
deferred is now a number (DATA-01).** TARIFF-01 and SAFETY-07 both merged with
AC 3 - *"monthly peak held under the soft limit in >= 95 % of intervals over a