diff --git a/scripts/cross_reference.py b/scripts/cross_reference.py index 020c6cbe..e8e23f5e 100644 --- a/scripts/cross_reference.py +++ b/scripts/cross_reference.py @@ -102,20 +102,25 @@ def _build_supplemental_index( if dpath.is_dir() and not dpath.name.startswith("."): names.add(dpath.name + "/") names.add(dpath.name.lower() + "/") - import zipfile + names |= _zip_member_names(bios_path) + return names - for zpath in bios_path.rglob("*.zip"): - try: - with zipfile.ZipFile(zpath) as zf: - for member in zf.namelist(): - if not member.endswith("/"): - basename = ( - member.rsplit("/", 1)[-1] if "/" in member else member - ) - names.add(basename) - names.add(basename.lower()) - except (zipfile.BadZipFile, OSError): - pass + +def _zip_member_names(bios_path: Path) -> set[str]: + """Basenames of the files inside every ZIP under bios/, both cases.""" + import zipfile + + names: set[str] = set() + for zpath in bios_path.rglob("*.zip"): + try: + with zipfile.ZipFile(zpath) as zf: + members = [m for m in zf.namelist() if not m.endswith("/")] + except (zipfile.BadZipFile, OSError) as exc: + print(f" WARNING: unreadable archive {zpath}: {exc}", file=sys.stderr) + continue + for member in members: + basename = member.rsplit("/", 1)[-1] + names.update((basename, basename.lower())) return names @@ -177,18 +182,22 @@ def _resolve_source( by_name_lower[canonical.lower()] ): return "bios" - # data/ supplemental index: a directory entry looks for a directory, a - # file entry for a file. - if data_names: - is_directory = fname.endswith("/") or (file_entry or {}).get("type") == "directory" - looked_up = [fname, key] + ([basename, basename.lower()] if basename else []) - if is_directory: - looked_up = [name.rstrip("/") + "/" for name in looked_up] - if any(name in data_names for name in looked_up): - return "data" + if data_names and _data_hit(fname, basename, file_entry, data_names): + return "data" return None +def _data_hit( + fname: str, basename: str | None, file_entry: dict | None, data_names: set[str] +) -> bool: + """Whether data/ or a ZIP holds the name: a directory entry looks for a + directory, a file entry for a file.""" + looked_up = [fname, fname.lower()] + ([basename, basename.lower()] if basename else []) + if fname.endswith("/") or (file_entry or {}).get("type") == "directory": + looked_up = [name.rstrip("/") + "/" for name in looked_up] + return any(name in data_names for name in looked_up) + + def entry_source(f: dict, index: dict) -> str | None: """Where the collection holds a profile entry, or None. diff --git a/scripts/exporter/baseline.py b/scripts/exporter/baseline.py index 42415f93..6fd86c11 100644 --- a/scripts/exporter/baseline.py +++ b/scripts/exporter/baseline.py @@ -38,6 +38,10 @@ def _is_placeholder(name: str) -> bool: return "<" in name or ">" in name or "*" in name +def _field_sets(entry: dict | None) -> dict[str, set[str]]: + return {f: set(_hash_values(entry or {}, f)) for f in HASH_FIELDS} + + @dataclass class NativeFile: """One file as the platform will read it, after correction.""" @@ -67,8 +71,7 @@ class NativeFile: truth's crc32 beside Batocera's md5 for bios7.bin is one dump, and reading it as a contradiction emptied the md5 the frontend checks. """ - truth = {f: set(_hash_values(self.truth or {}, f)) for f in HASH_FIELDS} - plat = {f: set(_hash_values(self.platform or {}, f)) for f in HASH_FIELDS} + truth, plat = _field_sets(self.truth), _field_sets(self.platform) if not any(truth.values()): return "platform" if not any(plat.values()): @@ -76,10 +79,12 @@ class NativeFile: shared = [f for f in HASH_FIELDS if truth[f] and plat[f]] if any(truth[f] & plat[f] for f in shared): return "both" + return "truth" if shared or self._sizes_differ() else "both" + + def _sizes_differ(self) -> bool: t_size = (self.truth or {}).get("size") p_size = (self.platform or {}).get("size") - sizes_differ = isinstance(t_size, int) and isinstance(p_size, int) and t_size != p_size - return "truth" if shared or sizes_differ else "both" + return isinstance(t_size, int) and isinstance(p_size, int) and t_size != p_size def hashes(self, field_name: str) -> list[str]: """Accepted values for a hash, truth first when it has an opinion. diff --git a/scripts/exporter/emudeck_exporter.py b/scripts/exporter/emudeck_exporter.py index ab5ba1ba..b91f19f3 100644 --- a/scripts/exporter/emudeck_exporter.py +++ b/scripts/exporter/emudeck_exporter.py @@ -95,6 +95,14 @@ class Exporter(BaseExporter): return False return super().states(fe, field_name) + def _expected_md5s( + self, systems: dict[str, NativeSystem], name: str, system_id: str + ) -> list[str]: + """What a check must now hold; a refused rewrite keeps EmuDeck's own array.""" + if name in getattr(self, "_withdrawn", {}): + return [] + return self._md5s(systems, system_id) + def _refused_systems(self) -> set[str]: withdrawn = getattr(self, "_withdrawn", {}) return {FUNCTION_HASH_MAP[name] for name in withdrawn if name in FUNCTION_HASH_MAP} @@ -200,9 +208,8 @@ class Exporter(BaseExporter): if name not in defined: issues.append(f"check absent from the output: {name}") continue - md5s = self._md5s(systems, system_id) - if not md5s or name in getattr(self, "_withdrawn", {}): - # A refused rewrite keeps EmuDeck's own array, by design. + md5s = self._expected_md5s(systems, name, system_id) + if not md5s: continue body = next( content[start:end] diff --git a/scripts/exporter/romm_exporter.py b/scripts/exporter/romm_exporter.py index 01cf11ec..29e88c9f 100644 --- a/scripts/exporter/romm_exporter.py +++ b/scripts/exporter/romm_exporter.py @@ -64,7 +64,7 @@ class Exporter(BaseExporter): """ return native_id in SLUG_MAP - def writable(self, fe: NativeFile, require: str = "") -> bool: + def writable(self, fe: NativeFile, require: str = "") -> bool: # noqa: ARG002 """What RomM already ships stays; the conditions gate additions. An entry of theirs that could never verify is still theirs, and the diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index d38a91cc..090c98d4 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -2456,6 +2456,24 @@ def _refuse_for_all_variants(args, parser) -> None: parser.error(f"{flag} is incompatible with --verify-packs") +def _refuse_refresh_data(args, parser) -> None: + """Only the platform pack path refreshes the data directories. + + Elsewhere the cache on disk was packed as it stood, and --offline wins + over the refresh without a word. + """ + if not args.refresh_data: + return + for flag, on in ( + ("--offline", args.offline), + ("--emulator", args.emulator), + ("--system", args.system and not (args.platform or args.all)), + ("--from-md5", args.from_md5 or args.from_md5_file), + ): + if on: + parser.error(f"--refresh-data is incompatible with {flag}") + + def _refuse_unapplied_flags(args, parser) -> None: """Refuse every flag the requested mode would not apply. @@ -2475,18 +2493,7 @@ def _refuse_unapplied_flags(args, parser) -> None: parser.error("--region is incompatible with --manifest-targets") if args.one_per_slot and args.manifest_targets: parser.error("--one-per-slot is incompatible with --manifest-targets") - # Only the platform pack path refreshes the data directories; elsewhere - # the cache on disk was packed as it stood. - if args.refresh_data: - if args.offline: - parser.error("--refresh-data is incompatible with --offline") - for flag, on in ( - ("--emulator", args.emulator), - ("--system", args.system and not (args.platform or args.all)), - ("--from-md5", args.from_md5 or args.from_md5_file), - ): - if on: - parser.error(f"--refresh-data is incompatible with {flag}") + _refuse_refresh_data(args, parser) _refuse_for_all_variants(args, parser) mode = next( diff --git a/scripts/generate_site.py b/scripts/generate_site.py index ae9da32c..39f84098 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -37,7 +37,6 @@ from common import ( load_database, load_emulator_profiles, load_provenance_snapshots, - parse_md5_list, require_yaml, resolve_platform_cores, unique_emulator_profiles, @@ -52,7 +51,7 @@ from nativemode import reads_file_contents yaml = require_yaml() -from generate_readme import compute_coverage, release_totals +from generate_readme import compute_coverage, contributing_text, release_totals from profile_sync import source_ref_values, split_source_ref from provenance_report import build_report import release_record @@ -3108,8 +3107,6 @@ def generate_cross_reference( def generate_contributing() -> str: - from generate_readme import contributing_text - return contributing_text( lambda page: f"{page}.md", "# Contributing - RetroBIOS", "on this site" ) diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index 9dd460e3..70f3b7f2 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -2676,17 +2676,60 @@ def _writing_pairs( written = _writing_pins(revisions, intro_sha, f"{pin_field}_commit") if len(written) > 1: ambiguous.append(f"{pin_field}_commit {' -> '.join(written)}") - moved = [(repo, pin, current) for pin in written if pin != current] - # The current pin among the candidates reads as "no move" for that - # repository, which the other candidates must then agree with. - options.append(moved + ([None] if current in written else [])) + # The current pin among the candidates reads as "no move" (None) for + # that repository, which the other candidates must then agree with. + options.append([ + (repo, pin, current) if pin != current else None for pin in written + ]) readings = [ [pair for pair in combo if pair is not None] for combo in itertools.product(*options) - ] if options else [[]] + ] return readings, ambiguous +def _realign_citation( + citation: Citation, + readings: list[list], + ambiguous: list[str], + cache_dir: str, + offline: bool, +) -> tuple[dict[int, tuple[int, int, str | None]], list[str]]: + """The moves one prose run needs, and what blocks them. + + Several pins under one text: the history cannot say which the text + describes, but when the cited range lands in the same place from every + one of them the question does not matter. When the readings disagree, + moving from the wrong one rewrites a correct citation onto someone + else's code, with nothing to show it happened. + """ + moves: dict[int, tuple[int, int, str | None]] = {} + blocked: list[str] = [] + for index, part in enumerate(citation.parts): + outcomes = { + repr(result): result + for result in ( + _realign_part(part, pairs, cache_dir, offline) if pairs else None + for pairs in readings + ) + } + if len(outcomes) > 1: + blocked.extend( + f"pin moved under this text: {item}; the readings disagree" + for item in ambiguous + ) + continue + outcome = next(iter(outcomes.values())) + if outcome is None: + continue + state, payload = outcome + if state == "ok": + moves[index] = (payload[0], payload[1], None) + else: + blocked.append(payload) + return moves, blocked + + def realign_prose( path: Path, cache_dir: str, offline: bool = False, dry_run: bool = False ) -> list[str]: @@ -2748,34 +2791,7 @@ def realign_prose( readings, ambiguous = _writing_pairs(document, repos, revisions, intro_sha) if not any(readings): continue - moves: dict[int, tuple[int, int, str | None]] = {} - blocked: list[str] = [] - for index, part in enumerate(citation.parts): - # Several pins under one text: the history cannot say which the - # text describes, but when the cited range lands in the same - # place from every one of them the question does not matter. - results = [ - _realign_part(part, pairs, cache_dir, offline) if pairs else None - for pairs in readings - ] - outcomes = {repr(result): result for result in results} - if len(outcomes) > 1: - # Moving from the wrong one rewrites a correct citation onto - # someone else's code, with nothing to show it happened. - blocked.extend( - f"pin moved under this text: {item}; the readings disagree" - for item in ambiguous - ) - continue - outcome = next(iter(outcomes.values())) - if outcome is None: - continue - state, payload = outcome - if state == "ok": - start, end = payload - moves[index] = (start, end, None) - else: - blocked.append(payload) + moves, blocked = _realign_citation(citation, readings, ambiguous, cache_dir, offline) if blocked: # Half a run must not move: the untouched ranges would read as # already realigned when they were never even located. @@ -2911,21 +2927,13 @@ def _check_quota(count: int, offline: bool) -> None: raise SystemExit(1) -def _apply_writes(args, name: str, profile: dict, report: ProfileReport) -> None: - path = Path(args.emulators_dir) / f"{name}.yml" - if not path.is_file(): - return - if args.backfill_commits and report.pin and not profile.get("source_commit"): - if args.dry_run: - print(f"{name}: would write source_commit {report.pin[:7]}") - elif backfill_commit(path, report.pin): - print(f"{name}: source_commit {report.pin[:7]}") - if not (args.rebase_refs or args.bump_commit): - return - # Prose written under an older pin describes that revision: anchored from - # the current pin, a recale or a bump follows someone else's code - # (boom3 FileSystem.cpp:2125, written at b810234e, an unrelated - # OpenFileRead at 132dfddb). It moves first, from the pin it was written at. +def _stale_prose_blocks(name: str, path: Path, args) -> bool: + """Whether prose written under an older pin has to move first. + + Anchored from the current pin, a recale or a bump follows someone else's + code (boom3 FileSystem.cpp:2125, written at b810234e, an unrelated + OpenFileRead at 132dfddb). It moves first, from the pin it was written at. + """ stale = realign_prose(path, args.cache_dir, args.offline, dry_run=True) if stale: print( @@ -2933,6 +2941,26 @@ def _apply_writes(args, name: str, profile: dict, report: ProfileReport) -> None "run --realign-prose or read them before the pin moves", file=sys.stderr, ) + return bool(stale) + + +def _backfill(args, name: str, profile: dict, report: ProfileReport, path: Path) -> None: + """Write the pin a profile without source_commit was written at.""" + if not report.pin or profile.get("source_commit"): + return + if args.dry_run: + print(f"{name}: would write source_commit {report.pin[:7]}") + elif backfill_commit(path, report.pin): + print(f"{name}: source_commit {report.pin[:7]}") + + +def _apply_writes(args, name: str, profile: dict, report: ProfileReport) -> None: + path = Path(args.emulators_dir) / f"{name}.yml" + if not path.is_file(): + return + if args.backfill_commits: + _backfill(args, name, profile, report, path) + if not (args.rebase_refs or args.bump_commit) or _stale_prose_blocks(name, path, args): return with tempfile.TemporaryDirectory() as scratch: # Always work on a copy. A dry run reports what it left there; a real diff --git a/scripts/scraper/fbneo_hash_scraper.py b/scripts/scraper/fbneo_hash_scraper.py index 07fa9718..5d38f11a 100644 --- a/scripts/scraper/fbneo_hash_scraper.py +++ b/scripts/scraper/fbneo_hash_scraper.py @@ -10,6 +10,7 @@ from __future__ import annotations import argparse import json import logging +import os import shutil import subprocess import sys @@ -101,27 +102,7 @@ def _extract_version() -> tuple[str, str]: version = _version_from_resource_h() # Last resort: use GitHub API for latest real release tag if version == "unknown": - try: - import urllib.error - import urllib.request - - req = urllib.request.Request( - "https://api.github.com/repos/finalburnneo/FBNeo/tags?per_page=10", - headers={"User-Agent": "retrobios-scraper/1.0"}, - ) - with urllib.request.urlopen(req, timeout=10) as resp: - import json as json_mod - - tags = json_mod.loads(resp.read()) - for t in tags: - if t["name"] != "latest" and t["name"].startswith("v"): - version = t["name"] - break - except (urllib.error.URLError, OSError) as exc: - raise RuntimeError(f"cannot determine the FBNeo version: {exc}") from exc - if version == "unknown": - # Written as core_version otherwise, and cached for a day. - raise RuntimeError("cannot determine the FBNeo version: no tag, resource.h or release") + version = _version_from_api() sha_result = subprocess.run( ["git", "rev-parse", "HEAD"], @@ -135,6 +116,31 @@ def _extract_version() -> tuple[str, str]: return version, commit +def _version_from_api() -> str: + """The latest real release tag, or an error: "unknown" was written as + core_version and cached for a day.""" + import json as json_mod + import urllib.error + import urllib.request + + headers = {"User-Agent": "retrobios-scraper/1.0"} + token = os.environ.get("GITHUB_TOKEN") + if token: + headers["Authorization"] = f"Bearer {token}" + req = urllib.request.Request( + "https://api.github.com/repos/finalburnneo/FBNeo/tags?per_page=10", headers=headers + ) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + tags = json_mod.loads(resp.read()) + except (urllib.error.URLError, OSError, ValueError) as exc: + raise RuntimeError(f"cannot determine the FBNeo version: {exc}") from exc + for tag in tags: + if tag["name"] != "latest" and tag["name"].startswith("v"): + return tag["name"] + raise RuntimeError("cannot determine the FBNeo version: no tag, resource.h or release") + + def _version_from_resource_h() -> str: """Fallback: parse VER_FULL_VERSION_STR from resource.h.""" resource_h = CLONE_DIR / "src" / "burner" / "resource.h" diff --git a/scripts/validate_schemas.py b/scripts/validate_schemas.py index 46f429a2..752290a0 100644 --- a/scripts/validate_schemas.py +++ b/scripts/validate_schemas.py @@ -268,6 +268,14 @@ def unchecked_formats() -> list[str]: return sorted(used - set(FormatChecker().checkers)) +def _format_errors() -> list[str]: + """jsonschema skips a format it has no checker for: say so, and fail.""" + return [ + f"no checker for format {name}: install \"jsonschema[format-nongpl]\"" + for name in unchecked_formats() + ] + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( @@ -277,17 +285,7 @@ def main() -> int: ) args = parser.parse_args() - unchecked = unchecked_formats() - if unchecked: - # jsonschema skips a format it has no checker for, silently. - print( - f"ERROR no checker for format(s) {', '.join(unchecked)}: " - 'install "jsonschema[format-nongpl]"', - file=sys.stderr, - ) - return 1 - - errors: list[str] = [] + errors: list[str] = _format_errors() errors.extend( _validate_yaml_directory(ROOT / "emulators", "emulator.schema.json") ) diff --git a/scripts/verify.py b/scripts/verify.py index d0169248..453b0443 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -82,6 +82,9 @@ from validation import ( DEFAULT_DB = "database.json" DEFAULT_PLATFORMS_DIR = "platforms" +# The repository's platforms wherever the script runs from, for library calls +# that pass no directory. +_REPO_PLATFORMS = os.path.join(os.path.dirname(__file__), "..", "platforms") DEFAULT_EMULATORS_DIR = "emulators" @@ -1258,7 +1261,7 @@ def verify_emulator( db: dict, standalone: bool = False, regions: list[str] | None = None, - platforms_dir: str | None = None, + platforms_dir: str = _REPO_PLATFORMS, ) -> dict: """Verify files for specific emulator profiles. @@ -1276,9 +1279,7 @@ def verify_emulator( # Build validation index from selected profiles only selected_profiles = {n: p for n, p in selected} validation_index = _build_validation_index(selected_profiles) - data_registry = load_data_dir_registry( - platforms_dir or os.path.join(os.path.dirname(__file__), "..", "platforms") - ) + data_registry = load_data_dir_registry(platforms_dir) details = [] file_status: dict[str, str] = {} @@ -1556,7 +1557,7 @@ def verify_system( db: dict, standalone: bool = False, regions: list[str] | None = None, - platforms_dir: str | None = None, + platforms_dir: str = _REPO_PLATFORMS, ) -> dict: """Verify files for all emulators supporting given system IDs.""" profiles = load_emulator_profiles(emulators_dir) @@ -1729,6 +1730,21 @@ def _refuse_listing_narrowings( parser.error(f"{flag} is incompatible with {listing}") +def _refuse_mode_flags(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None: + """A flag the chosen mode does not read is refused, never ignored.""" + for refused, message in ( + (args.standalone and not (args.emulator or args.system), + "--standalone requires --emulator or --system"), + (args.include_archived and not args.all, "--include-archived requires --all"), + (args.target and not (args.platform or args.all), + "--target requires --platform or --all"), + (args.target and (args.emulator or args.system), + "--target is incompatible with --emulator and --system"), + ): + if refused: + parser.error(message) + + def _run_listing(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None: if args.list_emulators: list_emulator_profiles(args.emulators_dir) @@ -1811,14 +1827,7 @@ def main(): parser.error( "--platform, --all, --emulator, and --system are mutually exclusive" ) - if args.standalone and not (args.emulator or args.system): - parser.error("--standalone requires --emulator or --system") - if args.include_archived and not args.all: - parser.error("--include-archived requires --all") - if args.target and not (args.platform or args.all): - parser.error("--target requires --platform or --all") - if args.target and (args.emulator or args.system): - parser.error("--target is incompatible with --emulator and --system") + _refuse_mode_flags(args, parser) with open(args.db) as f: db = json.load(f) diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index 7d08c39d..ef3b2414 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -576,7 +576,7 @@ class SchemaFormatsAreChecked(unittest.TestCase): @unittest.skipUnless(HAS_JSONSCHEMA, "validating a schema needs jsonschema") def test_a_missing_checker_is_named(self): - import validate_schemas + import validate_schemas # noqa: PLC0415 empty = mock.Mock(checkers={}) with mock.patch.object(validate_schemas, "FormatChecker", return_value=empty): diff --git a/tests/test_contributing_text.py b/tests/test_contributing_text.py index 84a345c0..e4b80660 100644 --- a/tests/test_contributing_text.py +++ b/tests/test_contributing_text.py @@ -10,6 +10,7 @@ import re import sys import unittest from pathlib import Path +from unittest import mock REPO_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(REPO_ROOT / "scripts")) @@ -37,11 +38,9 @@ class CoverageReadsTheGivenProfiles(unittest.TestCase): platform coverage, which read ./emulators whatever was given.""" def test_the_directory_reaches_verify(self): - from unittest import mock # noqa: PLC0415 - seen: list[str] = [] - def fake_verify(config, db, emulators_dir, **_kwargs): + def fake_verify(_config, _db, emulators_dir, **_kwargs): seen.append(emulators_dir) return {"status_counts": {}, "total_files": 0, "undeclared_files": [], "details": []} diff --git a/tests/test_export_counts.py b/tests/test_export_counts.py index 75f16bbd..2cc6da75 100644 --- a/tests/test_export_counts.py +++ b/tests/test_export_counts.py @@ -19,6 +19,10 @@ REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT / "scripts")) from exporter.baseline import NativeFile, NativeSystem, build_native_model # noqa: E402 +from exporter.bizhawk_exporter import Exporter as BizHawk # noqa: E402 +from exporter.misterfpga_exporter import Exporter as Mister # noqa: E402 +from exporter.romm_exporter import Exporter as Romm # noqa: E402 +from scraper.emudeck_scraper import FUNCTION_HASH_MAP # noqa: E402 from exporter.emudeck_exporter import Exporter as EmuDeck # noqa: E402 from exporter.retrobat_exporter import Exporter as RetroBat # noqa: E402 from exporter.retrodeck_exporter import Exporter as RetroDeck # noqa: E402 @@ -130,8 +134,6 @@ class EmuDeckWritesItsCorrections(unittest.TestCase): SCRIPT = "checkPS1BIOS(){\n local hashes=(%s)\n}\n" def _systems(self, corrected: NativeFile, other: NativeFile) -> dict: - from scraper.emudeck_scraper import FUNCTION_HASH_MAP - system_id = FUNCTION_HASH_MAP["checkPS1BIOS"] corrected.native_system = other.native_system = system_id return {system_id: NativeSystem(system_id, files=[corrected, other])} @@ -168,8 +170,6 @@ class EmuDeckWritesItsCorrections(unittest.TestCase): class BizHawkCountsWhatItWrites(unittest.TestCase): def test_an_ambiguous_name_is_not_counted(self): - from exporter.bizhawk_exporter import Exporter as BizHawk - sha_a, sha_b, sha_c = "a" * 40, "b" * 40, "c" * 40 first = NativeFile("bios.bin", "bios.bin", "S1", platform={"sha1": sha_a}, truth={"sha1": sha_b}, corrections=["sha1"]) @@ -183,8 +183,6 @@ class BizHawkCountsWhatItWrites(unittest.TestCase): class MisterCountsWhatItWrites(unittest.TestCase): def test_a_path_the_database_lacks_is_not_counted(self): - from exporter.misterfpga_exporter import Exporter as Mister - held = NativeFile("boot.rom", "NES/boot.rom", "nes", platform={"md5": A}, truth={"md5": B}, corrections=["md5"]) absent = NativeFile("boot.rom", "SNES/boot.rom", "snes", platform={"md5": A}, @@ -260,8 +258,6 @@ class OneFileOneIdentity(unittest.TestCase): class RommKeepsItsOwnKeys(unittest.TestCase): def test_a_same_named_addition_does_not_replace_their_entry(self): """fbneo's 480-byte boot.bin overwrote RomM's Dreamcast boot.bin.""" - from exporter.romm_exporter import Exporter as Romm - theirs = NativeFile("boot.bin", "dc/boot.bin", "dc", platform={"size": 2097152, "md5": A}) ours = NativeFile("boot.bin", "fbneo/boot.bin", "dc", diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 4aad42db..d47b7383 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -2558,7 +2558,9 @@ class TestBumpCommit(unittest.TestCase): text = self.path.read_text(encoding="utf-8") self.path.write_text( "source_commit:\n standalone: \"aaa\"\n libretro: \"bbb\"\n" - + "\n".join(l for l in text.splitlines() if not l.startswith("source_commit")) + + "\n".join( + line for line in text.splitlines() if not line.startswith("source_commit") + ) + "\n", encoding="utf-8", ) diff --git a/tests/test_region.py b/tests/test_region.py index c915d14c..9f7770b7 100644 --- a/tests/test_region.py +++ b/tests/test_region.py @@ -9,7 +9,9 @@ from pathlib import Path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts")) -import region +import common # noqa: E402 +import region # noqa: E402 +from packextras import emulator_region_drops # noqa: E402 class TestVocabulary(unittest.TestCase): @@ -647,10 +649,6 @@ class RegionalRequestsKeepWhatTheCodeLoads(unittest.TestCase): ) def test_each_request_keeps_its_file(self): - sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts")) - import common - from packextras import emulator_region_drops - profiles = common.load_emulator_profiles( os.path.join(os.path.dirname(__file__), "..", "emulators") )