diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0bb057c1..7cbdbe4a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,6 +10,7 @@ on: # branch runs the full lane on every push so a phase's landing is not gated on # someone remembering to dispatch the workflow by hand. - feat/disaggregated + - codex/fix-github-ci workflow_dispatch: inputs: baseline_sha: @@ -28,6 +29,7 @@ jobs: contents: read env: BUILD_DIR: build-linux + MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci') && 'ON' || 'OFF' }} CCACHE_BASEDIR: ${{ github.workspace }} CCACHE_COMPRESS: "true" CCACHE_DIR: ${{ github.workspace }}/.ccache @@ -100,6 +102,11 @@ jobs: -DMOBILEGL_BUILD_INTEGRATION_TEST=ON \ -DMOBILEGL_ITEST_VK_ICD=/usr/share/vulkan/icd.d/lvp_icd.json \ -DMOBILEGL_BUILD_TRACE_REPLAY=OFF \ + -DMOBILEGL_BUILD_DISAGGREGATED="${MOBILEGL_CI_DISAGGREGATED}" \ + -DMOBILEGL_BUILD_DISAGGREGATED_INPROC="${MOBILEGL_CI_DISAGGREGATED}" \ + -DMOBILEGL_PIPE_PUSH="${MOBILEGL_CI_DISAGGREGATED}" \ + -DMOBILEGL_PIPE_VERIFY=OFF \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DBENCHMARK_DOWNLOAD_DEPENDENCIES=ON \ -DBENCHMARK_ENABLE_TESTING=OFF \ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 @@ -107,6 +114,14 @@ jobs: - name: Build run: cmake --build "${BUILD_DIR}" --parallel "$(nproc)" + - name: Prove primary runtime compilation and identity + run: | + python3 scripts/ci/runtime_mode_proof.py self-test + mode=monolith + if [ "${MOBILEGL_CI_DISAGGREGATED}" = "ON" ]; then mode=disaggregated; fi + python3 scripts/ci/runtime_mode_proof.py build --build-dir "${BUILD_DIR}" \ + --mode "${mode}" --output "${BUILD_DIR}/runtime-mode.json" + - name: Show ccache stats if: always() run: ccache --show-stats @@ -145,6 +160,7 @@ jobs: --exclude='build.ninja' \ --exclude='cmake_install.cmake' \ -czf ci-artifacts/mobilegl-linux-runtime.tgz \ + "${BUILD_DIR}/runtime-mode.json" \ "${BUILD_DIR}/CTestTestfile.cmake" \ "${BUILD_DIR}/MobileGL/MG_Test" \ "${BUILD_DIR}/MobileGL/MG_Benchmark" \ @@ -158,6 +174,73 @@ jobs: path: ci-artifacts/mobilegl-linux-runtime.tgz if-no-files-found: error + # The pull-library negative control must not consume the primary artifact: + # on disaggregation branches that artifact now contains the real transport. + build-linux-monolith-control: + name: build explicit pull runtime control + runs-on: ubuntu-latest + env: + BUILD_DIR: build-monolith-control + CCACHE_BASEDIR: ${{ github.workspace }} + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_COMPRESS: "true" + CCACHE_NOHASHDIR: "true" + steps: + - name: Set Swap Space + uses: pierotofy/set-swap-space@v1.0 + with: + swap-size-gb: 32 + - name: Checkout repo + uses: actions/checkout@v6 + with: + submodules: recursive + - name: Get CMake + uses: lukka/get-cmake@v4.3.3 + - name: Restore pull compiler cache + uses: actions/cache/restore@v5 + with: + path: .ccache + key: ${{ runner.os }}-test-build-linux-ccache-v1 + - name: Prepare Vulkan SDK + uses: humbletim/setup-vulkan-sdk@v1.2.1 + with: + vulkan-query-version: 1.4.304.1 + vulkan-components: Vulkan-Headers, Vulkan-Loader + vulkan-use-cache: true + - name: Update glslang external sources + working-directory: 3rdparty/glslang + run: python update_glslang_sources.py + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y ccache clang-20 clang++-20 lld-20 libc++-20-dev libc++abi-20-dev libvulkan-dev libegl1-mesa-dev libgles2-mesa-dev libgl1-mesa-dri mesa-vulkan-drivers ninja-build + - name: Configure explicit pull control + run: | + cmake -S . -B "${BUILD_DIR}" -G Ninja \ + -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DCMAKE_BUILD_TYPE=Release -DMOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_INFO \ + -DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF \ + -DMOBILEGL_BUILD_INTEGRATION_TEST=OFF -DMOBILEGL_BUILD_TRACE_REPLAY=OFF \ + -DMOBILEGL_BUILD_DISAGGREGATED=OFF -DMOBILEGL_BUILD_DISAGGREGATED_INPROC=OFF \ + -DMOBILEGL_PIPE_PUSH=OFF -DMOBILEGL_PIPE_VERIFY=OFF \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 + - name: Build and identify pull control + run: | + cmake --build "${BUILD_DIR}" --target MobileGL --parallel "$(nproc)" + python3 scripts/ci/runtime_mode_proof.py build --build-dir "${BUILD_DIR}" \ + --mode monolith --output "${BUILD_DIR}/runtime-mode.json" + - name: Package pull control + run: | + tar -czf mobilegl-linux-monolith-control.tgz \ + "${BUILD_DIR}/libMobileGL.so" "${BUILD_DIR}/runtime-mode.json" + - name: Upload pull control + uses: actions/upload-artifact@v7 + with: + name: mobilegl-linux-monolith-control + path: mobilegl-linux-monolith-control.tgz + if-no-files-found: error + test: runs-on: ubuntu-latest needs: build-linux @@ -215,8 +298,18 @@ jobs: if-no-files-found: ignore integration: + name: integration (${{ matrix.lane }}, ${{ matrix.transport }}) runs-on: ubuntu-latest needs: build-linux + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON((github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci') && '[{"lane":"monolith-control","transport":"monolith"},{"lane":"inproc-acceptance","transport":"inproc"}]' || '[{"lane":"monolith-control","transport":"monolith"}]') }} + env: + MOBILEGL_TRANSPORT: ${{ matrix.transport }} + MOBILEGL_IPC_ROLE_SPLIT_STATE: "1" + MOBILEGL_IPC_STRICT_ERRORS: "1" + MOBILEGL_IPC_RUN_AHEAD: "1" steps: - name: Checkout repo @@ -290,20 +383,43 @@ jobs: # coverage. The flag is NOT baked into the ctest ENVIRONMENT properties, # so an inline env reaches the test processes (unlike the ICD pin above). if [ "${{ secrets.ACTIONS_STEP_DEBUG }}" = "true" ]; then - ctest -V -L integration-gpu --no-tests=error + ctest -V -L integration-gpu --no-tests=error --output-junit "${RUNNER_TEMP}/integration.xml" MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1 ctest -V -L integration-gpu \ - -R 'Buffer|Readback|Atomic|Ssbo|Arena' --no-tests=error + -R 'Buffer|Readback|Atomic|Ssbo|Arena' --no-tests=error --output-junit "${RUNNER_TEMP}/integration-staged-copy.xml" else - ctest --output-on-failure -L integration-gpu --no-tests=error + ctest --output-on-failure -L integration-gpu --no-tests=error --output-junit "${RUNNER_TEMP}/integration.xml" MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1 ctest --output-on-failure -L integration-gpu \ - -R 'Buffer|Readback|Atomic|Ssbo|Arena' --no-tests=error + -R 'Buffer|Readback|Atomic|Ssbo|Arena' --no-tests=error --output-junit "${RUNNER_TEMP}/integration-staged-copy.xml" fi + - name: Prove both backend roles execute inproc in the primary artifact + if: matrix.transport == 'inproc' + working-directory: build-linux + run: | + ctest -L integration-p5f-rsp --show-only=json-v1 > "${RUNNER_TEMP}/primary-inproc-lane.json" + ctest -L integration-p5f-rsp --no-tests=error --output-on-failure \ + --output-junit "${RUNNER_TEMP}/primary-inproc.xml" + python3 ../scripts/ci/runtime_mode_proof.py logs \ + --discovery "${RUNNER_TEMP}/primary-inproc-lane.json" \ + --junit "${RUNNER_TEMP}/primary-inproc.xml" --output "${RUNNER_TEMP}/primary-inproc-proof.json" + + - name: Upload integration lane evidence + if: always() + uses: actions/upload-artifact@v7 + with: + name: integration-evidence-${{ matrix.lane }}-${{ matrix.transport }} + path: | + ${{ runner.temp }}/integration*.xml + ${{ runner.temp }}/primary-inproc* + build-linux/runtime-mode.json + build-linux/MobileGL/MG_IntegrationTest/p5f-rsp-*.log + if-no-files-found: warn + - name: Upload core dumps if: failure() uses: actions/upload-artifact@v7 with: - name: integration-core-dumps + name: integration-core-dumps-${{ matrix.lane }}-${{ matrix.transport }} path: /tmp/core.* if-no-files-found: ignore @@ -739,8 +855,8 @@ jobs: # P5's split build. The verify pair cloned, because the problem is the same one: a third # configuration of the same sources whose whole value depends on the option having taken. # - # IT CANNOT RIDE ON build-linux'S ARTIFACT. build-linux passes no -DMOBILEGL_PIPE_PUSH, so it is - # a PULL build; MOBILEGL_BUILD_DISAGGREGATED implies MOBILEGL_PIPE_PUSH (the split path decodes + # This dedicated build keeps the protocol/strict controls independent of the branch's + # primary runtime mode. MOBILEGL_BUILD_DISAGGREGATED implies MOBILEGL_PIPE_PUSH (the split path decodes # into the MGPipe applier and PIPE_PUSH is what compiles the applier), so the split arm needs a # build of its own exactly as the verify arm does. # @@ -1889,6 +2005,8 @@ jobs: trace-cases: name: trace case matrix runs-on: ubuntu-latest + env: + MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci') && 'ON' || 'OFF' }} needs: - test - benchmark @@ -1905,7 +2023,10 @@ jobs: - name: Load trace cases id: trace-cases run: | - echo "matrix=$(python3 tools/trace_replay/trace_cases.py --ci --format github-test-matrix)" >> "$GITHUB_OUTPUT" + python3 tools/trace_replay/trace_cases.py --self-test-transport-matrices + matrix_format=github-test-matrix + if [ "${MOBILEGL_CI_DISAGGREGATED}" = "ON" ]; then matrix_format=github-test-transport-matrix; fi + echo "matrix=$(python3 tools/trace_replay/trace_cases.py --ci --format "${matrix_format}")" >> "$GITHUB_OUTPUT" echo "names=$(python3 tools/trace_replay/trace_cases.py --ci --format names)" >> "$GITHUB_OUTPUT" # The subset the verify build retraces ("verify": true in trace_cases.json). It is a # SUBSET of the matrix above, so retrace-verify needs no fixtures of its own. @@ -1991,8 +2112,13 @@ jobs: if-no-files-found: error retrace: - name: retrace (${{ matrix.backend }}, ${{ matrix.case }}) + name: retrace (${{ matrix.backend }} / ${{ matrix.lane || 'monolith-control' }} / ${{ matrix.transport || 'monolith' }}, ${{ matrix.case }}) runs-on: ubuntu-latest + env: + MOBILEGL_TRANSPORT: ${{ matrix.transport || 'monolith' }} + MOBILEGL_IPC_ROLE_SPLIT_STATE: "1" + MOBILEGL_IPC_STRICT_ERRORS: "1" + MOBILEGL_IPC_RUN_AHEAD: "1" needs: - build-linux - build-retrace @@ -2082,7 +2208,7 @@ jobs: if: failure() uses: actions/upload-artifact@v7 with: - name: retrace-core-dumps-${{ matrix.backend }}-${{ matrix.case }} + name: retrace-core-dumps-${{ matrix.lane || 'monolith-control' }}-${{ matrix.transport || 'monolith' }}-${{ matrix.backend }}-${{ matrix.case }} path: /tmp/core.* if-no-files-found: ignore @@ -2090,7 +2216,7 @@ jobs: if: always() uses: actions/upload-artifact@v7 with: - name: retrace-result-${{ matrix.backend }}-${{ matrix.case }} + name: retrace-result-${{ matrix.lane || 'monolith-control' }}-${{ matrix.transport || 'monolith' }}-${{ matrix.backend }}-${{ matrix.case }} path: | build-retrace/tools/trace_replay/${{ matrix.case }}/actual-images/** build-retrace/tools/trace_replay/${{ matrix.case }}/${{ matrix.backend }}/output/** @@ -2101,6 +2227,8 @@ jobs: runs-on: ubuntu-latest needs: retrace if: ${{ always() && needs.retrace.result != 'skipped' }} + env: + MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci') && 'ON' || 'OFF' }} steps: - name: Checkout repo @@ -2115,25 +2243,52 @@ jobs: with: node-version: '22' - - name: Download retrace results + - name: Download monolith control results uses: actions/download-artifact@v8 with: - pattern: retrace-result-* - path: retrace-artifacts + pattern: retrace-result-monolith-control-monolith-* + path: retrace-artifacts/monolith-control - - name: Render retrace summary + - name: Download inproc acceptance results + if: env.MOBILEGL_CI_DISAGGREGATED == 'ON' + uses: actions/download-artifact@v8 + with: + pattern: retrace-result-inproc-acceptance-inproc-* + path: retrace-artifacts/inproc-acceptance + + - name: Render monolith control summary run: | node tools/trace_replay/render_retrace_summary.mjs \ - --input retrace-artifacts \ - --output-dir retrace-summary \ - --title "MobileGL Linux retrace overview" \ - --group-label "Linux" \ - --html mobilegl-linux-retrace-overview.html + --input retrace-artifacts/monolith-control \ + --output-dir retrace-summary/monolith-control \ + --title "MobileGL Linux monolith control" \ + --group-label "Linux / monolith control" \ + --html mobilegl-linux-monolith-control.html - - name: Upload retrace summary + - name: Render inproc acceptance summary + if: env.MOBILEGL_CI_DISAGGREGATED == 'ON' + run: | + node tools/trace_replay/render_retrace_summary.mjs \ + --input retrace-artifacts/inproc-acceptance \ + --output-dir retrace-summary/inproc-acceptance \ + --title "MobileGL Linux inproc acceptance" \ + --group-label "Linux / inproc acceptance" \ + --html mobilegl-linux-inproc-acceptance.html + + - name: Upload monolith control summary uses: actions/upload-artifact@v7 with: - path: retrace-summary/mobilegl-linux-retrace-overview.html + name: mobilegl-linux-monolith-control-summary-${{ github.sha }} + path: retrace-summary/monolith-control/mobilegl-linux-monolith-control.html + archive: false + if-no-files-found: error + + - name: Upload inproc acceptance summary + if: env.MOBILEGL_CI_DISAGGREGATED == 'ON' + uses: actions/upload-artifact@v7 + with: + name: mobilegl-linux-inproc-acceptance-summary-${{ github.sha }} + path: retrace-summary/inproc-acceptance/mobilegl-linux-inproc-acceptance.html archive: false if-no-files-found: error @@ -2319,14 +2474,14 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 240 needs: - - build-linux + - build-linux-monolith-control - build-linux-split - build-retrace - trace-cases - trace-fixtures - # build-linux is needed for the negative control ONLY: its pull library is what the control + # The separate OFF/pull artifact is for the negative control ONLY: its library is what the control # swaps in to prove the transport-resolution assertion is load-bearing. - if: ${{ always() && needs.build-linux.result == 'success' && needs.build-linux-split.result == 'success' && needs.build-retrace.result == 'success' && needs.trace-cases.result == 'success' }} + if: ${{ always() && needs.build-linux-monolith-control.result == 'success' && needs.build-linux-split.result == 'success' && needs.build-retrace.result == 'success' && needs.trace-cases.result == 'success' }} strategy: fail-fast: false max-parallel: 4 @@ -2378,13 +2533,13 @@ jobs: - name: Download Linux pull runtime (negative control) uses: actions/download-artifact@v8 with: - name: mobilegl-linux-runtime + name: mobilegl-linux-monolith-control path: pull-runtime - name: Unpack the pull runtime (negative control) run: | - tar -xzf pull-runtime/mobilegl-linux-runtime.tgz -C pull-runtime - test -f pull-runtime/build-linux/libMobileGL.so + tar -xzf pull-runtime/mobilegl-linux-monolith-control.tgz -C pull-runtime + test -f pull-runtime/build-monolith-control/libMobileGL.so - name: Unpack the SPLIT runtime as the library under test # build-retrace's CTestTestfile.cmake has the absolute path @@ -2451,7 +2606,7 @@ jobs: working-directory: build-retrace/tools/trace_replay env: CONTROL_TMPDIR: ${{ runner.temp }} - PULL_LIBRARY: ${{ github.workspace }}/pull-runtime/build-linux/libMobileGL.so + PULL_LIBRARY: ${{ github.workspace }}/pull-runtime/build-monolith-control/libMobileGL.so FROZEN_LIBRARY: ${{ github.workspace }}/build-linux/libMobileGL.so run: >- bash "${GITHUB_WORKSPACE}/scripts/ci/retrace_pull_library_control.sh" diff --git a/scripts/ci/runtime_mode_proof.py b/scripts/ci/runtime_mode_proof.py new file mode 100644 index 00000000..1a49b8ec --- /dev/null +++ b/scripts/ci/runtime_mode_proof.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""Prove the primary runtime's compiled shape and actual inproc GPU execution.""" +import argparse +import hashlib +import importlib.util +import json +from pathlib import Path +import re +import shlex +import subprocess +import tempfile +from unittest.mock import patch + + +ROOT = Path(__file__).resolve().parents[2] +RSP_CASES = {backend + '.Split.P5fRsp.F1WireScenario.EachWireFrameHasZeroResidualPulls' + for backend in ('DirectGLES', 'DirectVulkan')} + + +def build_proof(build, mode): + library = build / 'libMobileGL.so' + commands = json.loads((build / 'compile_commands.json').read_text()) + production = [row for row in commands + if row['file'].replace('\\', '/').endswith('/MobileGL/ConfigLoader.cpp')] + if not production: + raise RuntimeError('no ConfigLoader production compilation found') + definitions = [] + for row in production: + argv = row.get('arguments') or shlex.split(row['command']) + flags = {arg[2:].split('=', 1)[0]: arg[2:].split('=', 1)[1] if '=' in arg else '1' + for arg in argv if arg.startswith('-D')} + expected = mode == 'disaggregated' + if any((flags.get(name) == '1') != expected + for name in ('MOBILEGL_BUILD_DISAGGREGATED', 'MOBILEGL_PIPE_PUSH')): + raise RuntimeError(f'production macros disagree with {mode}: {flags}') + definitions.append({'source': row['file'], 'definitions': flags}) + symbols = subprocess.check_output(['nm', '--defined-only', str(library)], text=True) + if len(symbols.splitlines()) < 1000: + raise RuntimeError('library appears stripped; symbol absence would prove nothing') + remote = sum('MG_Remote' in line for line in symbols.splitlines()) + applier = sum('MGPipeApply' in line for line in symbols.splitlines()) + if (mode == 'disaggregated' and (not remote or not applier)) or (mode == 'monolith' and (remote or applier)): + raise RuntimeError(f'{mode} symbol mismatch: MG_Remote={remote}, MGPipeApply={applier}') + return {'mode': mode, 'source_sha': subprocess.check_output(['git', '-C', str(ROOT), 'rev-parse', 'HEAD'], text=True).strip(), + 'library': str(library), 'sha256': hashlib.sha256(library.read_bytes()).hexdigest(), + 'defined_symbols': len(symbols.splitlines()), 'remote_symbols': remote, 'applier_symbols': applier, + 'production_compilation': definitions} + + +def inproc_log_proof(text): + marker = 'Config: MOBILEGL_TRANSPORT=inproc - the MGPipe record stream' + configs = re.findall(r'Config: IPC[^\r\n]*', text) + required = ('strict=1', 'role-split-state=1', 'run-ahead=1') + fatals = re.findall(r'^.*Fatal\{.*$', text, re.MULTILINE) + if marker not in text or not configs or not all( + all(re.search(r'\b' + re.escape(setting) + r'\b', line) for setting in required) for line in configs) or fatals: + raise RuntimeError('library log does not prove real inproc / strict / role state / zero Fatal') + return {'transport': 'inproc', 'ipc_config_lines': configs, 'fatal_count': 0} + + +def logs_proof(discovery, junit): + spec = importlib.util.spec_from_file_location('split_log_paths', ROOT / 'MobileGL/MG_IntegrationTest/Harness/split_log_paths.py') + helper = importlib.util.module_from_spec(spec) + spec.loader.exec_module(helper) + lane = json.loads(discovery.read_text()) + names = [test['name'] for test in lane['tests']] + if len(names) != 2 or set(names) != RSP_CASES: + raise RuntimeError(f'primary inproc proof must execute both exact backend cases: {names}') + helper.require_green(lane, str(junit), sorted(RSP_CASES)) + logs = helper.marker_log_paths(lane) + proof = {} + for name in sorted(RSP_CASES): + path = Path(logs[name]) + proof[name] = {'private_log': str(path), **inproc_log_proof(path.read_text(encoding='utf-8', errors='replace'))} + return proof + + +def self_test(): + marker = 'Config: MOBILEGL_TRANSPORT=inproc - the MGPipe record stream crosses a real ring' + config = 'Config: IPC run-ahead=1 strict=1 role-split-state=1' + good = marker + '\n' + config + inproc_log_proof(good) + for bad in ('', 'Config: Accepted env variable: MOBILEGL_TRANSPORT=inproc\n' + config, + good.replace('strict=1', 'strict=0'), good.replace('role-split-state=1', 'role-split-state=0'), + good.replace('run-ahead=1', 'run-ahead=0'), good + '\nFatal{ProtocolCorruption, example}'): + try: + inproc_log_proof(bad) + except RuntimeError: + pass + else: + raise AssertionError('invalid runtime log passed') + with tempfile.TemporaryDirectory(prefix='mobilegl-runtime-proof-') as directory: + build = Path(directory) + (build / 'libMobileGL.so').write_bytes(b'checker fixture, not a real runtime') + normal = ''.join(f'00000000 T fixture{i}\n' for i in range(1000)) + remote = normal + '00000000 T MG_Remote_fixture\n00000000 T MGPipeApply_fixture\n' + for mode, flags, symbols, succeeds in ( + ('monolith', '', normal, True), + ('disaggregated', '-DMOBILEGL_BUILD_DISAGGREGATED=1 -DMOBILEGL_PIPE_PUSH=1', remote, True), + ('disaggregated', '-DMOBILEGL_BUILD_DISAGGREGATED=1', remote, False), + ('disaggregated', '-DMOBILEGL_BUILD_DISAGGREGATED=1 -DMOBILEGL_PIPE_PUSH=1', normal, False), + ('monolith', '', remote, False), + ('monolith', '', '00000000 T stripped\n', False), + ): + (build / 'compile_commands.json').write_text(json.dumps([ + {'file': '/fixture/MobileGL/ConfigLoader.cpp', 'command': f'clang++ {flags} -c ConfigLoader.cpp'}])) + with patch('subprocess.check_output', side_effect=lambda args, **kwargs: symbols if args[0] == 'nm' else 'fixture-sha\n'): + try: + build_proof(build, mode) + except RuntimeError: + if succeeds: raise + else: + if not succeeds: raise AssertionError('invalid compiled shape passed') + print('runtime mode proof: compiled-shape and actual-transport positive/negative controls passed') + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest='command', required=True) + sub.add_parser('self-test') + build = sub.add_parser('build') + build.add_argument('--build-dir', type=Path, required=True) + build.add_argument('--mode', choices=('disaggregated', 'monolith'), required=True) + logs = sub.add_parser('logs') + logs.add_argument('--discovery', type=Path, required=True) + logs.add_argument('--junit', type=Path, required=True) + for child in (build, logs): child.add_argument('--output', type=Path, required=True) + args = parser.parse_args() + if args.command == 'self-test': + self_test() + return + proof = build_proof(args.build_dir, args.mode) if args.command == 'build' else logs_proof(args.discovery, args.junit) + args.output.write_text(json.dumps(proof, indent=2), encoding='utf-8') + print(f'{args.command} runtime proof written to {args.output}') + + +if __name__ == '__main__': + main()