From 81994940c41e11ec517bdf6b3194e85d3ed39517 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 17 Oct 2026 23:19:16 -0400 Subject: [PATCH] [CI] (.github/workflows, docs/ci, docs/Disaggregated, MG_IntegrationTest, MG_IntegrationTest/Harness, scripts/ci, scripts/data): retire the split barrier and map-push negative controls - skip census: lane junit-integration-split/control retired with its step --- .github/workflows/test.yml | 31 +- MobileGL/MG_IntegrationTest/CMakeLists.txt | 2 +- .../Harness/split_log_paths.py | 64 +---- docs/Disaggregated/OPEN-QUESTIONS.md | 2 +- docs/Disaggregated/design/10-phase-shapes.md | 2 +- docs/ci/ci-cleanup-log.md | 3 + scripts/ci/control_smoke_test.sh | 143 ---------- scripts/ci/dualblock_negative_control.sh | 3 +- scripts/ci/split_negative_controls.sh | 216 -------------- .../ci/testdata/split_private_log_smoke.sh | 47 --- scripts/ci/testdata/stub_ctest.sh | 269 ------------------ scripts/ci/wait_boundary_negative_control.py | 105 ------- scripts/data/skip_census_baseline.json | 9 - 13 files changed, 13 insertions(+), 883 deletions(-) delete mode 100644 scripts/ci/control_smoke_test.sh delete mode 100644 scripts/ci/split_negative_controls.sh delete mode 100644 scripts/ci/testdata/split_private_log_smoke.sh delete mode 100755 scripts/ci/testdata/stub_ctest.sh delete mode 100644 scripts/ci/wait_boundary_negative_control.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a31cf62..ebb836ed 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1689,11 +1689,7 @@ jobs: ulimit -c unlimited export MOBILEGL_FLATC_EXECUTABLE="${RUNNER_TEMP}/flatc-build/flatc" test -x "${MOBILEGL_FLATC_EXECUTABLE}" - # The exit code is kept beside the JUnit: the controls step tallies this run as its baseline. - rc=0 - ctest --output-on-failure -L integration-split --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/split-baseline.xml" || rc=$? - echo "${rc}" > "${RUNNER_TEMP}/split-baseline.rc" - [ "${rc}" -eq 0 ] || exit "${rc}" + ctest --output-on-failure -L integration-split --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/split-baseline.xml" python3 ../scripts/ci/junit_tally.py "${RUNNER_TEMP}/split-baseline.xml" --require-split-ran # Exit gate 9.3: the spawn arm green at the same count. It runs the same cases through a # SECOND PROCESS, so a failure here that the line above did not see is a transport fact @@ -1782,21 +1778,6 @@ jobs: print(f"the two arms agree on all {len(a)} entries, name and status") PY - # Negative controls for the verb barrier (MOBILEGL_IPC_VERB_BARRIER=0) and the persistent-map - # push (MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0): each knob must turn its selected entries red. - # The body is scripts/ci/split_negative_controls.sh, so scripts/ci/control_smoke_test.sh can - # run the same lines off a runner. It needs a green baseline with at least one passed entry, - # tallied from the lane step's JUnit and exit code (re-run only when that step never ran), - # and accepts a red only when it carries the control's own diagnostic. - - name: Negative controls (barrier, map push) - if: ${{ !cancelled() }} - working-directory: ${{ env.SPLIT_BUILD_DIR }} - env: - MOBILEGL_ITEST_REQUIRE_GPU: "1" - CONTROL_TMPDIR: ${{ runner.temp }} - SPLIT_BASELINE_JUNIT: ${{ runner.temp }}/split-baseline.xml - run: bash "${GITHUB_WORKSPACE}/scripts/ci/split_negative_controls.sh" - # build-linux ran the server link-closure ratchet on the build these lanes took, with # continue-on-error so that a red there does not skip build-linux's own consumers. - name: Server link-closure ratchet of the reused build @@ -3869,15 +3850,11 @@ jobs: python3 scripts/gen_pipe_field_ownership.py --check python3 scripts/gen_pipe_field_ownership.py --self-test - # Negative controls must themselves be able to go red (ARCHITECTURE.md §13.2). - # control_smoke_test.sh runs the split control script against a stubbed ctest: an unrelated - # failure or an empty selection must make it report FAILED, and the scenarios' real - # diagnostics must make it report PASSED. The Python tests run the trace and TCP lane tooling - # on stubs and synthetic data; test_run_trace_case.py feeds run_trace_case.cmake canned logs. - # No branch guard; no build, GPU or device. + # Negative controls must themselves be able to go red (ARCHITECTURE.md §13.2). The Python + # tests run the trace and TCP lane tooling on stubs and synthetic data; test_run_trace_case.py + # feeds run_trace_case.cmake canned logs. No branch guard; no build, GPU or device. - name: Control and trace-tool smoke tests (R-16) run: | - bash scripts/ci/control_smoke_test.sh python3 scripts/ci/test_trace_infrastructure.py python3 scripts/ci/tcp_lane_tools_test.py python3 tools/trace_replay/test_run_tcp_matrix.py diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index dcff47de..525c6118 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -2410,7 +2410,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED) # running it here captured only the calls made ABOVE this line and silently dropped the F1 # and Ct blocks below it. SplitLogPaths.PrivateAndDistinct is what caught that, naming # F1WireScenario.ClearBufferfiPixels; the generated file now sees every call. - # Python is mandatory for this split integration gate (also used by the CI controls). + # Python is mandatory for this split integration gate. # Do not silently omit the ownership check when the interpreter is unavailable. find_package(Python3 REQUIRED COMPONENTS Interpreter) add_test(NAME SplitLogPaths.PrivateAndDistinct diff --git a/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py b/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py index 96c9d726..99839b56 100644 --- a/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py +++ b/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py @@ -7,8 +7,7 @@ # SPDX-License-Identifier: LGPL-3.0-only # End of Source File Header -"""Validate discovered Split log ownership; read only freshly reset control logs.""" -import os +"""Validate discovered Split log ownership and read the entries' private logs.""" import json from pathlib import Path import re @@ -699,66 +698,7 @@ def main(): f"{' '.join(f'{k}={v}' for k, v in expected.items())}: " f"{['proven', '', '', 'a default differs'][code] if code != 1 else 'FAILED'}") sys.exit(code) - selected = paths(json.loads(Path(sys.argv[2]).read_text())) - selected = {name: path for name, path in selected.items() if re.search(sys.argv[3], name)} - # The SAME exclusion run_control hands ctest -E. Without it this helper and ctest disagree - # about the subject set, and the helper reports an entry ctest never ran as "did not run" - # (P5e, ID-122). ctest -R is POSIX ERE and cannot express the exclusion inline. - _excl = os.environ.get("SPLIT_LOG_EXCLUDE", "") - if _excl: - selected = {n: q for n, q in selected.items() if not re.search(_excl, n)} - if not selected: - raise ValueError(f"integration-split: empty selection for {sys.argv[3]}") - if mode == "reset": - for path in selected.values(): - Path(path).unlink(missing_ok=True) - elif mode == "results": - cases = ET.parse(sys.argv[4]).getroot().findall(".//testcase") - label = sys.argv[5] - by_name = {} - for case in cases: - by_name.setdefault(case.get("name"), []).append(case) - skipped = sum(any(c.find("skipped") is not None for c in by_name.get(n, [])) - for n in selected) - # ID-62: pre-flight Fatal is not evidence that a selected entry ran. - if skipped: - raise ValueError(f"{label} control: the knob killed the pre-flight, not the entry - " - f"{skipped} selected entries skipped") - missing = sum(len(by_name.get(n, [])) != 1 or - by_name[n][0].get("status") in ("notrun", "disabled") for n in selected) - if missing: - raise ValueError(f"{label} control: {missing} selected entries did not run") - not_failed = sum(c.find("failure") is None or c.get("status") != "fail" - for n in selected for c in by_name[n]) - if not_failed: - raise ValueError(f"{label} control: {not_failed} selected entries did not fail") - elif mode == "assertion": - cases = ET.parse(sys.argv[4]).getroot().findall(".//testcase") - by_name = {case.get("name"): case for case in cases} - for name in selected: - case = by_name.get(name) - output = "" if case is None else " ".join(" ".join(case.itertext()).split()) - if not re.search(sys.argv[5], output): - raise ValueError(f"E3(a) FAILED: {name} red lacks its persistent-map push diagnostic") - elif mode == "evidence": - missing = [] - label = sys.argv[5] if len(sys.argv) > 5 else "" - for name, path in selected.items(): - if any_role_file(path) and re.search(sys.argv[4], read_role_logs(path)): - print(f"private-log evidence: {name}: {path}") - else: - missing.append(f"{name} ({path})") - if missing: - if label: - raise ValueError(f"{label} FAILED: no selected private log carries /{sys.argv[4]}/. " - "The library's own line is the only channel for this: ctest's " - "transcript is a FALSE ZERO for library output, because the console " - "sink is compiled out of the configurations these lanes run. " - + ", ".join(missing)) - raise ValueError("E1 FAILED: selected private logs lack expected Fatal{BarrierViolation, \"\"} line: " - + ", ".join(missing)) - else: - raise ValueError(f"unknown mode: {mode}") + raise ValueError(f"unknown mode: {mode}") if __name__ == "__main__": diff --git a/docs/Disaggregated/OPEN-QUESTIONS.md b/docs/Disaggregated/OPEN-QUESTIONS.md index 52bf6540..3705db09 100644 --- a/docs/Disaggregated/OPEN-QUESTIONS.md +++ b/docs/Disaggregated/OPEN-QUESTIONS.md @@ -27,4 +27,4 @@ P0 已回答的不再列出(spike A 的域、spike B 的分档、`posix_spawn` 21. **配对 / 认证的形式。** **已裁定(ID-P7-3,2026-09-22):令牌足够,不做 TLS。** 威胁模型是「任何人都能连的远程代码路径」而不是窃听:令牌常量时间比较、≥16 字节、无令牌只 loopback、数据面以 `Welcome.dataNonce` 绑定到已认证的控制连接、fork 前认证。TLS 作为 P12 的开放项保留。 22. **client 平台范围。** 默认先 Linux + Android(现有 CI 能验),Windows client 第二批(`ShmSegmentWin32` 在;`pipe:` 具名拒绝作废、TCP 替代),macOS 仍不拆分。这决定 `ARCHITECTURE.md` §15.3 "Windows 机器不是正确性门"要不要改。 23. **半开连接的检测。** Wi-Fi 掉线不产生挂断事件;dl 的闩取自描述符挂断,TCP 上需要 keepalive / 心跳超时并**由传输层报告为挂断**——这与"绝不取自 apply 超时"不矛盾,写清楚免得重吵。P6.5 ct。 -24. **E1 对照的重定义(ID-122)。** **已关闭(2026-09-29 债务审查)**:`10bfd29f` 把 E1 的像素竞速换成观测协议等待的负控 `scripts/ci/wait_boundary_negative_control.py`,由 `split_negative_controls.sh:174` 调用。 +24. **E1 对照的重定义(ID-122)。** **已关闭(2026-09-29 债务审查)**:`10bfd29f` 把 E1 的像素竞速换成观测协议等待的负控 `scripts/ci/wait_boundary_negative_control.py`。2026-10-18 该负控退役:探针看得见缺失的等待,改由常驻单元用例 `RemoteWaitBoundaryControl.AnUnwaitedPublishIsSeenAsNoWait` 证明(同一个 `ApplierReset`,发布后不等待,必须读到 parked=0)。 diff --git a/docs/Disaggregated/design/10-phase-shapes.md b/docs/Disaggregated/design/10-phase-shapes.md index 2a678eee..31a4206c 100644 --- a/docs/Disaggregated/design/10-phase-shapes.md +++ b/docs/Disaggregated/design/10-phase-shapes.md @@ -11,7 +11,7 @@ | 17.1 | verb barrier 与诚实的同地址空间传输(P5) | 同地址空间不得成为旁路(R-2):encoder 把 `MGHostSpan::Ptr` 恒写 `nullptr`;reply 以记录序号为 slot id(R-3);等待看门狗 120 s 只判死锁,不是性能门 | [`docs/archive/notes/p5/README.md`](../../archive/notes/p5/README.md) | | 17.2 | 后端槽三类、caps、tight readback(P5) | A 本地答 / B 发射 / C 具名拒绝,永不回落 monolith(R-4);能力只读 `CallMask`(R-8);`ReadPixels` 线上恒 tight,client 按自己的 pack state 散射 | 同上 | | 17.3 | server 角色、shadow 与退出(P5) | apply 线程终身持有 native context,EGL 罕见操作经控制邮箱进 apply 线程;staged shadow 只交 server-owned 拷贝;有界退出顺序 | 同上 | -| 17.4 | lane 隔离(P5) | 每条 split 条目私有日志;E1 / E3 控制把"选中条目被跳过"判为失败 | 同上 | +| 17.4 | lane 隔离(P5) | 每条 split 条目私有日志;E1 与 E3(a) 的对照是常驻用例(`RemoteWaitBoundaryControl.AnUnwaitedPublishIsSeenAsNoWait`、`PersistentCoherentMapScenario.ThePushIsTheOnlyRouteAWriteTakesToTheServer`),不重跑 lane | 同上 | | 17.5 | class-C verb 迁移(P5b) | 迁移顺序由动态普查决定;一次迁移 = 一个发射器 + 一个 sink 体;记录逐字带 GL 调用参数,未测量的形式按名拒绝(规则 D) | [`docs/archive/notes/p5b/README.md`](../../archive/notes/p5b/README.md) | | 17.6 | 共享地址空间访问归零(P5c) | server 端纹理 staged shadow;`SEG_EVENT` 是唯一反向通道;sink / twin 按记录句柄解析;`applier_reset` / `object_death` 控制记录;角色守卫 `Fatal{RoleViolation}` | [`docs/archive/notes/p5c/README.md`](../../archive/notes/p5c/README.md) | | 17.7 | run-ahead(P5e) | 规则 F:unbarriered 记录的 apply 不读任何 client 内存(违反即具名 Fatal,与 strict 开关无关);`gPipeInputs` 是 server 角色内存;`RunAheadArmed()` 在首次 caps 采纳时锁存,只能关不能开;`RUN_AHEAD=0` 是 A/B 对照,`VERB_BARRIER=0` 会连带关掉 run-ahead | [`docs/archive/notes/p5e/README.md`](../../archive/notes/p5e/README.md) | diff --git a/docs/ci/ci-cleanup-log.md b/docs/ci/ci-cleanup-log.md index 58af3f36..cfbd2140 100644 --- a/docs/ci/ci-cleanup-log.md +++ b/docs/ci/ci-cleanup-log.md @@ -54,6 +54,9 @@ them wrong. | 2026-10-17 | `Magma split lane (strict, dual-block)` on the disaggregation branches when the dual-block step proved its defaults | Test / `integration-split-strict` | the dual-block census run covers the `integration-magma-split` label in the same environment | the dual-block census run | | 2026-10-17 | the 75 retrace-split inproc rows other than OpenRA's, when `build-linux-split` reuses `build-linux` | Test / `retrace-split` | the retrace job's inproc-acceptance rows replay the same cases on the same library, strict and with the dual block and run-ahead on; no one-sided pair since 10-05 | the inproc-acceptance rows, which now prove per leg from the client log that they run with strict=1, role-split-state=1 and run-ahead=1 (`MGTRACE_EXPECT_IPC`) and keep a refusal census; every remaining retrace-split leg proves its defaults (strict=0, role-split-state=1, run-ahead=1) and a changed default reds every leg with the rows to restore; `trace_cases.py --self-test-split-matrix` (disaggregation branches) proves every dropped row is an acceptance row; OpenRA's inproc rows stay for the wire evidence | | 2026-10-17 | `Transport resolution check`'s grep of one entry's log for the inproc resolution sentence | Test / `integration-split` | it checked one entry (`DirectGLES.Split.PersistentMapArm.`) after the fact | in-test: the split fixture (`Harness/ScenarioFixture.h`) now fails every armed Split/Spawn/Tcp entry whose process did not resolve the transport its registration claims, or whose server is not where the claim says (a thread here for inproc, a process of its own for spawn and tcp), from the running process (`SplitRuntimePeek`, `ClientSession::PeerServerPid()`); `SplitLaneHarness.TransportClaimRule` (unit) proves the rule red on canned inputs; the step keeps the IPC default proof as `Default-knob resolution check` | +| 2026-10-18 | `Negative controls (barrier, map push)` (`scripts/ci/split_negative_controls.sh`; skip-census lane `junit-integration-split/control`) | Test / `integration-split` | it reran the split entries with `MOBILEGL_IPC_VERB_BARRIER=0` and `MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0`, two product knobs that exist only for it; what was left without them (the ownership check and the baseline tally) repeats `SplitLogPaths.PrivateAndDistinct` and the lane step's own `junit_tally.py --require-split-ran` | E1: `RemoteWaitBoundaryControl.AnUnwaitedPublishIsSeenAsNoWait` (unit, always on); E3(a): `PersistentCoherentMapScenario.ThePushIsTheOnlyRouteAWriteTakesToTheServer` on the inproc, spawn, tcp and small-ring split entries (always on) | +| 2026-10-18 | `scripts/ci/wait_boundary_negative_control.py` | Test / `integration-split` | E1's body inside the step above: the three boundary cases rerun with the barrier off | `RemoteWaitBoundaryControl.AnUnwaitedPublishIsSeenAsNoWait`: the same ApplierReset published with no wait must read parked=0, the observation the owed waits read as parked=1 | +| 2026-10-18 | `scripts/ci/control_smoke_test.sh`, `scripts/ci/testdata/stub_ctest.sh`, `scripts/ci/testdata/split_private_log_smoke.sh`, and the `reset` / `results` / `assertion` / `evidence` modes of `split_log_paths.py` | Test / pipe-gates (R-16) | smoke cases and helpers of the step above only | none needed | ## Kept after investigation diff --git a/scripts/ci/control_smoke_test.sh b/scripts/ci/control_smoke_test.sh deleted file mode 100644 index 65410c81..00000000 --- a/scripts/ci/control_smoke_test.sh +++ /dev/null @@ -1,143 +0,0 @@ -#!/bin/bash -# MobileGL - scripts/ci/control_smoke_test.sh -# 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 - -# A smoke test for the CI negative controls themselves. -# -# A negative control is a gate, and a workflow `run:` block only executes on a runner, so this -# file runs the real control script - the same file .github/workflows/test.yml invokes, not a copy -# of it - against a stubbed ctest, and checks that it passes exactly when it should. -# -# The case that matters is the first one. A stubbed ctest reports a non-empty selection and then -# fails with UNRELATED_CONTROL_FAILURE: a reason that has nothing to do with the knob the control -# turns. The control must report FAILED on it, and PASSED only on its scenarios' own diagnostic. -# -# usage: control_smoke_test.sh -set -u - -HERE="$(cd "$(dirname "$0")" && pwd)" -WORK="$(mktemp -d)" || exit 1 -trap 'rm -rf "${WORK}"' EXIT - -STUB_DIR="${WORK}/stub" -mkdir -p "${STUB_DIR}" -cp "${HERE}/testdata/stub_ctest.sh" "${STUB_DIR}/ctest" -chmod +x "${STUB_DIR}/ctest" - -passes=0 -failures=0 - -# expect