Files
MobileGL/scripts/ci/tcp_log_forwarding_control.py
T

151 lines
8.0 KiB
Python

#!/usr/bin/env python3
"""Run the four real arming scenarios with server log forwarding enabled and disabled."""
import argparse
import errno
import json
import os
from pathlib import Path
import re
import signal
import socket
import subprocess
import time
import xml.etree.ElementTree as ET
CASES = {
'unlocated': ('DirectGLES', 'UnlocatedIoBlockScenario.TheEmulationIsActuallyArmedWhenTheEnvironmentPinsItOn',
{'MOBILEGL_ESPRYT_UNLOCATED_IO_BLOCKS': '1'}, 'WITHOUT their layout(location) qualifier'),
'primitives': ('DirectVulkan', 'PrimitivesGeneratedNoXfbScenario.TheRerouteIsActuallyArmedWhenTheEnvironmentPinsItOn',
{'MOBILEGL_MAGMA_PRIMGEN_QUERY_REROUTE': '1'}, 'PRIMITIVES_GENERATED reroute engaged'),
'point-size': ('DirectGLES', 'PointSizeDemotionScenario.TheDemotionIsActuallyArmedWhenTheEnvironmentPinsItOn',
{'MOBILEGL_POINT_SIZE_DEMOTION': '1'}, 'demoted to an ordinary varying'),
'verify': ('DirectGLES', 'PipeVerifyArmingScenario.Armed',
{'MOBILEGL_PIPE_VERIFY': '1', 'MGITEST_PIPE_ARMING_LANE': '1'}, 'MGPipe: verify armed'),
}
def read(path):
return path.read_text(errors='replace') if path.exists() else ''
def run_arm(build, out, case, forward):
backend, test, knobs, marker = CASES[case]
out.mkdir(parents=True, exist_ok=True)
with socket.socket() as reserve:
reserve.bind(('127.0.0.1', 0))
port = reserve.getsockname()[1]
endpoint = f'tcp://127.0.0.1:{port}'
env = dict(os.environ, MOBILEGL_TRANSPORT='spawn', MOBILEGL_BACKEND_TYPE=backend,
MOBILEGL_IPC_REQUIRE_SAME_BUILD='1', MOBILEGL_IPC_TOKEN='', EGL_PLATFORM='surfaceless',
LIBGL_ALWAYS_SOFTWARE='1', MOBILEGL_ITEST_REQUIRE_GPU='1', **knobs)
for name in ('MOBILEGL_PIPE_VERIFY_CORRUPT', 'MOBILEGL_IPC_CONTROL', 'MOBILEGL_IPC_DIAL'):
env.pop(name, None)
if Path('/usr/share/glvnd/egl_vendor.d/50_mesa.json').exists():
env['__EGL_VENDOR_LIBRARY_FILENAMES'] = '/usr/share/glvnd/egl_vendor.d/50_mesa.json'
if Path('/usr/share/vulkan/icd.d/lvp_icd.json').exists():
env.setdefault('VK_ICD_FILENAMES', '/usr/share/vulkan/icd.d/lvp_icd.json')
peer_env = dict(env, MOBILEGL_IPC_ROLE='server', MOBILEGL_IPC_DIAL='no',
MOBILEGL_IPC_LOG_FORWARD=str(int(forward)), MOBILEGL_LOG_FILE_PATH=str(out / 'peer.log'))
for name in ('MOBILEGL_TRANSPORT', 'MOBILEGL_IPC_SERVER_PATH', 'MOBILEGL_IPC_RING_MB', 'MOBILEGL_IPC_STAGE_MB'):
peer_env.pop(name, None)
with (out / 'supervisor.log').open('wb') as output:
server = subprocess.Popen([str(build / 'libMobileGLServer.so'), endpoint, '--serve'],
env=peer_env, stdout=output, stderr=output, start_new_session=True)
try:
for _ in range(100):
if server.poll() is not None:
raise RuntimeError('supervisor exited before listening: ' + read(out / 'supervisor.log'))
with socket.socket() as probe:
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
probe.bind(('127.0.0.1', port))
except OSError as error:
if error.errno == errno.EADDRINUSE:
break
raise
time.sleep(.05)
else:
raise RuntimeError('supervisor did not listen')
client_env = dict(env, MOBILEGL_IPC_CONTROL=endpoint, MOBILEGL_IPC_DATA='stream',
MOBILEGL_IPC_LOG_FORWARD='1', MOBILEGL_LOG_FILE_PATH=str(out / 'client.log'),
MGITEST_SPLIT_LANE='1', MGITEST_TCP_LANE='1')
result = subprocess.run([str(build / 'MobileGL/MG_IntegrationTest/MobileGLIntegrationTest'),
'--gtest_filter=' + test, '--gtest_output=xml:' + str(out / 'result.xml')],
env=client_env, capture_output=True, text=True, timeout=120)
(out / 'stdout.log').write_text(result.stdout + result.stderr)
finally:
try:
os.killpg(server.pid, signal.SIGTERM)
except ProcessLookupError:
pass
server.wait(timeout=10)
passed = failed = skipped = 0
marker_assertion_failed = False
if (out / 'result.xml').exists():
for entry in ET.parse(out / 'result.xml').getroot().iter('testcase'):
if entry.find('skipped') is not None or entry.get('status') == 'notrun':
skipped += 1
elif entry.find('failure') is not None:
failed += 1
marker_assertion_failed = any(marker in (failure.get('message', '') + (failure.text or ''))
for failure in entry.findall('failure'))
else:
passed += 1
client_log = read(out / 'client.client.log')
return {'case': test, 'returncode': result.returncode, 'passed': passed, 'failed': failed, 'skipped': skipped,
'tcp_armed': bool(re.search(r'control=tcp data=stream server=\S+ pid=[1-9][0-9]*', client_log)),
'marker_in_client': marker in client_log,
'marker_in_server': marker in read(out / 'peer.server.log'),
'marker_forwarded': marker in read(out / 'client.server.log'),
'marker_assertion_failed': marker_assertion_failed,
'output': str(out)}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--build-dir', type=Path, required=True)
parser.add_argument('--verify-build-dir', type=Path)
parser.add_argument('--out', type=Path, required=True)
parser.add_argument('--case', action='append', choices=CASES,
help='Explicit subset; the default runs all four and requires a verify build')
args = parser.parse_args()
selected = args.case or list(CASES)
if 'verify' in selected and not args.verify_build_dir:
parser.error('the verify scenario requires --verify-build-dir configured with MOBILEGL_PIPE_VERIFY=ON')
args.out.mkdir(parents=True, exist_ok=True)
results = {}
for case in selected:
build = args.verify_build_dir if case == 'verify' else args.build_dir
arms = {}
for forward in (True, False):
name = 'forward-on' if forward else 'forward-off'
try:
arms[name] = run_arm(build, args.out / case / name, case, forward)
except Exception as error:
arms[name] = {'error': str(error)}
on, off = arms['forward-on'], arms['forward-off']
if case == 'verify':
# PipeFill owns this marker in the client. Disabling peer forwarding
# must leave it visible; requiring red here would test a false design.
arms['expectation'] = 'client-owned marker remains green with peer forwarding disabled'
arms['proved'] = all(arm.get('passed') == 1 and arm.get('returncode') == 0
and arm.get('tcp_armed') and arm.get('marker_in_client')
for arm in (on, off))
else:
arms['expectation'] = 'server-owned marker passes with forwarding and fails without it'
arms['proved'] = bool(on.get('passed') == 1 and on.get('returncode') == 0
and on.get('tcp_armed') and on.get('marker_in_server') and on.get('marker_forwarded')
and off.get('failed') == 1 and not off.get('skipped') and off.get('tcp_armed')
and off.get('marker_in_server') and not off.get('marker_forwarded')
and off.get('marker_assertion_failed')
and not off.get('marker_in_client'))
results[case] = arms
print(case + ': ' + ('PASS' if arms['proved'] else 'NOT PROVEN'), flush=True)
(args.out / 'summary.json').write_text(json.dumps(results, indent=2))
return 0 if all(result['proved'] for result in results.values()) else 1
if __name__ == '__main__':
raise SystemExit(main())