Give a push-triggered job somewhere to report a failure
report_job_log live-smoke-test passed: live smoke test log line REGRESSION-CHECK-LIVE-SMOKE

report_job_log.py only ever commented on an open PR, so a push to
development/main -- the shape of a publish pipeline, which has no
pull_request trigger -- had nowhere to post: the branch pushed to is the
merge's target, not the already-closed PR that produced it. Combined with
job logs 500ing on this Gitea, that made a push-triggered failure completely
invisible (weblib-viewer run 1769, diagnosed only by hardcoding --pr on a
throwaway commit).

A GitHub-style commit-comment endpoint, the first thing tried, does not
exist on this Gitea: 404, and absent from /swagger.v1.json entirely. What
does exist and was verified live: GET commits/{sha}/pull resolves the PR
that produced a given commit, open or closed, across both merge styles this
org uses -- so the closed PR the push actually came from can still be found
and commented on as before. When even that finds nothing (a commit that was
never part of any PR), fall back to a commit status, since that endpoint is
already proven to work here.

No CLI changes; existing callers are unaffected.

Closes #14

Co-authored-by: bit <bit@das-labor.org>
This commit is contained in:
2026-09-10 10:46:23 +00:00
co-authored by bit
parent 8c0088987b
commit c2947db08a
3 changed files with 340 additions and 21 deletions
+155 -1
View File
@@ -42,10 +42,16 @@ ACTIONS_ENV = [
class FakeGitea(BaseHTTPRequestHandler):
"""Just enough of the API: list open PRs, and accept a comment."""
"""Just enough of the API: list open PRs, resolve a commit's merged PR,
accept a comment, and accept a commit status."""
comments = [] # class-level; reset per test
statuses = []
pulls = []
# sha -> pull dict, mirroring GET .../commits/{sha}/pull. A sha absent
# from this map 404s, the same shape Gitea returns for a commit that was
# never part of any PR.
pull_for_sha = {}
def _send(self, code, payload):
raw = json.dumps(payload).encode()
@@ -56,6 +62,14 @@ class FakeGitea(BaseHTTPRequestHandler):
self.wfile.write(raw)
def do_GET(self):
if "/commits/" in self.path and self.path.endswith("/pull"):
sha = self.path.split("/commits/", 1)[1].rsplit("/pull", 1)[0]
pull = type(self).pull_for_sha.get(sha)
if pull is None:
return self._send(
404, {"message": "pull request does not exist "
"[id: 0, issue_id: 0]"})
return self._send(200, pull)
if "/pulls" in self.path:
return self._send(200, type(self).pulls)
self._send(404, {"message": "no"})
@@ -63,6 +77,16 @@ class FakeGitea(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get("Content-Length") or 0)
payload = json.loads(self.rfile.read(length) or b"{}")
if "/statuses/" in self.path:
type(self).statuses.append({
"path": self.path,
"auth": self.headers.get("Authorization"),
"context": payload.get("context"),
"state": payload.get("state"),
"description": payload.get("description", ""),
"target_url": payload.get("target_url"),
})
return self._send(201, {"id": 1, "state": payload.get("state")})
type(self).comments.append({
"path": self.path,
"auth": self.headers.get("Authorization"),
@@ -79,7 +103,9 @@ class ServerTestCase(unittest.TestCase):
def setUp(self):
FakeGitea.comments = []
FakeGitea.statuses = []
FakeGitea.pulls = []
FakeGitea.pull_for_sha = {}
self.server = HTTPServer(("127.0.0.1", 0), FakeGitea)
self.host = "http://127.0.0.1:%d" % self.server.server_address[1]
# `shutdown()` only takes effect on the next poll, so the default
@@ -115,6 +141,10 @@ class ServerTestCase(unittest.TestCase):
"""
return FakeGitea.comments
def posted_statuses(self):
"""Same idea as `posted()`, for the commit-status fallback."""
return FakeGitea.statuses
def run_main(self, *argv):
"""The entry point in-process, so a traceback is readable."""
out = io.StringIO()
@@ -293,6 +323,130 @@ class TestEndToEnd(ServerTestCase):
self.assertIn("--status", proc.stdout)
# A commit that landed by merging a now-closed PR into development/main --
# weblib-ci#14's actual failure mode. A real 40-char sha, because
# pull_request_for_sha silently treats a truncated one the same as "no PR".
PUSH_SHA = "c9d6db67d9c20f8ab133f4fb5fea359e9c474274"
class TestPushWithNoOpenPR(ServerTestCase):
"""A `push` event: no event payload, no GITHUB_HEAD_REF, and the pushed
branch (GITHUB_REF_NAME) is the *target* of a merge, not a PR head -- the
exact shape of weblib-ci#14 (weblib-viewer run 1769, PR#27 merged into
`development`)."""
def _run(self, *argv, env=None):
full = dict(os.environ)
full.pop("GITEA_HOST", None)
full.update({
"GITHUB_REPOSITORY": "weblib/weblib-ci",
"GITHUB_SERVER_URL": self.host,
"GITEA_TOKEN": "t0ken",
"GITHUB_JOB": "publish",
"GITHUB_SHA": PUSH_SHA,
"GITHUB_REF_NAME": "development",
})
full.update(env or {})
return subprocess.run([sys.executable, SCRIPT, self.log, *argv],
capture_output=True, text=True, env=full,
cwd=HERE)
def test_falls_back_to_the_pr_the_push_merged(self):
"""No open PR has `development` as its head -- it never does -- but
the merge commit itself resolves to the PR that produced it, even
though that PR is now closed."""
FakeGitea.pulls = []
FakeGitea.pull_for_sha = {PUSH_SHA: {"number": 27, "state": "closed"}}
proc = self._run()
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn("reported to weblib/weblib-ci#27", proc.stdout)
posted = self.posted()
self.assertEqual(len(posted), 1, posted)
self.assertEqual(posted[0]["path"],
"/api/v1/repos/weblib/weblib-ci/issues/27/comments")
self.assertIn("DISTINCTIVE-LINE", posted[0]["body"])
self.assertEqual(self.posted_statuses(), [],
"a resolvable PR must not also get a status")
def test_branch_lookup_still_wins_when_it_applies(self):
"""Ordering: an *open* PR whose head matches the branch is found
first, so the (potentially stale) sha lookup is never even tried."""
FakeGitea.pulls = [{"number": 5, "head": {"ref": "development"}}]
FakeGitea.pull_for_sha = {PUSH_SHA: {"number": 999}}
proc = self._run()
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn("reported to weblib/weblib-ci#5", proc.stdout)
self.assertNotIn("999", proc.stdout)
def test_no_pr_anywhere_reports_a_commit_status(self):
"""Not even the sha lookup finds anything -- a commit that was never
part of any PR. This must still report *somewhere*, not silently
exit 0 the way #14 did."""
FakeGitea.pulls = []
FakeGitea.pull_for_sha = {}
proc = self._run()
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn(f"reported to weblib/weblib-ci@{PUSH_SHA[:8]}",
proc.stdout)
self.assertEqual(self.posted(), [], "no PR exists to comment on")
statuses = self.posted_statuses()
self.assertEqual(len(statuses), 1, statuses)
s = statuses[0]
self.assertEqual(s["path"],
f"/api/v1/repos/weblib/weblib-ci/statuses/{PUSH_SHA}")
self.assertEqual(s["auth"], "token t0ken")
self.assertEqual(s["state"], "failure")
self.assertIn("publish", s["description"])
self.assertIn("DISTINCTIVE-LINE", s["description"])
self.assertIn(PUSH_SHA, s["target_url"])
def test_commit_status_state_follows_the_reported_outcome(self):
FakeGitea.pulls = []
FakeGitea.pull_for_sha = {}
for status, want_state in [("passed", "success"),
("cancelled", "warning"),
("skipped", "skipped"),
("neutral", "error")]:
with self.subTest(status=status):
FakeGitea.statuses = []
proc = self._run("--status", status)
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertEqual(self.posted_statuses()[0]["state"], want_state)
def test_no_sha_and_no_pr_does_not_crash_or_post_anything(self):
"""The one case with nowhere at all to report: still exits 0 and
says so, rather than raising or posting a status with an empty sha
in the URL."""
FakeGitea.pulls = []
FakeGitea.pull_for_sha = {}
proc = self._run(env={"GITHUB_SHA": "", "GITHUB_REF_NAME": "development"})
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn("nothing to report", proc.stdout)
self.assertEqual(self.posted(), [])
self.assertEqual(self.posted_statuses(), [])
def test_existing_pr_event_path_is_unaffected(self):
"""The ordinary pull_request-event path -- unrelated to any of the
push-triggered fallback -- still goes straight to the comment, and
never touches the sha or status endpoints."""
path = os.path.join(self.mkdtemp(), "event.json")
with open(path, "w") as fh:
json.dump({"pull_request": {"number": 11}}, fh)
FakeGitea.pull_for_sha = {PUSH_SHA: {"number": 999}} # must be ignored
proc = self._run(env={"GITHUB_EVENT_PATH": path})
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn("reported to weblib/weblib-ci#11", proc.stdout)
self.assertEqual(self.posted_statuses(), [])
class TestStatusPhrase(unittest.TestCase):
"""The unit under the header, without a server."""