mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[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:
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user