merge: the report's body is truth; comments are the changelog
This commit is contained in:
@@ -48,6 +48,19 @@ worse than no alert, because one day it carries a security fix.
|
||||
count as shipped and a break in it would fail the build as a live outage — on a
|
||||
version nobody is running yet.
|
||||
|
||||
- **An unchanged fingerprint froze the bug report's body, not just its comments.** Two
|
||||
questions were sharing one answer. *Have the findings changed?* gates **comments** —
|
||||
they notify, and a daily "still broken, same as yesterday" is what teaches everyone
|
||||
to ignore the one that finally matters. *Is the body still true?* gates the **body** —
|
||||
and editing an issue body notifies nobody on either forge, so keeping it honest is
|
||||
free. Conflated, the report could never be corrected while the findings held steady,
|
||||
and the fingerprint deliberately ignores everything that moves on its own — healthy
|
||||
rows, the hardware-verified column, point releases, and how a row is labelled. The
|
||||
`master` → `27-dev` relabel above would have reached the README and never the issue
|
||||
anybody actually opens. The body is now rewritten whenever it is out of date (after
|
||||
normalising line endings, so a forge round-tripping `\r\n` does not cause a rewrite
|
||||
every run) and comments remain strictly a changelog of real changes.
|
||||
|
||||
- **The compatibility bot filed a new duplicate bug report on every Gitea run.**
|
||||
`find_issue()` skipped pull requests by testing for the *presence* of the
|
||||
`pull_request` key. GitHub omits that key on a plain issue; Gitea sends it as
|
||||
|
||||
@@ -15,6 +15,7 @@ dangerous thing this file can say -- it means "TrueNAS does this now, retire the
|
||||
module" -- and it rests on nothing more than a substring match.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -725,3 +726,83 @@ class TestMasterIsNotTheNextRelease:
|
||||
# ...and the ordinary rows are untouched.
|
||||
assert "| 25.10.4 |" in md
|
||||
assert "| 26.0.0-BETA.3 _(unreleased)_ |" in md
|
||||
|
||||
|
||||
class TestTheBodyIsTruthAndCommentsAreTheChangelog:
|
||||
"""An unchanged fingerprint used to freeze the BODY, not just silence the comments.
|
||||
|
||||
Two different questions were sharing one answer. "Have the findings changed?" gates
|
||||
COMMENTS -- they notify, and a daily "still broken, same as yesterday" is what
|
||||
teaches people to ignore the one that finally matters. But "is the body still
|
||||
true?" gates the BODY, and editing a body notifies nobody, so keeping it honest
|
||||
costs nothing.
|
||||
|
||||
Conflated, an unchanged fingerprint meant the report could never be corrected --
|
||||
and the fingerprint deliberately ignores everything that moves on its own, which
|
||||
includes how a row is LABELLED. Relabelling master `27-dev` would have reached the
|
||||
README and never the issue anybody opens.
|
||||
"""
|
||||
|
||||
ROWS = [{"ref": "master", "unreleased": True,
|
||||
"modules": check_files(with_(**{
|
||||
"plugins/cloud_backup/restic.py":
|
||||
"class ResticConfig:\n cmd: list\n\n"
|
||||
"def get_restic_config(entry, credentials):\n pass\n",
|
||||
}))}]
|
||||
|
||||
def _run(self, monkeypatch, tmp_path, existing_body):
|
||||
calls = []
|
||||
|
||||
def fake(url, token, method="GET", data=None):
|
||||
calls.append((method, url, data))
|
||||
if url.endswith("/issues?state=all&per_page=100&limit=100"):
|
||||
return [{"number": 1, "title": compat_publish.TITLE,
|
||||
"state": "open", "body": existing_body,
|
||||
"pull_request": None}]
|
||||
return {"number": 1}
|
||||
|
||||
monkeypatch.setattr(compat_publish, "_call", fake)
|
||||
matrix = tmp_path / "m.json"
|
||||
matrix.write_text(json.dumps(self.ROWS))
|
||||
compat_publish.main([
|
||||
"prog", "--api", "https://forge/api", "--token", "t",
|
||||
"--matrix", str(matrix)])
|
||||
return calls
|
||||
|
||||
def _writes(self, calls):
|
||||
patched = [c for c in calls if c[0] == "PATCH"]
|
||||
commented = [c for c in calls if c[0] == "POST" and c[1].endswith("/comments")]
|
||||
return patched, commented
|
||||
|
||||
def test_identical_body_and_findings_touches_nothing(self, monkeypatch, tmp_path):
|
||||
body = compat.render_issue(self.ROWS)
|
||||
patched, commented = self._writes(self._run(monkeypatch, tmp_path, body))
|
||||
assert not patched and not commented, "a quiet run must be completely silent"
|
||||
|
||||
def test_a_relabel_refreshes_the_body_but_says_NOTHING(self, monkeypatch, tmp_path):
|
||||
# Same findings (same fingerprint), different rendering -- the exact shape of
|
||||
# the master -> 27-dev relabel.
|
||||
stale = compat.render_issue(self.ROWS).replace("27-dev", "unreleased")
|
||||
assert compat.extract_fingerprint(stale) == compat.fingerprint(self.ROWS)
|
||||
|
||||
patched, commented = self._writes(self._run(monkeypatch, tmp_path, stale))
|
||||
assert patched, "the body was left stale, so the issue keeps telling lies"
|
||||
assert "27-dev" in patched[0][2]["body"]
|
||||
assert not commented, (
|
||||
"a rendering change is not news -- commenting on it is how the bot gets "
|
||||
"muted before the next real finding"
|
||||
)
|
||||
|
||||
def test_a_REAL_findings_change_still_comments(self, monkeypatch, tmp_path):
|
||||
# ...and the fix must not have made it mute.
|
||||
stale = compat.render_issue(self.ROWS).replace(
|
||||
compat.fingerprint(self.ROWS), "0" * 16)
|
||||
patched, commented = self._writes(self._run(monkeypatch, tmp_path, stale))
|
||||
assert patched and commented, "a genuine change must still notify"
|
||||
|
||||
def test_a_body_differing_only_by_CRLF_is_not_rewritten(self, monkeypatch, tmp_path):
|
||||
# Forges round-trip line endings. Without normalising, every run would rewrite
|
||||
# the body -- silent, but it churns updated_at and looks freshly touched daily.
|
||||
body = compat.render_issue(self.ROWS).replace("\n", "\r\n")
|
||||
patched, _ = self._writes(self._run(monkeypatch, tmp_path, body))
|
||||
assert not patched
|
||||
|
||||
+35
-5
@@ -62,6 +62,19 @@ def _call(url, token, method="GET", data=None):
|
||||
return json.load(r) if r.length != 0 else {}
|
||||
|
||||
|
||||
def _same(a, b):
|
||||
"""Is the issue body already what we would write?
|
||||
|
||||
Compared after normalising line endings and trailing space: forges are free to
|
||||
round-trip `\r\n`, and a body that only "differs" by that would be rewritten on
|
||||
every single run -- a silent edit, but a pointless one that churns `updated_at`
|
||||
and makes the issue look freshly touched every morning.
|
||||
"""
|
||||
def norm(s):
|
||||
return "\n".join(line.rstrip() for line in (s or "").replace("\r\n", "\n").split("\n")).strip()
|
||||
return norm(a) == norm(b)
|
||||
|
||||
|
||||
def find_issue(api, token, title):
|
||||
"""The LOWEST-numbered issue with this title, open or closed.
|
||||
|
||||
@@ -124,11 +137,24 @@ def main(argv):
|
||||
have = extract_fingerprint(issue.get("body") or "")
|
||||
n = issue["number"]
|
||||
|
||||
# ── the findings are UNCHANGED: say nothing ──────────────────────────────
|
||||
# ── two different questions, and they were being answered with one answer ────
|
||||
#
|
||||
# This is the whole point. A daily "still broken, same as yesterday" comment is
|
||||
# what taught everyone to ignore the last one.
|
||||
if have == want and issue["state"] == "open":
|
||||
# * IS THE BODY STILL TRUE? -> if not, rewrite it. Editing an issue body
|
||||
# notifies NOBODY on either forge, so keeping it honest is free.
|
||||
# * HAVE THE FINDINGS CHANGED? -> only then comment. Comments DO notify, and a
|
||||
# daily "still broken, same as yesterday" is what teaches everyone to ignore
|
||||
# the one that finally matters.
|
||||
#
|
||||
# Conflating them meant an unchanged FINGERPRINT froze the BODY. The fingerprint
|
||||
# deliberately ignores everything that moves on its own -- healthy rows, the
|
||||
# hardware-verified column, point releases, how a row is LABELLED -- so none of
|
||||
# that could ever reach the report. Relabelling master `27-dev` (it is not the
|
||||
# next release; a red row there was reading as "the version you are about to
|
||||
# install is broken") would have shipped to the README and never to the issue
|
||||
# anybody actually opens.
|
||||
body_is_current = _same(issue.get("body"), body)
|
||||
|
||||
if have == want and issue["state"] == "open" and body_is_current:
|
||||
print(f"#{n} is already current ({want}) — staying quiet")
|
||||
return 0
|
||||
|
||||
@@ -146,8 +172,12 @@ def main(argv):
|
||||
)
|
||||
_call(f"{args.api}/issues/{n}/comments", args.token, "POST", {"body": note})
|
||||
print(f"updated #{n}: {have} -> {want}")
|
||||
else:
|
||||
elif issue["state"] != "open":
|
||||
print(f"reopened #{n}")
|
||||
else:
|
||||
# Same findings, new rendering. Silent by design: nothing has changed that
|
||||
# anybody needs waking up for, but the report should not be telling lies.
|
||||
print(f"#{n}: findings unchanged ({want}); body refreshed silently")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user