feat: mark each pack part with its siblings

This commit is contained in:
Abdessamad Derraz committed 2026-10-23 07:09:43 +02:00
1 parent e86defdb1e
commit c2de7c3a8e
8 files changed
+218 -25

No files matched your search

+9
View File
@@ -111,6 +111,15 @@ class DownloadLinksNameTheFile(unittest.TestCase):
)
self.assertIn("part 1 of 2", cell)
def test_a_split_pack_says_how_many_files_to_download(self):
"""One link taken for the pack left half of it out of the folder."""
self.assertIn(
"Download, 2 files, all needed: ",
download_links("batocera", SPLIT_RECORD, "../..", "Download"),
)
buttons = download_links("batocera", SPLIT_RECORD, REPO, "Pack", " { .x }")
self.assertTrue(buttons.startswith("2 files to download, all needed: "), buttons)
def test_buttons_carry_their_attributes_on_each_part(self):
cell = download_links("batocera", SPLIT_RECORD, REPO, "Pack", " { .x }")
self.assertEqual(cell.count("{ .x }"), 2)
+10 -1
View File
@@ -446,8 +446,11 @@ class DownloaderCase(unittest.TestCase):
"pack staged in the system temp directory",
)
searched = list(self.dest.parent.iterdir()) + list(self.dest.rglob("*"))
# A part's marker is extracted on purpose: install.py --check reads it.
leftovers = sorted(
p.name for p in searched if "_BIOS_Pack" in p.name or p.name.startswith(".")
p.name for p in searched
if ("_BIOS_Pack" in p.name or p.name.startswith("."))
and not split_pack.is_marker(p.name)
)
self.assertEqual(leftovers, [], "download staging left behind")
@@ -573,6 +576,12 @@ class ZipPartsCase(DownloaderCase):
"""A pack published as parts that are each an archive."""
zip_parts = True
# Bytes deflate cannot shrink: each member outweighs the marker a part
# carries, so the pack splits in two.
payload = {
"bios/scph5501.bin": b"".join(hashlib.sha256(b"us%d" % n).digest() for n in range(64)),
"bios/dc/dc_boot.bin": b"".join(hashlib.sha256(b"dc%d" % n).digest() for n in range(64)),
}
class ZipPartsTests:
+8 -3
View File
@@ -341,6 +341,11 @@ class TheTableDescribesTheRelease(unittest.TestCase):
self.assertTrue(f"| RetroArch | {files:,} |" in readme)
def _incompressible(seed: int) -> bytes:
"""960 bytes deflate cannot shrink, so a member outweighs a part marker."""
return b"".join(hashlib.sha256(bytes([seed, n])).digest() for n in range(30))
class ReleaseRecord(unittest.TestCase):
OWNERS = {
"Whole_1.0_BIOS_Pack.zip": ["whole"],
@@ -356,7 +361,7 @@ class ReleaseRecord(unittest.TestCase):
self._pack("Whole_1.0_BIOS_Pack.zip", {"a.bin": b"a" * 10, "README.txt": b"r"})
big = self._pack(
"Demo_2.0_BIOS_Pack.zip",
{f"dir/file{n}.bin": bytes([n]) * 700 + bytes(range(256)) for n in range(6)},
{f"dir/file{n}.bin": _incompressible(n) for n in range(6)},
)
self.parts = split_pack.split_pack(big, limit=big.stat().st_size - 1)
@@ -375,7 +380,7 @@ class ReleaseRecord(unittest.TestCase):
self.assertEqual(record["tag"], "v1")
demo = record["packs"]["Demo_2.0_BIOS_Pack.zip"]
self.assertEqual(demo["files"], 6)
self.assertEqual(demo["extracted_size"], 6 * (700 + 256))
self.assertEqual(demo["extracted_size"], 6 * 960)
self.assertEqual(demo["assets"], [part.name for part in self.parts])
self.assertEqual(
demo["download_size"], sum(part.stat().st_size for part in self.parts)
@@ -399,7 +404,7 @@ class ReleaseRecord(unittest.TestCase):
pack, leaves both: summed, the download size doubled."""
self._pack(
"Demo_2.0_BIOS_Pack.zip",
{f"dir/file{n}.bin": bytes([n]) * 700 + bytes(range(256)) for n in range(6)},
{f"dir/file{n}.bin": _incompressible(n) for n in range(6)},
)
with self.assertRaises(release_record.UnfinishedSplitError):
release_record.build_record(self.dist, "v1", owners=self.OWNERS)
+57
View File
@@ -67,6 +67,8 @@ class SplitFixture(unittest.TestCase):
with zipfile.ZipFile(part) as archive:
self.assertIsNone(archive.testzip())
for name in archive.namelist():
if split_pack.is_marker(name):
continue
self.assertNotIn(name, found, "a file sits in two parts")
found[name] = archive.read(name)
return found
@@ -137,6 +139,61 @@ class PartsAreArchives(SplitFixture):
self.assertTrue(modes["bin/engine"] & stat.S_IXUSR)
class EachPartNamesItsSiblings(SplitFixture):
"""A part extracted alone looked like a complete pack. Each part now
carries a marker naming the pack, its number and every part with its
size, so a missing one can be named from the extracted folder."""
def _markers(self, parts: list[Path]) -> dict[str, str]:
found = {}
for part in parts:
with zipfile.ZipFile(part) as archive:
names = [n for n in archive.namelist() if split_pack.is_marker(n)]
self.assertEqual(len(names), 1, part.name)
found[part.name] = archive.read(names[0]).decode("utf-8")
return found
def test_every_part_carries_one_marker(self):
parts = split_pack.split_pack(self.pack, limit=LIMIT)
count = len(parts)
for index, part in enumerate(parts, 1):
with zipfile.ZipFile(part) as archive:
self.assertIn(f"Demo_1.0_BIOS_Pack.part{index}of{count}.txt",
archive.namelist())
def test_the_marker_names_the_pack_the_part_and_every_size(self):
parts = split_pack.split_pack(self.pack, limit=LIMIT)
count = len(parts)
for index, (name, text) in enumerate(self._markers(parts).items(), 1):
with self.subTest(part=name):
lines = text.splitlines()
self.assertIn("pack: Demo_1.0_BIOS_Pack.zip", lines)
self.assertIn(f"part: {index} of {count}", lines)
listed = [line.split()[1:] for line in lines if line.startswith("file:")]
self.assertEqual(
listed,
[[str(p.stat().st_size), p.name] for p in parts],
)
def test_the_parts_still_fit_with_their_markers(self):
for part in split_pack.split_pack(self.pack, limit=LIMIT):
self.assertLess(part.stat().st_size, LIMIT)
def test_a_pack_holding_a_marker_name_is_refused(self):
members = dict(self.members)
members["Demo_1.0_BIOS_Pack.part1of2.txt"] = b"x"
self.pack.unlink()
self._build(self.pack, members)
with self.assertRaises(ValueError):
split_pack.split_pack(self.pack, limit=LIMIT)
self.assertTrue(self.pack.is_file())
def test_a_marker_is_not_a_part(self):
self.assertTrue(split_pack.is_marker("Demo_BIOS_Pack.part2of3.txt"))
self.assertFalse(split_pack.is_part("Demo_BIOS_Pack.part2of3.txt"))
self.assertFalse(split_pack.is_marker("Demo_BIOS_Pack.part2of3.zip"))
class SplitHoldsTheDirectory(SplitFixture):
"""split_pack read and deleted packs in a directory a build was writing:
the build could truncate the pack under the split, or the split delete