Files
MobileGL/scripts/ci/retrace_split_evidence.py
T
swung0x48 bc0111c99e [Test] (.github/workflows, scripts/ci, tools/trace_replay): assert each split leg's transport in-test
- run_trace_case.cmake writes output/split-evidence.txt only after its transport assertions pass
- MGTRACE_WIRE_EVIDENCE (a harness name) turns on the library's counters and requires DrawVbo records on the wire and draws on the server role
- retrace-split's OpenRA legs ask for wire evidence; every split leg, and every inproc retrace row, checks its evidence file with retrace_split_evidence.py
- test_run_trace_case.py runs the real script on canned logs: each assertion goes red on its bad log, and pipe-gates runs it
2026-10-17 14:08:15 -04:00

99 lines
4.3 KiB
Python

#!/usr/bin/env python3
# MobileGL - scripts/ci/retrace_split_evidence.py
# Copyright (c) 2025-2026 MobileGL-Dev
# Licensed under the GNU Lesser General Public License v3.0:
# https://www.gnu.org/licenses/gpl-3.0.txt
# https://www.gnu.org/licenses/lgpl-3.0.txt
# SPDX-License-Identifier: LGPL-3.0-only
# End of Source File Header
"""Check the evidence file a split retrace leg leaves behind.
tools/trace_replay/run_trace_case.cmake writes output/split-evidence.txt only after every
assertion of its MOBILEGL_TRANSPORT block passed: the client log carried the transport marker
(and, under spawn, the separate server pid), a server log existed, no Fatal{ and no MGWIRE-FLOOR
line, and, on the legs that asked for it, draw records on the wire and draws on the server. The
leg's ctest exit status alone cannot tell "the assertions passed" from "the assertions never ran",
so the workflow runs this after ctest: a missing file means the transport block was skipped.
Usage: retrace_split_evidence.py FILE --transport T [--case C] [--backend B] [--require-wire]
"""
import argparse
from pathlib import Path
import sys
def parse(text):
values = {}
for line in text.splitlines():
if '=' in line:
key, value = line.split('=', 1)
values[key.strip()] = value.strip()
return values
def count(values, key):
try:
return int(values.get(key, ''))
except ValueError:
return None
def problems_in(values, transport, case=None, backend=None, require_wire=False):
problems = []
if values.get('transport') != transport:
problems.append(f"transport is '{values.get('transport')}', expected '{transport}'")
if case is not None and values.get('case') != case:
problems.append(f"case is '{values.get('case')}', expected '{case}'")
if backend is not None and values.get('backend') != backend:
problems.append(f"backend is '{values.get('backend')}', expected '{backend}'")
if not values.get('marker'):
problems.append('no transport marker recorded')
if values.get('server_log') != '1':
problems.append('no server log recorded')
for key in ('fatal_lines', 'floor_lines'):
if count(values, key) != 0:
problems.append(f"{key} is '{values.get(key)}', expected 0")
tcp = values.get('control', '-').startswith('tcp://')
if transport == 'spawn' and values.get('second', '-') == '-':
problems.append('spawn leg recorded no server-process sentence')
wire = values.get('wire') == 'ON'
if require_wire and not wire:
problems.append('wire evidence was required and the leg did not collect it')
if wire:
records = count(values, 'wire_draw_records')
if records is None or records < 1:
problems.append(f"wire_draw_records is '{values.get('wire_draw_records')}', expected at least 1")
draws = values.get('server_draws')
if not (tcp and draws == 'n/a'):
if count(values, 'server_draws') is None or count(values, 'server_draws') < 1:
problems.append(f"server_draws is '{draws}', expected at least 1")
return problems
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('evidence', type=Path)
parser.add_argument('--transport', required=True)
parser.add_argument('--case')
parser.add_argument('--backend')
parser.add_argument('--require-wire', action='store_true')
args = parser.parse_args(argv)
if not args.evidence.is_file():
print(f'::error::{args.evidence} does not exist: run_trace_case.cmake writes it only after its '
f'MOBILEGL_TRANSPORT assertions passed, so on this leg they did not run or did not finish')
return 1
values = parse(args.evidence.read_text(errors='replace'))
problems = problems_in(values, args.transport, args.case, args.backend, args.require_wire)
if problems:
print(f'::error::{args.evidence}: ' + '; '.join(problems))
return 1
print(f"split evidence: {values.get('case')} {values.get('backend')} transport={values.get('transport')} "
f"second='{values.get('second')}' wire={values.get('wire')} "
f"wire_draw_records={values.get('wire_draw_records')} server_draws={values.get('server_draws')}")
return 0
if __name__ == '__main__':
sys.exit(main())