[CI] (.github/workflows, docs/ci, scripts/ci): require optional census rows on the path that runs them

- skip_census.py check --require LANE: an optional lane the run took the path for is red when absent, and a name the baseline lacks is red too; self-test cases
- skip-census needs build-guard and reads integration-split's new defaults-on output: any verdict but identical requires unit-split, arm-inproc and arm-monolith, and unproven IPC defaults require the two arm rows
This commit is contained in:
2026-10-17 17:13:51 -04:00
parent 43e671f027
commit 9eb92f83c9
3 changed files with 46 additions and 8 deletions
+20 -1
View File
@@ -1557,6 +1557,9 @@ jobs:
timeout-minutes: 120
needs: [build-guard, build-linux-split, build-linux]
if: ${{ !cancelled() && needs.build-linux-split.result == 'success' && (needs.build-guard.outputs.verdict != 'identical' || needs.build-linux.result == 'success') }}
# The skip census reads whether 'Default-knob resolution check' proved the IPC defaults.
outputs:
defaults-on: ${{ steps.resolution.outputs.defaults-on }}
# Short-lived test processes skip the glslang prewarm, as in the test job.
env:
@@ -4067,6 +4070,7 @@ jobs:
name: skip census
runs-on: ubuntu-26.04
needs:
- build-guard
- test
- integration
- integration-verify
@@ -4084,10 +4088,25 @@ jobs:
with:
pattern: junit-*
path: junit
# The optional rows run on some paths only, and on the path this run took they are required:
# without a reused build (any build-guard verdict but identical) integration-split runs its
# unit lane and Transport A/B, and with one A/B still runs when the IPC defaults were not
# proven.
- name: Skip census
env:
GUARD_VERDICT: ${{ needs.build-guard.outputs.verdict }}
SPLIT_DEFAULTS_ON: ${{ needs.integration-split.outputs.defaults-on }}
run: |
required=()
if [ "${GUARD_VERDICT}" != identical ]; then
required+=(--require junit-integration-split/unit-split)
fi
if [ "${GUARD_VERDICT}" != identical ] || [ "${SPLIT_DEFAULTS_ON}" != true ]; then
required+=(--require junit-integration-split/arm-inproc --require junit-integration-split/arm-monolith)
fi
echo "build-guard verdict '${GUARD_VERDICT:-none}', split IPC defaults proven '${SPLIT_DEFAULTS_ON:-no}': ${required[*]:-no optional row required}"
python3 scripts/ci/skip_census.py check junit \
--baseline scripts/data/skip_census_baseline.json --report skip-census.json
--baseline scripts/data/skip_census_baseline.json --report skip-census.json "${required[@]}"
- name: Upload census
if: ${{ always() }}
uses: actions/upload-artifact@v7
+2 -2
View File
@@ -47,8 +47,8 @@ them wrong.
| 2026-10-17 | the pre-flip arm of `Strict split lane` (red-by-design while `kMGPipeP5eRunAheadReady` was false) and the "GREEN with rows outstanding" rule of `Dual-block census lane` | Test / `integration-split-strict` | dead: the constant is true, and `dualblock-expected-fatals.txt` has no rows, so neither branch could run | the hard-green arm and its two-sided marker census, and the empty-census rule (rows == 0 requires ctest success), both kept |
| 2026-10-17 | `scripts/ci/tcp_log_forwarding_control.py` | none | no caller outside the archived planning notes | none needed |
| 2026-10-17 | the compile of `build-linux-split` on the disaggregation branches (when `build-guard` proves the configurations identical) | Test / `build-linux-split` | it built the library `build-linux` builds there, from a configuration that differs only by the benchmarks (211 paired runs never disagreed); it delayed every split consumer by up to 9 minutes | the `build-guard` job configures both lines on one runner and compares them with `build_fingerprint.py` (cache, compile commands, link statements, CTest files; benchmarks excluded; one shared FetchContent directory, a failed configure or one killed after 10 minutes retried once) and records its image and compiler; only the verdict `identical` lets the consumers take `build-linux`'s artifacts, and only a Release one, while `different` (annotated as a warning) and `unknown` (a configure that failed twice, any error, too little evidence; a notice) keep both builds and the full split lanes; the guard job's own failure never fails the run; `build-linux` runs the link ratchet on every disaggregation build (artifact `link-ratchet-primary`) and integration-split reds on it when it takes that build; dev runs no guard and always builds |
| 2026-10-17 | `Transport A/B (monolith vs inproc)` (skip-census lanes `arm-monolith`, `arm-inproc` now optional) | Test / `integration-split` | with `build-linux` reused it repeats the integration job's two legs on the same library (79 paired runs, no one-sided red) | the integration legs; `Default-knob resolution check` (disaggregation branches) reads 'Split lanes' own run and proves, from every passed entry's client log, strict=0, role-split-state=1 and run-ahead=1 unless pinned (`split_log_paths.py ipc-config`); A/B still runs when a default differs or the build is not reused |
| 2026-10-17 | `Unit tests on the split runtime` (skip-census lane `unit-split` now optional) | Test / `integration-split` | with `build-linux` reused the test job runs the same unit lane on the same build (182 paired runs; the one recurring one-sided red was a fixed test race) | the test job, which now carries `MOBILEGL_ITEST_REQUIRE_GPU=1` and `libegl-mesa0` on the three branches; the strict unit step is unchanged |
| 2026-10-17 | `Transport A/B (monolith vs inproc)` (skip-census lanes `arm-monolith`, `arm-inproc` now optional) | Test / `integration-split` | with `build-linux` reused it repeats the integration job's two legs on the same library (79 paired runs, no one-sided red) | the integration legs; `Default-knob resolution check` (disaggregation branches) reads 'Split lanes' own run and proves, from every passed entry's client log, strict=0, role-split-state=1 and run-ahead=1 unless pinned (`split_log_paths.py ipc-config`); A/B still runs when a default differs or the build is not reused, and the skip census then requires the two arm rows |
| 2026-10-17 | `Unit tests on the split runtime` (skip-census lane `unit-split` now optional) | Test / `integration-split` | with `build-linux` reused the test job runs the same unit lane on the same build (182 paired runs; the one recurring one-sided red was a fixed test race) | the test job, which now carries `MOBILEGL_ITEST_REQUIRE_GPU=1` and `libegl-mesa0` on the three branches; the strict unit step is unchanged; without a reused build the skip census requires `unit-split` |
| 2026-10-17 | the integration-gpu entries that pin their own `MOBILEGL_TRANSPORT` (804 inproc, 1446 spawn, 131 monolith) and the 12 `TcpServer` fixture entries only they need, in the inproc-acceptance leg (both passes) | Test / `integration` (inproc-acceptance) | their pin overrides the only variable the two legs differ in, so they ran identically in both (173 paired runs; the one one-sided red was a port bind fixed by cde6512b) | the monolith-control leg, which runs them on every branch; `transport_pinned.py` proves per run that the trimmed selection lost exactly that population and that the monolith-control leg ran every one; when it cannot, the leg runs everything under the JUnit stems `integration-all` and `integration-staged-copy-all`; skip census: the monolith-control lanes list their own names, the inproc-acceptance lanes drop the pinned ones, and two optional `-all` rows (the full names, the old skip lists) stand in for them on that fallback |
| 2026-10-17 | `Strict split lane` on the disaggregation branches (dev keeps it) | Test / `integration-split-strict` | with the role-split state defaulting on, its environment equals the dual-block census lane's, whose entries are a superset (31 paired runs after the flip, never red only in the strict lane) | the dual-block step's first run leaves the knob at its default, proves per entry from its client log that strict, the dual block and run-ahead resolved on (`ipc-config`), writes the integration-split part of its JUnit as `strict-split.xml` (every name exactly once, `junit_tally.py --select-from`), and makes the strict lane's checks on it (hard green, two-sided marker census, StrictArming ran); a default that changed reruns the census with the knob set |
| 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 |
+24 -5
View File
@@ -26,13 +26,15 @@ into the tree (scripts/data/skip_census_baseline.json):
a build-equality guard falls back); its absence is a note, and when it runs it is checked
like any other lane;
* an optional row that also carries "instead_of": "<lane>" is that lane's whole-run fallback:
when it ran, the lane it stands in for may be absent (a note), and is required otherwise.
when it ran, the lane it stands in for may be absent (a note), and is required otherwise;
* `check --require LANE` names an optional lane that the run took the path for, so its
absence is red in that run.
A deliberate retirement edits the baseline in the same commit and names the cases in the commit
message (PLAN-P13 §4.2).
Usage:
skip_census.py check <junit-dir> --baseline <json> [--report <json>]
skip_census.py check <junit-dir> --baseline <json> [--report <json>] [--require LANE]...
skip_census.py write <junit-dir> --baseline <json>
skip_census.py --self-test
<junit-dir> holds one sub-directory per lane (actions/download-artifact with a pattern lays
@@ -144,8 +146,10 @@ def keep_optional_rows(written, previous):
return dict(sorted(written.items()))
def compare(lanes, baseline):
def compare(lanes, baseline, required=()):
red, info = [], []
for lane in sorted(set(required) - set(baseline)):
red.append(f'lane {lane}: required by this run, but the baseline has no such row')
stood_in = {row['instead_of']: lane for lane, row in baseline.items()
if row.get('optional') and row.get('instead_of') and lane in lanes}
for lane, known in sorted(baseline.items()):
@@ -154,9 +158,13 @@ def compare(lanes, baseline):
if lane in stood_in:
info.append(f'lane {lane}: not run ({stood_in[lane]} ran instead)')
continue
if known.get('optional'):
if known.get('optional') and lane not in required:
info.append(f"lane {lane}: not run (optional: {known['optional']})")
continue
if known.get('optional'):
red.append(f"lane {lane}: no JUnit in this run, which took the path that runs it "
f"({known['optional']})")
continue
red.append(f'lane {lane}: no JUnit at all in this run (the job did not run or did not upload)')
continue
allowed = set(known['skipped'])
@@ -188,6 +196,8 @@ def run(argv):
parser.add_argument('junit_dir')
parser.add_argument('--baseline', required=True, type=pathlib.Path)
parser.add_argument('--report', type=pathlib.Path)
parser.add_argument('--require', action='append', default=[], metavar='LANE',
help='check: an optional lane this run took the path for, so it must be present')
args = parser.parse_args(argv)
lanes = collect(args.junit_dir)
if not lanes:
@@ -207,7 +217,7 @@ def run(argv):
if not args.baseline.exists():
print(f'::error::skip census: no baseline at {args.baseline}; write one from a full run')
return 1
red, info = compare(lanes, resolve_baseline(json.loads(args.baseline.read_text())))
red, info = compare(lanes, resolve_baseline(json.loads(args.baseline.read_text())), args.require)
for line in info:
print(f'note: {line}')
for line in red:
@@ -268,6 +278,15 @@ def self_test():
junit(both / 'junit-other' / 'other.xml', [('A', 'skip')])
assert quiet(['check', str(both), '--baseline', str(optional)]) == 1, 'a present optional lane is checked'
assert quiet(['check', str(lost), '--baseline', str(optional)]) == 1, 'a plain lane is still required'
assert quiet(['check', str(good), '--baseline', str(optional), '--require', 'junit-other/other']) == 1, \
'an optional lane the run took the path for is required'
present = tmp / 'present'
junit(present / 'junit-unit' / 'unit.xml', [('A', 'pass'), ('B', 'skip'), ('C', 'pass')])
junit(present / 'junit-other' / 'other.xml', [('A', 'pass')])
assert quiet(['check', str(present), '--baseline', str(optional), '--require', 'junit-other/other']) == 0, \
'a required optional lane that ran is checked like any other'
assert quiet(['check', str(good), '--baseline', str(optional), '--require', 'junit-no/such']) == 1, \
'a required lane the baseline does not know is red'
kept = keep_optional_rows(to_baseline(collect(good)), rows)
assert kept['junit-other/other'] == rows['junit-other/other'], 'write keeps an absent optional row'
# A whole-run fallback lane stands in for a trimmed one: the leg ran everything under its