73 lines
5.1 KiB
Python
73 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Frozen local Workbench community-statistics fixture; no live server or installed asset writes."""
|
|
from pathlib import Path
|
|
import hashlib,json,shutil,subprocess,sys,os,time,re
|
|
ROOT=Path(__file__).resolve().parents[3]
|
|
sys.path.insert(0,str(ROOT/'tools'))
|
|
from source_manifest import addon_manifest
|
|
from headless_game import assert_prefix_idle
|
|
name=sys.argv[1]
|
|
outcome=sys.argv[2]
|
|
if outcome not in {'victory','defeat'}: raise ValueError('Expected victory or defeat')
|
|
if not name.isalnum(): raise ValueError('Use an alphanumeric run suffix')
|
|
source=Path('/tmp/erri-community-statistics-source-'+name)
|
|
profile=Path('/tmp/erri-community-statistics-run-'+name)
|
|
compat=ROOT/'.local/playtest-compat-20260920'
|
|
assert_prefix_idle(compat)
|
|
source.mkdir();profile.mkdir()
|
|
for rel in addon_manifest(ROOT):
|
|
target=source/rel;target.parent.mkdir(parents=True,exist_ok=True);shutil.copy2(ROOT/rel,target)
|
|
shutil.copytree(ROOT/'tools',source/'tools',ignore=shutil.ignore_patterns('__pycache__'))
|
|
(source/'docs').mkdir();shutil.copy2(ROOT/'docs/external-addons.json',source/'docs/external-addons.json')
|
|
fixture=Path(__file__).with_name('native-fixture.c')
|
|
target=source/'addons/core/Scripts/Game/ERR/00_Core/ERR_ZCommunityStatisticsFixture.c'
|
|
target.write_text(fixture.read_text().replace('TEST_OUTCOME', outcome))
|
|
# Opt-in fixture is independent of the historical generic Core smoke.
|
|
p=source/'addons/core/Scripts/Game/ERR/00_Core/ERR_NativeBaseSmoke.c'
|
|
p.write_text(p.read_text().replace('array<string> independentFixtures = {','array<string> independentFixtures = {"community-statistics", '))
|
|
p=source/'tools/headless_workbench.py'
|
|
p.write_text(p.read_text().replace("choices=['coordinated-ai',", "choices=['community-statistics', 'coordinated-ai',").replace("a.smoke not in {'coordinated-ai',", "a.smoke not in {'community-statistics', 'coordinated-ai',"))
|
|
# The existing fatal-loss diagnostic is guarded by errSmoke; this Core-only
|
|
# runner uses errBaseSmoke. Adapt only that test-helper flag in the frozen copy.
|
|
p=source/'addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerMedical.c'
|
|
p.write_text(p.read_text().replace('System.GetCLIParam("errSmoke", enabled)', 'System.GetCLIParam("errBaseSmoke", enabled)'))
|
|
installed=Path.home()/'.local/share/Steam/steamapps/compatdata/1874880/pfx/drive_c/users/steamuser/Documents/My Games/ArmaReforger/addons'
|
|
(profile/'profile').mkdir()
|
|
(profile/'profile/EscapistsFactions.json').write_text(json.dumps({'escapists':'ussr','enemy':'us','independent':'fia'})+'\n')
|
|
(profiles:=profile/'fixture-inputs.json').write_text(json.dumps({'fixture_sha256':hashlib.sha256(fixture.read_bytes()).hexdigest(),'production_sources':addon_manifest(ROOT),'source':str(source),'profile':str(profile)},indent=2)+'\n')
|
|
command=[sys.executable,str(source/'tools/headless_workbench.py'),'--compat',str(compat),'--profile',str(profile),'--external-addons',str(installed),'--project','core','--smoke','community-statistics','--rounds','1','--timeout','300']
|
|
with (profile/'runner-output.log').open('w') as output:
|
|
result=subprocess.Popen(command,stdout=output,stderr=subprocess.STDOUT)
|
|
captured=False
|
|
while result.poll() is None:
|
|
if not captured and ((profile/'profile/results-ready.json').exists() or any('ERR STATS UI READY' in log.read_text(errors='replace') for log in (profile/'logs').glob('*/console.log'))):
|
|
# The owned runner logs its private Gamescope display. Wine may rewrite its
|
|
# command line, so /proc project-path matching alone can miss the renderer.
|
|
launch=profile/'headless-launch.log'
|
|
displays=re.findall(r'Starting Xwayland on (:\d+)',launch.read_text(errors='replace')) if launch.exists() else []
|
|
if displays:
|
|
try:
|
|
capture=subprocess.run(['import','-display',displays[-1],'-window','root',str(profile/'results-desktop.png')],stdout=subprocess.DEVNULL,stderr=subprocess.PIPE,timeout=5)
|
|
(profile/'capture.log').write_bytes(capture.stderr)
|
|
captured=capture.returncode==0
|
|
except subprocess.TimeoutExpired:
|
|
(profile/'capture.log').write_text('Private display capture timed out.\n')
|
|
for proc in Path('/proc').iterdir():
|
|
if captured: break
|
|
if not proc.name.isdigit(): continue
|
|
try:
|
|
args=(proc/'cmdline').read_bytes().decode(errors='replace').replace('\\', '/')
|
|
if 'ArmaReforgerWorkbenchSteamDiag.exe' not in args or str(source/'addons/core/addon.gproj') not in args: continue
|
|
values=dict(x.split('=',1) for x in (proc/'environ').read_bytes().decode(errors='replace').split('\0') if '=' in x)
|
|
if 'DISPLAY' not in values: continue
|
|
env=os.environ.copy()
|
|
for key in ['DISPLAY','XAUTHORITY']:
|
|
if key in values: env[key]=values[key]
|
|
capture=subprocess.run(['import','-display',values['DISPLAY'],'-window','root',str(profile/'results-desktop.png')],env=env,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,timeout=10)
|
|
if capture.returncode==0: captured=True;break
|
|
except (OSError,ValueError,subprocess.TimeoutExpired): pass
|
|
time.sleep(1)
|
|
print(json.dumps({'exit':result.returncode,'source':str(source),'profile':str(profile)}))
|
|
print('\n'.join((profile/'runner-output.log').read_text().splitlines()[-45:]))
|
|
raise SystemExit(result.returncode)
|