From 2775f0681d63a08c6e724c524550468d1a73b586 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Tue, 27 Oct 2026 01:22:07 +0100 Subject: [PATCH] test(acceptance): count a purge of a listed trash entry that then vanishes The reaper found an abandoned root in the trash and sent the delete. The reply did not confirm it, and the next listing no longer had the node, but the retry loop only counted deletes it sent itself. It waited for a node that was already gone, reported it as never trashed, and the leftover check stopped the run before any case ran. Callers that just found the node in the trash now say so, and the error of the first delete is kept for the warning. --- scripts/fuse-acceptance.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/scripts/fuse-acceptance.py b/scripts/fuse-acceptance.py index 09761ae..263ed1d 100755 --- a/scripts/fuse-acceptance.py +++ b/scripts/fuse-acceptance.py @@ -4014,30 +4014,36 @@ class Janitor: return [ f"{name} ({entry['uid']}) is still in the trash" for entry in matches - if not self.purge(entry["uid"], name) + if not self.purge(entry["uid"], name, listed=True) ] - def purge(self, uid: str, name: str) -> bool: + def purge(self, uid: str, name: str, listed: bool = False) -> bool: """Permanently delete one trashed node the run owns, by uid. By uid and never by name: a name can match something of the user's, a uid cannot. The node has to show up in the trash first, which is also what proves the trash step before it worked, and it has to be gone from - a fresh listing afterwards. + a fresh listing afterwards. `listed` says the caller just found it in + the trash. The server's own answer comes first, though: it only deletes a node that is in the trash, and it answers per uid. A listing is a whole-trash enumeration, which on a large trash can time out on every attempt and would otherwise keep a folder the server would delete on request. """ - with contextlib.suppress(Exception): + last_error = "" + try: reply = self.daemon.command("delete-forever", uid, stdin="yes\n") if re.search(r"\bpermanently deleted 1 item", reply): print(f"[cleanup] permanently deleted {name}") return True + except Exception as error: # noqa: BLE001 - retried, then reported + last_error = f": {error}" deadline = time.monotonic() + self.daemon.timeout - attempted = False - last_error = "" + # A node seen in the trash before that delete and gone after it was + # deleted by it, whatever it answered. Counting it as never sent kept + # waiting for a node that was already gone, and failed the run. + attempted = listed while True: try: if self.daemon.trashed(uid): @@ -4149,7 +4155,7 @@ class Janitor: for entry in trashed: if STALE_ROOT.match(entry["name"]) and entry["name"] not in live_names: print(f"[reap] permanently deleting an abandoned root from the trash: {entry['name']}") - if not orphan.purge(entry["uid"], entry["name"]): + if not orphan.purge(entry["uid"], entry["name"], listed=True): leftovers.append(f"{entry['name']} ({entry['uid']}) is still in the trash") return leftovers