Something went wrong. Try again.
Steam Deck Doom modding setup automation for Doom Runner, UZDoom, Brutal Doom, and Project Brutality.
brutal-doom moddb python doom-runner linux-gaming project-brutality uzdoom doom steam-deck
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357from __future__ import annotations
import loggingfrom pathlib import Pathfrom typing import Any, cast
import pytest
from doomdeck.application.doomrunner import ( DoomRunnerLiveConfigSettings, build_doomrunner_iwad_entries, build_doomrunner_options, build_doomrunner_options_model, build_doomrunner_preset, choose_doomrunner_default_iwad, choose_doomrunner_selected_preset, doomrunner_options_paths, doomrunner_quote_arg, launcher_slug, write_doomrunner_live_config,)from doomdeck.application.proton import proton_windows_pathfrom doomdeck.domain.doomrunner import ( DoomRunnerEngine, DoomRunnerIWAD, DoomRunnerMod, DoomRunnerOptions, DoomRunnerPreset, DoomRunnerPresetPaths,)from doomdeck.domain.models import DoomDeckErrorfrom doomdeck.domain.paths import build_dirsfrom doomdeck.domain.presets import EngineSpec, Preset, PresetManifest
def test_doomrunner_options_are_written_to_windows_portable_path_first(tmp_path) -> None: dirs = build_dirs(tmp_path / "Doom")
assert doomrunner_options_paths(dirs) == [ dirs.doomrunner / "options.json", dirs.xdg_data / "DoomRunner" / "options.json", dirs.xdg_config / "DoomRunner" / "options.json", ]
def test_doomrunner_iwad_entries_use_display_names_and_prefer_doom2(tmp_path) -> None: dirs = build_dirs(tmp_path / "Doom") dirs.iwads.mkdir(parents=True) (dirs.iwads / "DOOM.WAD").write_text("", encoding="utf-8") (dirs.iwads / "DOOM2.WAD").write_text("", encoding="utf-8")
entries = build_doomrunner_iwad_entries(dirs)
assert entries == [ {"name": "The Ultimate Doom", "path": proton_windows_path(dirs.iwads / "DOOM.WAD")}, {"name": "Doom II: Hell on Earth", "path": proton_windows_path(dirs.iwads / "DOOM2.WAD")}, ] assert choose_doomrunner_default_iwad(entries, dirs) == proton_windows_path(dirs.iwads / "DOOM2.WAD")
def test_doomrunner_preset_builds_mod_list_and_quoted_launcher_arguments(tmp_path) -> None: dirs = build_dirs(tmp_path / "Doom") preset = { "name": "Project Brutality", "iwad": str(dirs.iwads / "DOOM2.WAD"), "files": [str(dirs.project_brutality / "project-brutality.pk3")], "config": str(dirs.uzdoom_config / "modern config" / "uzdoom.ini"), "autoexec": str(dirs.uzdoom_config / "modern config" / "autoexec.cfg"), }
built = build_doomrunner_preset(dirs, preset)
assert launcher_slug("Project Brutality!") == "Project_Brutality" assert doomrunner_quote_arg(Path('/tmp/quoted "path".cfg')) == '"/tmp/quoted \\"path\\".cfg"' assert built["selected_engine"] == "doomdeck-uzdoom" assert built["mods"] == [{"path": proton_windows_path(dirs.project_brutality / "project-brutality.pk3"), "checked": True}] assert built["alternative_paths"]["save_dir"] == proton_windows_path(dirs.saves / "project_brutality") assert f'-config "{proton_windows_path(dirs.uzdoom_config / "modern config" / "uzdoom.ini")}"' in built["additional_args"] assert f'+exec "{proton_windows_path(dirs.uzdoom_config / "modern config" / "autoexec.cfg")}"' in built["additional_args"] assert "-savedir" not in built["additional_args"]
def test_doomrunner_preset_keeps_raw_wad_paths_for_launching(tmp_path) -> None: dirs = build_dirs(tmp_path / "Doom") wad_path = dirs.pwads / "DTWID.wad" preset = { "name": "Custom WAD", "iwad": str(dirs.iwads / "DOOM2.WAD"), "files": [str(wad_path)], "config": str(dirs.uzdoom_config / "classic" / "uzdoom.ini"), "autoexec": str(dirs.uzdoom_config / "classic" / "autoexec.cfg"), }
built = build_doomrunner_preset(dirs, preset)
assert built["mods"] == [{"path": proton_windows_path(wad_path), "checked": True}] assert "Doom The Way ID Did" not in str(built)
def test_doomrunner_options_select_highest_value_available_preset(tmp_path) -> None: dirs = build_dirs(tmp_path / "Doom") dirs.iwads.mkdir(parents=True) dirs.brutal.mkdir(parents=True) (dirs.iwads / "DOOM2.WAD").write_text("", encoding="utf-8") (dirs.brutal / "brutal-doom.pk3").write_text("", encoding="utf-8") manifest = { "presets": [ { "name": "Vanilla Doom", "iwad": str(dirs.iwads / "DOOM2.WAD"), "files": [], "config": str(dirs.uzdoom_config / "classic" / "uzdoom.ini"), "autoexec": str(dirs.uzdoom_config / "classic" / "autoexec.cfg"), }, { "name": "Brutal Doom", "iwad": str(dirs.iwads / "DOOM2.WAD"), "files": [str(dirs.brutal / "brutal-doom.pk3")], "config": str(dirs.uzdoom_config / "modern" / "uzdoom.ini"), "autoexec": str(dirs.uzdoom_config / "modern" / "autoexec.cfg"), }, ] }
options = build_doomrunner_options(dirs, manifest)
assert choose_doomrunner_selected_preset(options["presets"]) == "Brutal Doom" assert options["selected_preset"] == "Brutal Doom" assert options["engines"]["default_engine"] == "doomdeck-uzdoom" assert options["IWADs"]["default_iwad"] == proton_windows_path(dirs.iwads / "DOOM2.WAD") assert options["engines"]["engine_list"][0]["path"] == proton_windows_path(dirs.uzdoom / "uzdoom.exe") assert options["content_groups"] == manifest.get("content_groups", {}) assert options["video_options"]["resolution_x"] == 1280 assert options["video_options"]["resolution_y"] == 800
def test_doomrunner_options_model_serializes_existing_contract() -> None: options = DoomRunnerOptions( version="1.9.2", engine=DoomRunnerEngine( id="doomdeck-uzdoom", name="UZDoom", path="Z:/doom/source-ports/uzdoom/uzdoom.exe", config_dir="Z:/doom/configs/uzdoom", data_dir="Z:/doom", family="ZDoom", ), iwad_directory="Z:/doom/iwads", default_iwad="Z:/doom/iwads/DOOM2.WAD", iwads=(DoomRunnerIWAD(name="Doom II: Hell on Earth", path="Z:/doom/iwads/DOOM2.WAD"),), maps_directory="Z:/doom/pwads", mods_last_used_dir="Z:/doom/mods", presets=( DoomRunnerPreset( name="Brutal Doom", selected_engine="doomdeck-uzdoom", selected_iwad="Z:/doom/iwads/DOOM2.WAD", mods=(DoomRunnerMod(path="Z:/doom/mods/brutal-doom/brutal-doom.pk3"),), alternative_paths=DoomRunnerPresetPaths( config_dir="Z:/doom/configs/uzdoom/modern", save_dir="Z:/doom/saves/brutal_doom", screenshot_dir="Z:/doom/screenshots/brutal_doom", ), additional_args='-noautoload -config "Z:/doom/configs/uzdoom/modern/uzdoom.ini" +exec "Z:/doom/configs/uzdoom/modern/autoexec.cfg"', ), ), selected_preset="Brutal Doom", content_groups={"mods": []}, screen_width=1280, screen_height=800, )
serialized = options.as_json_object() engines = cast(dict[str, Any], serialized["engines"]) engine_list = cast(list[dict[str, Any]], engines["engine_list"]) iwads = cast(dict[str, Any], serialized["IWADs"]) presets = cast(list[dict[str, Any]], serialized["presets"]) video_options = cast(dict[str, Any], serialized["video_options"]) options_storage = cast(dict[str, Any], serialized["options_storage"])
assert serialized["version"] == "1.9.2" assert engine_list[0]["id"] == "doomdeck-uzdoom" assert iwads["IWAD_list"] == [{"name": "Doom II: Hell on Earth", "path": "Z:/doom/iwads/DOOM2.WAD"}] assert presets[0]["mods"] == [{"path": "Z:/doom/mods/brutal-doom/brutal-doom.pk3", "checked": True}] assert serialized["selected_preset"] == "Brutal Doom" assert serialized["content_groups"] == {"mods": []} assert video_options["resolution_x"] == 1280 assert options_storage["compat_opts"] == 2
def test_doomrunner_options_model_rejects_unknown_selected_preset() -> None: with pytest.raises(DoomDeckError, match="Selected Doom Runner preset"): DoomRunnerOptions( version="1.9.2", engine=DoomRunnerEngine( id="doomdeck-uzdoom", name="UZDoom", path="Z:/doom/source-ports/uzdoom/uzdoom.exe", config_dir="Z:/doom/configs/uzdoom", data_dir="Z:/doom", family="ZDoom", ), iwad_directory="Z:/doom/iwads", default_iwad="Z:/doom/iwads/DOOM2.WAD", iwads=(DoomRunnerIWAD(name="Doom II: Hell on Earth", path="Z:/doom/iwads/DOOM2.WAD"),), maps_directory="Z:/doom/pwads", mods_last_used_dir="Z:/doom/mods", presets=(), selected_preset="Missing", screen_width=1280, screen_height=800, )
def test_doomrunner_options_model_matches_existing_dict_builder(tmp_path) -> None: dirs = build_dirs(tmp_path / "Doom") dirs.iwads.mkdir(parents=True) dirs.brutal.mkdir(parents=True) (dirs.iwads / "DOOM2.WAD").write_text("", encoding="utf-8") (dirs.brutal / "brutal-doom.pk3").write_text("", encoding="utf-8") manifest = { "presets": [ { "name": "Brutal Doom", "iwad": str(dirs.iwads / "DOOM2.WAD"), "files": [str(dirs.brutal / "brutal-doom.pk3")], "config": str(dirs.uzdoom_config / "modern" / "uzdoom.ini"), "autoexec": str(dirs.uzdoom_config / "modern" / "autoexec.cfg"), }, ], "content_groups": {"mods": []}, }
model = build_doomrunner_options_model(dirs, manifest)
assert model.selected_preset == "Brutal Doom" assert model.as_json_object() == build_doomrunner_options(dirs, manifest)
def test_doomrunner_options_omit_modded_presets_without_installed_mod_files(tmp_path) -> None: dirs = build_dirs(tmp_path / "Doom") dirs.iwads.mkdir(parents=True) (dirs.iwads / "DOOM2.WAD").write_text("", encoding="utf-8") manifest = { "presets": [ { "name": "Vanilla Doom", "iwad": str(dirs.iwads / "DOOM2.WAD"), "files": [], "config": str(dirs.uzdoom_config / "classic" / "uzdoom.ini"), "autoexec": str(dirs.uzdoom_config / "classic" / "autoexec.cfg"), }, { "name": "Brutal Doom", "iwad": str(dirs.iwads / "DOOM2.WAD"), "files": [str(dirs.brutal / "brutal-doom.pk3")], "config": str(dirs.uzdoom_config / "modern" / "uzdoom.ini"), "autoexec": str(dirs.uzdoom_config / "modern" / "autoexec.cfg"), }, ] }
options = build_doomrunner_options(dirs, manifest)
preset_names = [preset["name"] for preset in options["presets"]] assert preset_names == ["Vanilla Doom"] assert options["selected_preset"] == "Vanilla Doom"
def test_doomrunner_options_accept_typed_manifest(tmp_path) -> None: dirs = build_dirs(tmp_path / "Doom") dirs.iwads.mkdir(parents=True) dirs.brutal.mkdir(parents=True) (dirs.iwads / "DOOM2.WAD").write_text("", encoding="utf-8") (dirs.brutal / "brutal-doom.pk3").write_text("", encoding="utf-8") manifest = PresetManifest( generated_at="2026-06-12T12:00:00", root=dirs.root, engine=EngineSpec( name="UZDoom", executable=dirs.uzdoom / "uzdoom.exe", family="UZDoom/ZDoom", config_directory=dirs.uzdoom_config, data_directory=dirs.root, ), iwad_directory=dirs.iwads, pwad_directory=dirs.pwads, mod_directories={"brutal_doom": dirs.brutal}, presets=( Preset( name="Brutal Doom", category="Brutal Doom", engine="UZDoom", iwad=dirs.iwads / "DOOM2.WAD", files=(dirs.brutal / "brutal-doom.pk3",), config=dirs.uzdoom_config / "modern" / "uzdoom.ini", autoexec=dirs.uzdoom_config / "modern" / "autoexec.cfg", launcher=dirs.launchers / "Brutal_Doom.sh", ), ), content_groups={"mods": []}, )
options = build_doomrunner_options(dirs, manifest)
assert options["selected_preset"] == "Brutal Doom" assert options["presets"][0]["mods"] == [{"path": proton_windows_path(dirs.brutal / "brutal-doom.pk3"), "checked": True}] assert options["content_groups"] == {"mods": []}
def test_doomrunner_live_config_refuses_to_write_while_doomrunner_is_running(tmp_path) -> None: dirs = build_dirs(tmp_path / "Doom") logger = logging.getLogger("test")
with pytest.raises(DoomDeckError, match="Doom Runner appears to be running"): write_doomrunner_live_config( dirs, {"presets": []}, DoomRunnerLiveConfigSettings(dry_run=False), logger, process_detector=lambda _patterns: True, )
def test_doomrunner_live_config_writes_options_and_preset_directories(tmp_path) -> None: dirs = build_dirs(tmp_path / "Doom") dirs.iwads.mkdir(parents=True) (dirs.iwads / "DOOM2.WAD").write_text("", encoding="utf-8") extra_options_path = tmp_path / "compatdata" / "DoomRunner" / "options.json" logger = logging.getLogger("test") manifest = { "presets": [ { "name": "Brutal Doom", "iwad": str(dirs.iwads / "DOOM2.WAD"), "files": [str(dirs.brutal / "brutal-doom.pk3")], "config": str(dirs.uzdoom_config / "modern" / "uzdoom.ini"), "autoexec": str(dirs.uzdoom_config / "modern" / "autoexec.cfg"), }, ] }
write_doomrunner_live_config( dirs, manifest, DoomRunnerLiveConfigSettings(dry_run=False, extra_options_paths=(extra_options_path,)), logger, process_detector=lambda _patterns: False, )
assert doomrunner_options_paths(dirs)[0].exists() assert doomrunner_options_paths(dirs)[1].exists() assert doomrunner_options_paths(dirs)[2].exists() assert extra_options_path.exists() assert (dirs.saves / "brutal_doom").is_dir() assert (dirs.screenshots / "brutal_doom").is_dir()