Take the job's outcome as an argument, not as an assumption
`report_job_log.py` hardcoded the word *failed* into the comment header. Every
caller guards the step with `if: failure()`, so it was true by construction --
until an unguarded probe in weblib-viewer#10 ran it on a job that passed, and
the successful run posted a comment reading as a failure report. Anyone
scrolling that PR would conclude the probe had failed.
The interesting uses of this script are exactly the ones that want
`if: always()`: a probe, or a job whose *output* is the point rather than its
exit code. Those all lied in the header.
`--status` now supplies the outcome. **The default is `failed`**, which is what
`if: failure()` means, so the four consuming repos are untouched -- a change in
required arguments would have broken all of them at once, since they take this
script from `@main`. `JOB_STATUS` in the environment does the same, matching how
every other argument here already reads its default from the Actions
environment.
`${{ job.status }}` yields `success`/`failure`/`cancelled`/`skipped` while a
human writing the flag reaches for `passed`/`failed`, so both spellings are
accepted and the expression can be passed straight through. An **unrecognised**
status goes into the header verbatim rather than being rejected: `argparse`'s
`choices=` would exit 2 on a value the table has not heard of, and the log --
the whole reason this script exists -- would never be posted. A reporter must
not become the thing that reports nothing.
The "no log file" note was status-dependent too; it claimed the step "failed
before the build started" regardless.
## Verified
`test_report_job_log.py`, new here: stdlib only and offline, posting to an
`http.server` on localhost that keeps what it is sent, so each test reads the
comment back. An exit status of 0 proves nothing -- the script deliberately
swallows HTTP errors so a failure to report cannot mask the failure being
reported. 21 tests, 0 skipped, 1.2s. Three of them drive the CLI as a
subprocess with only environment variables set, the way a workflow does.
Each check was shown to fire by injecting the fault and reverting it:
| injected fault | result |
|---|---|
| header hardcodes `failed` again (the original bug) | 10 failures |
| `DEFAULT_STATUS = "passed"` (would break the four callers) | 8 failures |
| unknown status raises, as `choices=` would | 2 errors |
| missing-log note keeps the failure wording | 1 failure |
| a stray `%` in the `--status` help text | 1 failure |
All five reverted; the file's checksum matches the pre-injection copy.
Also drops a tracked `__pycache__/report_job_log.cpython-313.pyc` and adds a
`.gitignore`. It was committed by accident in 061d8b2 and importing the module
from the tests rewrites it, so it would otherwise show up in every future diff
as stale bytecode of a file that had already changed.
Closes #8
Co-authored-by: bit <bit@das-labor.org>
This commit is contained in:
43
README.md
43
README.md
@@ -14,12 +14,25 @@ fetch it — which was measured to be the difference between one step and three.
|
||||
| file | what it does |
|
||||
|---|---|
|
||||
| `with-nixpkgs.sh` | Runs a command with one nixpkgs package on PATH, pinned to the *consuming* repo's `flake.lock`. Avoids `nix shell nixpkgs#x`, which re-resolves the registry and refetches a channel tarball whenever the branch moves. |
|
||||
| `report_job_log.py` | Posts the tail of a build log as a PR comment. Exists because `actions/jobs/{id}/logs` returns 500 for every id on Gitea 1.25.2, so a red job otherwise says only that it failed. |
|
||||
| `report_job_log.py` | Posts the tail of a build log as a PR comment. Exists because `actions/jobs/{id}/logs` returns 500 for every id on Gitea 1.25.2, so a red job otherwise says only that it failed. Takes `--status`; see below. |
|
||||
| `sync_blocked_label.py` | Keeps `Status/Blocked` in step with Gitea's dependency graph. Resolves the label from the repo *or the organisation*, and never touches an issue marked `Status/On Hold` or `Status/Abandoned`. |
|
||||
|
||||
All three are standard library / plain bash only. They are *run*, not built, so
|
||||
this repo has no flake.
|
||||
|
||||
`test_report_job_log.py` covers the reporter. It is standard library and
|
||||
offline — the forge it posts to is an `http.server` on localhost that keeps
|
||||
what it is sent, so a test reads the comment back rather than trusting an exit
|
||||
status of 0, which this script returns even when the POST failed.
|
||||
|
||||
```bash
|
||||
python3 test_report_job_log.py # 21 tests, ~1.2s, no network
|
||||
```
|
||||
|
||||
There is no workflow running it: this repo has no `.gitea/workflows` at all,
|
||||
and no `flake.lock` for `with-nixpkgs.sh` to read. Run it by hand before
|
||||
pushing.
|
||||
|
||||
## Using it
|
||||
|
||||
```yaml
|
||||
@@ -57,6 +70,34 @@ The `outputs.path` row is listed separately on purpose: the action *running* and
|
||||
its output *reaching the caller* are different claims, and a composite action
|
||||
returning an empty string is exactly the sort of thing that looks green.
|
||||
|
||||
### `report_job_log.py --status`
|
||||
|
||||
The header used to be hardcoded to *failed*. That is true of every caller here,
|
||||
because each guards the step with `if: failure()` — but a probe run under
|
||||
`if: always()` posted a failure report for a job that had passed
|
||||
(weblib-viewer#10, filed as #8).
|
||||
|
||||
**The default is still `failed`**, so a caller passing only the log path is
|
||||
unchanged. A step that can run on success has to say so:
|
||||
|
||||
```yaml
|
||||
- name: report the log
|
||||
if: always()
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: |
|
||||
bash "${{ steps.ci.outputs.path }}/with-nixpkgs.sh" python3 \
|
||||
python3 "${{ steps.ci.outputs.path }}/report_job_log.py" /tmp/build.log \
|
||||
--status "${{ job.status }}"
|
||||
```
|
||||
|
||||
`${{ job.status }}` yields `success`/`failure`/`cancelled`/`skipped`, so those
|
||||
spellings are accepted alongside `passed`/`failed`. `JOB_STATUS` in the
|
||||
environment does the same thing if a flag is awkward. An **unrecognised**
|
||||
status is put in the header verbatim rather than rejected: `argparse`'s
|
||||
`choices=` would exit 2 on a value this list has not heard of, and the log —
|
||||
the entire reason the script exists — would never be posted.
|
||||
|
||||
### Why not a flake input
|
||||
|
||||
These are scripts a workflow runs, not derivations. A flake input would cost a
|
||||
|
||||
Reference in New Issue
Block a user