chore: merge main into the launcher fixes

This commit is contained in:
Abdessamad Derraz committed 2026-10-23 18:32:08 +02:00
commit e6ce3085b1
56 files changed
+1627 -118

No files matched your search

+4 -1
View File
@@ -5102,7 +5102,10 @@ class EventsDescribeTheRun(unittest.TestCase, _SyntheticTree):
"bytes_total": 245}
for n, (dest, size, total) in enumerate(
(("c.bin", 70, 70), ("d.bin", 25, 95), ("b.bin", 150, 245)), 1)]
self.assertEqual([e for e in events if e["event"] != "progress"],
# The last transfer: every byte came over the network.
self.assertEqual([e for e in events if e["event"] == "transfer"][-1],
{"event": "transfer", "bytes_local": 0, "bytes_network": 245})
self.assertEqual([e for e in events if e["event"] not in ("progress", "transfer")],
self._head("install", installer) + [
{"event": "download", "platform": "retroarch", "files": 3, "bytes": 245},
*done,
+375
View File
@@ -13,11 +13,15 @@ from __future__ import annotations
import contextlib
import copy
import errno
import io
import json
import os
import subprocess
import sys
import time
import unittest
import urllib.error
import zipfile
from pathlib import Path
from unittest import mock
@@ -480,6 +484,377 @@ class MisplacedInTheBiosFolder(Scratch):
self.assertNotIn("copied from", proc.stdout)
def _unprivileged(test: unittest.TestCase) -> None:
if os.name != "posix" or os.geteuid() == 0:
test.skipTest("a file of mode 000 is read here anyway")
class ACopyThatFails(FromFolder):
"""A copy found by the plan that cannot be read when it is copied:
it read as "folder not writable", and a download failing after it lost
the first reason."""
def setUp(self):
super().setUp()
self.entry = json.loads(self.list.read_text())["files"][0]
self.source = self.keep("old/scph5501.bin", US)
self.copy = install.FolderIndex(self.old).find(self.entry)
self.assertIsNotNone(self.copy)
self.addCleanup(install.LOCAL.reset)
self.dest.mkdir()
def test_an_unreadable_source_falls_back_and_names_both_reasons(self):
_unprivileged(self)
self.source.chmod(0)
self.addCleanup(self.source.chmod, 0o644)
refused = urllib.error.URLError(ConnectionRefusedError(111, "Connection refused"))
with mock.patch.object(install, "_retry_delay", return_value=0), \
mock.patch.object(install.urllib.request, "urlopen", side_effect=refused):
dest, ok, reason = install._download_one(self.entry, self.dest, copy=self.copy)
self.assertFalse(ok)
self.assertTrue(reason.startswith(
f"{self.source.resolve()} could not be read: Permission denied, and "), reason)
self.assertNotIn(install.NOT_WRITABLE, reason)
self.assertIsInstance(reason, install.NetworkReason)
def test_a_read_error_is_the_source_and_not_the_folder(self):
class Failing(io.BytesIO):
def read(self, *_args):
raise OSError(errno.EIO, "Input/output error")
for source, words in (
(lambda: Failing(b""), "Input/output error"),
(mock.Mock(side_effect=PermissionError(errno.EACCES, "Permission denied")),
"Permission denied")):
with self.subTest(words=words):
result = install._write_verified(
self.entry, self.dest, self.dest / "scph5501.bin", source, None,
"the copy in E:\\", named="E:\\bios\\a.bin")
self.assertEqual(result, ("scph5501.bin", False,
f"E:\\bios\\a.bin could not be read: {words}"))
self.assertEqual(list(self.dest.iterdir()), [], "no temporary file is left")
def test_a_run_reading_a_pack_falls_back_to_the_pack_only(self):
_unprivileged(self)
self.source.chmod(0)
self.addCleanup(self.source.chmod, 0o644)
install.LOCAL.pack = mock.Mock(find=mock.Mock(return_value=None))
with mock.patch.object(install.urllib.request, "urlopen",
side_effect=AssertionError("network")):
_dest, ok, reason = install._download_one(self.entry, self.dest, copy=self.copy)
install.LOCAL.pack = None
self.assertFalse(ok)
self.assertEqual(reason, f"{self.source.resolve()} could not be read: "
f"Permission denied, and {install.NOT_IN_PACK}")
def _patched_zip(path: Path, name: str, data: bytes, flags: int = 0,
method: int | None = None) -> Path:
"""A stored ZIP of one member, its headers then given other flags or
another compression method: zipfile writes neither encryption nor
methods it cannot read."""
_zip(path, {})
with zipfile.ZipFile(path, "w", zipfile.ZIP_STORED) as archive:
archive.writestr(name, data)
raw = bytearray(path.read_bytes())
for signature, at in ((b"PK\x03\x04", 6), (b"PK\x01\x02", 8)):
start = raw.find(signature)
raw[start + at] |= flags
if method is not None:
raw[start + at + 2:start + at + 4] = method.to_bytes(2, "little")
path.write_bytes(bytes(raw))
return path
class UnreadableCopies(FromFolder):
"""A copy that could not be read was cached as no match and dropped:
the user saw a file to download that the old card held."""
def setUp(self):
super().setUp()
self.keep("scph5500.bin", JP)
self.keep("pcsx/extra.bin", EXTRA)
self.keep("gba_bios.bin", GBA)
def locked(self, relative: str, data: bytes) -> Path:
_unprivileged(self)
path = self.keep(relative, data)
path.chmod(0)
self.addCleanup(path.chmod, 0o644)
return path
def test_a_file_of_mode_000_is_named_in_every_mode(self):
path = self.locked("psx/scph5501.bin", US).resolve()
self.locked("notes.txt", b"not the size of any file")
line = f"{path} could not be read: Permission denied"
for options in (("--check",), ("--dry-run",), ()):
with self.subTest(options=options):
proc = self.install(*options)
self.assertEqual(proc.returncode, 1 if options != ("--dry-run",) else 0,
proc.stdout + proc.stderr)
self.assertIn(line, proc.stdout)
self.assertNotIn("notes.txt", proc.stdout)
def test_an_unreadable_copy_is_an_event(self):
path = self.locked("psx/scph5501.bin", US).resolve()
proc = self.install("--check", "--events")
events = _events(proc)
for event in events:
self.assertEqual(list(event), ["event", *install.EVENT_KEYS[event["event"]]])
self.assertEqual([e for e in events if e["event"] == "unreadable"], [{
"event": "unreadable", "platform": "retroarch", "folder": str(self.old),
"path": str(path), "member": "", "reason": "Permission denied"}])
kinds = [e["event"] for e in events]
self.assertLess(kinds.index("found"), kinds.index("unreadable"))
def test_a_copy_found_elsewhere_leaves_the_unreadable_one_unnamed(self):
self.locked("psx/scph5501.bin", US)
self.keep("psx/copy.bin", US)
proc = self.install("--check")
self.assertNotIn("could not be read", proc.stdout)
def test_a_folder_of_mode_000_is_named(self):
_unprivileged(self)
folder = self.old / "psx"
folder.mkdir()
folder.chmod(0)
self.addCleanup(folder.chmod, 0o755)
proc = self.install("--check")
self.assertIn(f"{folder.resolve()} could not be read: Permission denied", proc.stdout)
def test_members_that_cannot_be_read_say_why(self):
for flags, method, words in (
(0x1, None, "it is encrypted, a password is needed"),
(0, 6, "it is packed with a compression method this installer "
"cannot read")):
with self.subTest(words=words):
archive = _patched_zip(self.old / "psx.zip", "scph5501.bin", US,
flags, method).resolve()
proc = self.install("--check")
self.assertIn(f"scph5501.bin inside {archive} could not be read: {words}",
proc.stdout)
def test_an_input_output_error_is_named(self):
self.keep("psx/scph5501.bin", US)
index = install.FolderIndex(self.old)
entry = json.loads(self.list.read_text())["files"][0]
failing = OSError(errno.EIO, "Input/output error")
with mock.patch.object(install, "_sha1_file", side_effect=failing):
self.assertIsNone(index.find(entry))
found = install.unreadable_copies([entry], {}, [index])
# scph5500.bin holds another region of the same size: read too.
self.assertEqual([item.line() for item in found], [
f"{(self.old / 'psx' / 'scph5501.bin').resolve()} could not be read: "
"Input/output error",
f"{(self.old / 'scph5500.bin').resolve()} could not be read: "
"Input/output error"])
class WhereTheSearchGoes(FromFolder):
"""--from / walked the whole system, /proc and every drive mounted in
it. The search stays on the drive it starts on, and the folder is
checked before it is read, cheaply enough for a screen to do it."""
def test_a_folder_is_checked_without_being_read(self):
archive = _zip(self.root / "backup.zip", {"a": b"a"})
(self.root / "notes.txt").write_text("x")
self.assertIsNone(install.from_folder_problem(str(self.old)))
self.assertIsNone(install.from_folder_problem(str(archive)))
self.assertEqual(install.from_folder_problem(str(self.root / "nowhere")),
("not_found", "does not exist"))
self.assertEqual(install.from_folder_problem(str(self.root / "notes.txt")),
("not_a_folder", "is neither a folder nor a ZIP"))
if os.name == "posix":
self.assertEqual(install.from_folder_problem("/"),
("system", "is the root of the whole system"))
self.assertEqual(install.from_folder_problem("/proc")[0], "system")
def test_a_folder_that_cannot_be_listed_is_refused(self):
_unprivileged(self)
self.old.chmod(0)
self.addCleanup(self.old.chmod, 0o755)
self.assertEqual(install.from_folder_problem(str(self.old)),
("unreadable", "could not be read: Permission denied"))
proc = self.install("--events")
error = next(e for e in _events(proc) if e["event"] == "error")
self.assertEqual((error["stage"], error["reason"]), ("from", "unreadable"))
self.assertEqual(proc.returncode, 1)
@unittest.skipUnless(os.name == "posix", "a POSIX root")
def test_the_root_of_the_system_is_refused(self):
proc = self.run_installer("--manifest", str(self.list), "--from", "/", "--events")
error = next(e for e in _events(proc) if e["event"] == "error")
self.assertEqual((error["stage"], error["reason"]), ("from", "system"))
self.assertIn("/ is the root of the whole system", proc.stderr)
self.assertEqual(proc.returncode, 1)
def lstat_saying(self, folder: str, **fields):
real = os.lstat
def lstat(path, *args, **kwargs):
held = real(path, *args, **kwargs)
if Path(path).name != folder:
return held
values = {"st_dev": held.st_dev, "st_file_attributes": 0, **fields}
return mock.Mock(**values)
return mock.patch.object(install.os, "lstat", side_effect=lstat)
def test_another_drive_inside_is_named_and_not_entered(self):
self.keep("mnt/scph5501.bin", US)
self.keep("scph5500.bin", JP)
with self.lstat_saying("mnt", st_dev=os.lstat(self.old).st_dev + 1):
index = install.FolderIndex(self.old)
self.assertEqual(index.other_drives, [self.old.resolve() / "mnt"])
self.assertEqual(sorted(p.name for p in index.size), ["scph5500.bin"])
def test_a_junction_is_not_entered(self):
self.keep("junction/scph5501.bin", US)
with self.lstat_saying("junction", st_file_attributes=0x400):
index = install.FolderIndex(self.old)
self.assertEqual(index.size, {})
self.assertEqual(index.other_drives, [])
def test_pseudo_file_systems_are_never_entered(self):
if not install.PSEUDO_FOLDERS:
self.skipTest("no pseudo file system here")
index = install.FolderIndex(self.old)
for pseudo in sorted(install.PSEUDO_FOLDERS):
self.assertFalse(index._enters(Path(pseudo), os.lstat("/").st_dev), pseudo)
def test_a_search_cut_short_is_an_event(self):
for n in range(3):
self.keep(f"f{n}.bin", b"%d" % n)
out = io.StringIO()
install.EVENTS.open(out)
self.addCleanup(setattr, install.EVENTS, "stream", None)
self.addCleanup(install.LOCAL.reset)
with mock.patch.object(install, "MAX_SCANNED_FILES", 2), \
contextlib.redirect_stdout(io.StringIO()) as text:
install._open_from_folders([self.old])
self.assertIn(f"Read the first 2 files of {self.old} only", text.getvalue())
self.assertEqual([json.loads(line) for line in out.getvalue().splitlines()], [{
"event": "scanned", "folder": str(self.old), "files": 2, "truncated": True,
"other_drives": []}])
def test_each_folder_read_is_an_event_before_detect(self):
self.keep("scph5501.bin", US)
events = _events(self.install("--check", "--events"))
kinds = [e["event"] for e in events]
scanned = next(e for e in events if e["event"] == "scanned")
self.assertEqual(scanned, {"event": "scanned", "folder": str(self.old), "files": 1,
"truncated": False, "other_drives": []})
self.assertLess(kinds.index("start"), kinds.index("scanned"))
self.assertLess(kinds.index("scanned"), kinds.index("detect"))
class LocalAndNetworkBytes(unittest.TestCase):
"""The engine knows which bytes came from this machine. The interface
guessed it to show a download speed: the transfer event tells it."""
def test_the_counts_follow_each_source_and_each_rollback(self):
out = io.StringIO()
install.EVENTS.open(out)
self.addCleanup(setattr, install.EVENTS, "stream", None)
term = install.Terminal()
term.live, term.quiet = False, True
with mock.patch.object(install, "EVENT_PROGRESS_INTERVAL", 0):
progress = install.Progress(3, 100, term)
progress.add(10, resumed=True) # a partial copy resumed
progress.add(20) # from the network
progress.add(30, resumed=True, network=False) # a copy found here
progress.add(15, network=False) # the pack
progress.add(-30, resumed=True, network=False)
progress.add(-30) # a download dropped,
progress.forget_resumed(10) # its resumed part too
self.assertEqual((progress.bytes, progress.local), (15, 15))
events = [json.loads(line) for line in out.getvalue().splitlines()]
transfers = [e for e in events if e["event"] == "transfer"]
progresses = [e for e in events if e["event"] == "progress"]
self.assertEqual(len(transfers), len(progresses))
self.assertEqual([(t["bytes_local"], t["bytes_network"]) for t in transfers][:4],
[(10, 0), (10, 20), (40, 20), (55, 20)])
for transfer, update in zip(transfers, progresses):
self.assertEqual(transfer["bytes_local"] + transfer["bytes_network"],
update["bytes_done"])
def test_an_install_from_a_folder_and_the_network_splits_them(self):
with FakeRepository() as repo:
old = repo.root / "old"
payloads = FakeRepository.PAYLOADS
old.mkdir()
(old / "a.bin").write_bytes(payloads["scph5500.bin"][0])
(old / "b.bin").write_bytes(payloads["gba_bios.bin"][0])
proc = repo.run("--from", str(old), "--no-input", "--events")
self.assertEqual(proc.returncode, 0, proc.stderr)
events = _events(proc)
local = len(payloads["scph5500.bin"][0]) + len(payloads["gba_bios.bin"][0])
total = sum(len(p[0]) for p in payloads.values())
kinds = [e["event"] for e in events]
last = len(kinds) - 1 - kinds[::-1].index("transfer")
self.assertEqual(events[last], {"event": "transfer", "bytes_local": local,
"bytes_network": total - local})
self.assertLess(kinds.index("download"), last)
self.assertLess(last, kinds.index("summary"))
class AFolderAroundTheBiosFolder(FromFolder):
"""--from names a folder holding the BIOS folder: an archive this run
replaces was a source of members while it was being written."""
def test_an_archive_being_replaced_gives_no_member(self):
new_set = b"the archive the list installs" * 4
inner = b"a rom inside the old archive" * 3
_zip(self.dest / "set.zip", {"inner.bin": inner})
files = [_entry("set.zip", new_set, ["x"]), _entry("inner.bin", inner, ["x"])]
around = install.FolderIndex(self.root)
copies = install.plan_local_copies(files, self.dest, None, [around])
self.assertEqual(copies, {})
_zip(self.old / "copy.zip", {"inner.bin": inner})
copies = install.plan_local_copies(files, self.dest, None,
[install.FolderIndex(self.root)])
self.assertEqual([copies[id(e)].where() for e in files if id(e) in copies],
["old card/copy.zip, inside as inner.bin"])
class ManySameSizedFiles(FromFolder):
"""A card of 16 KB ROMs and a list of 16 KB entries: each entry sorted
and walked every file of its size."""
def test_each_file_is_read_once_and_the_order_holds(self):
for n in range(3000):
self.keep(f"roms/r{n:04}.bin", b"%016d" % n)
_zip(self.old / f"sets/s{n:04}.zip", {f"m{n}.bin": b"m%015d" % n})
entries = [{"dest": f"e{n}.bin", "size": 16, "sha1": f"{n:040}"}
for n in range(3000)]
wanted = [_entry("late.bin", b"%016d" % 2999, ["x"]),
_entry("m5.bin", b"m%015d" % 5, ["x"]),
_entry("r0007.bin", b"%016d" % 7, ["x"])]
index = install.FolderIndex(self.old)
start = time.monotonic()
copies = install.plan_local_copies(entries + wanted, self.dest, None, [index])
elapsed = time.monotonic() - start
self.assertEqual({e["dest"]: copies[id(e)].where() for e in wanted if id(e) in copies}, {
"late.bin": "roms/r2999.bin",
"m5.bin": "sets/s0005.zip, inside as m5.bin",
"r0007.bin": "roms/r0007.bin",
})
self.assertEqual(len(copies), 3)
self.assertLess(elapsed, 30, f"{elapsed:.1f} s")
def test_a_file_of_the_same_name_comes_first(self):
self.keep("a/other.bin", US)
self.keep("b/scph5501.bin", US)
_zip(self.old / "c.zip", {"x/gba.bin": GBA})
_zip(self.old / "d.zip", {"gba_bios.bin": GBA})
index = install.FolderIndex(self.old)
files = json.loads(self.list.read_text())["files"]
copies = install.plan_local_copies(files, self.dest, None, [index])
found = {e["dest"]: copies[id(e)].where() for e in files if id(e) in copies}
self.assertEqual(found["scph5501.bin"], "b/scph5501.bin")
self.assertEqual(found["gba_bios.bin"], "d.zip, inside as gba_bios.bin")
class TheSummaryLine(unittest.TestCase):
def test_the_parts_in_words(self):
line = install.sources_line([("E:\\", 38)], fixed=0, downloaded=4, missing=2)
+53
View File
@@ -19,6 +19,7 @@ import socket
import subprocess
import sys
import tempfile
import time
import unittest
import zipfile
from pathlib import Path
@@ -243,6 +244,58 @@ class CheckNamesWhyAFileIsRefused(Scratch):
self.assertNotIn("opened at", proc.stdout)
class ArchivesInTheBiosFolder(Scratch):
"""The reason an archive gives is the first archive, in the order the
folder is read, that holds the file or carries its name."""
def zipped(self, relative: str, members: dict[str, bytes]) -> None:
path = self.dest / relative
path.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(path, "w") as archive:
for name, data in members.items():
archive.writestr(name, data)
def test_each_reason_names_the_first_archive_that_answers(self):
self.zipped("a.zip", {"x.bin": b"x"})
self.zipped("x.zip", {"other": b"o"})
self.zipped("b.zip", {"other": b"o"})
self.zipped("c.zip", {"bios/b.bin": b"b"})
self.place("y.7z", b"7z")
self.zipped("sub/z.zip", {"Z.BIN": b"z"})
self.zipped("elsewhere/w.zip", {"w.bin": b"w"})
self.zipped("listed.zip", {"v.bin": b"v"})
entries = {dest: _entry(dest, dest.encode() * 3, ["x"]) for dest in (
"x.bin", "b.bin", "y.bin", "sub/z.bin", "w.bin", "v.bin")}
files = [*entries.values(), _entry("listed.zip", b"zip", ["x"])]
found = install.diagnose(list(entries.values()), [], files, self.dest)
self.assertEqual({dest: found.reasons.get(id(e), "") for dest, e in entries.items()}, {
"x.bin": "still zipped: it is inside a.zip",
"b.bin": "still zipped: b.zip needs extracting",
"y.bin": "still zipped: y.7z needs extracting",
"sub/z.bin": "still zipped: it is inside sub/z.zip",
"w.bin": "",
"v.bin": "",
})
def test_many_archives_and_many_missing_files_take_one_pass(self):
"""1,519 ZIPs and 2,000 missing files took 57 seconds: each missing
file walked every archive of the folder."""
self.dest.mkdir()
for n in range(3000):
with zipfile.ZipFile(self.dest / f"set{n:04}.zip", "w") as archive:
archive.writestr(f"rom{n}.bin", b"x")
(self.dest / f"same{n:04}.bin").write_bytes(b"%016d" % n)
missing = [{"dest": f"file{n}.bin", "size": 16 if n % 2 else 1000 + n,
"sha1": f"{n:040}"} for n in range(3000)]
missing.append(_entry("rom7.bin", b"r", ["x"]))
start = time.monotonic()
found = install.diagnose(missing, [], missing, self.dest)
elapsed = time.monotonic() - start
self.assertEqual(found.reasons, {
id(missing[-1]): "still zipped: it is inside set0007.zip"})
self.assertLess(elapsed, 30, f"{elapsed:.1f} s")
def _pack(directory: Path, name: str, members: dict[str, bytes]) -> Path:
"""A pack as generate_pack writes one: the files and a manifest.json."""
listed = [{"path": path, "sha1": _sha1(data), "md5": hashlib.md5(data).hexdigest(),
+117
View File
@@ -2284,6 +2284,17 @@ class TestLineEdits(unittest.TestCase):
with self.assertRaises(YamlWriteError):
find_field_line(SAMPLE, "source_ref", "a.c:10-12", index + 1)
def test_escaped_quotes_are_matched_as_parsed(self):
# etlegacy writes (FS_FOpenFileRead \\"pak0.pk3\\") inside a
# double-quoted source_ref: the line kept its backslashes and never
# equalled the parsed value, so the recale was refused.
value = 'files.c:5076 (FS_FOpenFileRead "pak0.pk3")'
text = SAMPLE.replace('"b.c:5"', '"files.c:5076 (FS_FOpenFileRead \\"pak0.pk3\\")"')
self.assertEqual(yaml.safe_load(text)["files"][1]["source_ref"], value)
moved = value.replace("5076", "5080")
out, _ = replace_field_line(text, "source_ref", value, moved)
self.assertEqual(yaml.safe_load(out)["files"][1]["source_ref"], moved)
def test_source_ref_outside_files_does_not_shift_the_match(self):
text = SAMPLE.replace(
"files:",
@@ -3276,6 +3287,22 @@ class TestCheckVersion(unittest.TestCase):
self.assertIn("already contains release v3.1.0", warning)
class APassedReleaseIsNewerThanTheTag(unittest.TestCase):
"""pcsx2x6 declares tag v0.2.22 while its latest published release is
v0.2.20: the pin holds v0.2.20 without core_version being older."""
def test_a_release_older_than_the_declared_tag_is_not_passed(self):
from unittest import mock # noqa: PLC0415
version = profile_sync.VersionReport(
"v0.2.22", "v0.2.22", "v0.2.20", "2026-07-25", True, "tag22"
)
args = argparse.Namespace(cache_dir="c", offline=True)
with mock.patch.object(profile_sync.upstream, "tag_commit", return_value="rel20"), \
mock.patch.object(profile_sync.upstream, "ancestry", return_value=(5, 0)):
self.assertIsNone(profile_sync._passed_release(args, "repo", version, "pin"))
class TestAncestry(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
@@ -3645,6 +3672,22 @@ class TestCollectCitations(unittest.TestCase):
self.assertEqual(citations[1].parts[0].path, "munt ROMInfo.cpp")
self.assertTrue(profile_sync.is_external_citation("munt ROMInfo.cpp"))
def test_project_word_ending_the_line_before_marks_the_path_external(self):
# genesis_plus_gx_expanded wrapped `(Batocera` and
# `package/batocera/...mk:7` one line apart in its notes, and the
# path was read against the declared repository: 1 to review.
document = {
"notes": "Batocera builds it at a0b5a033\n(Batocera\n"
"package/batocera/emulators/gpgx.mk:7), the same tree.\n"
}
citation = collect_citations(document)[0]
self.assertEqual(
citation.parts[0].path, "Batocera package/batocera/emulators/gpgx.mk"
)
self.assertTrue(profile_sync.is_external_citation(citation.parts[0].path))
sentence = {"notes": "It is read by the file\nsrc/gpgx.c:7 at boot.\n"}
self.assertEqual(collect_citations(sentence)[0].parts[0].path, "src/gpgx.c")
def test_project_word_resolves_inside_a_declared_tree(self):
tree = [
"mupen64plus-video-rice/upstream/src/Config.cpp",
@@ -3914,6 +3957,32 @@ class TestRebaseProse(unittest.TestCase):
"(q.c:9).", yaml.safe_load(content)["quirk_note"]
)
def test_folded_rename_keeps_the_separator_as_written(self):
# boom3 cites libretro.cpp:1635-1643, 2384-2405 in a folded note.
# The rename rewrote the run as one token without the space, and
# the written document no longer matched the intended one.
text = PROSE_SAMPLE.replace(
"files:",
"quirk_note: >\n Passes fs_game\n (q.c:10-12, 30-40). Checked"
" later.\nfiles:",
)
self.path.write_text(text, encoding="utf-8")
parts = [
PartResult(RefPart("q.c", 10, 12, "10-12"), "RENAMED", "src/q.c", 20, 22, []),
PartResult(RefPart("q.c", 30, 40, "30-40"), "RENAMED", "src/q.c", 50, 60, []),
]
applied = rebase_refs(
self.path,
self._report([self._prose_entry("q.c:10-12, 30-40", parts, "quirk_note")]),
)
self.assertEqual(
applied, ["quirk_note: q.c:10-12, 30-40 -> src/q.c:20-22, 50-60"]
)
self.assertIn(
"(src/q.c:20-22, 50-60).",
yaml.safe_load(self.path.read_text())["quirk_note"],
)
def test_parts_disagreeing_on_the_new_file_do_not_move(self):
parts = [
PartResult(RefPart("b.c", 2, 2, "2"), "RENAMED", "src/b.c", 5, 5, []),
@@ -4231,6 +4300,40 @@ class TestRealignProse(unittest.TestCase):
yaml.safe_load(text)["source_commit"], "pin",
)
def test_two_entries_sharing_a_name_keep_their_own_history(self):
# supermodel declares Games.xml once per build mode. Both notes
# answered to files[Games.xml].note, and the history of the libretro
# note decided where the standalone note was written.
sample = (
'emulator: Test\n'
'source: "https://github.com/o/n"\n'
'source_commit: "oldpin"\n'
'files:\n'
' - name: "Games.xml"\n'
' mode: standalone\n'
' note: "Parsed at startup (a.c:10)."\n'
' - name: "Games.xml"\n'
' mode: libretro\n'
' note: "Read through the VFS (b.c:2)."\n'
)
self._git("init", "-q")
self._git("config", "user.email", "t@t")
self._git("config", "user.name", "t")
self._commit(sample, "profile")
# The pin advances and the libretro note is rewritten, uncommitted.
self.path.write_text(
sample.replace('"oldpin"', '"pin"').replace("(b.c:2)", "(b.c:5)"),
encoding="utf-8",
)
self.files[("oldpin", "a.c")] = ["x"] * 9 + ["subject"]
self.files[("pin", "a.c")] = ["x"] * 13 + ["subject"]
self.files[("pin", "b.c")] = ["p"] * 4 + ["read"]
messages = profile_sync.realign_prose(self.path, self.tmp.name, dry_run=True)
self.assertEqual(
messages,
["would recale files[Games.xml@standalone].note: a.c:10 -> a.c:14"],
)
def test_pin_moved_under_committed_text_is_not_guessed(self):
"""A committed pin change under the same text may be a correction.
@@ -4809,6 +4912,20 @@ class UpstreamVendoredAsASubmodule(unittest.TestCase):
self.assertEqual(views[1].head, "nesbox-tip")
class AHeldUpstreamStaysOnItsPin(unittest.TestCase):
"""aps3e vendors RPCS3 1f080485 and yaps2 PCSX2 342db515: a bump moved
upstream_commit to the upstream HEAD neither port carries."""
def test_the_upstream_view_is_read_at_its_pin(self):
views = [
profile_sync.RepoView("fork", "p", "h", "source_commit", "source"),
profile_sync.RepoView("up", "vendored", "tip", "upstream_commit", "upstream"),
]
held = profile_sync._hold_upstream({"upstream_frozen": "vendored tree"}, views)
self.assertEqual((held[0].head, held[1].head), ("h", "vendored"))
self.assertEqual(profile_sync._hold_upstream({}, views), views)
class RealignReadsThePathAsWritten(unittest.TestCase):
"""hatarib cites hatari/src/tos.c, a path of hatariB. When only
upstream_commit moved, realign stripped the hatari/ prefix, read
-1
View File
@@ -37,7 +37,6 @@ UNLINKED: dict[str, str] = {
"dark-forces-ii": "Game Engines > OpenJKDF2",
"fpinball": "Other > future-pinball-fploader",
"fruitmachine": "Other > mfme",
"microsoft-xbox-360": "Other > xenia-edge",
"mysteries-of-the-sith": "Game Engines > OpenJKDF2",
"namco-na1": "Arcade > FBNeo",
"namco-nb1": "Arcade > FBNeo",