From 424af11dce86136a19ae5b78cb64c116c31c7e9a Mon Sep 17 00:00:00 2001 From: Don Petry <36422719+don-petry@users.noreply.github.com> Date: Sun, 8 Nov 2026 22:02:00 -0600 Subject: [PATCH] What Argon's read of #540 left: the unpack cap's exact bound, the redirect row, and the Head tests run as a file (#540) Argon read #540 after it merged (it was out of quota then): - The unpack cap is one check on the whole archive unpacked, so it is cumulative by construction, but its bound was held by nothing: a 5000-byte member against 4096. Now an archive of exactly UNPACKED bytes, two members of 2048 and 2049, is written, and one byte fewer allowed refuses it; the mutant "an archive of exactly the cap is refused" is killed by it. - adapters/AGENTS.md said an artifact's download "alone" follows its redirect, one row under the row that lets a tarball's follow too. It now says the download follows its redirect to the storage alone, as it does. - tests/test_harness.py's Head class stood after the __main__ block, so running the file as a script skipped it; the block is at the end. - Head's docstring said a refused archive is refused whole. unpack stops at the first check that fails and leaves what it wrote for its caller, which harness/read.py removes, as tests/test_steps.py's head_unreadable cases hold; the docstring says so. Co-Authored-By: Claude Opus 5.5 --- adapters/AGENTS.md | 2 +- tests/test_harness.py | 18 ++++++++++++++---- tools/mutants.py | 3 +++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/adapters/AGENTS.md b/adapters/AGENTS.md index 92699cea..5664a35c 100644 --- a/adapters/AGENTS.md +++ b/adapters/AGENTS.md @@ -10,7 +10,7 @@ Not built yet, by the stories that hold their rules: contract.py, binding.py, is | Only `github.py` imports what reaches the network, or names the storage | `tests/test_tools.py::test_only_the_transport_reaches_the_network`, `tests/test_tools.py::test_only_the_transport_names_githubs_artifact_storage` | | It sends HTTPS to GitHub's API alone, by a path from its root | `tests/test_adapters.py::test_a_request_stays_on_githubs_api` | | It follows no redirect but an artifact's download and a tarball's (0138), takes no proxy, bounds every answer, and uses its caller's token | `tests/test_adapters.py::test_the_live_transport_takes_its_token_from_its_caller_and_follows_no_redirect` | -| An artifact's download alone follows its redirect, to GitHub's artifact storage alone, over HTTPS | `tests/test_adapters.py::test_only_an_artifacts_download_follows_its_redirect_and_only_to_githubs_storage` | +| An artifact's download follows its redirect to GitHub's artifact storage alone, over HTTPS | `tests/test_adapters.py::test_only_an_artifacts_download_follows_its_redirect_and_only_to_githubs_storage` | | No token is sent to the storage, and what it answers is bounded | `tests/test_adapters.py::test_the_live_transport_sends_no_token_to_the_storage_and_bounds_what_it_holds` | | A download is bounded at 64 KiB, 1 MiB for a pass record, read out to 8 MiB; a Run's record is found by its pass id | `tests/test_adapters.py::test_a_pass_record_alone_is_downloaded_under_a_wider_bound`, `tests/test_steps.py::test_a_runs_record_is_found` | | The last two passes' records are read, this pass's run passed over; what cannot be read is none, never fewer | `tests/test_adapters.py::test_the_last_two_pass_records_are_read`, `tests/test_adapters.py::test_unreadable_pass_records_are_none` | diff --git a/tests/test_harness.py b/tests/test_harness.py index ff28bd1e..75e4e0d0 100644 --- a/tests/test_harness.py +++ b/tests/test_harness.py @@ -1093,9 +1093,6 @@ def test_the_cli_starts_in_an_empty_directory_with_only_its_environment(self): self.assertEqual(done.stdout, "\ufffd{") -if __name__ == "__main__": - unittest.main() - def resummed(header: bytes, at: int, value: bytes) -> bytes: """A tar header with `value` at `at`, its checksum made its own again.""" @@ -1108,7 +1105,9 @@ def resummed(header: bytes, at: int, value: bytes) -> bytes: class Head(unittest.TestCase): """`harness/head.py`: a pull request's head read as its tarball and written by hand, member by member, into a folder - of its own (decision 0138). Each archive it refuses, it refuses whole, by the check that names why.""" + of its own (decision 0138). Each archive it refuses, it refuses at the first check that fails, by the check that names + why; what it wrote before that is its caller's to remove, as `harness/read.py` removes it + (`tests/test_steps.py`'s cases of `head_unreadable`).""" SHA = "9f1c2d3e4b5a69788796a5b4c3d2e1f0a9b8c7d6" @@ -1254,6 +1253,13 @@ def test_the_unpacker_holds_its_caps_and_writes_nothing_outside_its_folder(self) "more than 4 members") head.MEMBERS, head.UNPACKED = original[0], 4096 self.refused(support.tarball(sha, [("big", b"0", b"x" * 5000)]), "more than 4096 bytes") + # The cap is on the whole archive unpacked, every member's header and padding among it: one of exactly that + # many bytes is written, and one byte fewer allowed refuses it (Argon's read of #540). + data = support.tarball(sha, [("a", b"0", b"x" * 2048), ("b", b"0", b"x" * 2049)]) + head.UNPACKED = len(zlib.decompress(data, 16 + zlib.MAX_WBITS)) + self.unpacked(data) + head.UNPACKED -= 1 + self.refused(data, "more than %d bytes" % head.UNPACKED) finally: head.MEMBERS, head.UNPACKED = original # The folder is new: one that stands already is never written into. @@ -1400,3 +1406,7 @@ def body(*entries, truncated=False): self.assertIsInstance(err, head.Refused) else: self.fail("read, not refused") + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/mutants.py b/tools/mutants.py index 2964fd16..79f6dc8a 100644 --- a/tools/mutants.py +++ b/tools/mutants.py @@ -7096,6 +7096,9 @@ def moved_before(rel: str, block: str, anchor: str) -> tuple: ('a tarball not answered is unpacked', 'harness/head.py', ' if answer.get("status") != 200 or not isinstance(answer.get("body"), str):', ' if not isinstance(answer.get("body"), str):', ('tests.test_harness.Head.test_the_head_of_p60_is_unpacked_as_codeload_served_it',)), + ('an archive of exactly the cap is refused', 'harness/head.py', + ' if len(tar) > UNPACKED or unzip.unconsumed_tail:', + ' if len(tar) >= UNPACKED or unzip.unconsumed_tail:', ('tests.test_harness.Head.test_the_unpacker_holds_its_caps_and_writes_nothing_outside_its_folder',)), ('an archive over the cap is cut short and read', 'harness/head.py', ' if len(tar) > UNPACKED or unzip.unconsumed_tail:', ' if False:', ('tests.test_harness.Head.test_the_unpacker_holds_its_caps_and_writes_nothing_outside_its_folder',)),