Files
EscapistsReintroduced/tests/test_vanilla_game_control.py
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
2026-09-20 16:42:52 +02:00

72 lines
4.9 KiB
Python

"""Vanilla control command/profile/readiness tests; never starts engine or audio."""
from pathlib import Path
import json
import sys
import tempfile
import unittest
from types import SimpleNamespace
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1]/'tools'))
import headless_game as runner
BASE = '''00 ENGINE : Loaded addons:
00 ENGINE : gproj: './addons/core/core.gproj' guid: '5614BBCCBB55ED1C'
00 ENGINE : gproj: './addons/data/ArmaReforger.gproj' guid: '58D0FB3206B6F859'
00 SCRIPT : Module: GameLib; defines: "PLATFORM_WINDOWS"
00 SCRIPT : Module: Game; defines: "PLATFORM_WINDOWS"
00 WORLD : Entities load 'worlds/MainMenuWorld/MainMenuWorld.ent'
00 DEFAULT : Entered main menu.
'''
class VanillaControl(unittest.TestCase):
def test_command_has_no_successor_path_addons_helper_server_or_skip(self):
with patch.object(Path, 'read_text', side_effect=AssertionError('Control must not inspect successor project')):
command=runner.command_for(Path('/source'),Path('/steam'),Path('/private-prefix'),Path('/private-profile'),runner.CONTROL)
for arg in ('-addonsDir','-addons','-errOrdinaryScenario','-world','-server','-config','-noSplash'):
self.assertNotIn(arg,command)
self.assertFalse(any('/source' in part for part in command))
self.assertIn('-noBackend',command);self.assertIn('-noSound',command)
self.assertEqual(command[:3],['gamescope','--backend','headless'])
self.assertEqual(command[command.index('-profile')+1],'Z:/private-profile')
self.assertTrue(any(part.endswith('/ArmaReforgerSteam.exe') for part in command))
def test_unknown_project_refused(self):
with self.assertRaises(ValueError): runner.command_for(Path('/s'),Path('/s'),Path('/p'),Path('/p'),'other')
def test_profile_must_be_fresh_or_control_owned(self):
with tempfile.TemporaryDirectory() as tmp:
p=Path(tmp)/'profile';runner.validate_control_profile(p);self.assertFalse(p.exists())
p.mkdir();(p/runner.PROFILE_MARKER).write_text(json.dumps(runner.PROFILE_ID))
with self.assertRaises(ValueError):runner.validate_control_profile(p)
(p/runner.CONTROL_MARKER).write_text(json.dumps(runner.CONTROL_ID));runner.validate_control_profile(p)
(p/runner.CONTROL_MARKER).unlink();(p/runner.CONTROL_MARKER).symlink_to(p/runner.PROFILE_MARKER)
with self.assertRaises(ValueError):runner.validate_control_profile(p)
def test_strict_main_menu_control_not_mission_success(self):
report=runner.game_evidence(BASE,runner.CONTROL)
self.assertTrue(report['ready']);self.assertTrue(report['control_only'])
for key in ('scenario_header_verified','local_participant_verified','normal_mission_ready'):
self.assertFalse(report[key])
self.assertEqual(report['assertions_passed'],0)
def test_available_inventory_is_not_loaded_addon_evidence(self):
self.assertFalse(runner.vanilla_evidence(BASE.replace('Loaded addons:','Available addons:'))['ready'])
self.assertFalse(runner.vanilla_evidence(BASE.replace("00 ENGINE : gproj: './addons/core/core.gproj' guid: '5614BBCCBB55ED1C'\n",''))['ready'])
def test_unexpected_loaded_addon_fails_even_with_clean_menu(self):
foreign=BASE.replace("00 SCRIPT : Module: GameLib", "00 ENGINE : gproj: '/addon/addon.gproj' guid: '2528466DE220D91C'\n00 SCRIPT : Module: GameLib")
report=runner.vanilla_evidence(foreign);self.assertTrue(report['failed']);self.assertFalse(report['ready'])
def test_no_readiness_without_each_module_world_and_menu(self):
for omitted in ('Module: GameLib;', 'Module: Game;', "Entities load 'worlds/MainMenuWorld/MainMenuWorld.ent'", 'Entered main menu.'):
self.assertFalse(runner.vanilla_evidence(BASE.replace(omitted,'absent'))['ready'],omitted)
def test_errors_wrong_defines_and_successor_activity_fail(self):
for addition in ("00 GUI (E): Unknown class 'SCR_WidgetExportRuleRoot'",'ERR SMOKE PASS: anything','ERR ORDINARY: ready','Reintroduced [mission]: phase 0 -> 1'):
report=runner.vanilla_evidence(BASE+addition);self.assertTrue(report['failed']);self.assertFalse(report['ready'])
for define in ('WORKBENCH','ENF_WB'):
self.assertTrue(runner.vanilla_evidence(BASE.replace('PLATFORM_WINDOWS','PLATFORM_WINDOWS,'+define))['failed'])
def test_control_bound_refusal_never_launches_or_configures_audio(self):
args=SimpleNamespace(steam=Path('/steam'),compat=Path('/prefix'),profile=Path('/profile'),timeout=121,settle=15,failure_grace=0,project=runner.CONTROL)
with patch.object(runner,'assert_no_game'),patch.object(runner.subprocess,'Popen',side_effect=AssertionError('launch')),patch.object(runner.subprocess,'run',side_effect=AssertionError('audio/process')):
with self.assertRaisesRegex(ValueError,'bounded'):runner.run(args)
def test_latched_control_error_cannot_be_overwritten_by_later_menu(self):
monitor=runner.StartupMonitor(0,120,15,5)
self.assertIsNone(monitor.observe(runner.vanilla_evidence(BASE+'00 GUI (E): bad'),1))
self.assertEqual(monitor.observe(runner.vanilla_evidence(BASE),6),'game-failed')
if __name__=='__main__':unittest.main()