import hashlib import os from pathlib import Path import pytest from app.converter.compatibility import audit_ir, blocking_names from app.converter.rpy_parser import RpyParser, ScriptData from app.games import renpy_builds from app.import_library import check def parse(source): data = ScriptData() data.prescan(source) RpyParser(data, "script.rpy").parse(source) audit_ir(data) return data def test_mist_engine_calls_are_audited_and_waiting_helpers_propagate(): data = parse('init python:\n def video_player():\n renpy.movie_cutscene("[movieplaying]")\n' ' def wait_for_key():\n return ui.interact()\n' 'label start:\n $ video_player()\n $ result = wait_for_key()\n' ' $ renpy.free_memory()\n $ achievement.clear_all()\n return\n') assert not data.blockers assert blocking_names(data) == ["video_player", "wait_for_key"] def test_gallery_and_runtime_thumbnail_declarations_are_supported(): data = parse('init python:\n gallery = Gallery()\n gallery.button("art")\n' ' gallery.image("art")\n' ' renpy.image("thumb", im.Scale(ImageReference("art"), 384, 216))\n' ' SetMixer("ambiant", 0.5)()\nlabel start:\n return\n') assert not data.blockers def test_movie_mask_and_loop_survive_conversion_and_alias_resolution(): from app.converter.pipeline import _resolve_image data = parse('image effect = Movie(play="effect.mpg", mask="mask.mpg", loop=False)\n' 'label start:\n show effect\n return\n') assert data.images["effect"] == {"movie": "effect.mpg", "movieMask": "mask.mpg", "movieLoop": False} missing = set() image = _resolve_image(data.images["effect"], {"effect.mpg": "effect.webm", "mask.mpg": "mask.webm"}, missing) assert image == {"movie": "effect.webm", "movieMask": "mask.webm", "movieLoop": False} assert not missing def test_unknown_movie_mask_cannot_silently_become_an_opaque_movie(): data = parse('image effect = Movie(play="effect.webm", mask=choose_mask())\n' 'label start:\n show effect\n return\n') assert any("маска" in message for message in data.blockers) def test_mist_waivers_do_not_hide_new_or_more_frequent_errors(): data = parse('label start:\n jump robinspent\n jump robinspent\n' ' jump breakfastdialog\n $ unsupported_new_call()\n') waived = renpy_builds.waive_known(data, "renpy/mist-1.0.3") assert waived == ["Не найдена цель перехода: breakfastdialog"] assert data.blockers["Не найдена цель перехода: robinspent"] == 2 assert any("unsupported_new_call" in message for message in data.blockers) def test_build_content_digest_rejects_same_size_modified_script_archive(tmp_path, monkeypatch): archive = tmp_path / "scripts.rpa" archive.write_bytes(b"original") fingerprint = renpy_builds.fingerprint(tmp_path) monkeypatch.setitem(renpy_builds.BUILDS, fingerprint, { "id": "test/content-pinned", "verify": {"scripts.rpa": hashlib.sha256(b"original").hexdigest()}, "defects": {}}) assert renpy_builds.identify(tmp_path) == "test/content-pinned" archive.write_bytes(b"modified") assert renpy_builds.fingerprint(tmp_path) == fingerprint assert renpy_builds.identify(tmp_path) is None @pytest.mark.skipif(not os.environ.get("PGAMES_MIST_ORIGINAL"), reason="Original MIST distribution is local") def test_original_mist_passes_with_only_confirmed_author_defects(): report = check(Path(os.environ["PGAMES_MIST_ORIGINAL"])) assert report["compatible"] and report["hasStart"] assert report["build"] == "renpy/mist-1.0.3" assert len(report["knownDefects"]) == 5 assert not report.get("issues")