All checks were successful
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 6s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 7s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 6s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 6s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 6s
WAI operation catalogue / does this change touch it (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 6s
WAI energy receipts / does this change touch it (pull_request) Successful in 5s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 6s
wai-fleet service / does this change touch it (pull_request) Successful in 6s
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Successful in 7s
wai-ledger service / does this change touch it (pull_request) Successful in 6s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 1m25s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 6s
wai-quantum for JavaScript / does this change touch it (pull_request) Successful in 6s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 6s
wai-sink-android native sink library / does this change touch it (pull_request) Successful in 6s
wai-sink-swift native sink package / does this change touch it (pull_request) Successful in 6s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m57s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m7s
WAI Studio / does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 6s
wai-web wasm32 / does this change touch it (pull_request) Successful in 6s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 29s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 2m1s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 2m59s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m1s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 26s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m19s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 16s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m4s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 1m15s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 5m15s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 6m19s
wai-fleet service / service · macos (pull_request) Successful in 1m2s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 2m46s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 1m43s
wai-ledger service / service · macos (pull_request) Successful in 1m24s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 1m2s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 4m18s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m19s
wai-pack / wai-pack · macos (pull_request) Successful in 3m8s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 30s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 7m49s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 3m23s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m39s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m9s
wai-fleet service / service · linux (pull_request) Successful in 2m5s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m28s
wai-ledger service / service · linux (pull_request) Successful in 1m43s
wai-pack / wai-pack · linux (pull_request) Successful in 1m55s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 3m0s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 1m10s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 16m12s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 11m32s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 4m51s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 13m12s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 15m2s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 4m3s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 16m34s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m40s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 17m22s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m44s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m46s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 34s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 17s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 9m28s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 5m7s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 14m49s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m34s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m16s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 11m4s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 13m6s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 5m58s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 10m55s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 9m51s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m15s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 9m21s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 30m14s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h4m7s
Every change to WAI added an entry at the head of wai/CHANGELOG.md, and every new module appended to the hand-kept lists of .github/workflows/wai-conformance.yml. So any two branches conflicted on those lines and had to merge one after another. This moves both into files a change adds. - Changelog entries: a change adds wai/changes/<yyyy-mm-dd>-<slug>.md holding the entry text. scripts/changelog-assemble.py renders the entries into CHANGELOG.md's Unreleased section for a release, in a stable order. scripts/check-changelog-fragment.py, run by repo-checks on every pull request, fails a pull request that changes wai/ and adds or edits no entry file directly in wai/changes/ (a release passes, and so does a commit with the trailer line "Changelog: none"). A pull request opened before the check reached main passes as it is; one opened after it that adds its entry to the head of Unreleased fails and is told where the entry goes. wai breaking reads breaking changes from entry files too. - The conformance workflow: each per-module verifier step is a file in .github/wai-conformance/verifiers/, each corpus a file in corpora/, and the integer-kernel tests carry a tag line. scripts/wai-conformance-lists.py derives every list the workflow kept by hand (WAI_FEATURES, the union --bin and --test lists, the features-alone lists, the kernel and corpus steps) from those files when the workflow runs. Its `selftest --exact` holds the derived lists to the hand lists of the commit before this one, and its dry-run prints every command each job runs: the dry-runs before and after this change differ only in the gate's path list, which gains the two paths this change adds. - What stays shared, and how to insert there, is written in wai/CONTRIBUTING.md and wai/changes/README.md: wai-rs Cargo.toml features and [[test]] entries, lib.rs, registry rows, wai/.gitattributes and the live corpus table take an insert beside the related entry, never at the end. Co-Authored-By: Claude Opus 5.5 <[email protected]>
193 lines
8.3 KiB
Python
Executable file
193 lines
8.3 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""check-tests-in-ci.py: every integration test of a crate is run by a job, and none
|
|
can run empty.
|
|
|
|
The conformance workflows name the tests they run, one `--test NAME` at a time: the
|
|
crate is too large to run whole on every leg. So a test file nobody adds to a step never
|
|
runs, and nothing says so. And a file that is `#![cfg(feature = "...")]` compiles, in a
|
|
step that lacks the feature, to a binary with no tests in it that reports `ok`.
|
|
|
|
A step may also take its tests from elsewhere: the steps of wai-conformance.yml's
|
|
independent verifiers are the files of `.github/wai-conformance/verifiers/`, and a step that
|
|
runs `scripts/wai-conformance-lists.py tests TAG` or `test-tagged TAG` runs every test file
|
|
whose line `// wai-conformance: TAG...` names TAG. Both count as naming the test.
|
|
|
|
For each crate listed in CRATES this requires that
|
|
|
|
1. every `tests/*.rs` is named by `--test NAME` in a workflow or a verifier step's file,
|
|
or carries a tag a workflow's step takes, or is listed in the crate's
|
|
`tests/NOT-IN-CI.txt` with the reason no job runs it;
|
|
2. every `tests/*.rs` whose crate-level `#![cfg(...)]` names features has a `[[test]]`
|
|
entry in Cargo.toml whose `required-features` are exactly those features, so that
|
|
naming it in a step without them stops cargo with an error;
|
|
3. `NOT-IN-CI.txt` names only test files that exist and that no workflow names, each
|
|
with a reason;
|
|
4. every `[[test]]` entry names a file that exists.
|
|
|
|
A crate whose workflow runs `cargo test` over all its targets needs no entry in CRATES:
|
|
cargo finds its tests itself.
|
|
|
|
check-tests-in-ci.py [--root DIR]
|
|
|
|
Exit 0 when all four hold, 1 with one line a finding otherwise. Standard library only.
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
# Crates whose workflows name their tests one by one.
|
|
CRATES = ["wai/wai-rs"]
|
|
|
|
NOT_IN_CI = "NOT-IN-CI.txt"
|
|
|
|
|
|
# The verifier steps wai-conformance.yml runs from their own files.
|
|
STEP_DIRS = [os.path.join(".github", "wai-conformance", "verifiers")]
|
|
|
|
|
|
def workflow_test_names(root):
|
|
"""Every NAME of a `--test NAME` in a workflow file or a verifier step's file."""
|
|
names = set()
|
|
wf = os.path.join(root, ".github", "workflows")
|
|
files = [os.path.join(wf, f) for f in sorted(os.listdir(wf)) if f.endswith((".yml", ".yaml"))]
|
|
for d in STEP_DIRS:
|
|
d = os.path.join(root, d)
|
|
if os.path.isdir(d):
|
|
files += [os.path.join(d, f) for f in sorted(os.listdir(d)) if f.endswith(".sh")]
|
|
for path in files:
|
|
with open(path, encoding="utf-8") as fh:
|
|
names.update(re.findall(r"--test[ =]([A-Za-z0-9_-]+)", fh.read()))
|
|
return names
|
|
|
|
|
|
def workflow_tags(root):
|
|
"""Every tag a workflow step takes its tests by: `wai-conformance-lists.py tests TAG...`
|
|
or `test-tagged TAG...`."""
|
|
tags = set()
|
|
wf = os.path.join(root, ".github", "workflows")
|
|
for f in sorted(os.listdir(wf)):
|
|
if f.endswith((".yml", ".yaml")):
|
|
with open(os.path.join(wf, f), encoding="utf-8") as fh:
|
|
for m in re.finditer(r"wai-conformance-lists\.py (?:tests|test-tagged)((?: [a-z0-9-]+)+)", fh.read()):
|
|
tags.update(m.group(1).split())
|
|
return tags
|
|
|
|
|
|
def file_tags(path):
|
|
"""The tags of a test file's `// wai-conformance:` lines."""
|
|
with open(path, encoding="utf-8") as fh:
|
|
return {t for line in re.findall(r"^//\s*wai-conformance:\s*(.*?)\s*$", fh.read(), re.M) for t in line.split()}
|
|
|
|
|
|
def crate_cfg_features(path):
|
|
"""The features a test file's crate-level `#![cfg(...)]` requires, as a sorted list;
|
|
[] when it has none. None when the attribute is a form this check does not read
|
|
(`any`, `not`, or something that is not a feature), which is a finding."""
|
|
with open(path, encoding="utf-8") as fh:
|
|
src = fh.read()
|
|
feats = set()
|
|
for m in re.finditer(r"^#!\[cfg\((.*)\)\]\s*$", src, re.M):
|
|
inner = m.group(1).strip()
|
|
body = inner
|
|
am = re.fullmatch(r"all\((.*)\)", inner, re.S)
|
|
if am:
|
|
body = am.group(1)
|
|
parts = [p.strip() for p in body.split(",") if p.strip()]
|
|
for p in parts:
|
|
fm = re.fullmatch(r'feature\s*=\s*"([A-Za-z0-9_-]+)"', p)
|
|
if not fm:
|
|
return None
|
|
feats.add(fm.group(1))
|
|
return sorted(feats)
|
|
|
|
|
|
def cargo_tests(path):
|
|
"""The `[[test]]` entries of a Cargo.toml: {name: (path, required-features or None)}."""
|
|
with open(path, encoding="utf-8") as fh:
|
|
text = fh.read()
|
|
out = {}
|
|
for block in re.split(r"^\[\[", text, flags=re.M)[1:]:
|
|
if not block.startswith("test]]"):
|
|
continue
|
|
body = re.split(r"^\[", block[len("test]]"):], flags=re.M)[0]
|
|
name = re.search(r'^name\s*=\s*"([^"]+)"', body, re.M)
|
|
if not name:
|
|
continue
|
|
p = re.search(r'^path\s*=\s*"([^"]+)"', body, re.M)
|
|
rf = re.search(r"^required-features\s*=\s*\[(.*?)\]", body, re.M | re.S)
|
|
feats = sorted(re.findall(r'"([^"]+)"', rf.group(1))) if rf else None
|
|
out[name.group(1)] = (p.group(1) if p else f"tests/{name.group(1)}.rs", feats)
|
|
return out
|
|
|
|
|
|
def not_in_ci(path):
|
|
"""`NOT-IN-CI.txt`: {name: reason}. A line is a name, blank space, and the reason;
|
|
`#` starts a comment line."""
|
|
out, bad = {}, []
|
|
if not os.path.exists(path):
|
|
return out, bad
|
|
with open(path, encoding="utf-8") as fh:
|
|
for n, line in enumerate(fh, 1):
|
|
line = line.rstrip("\n")
|
|
if not line.strip() or line.lstrip().startswith("#"):
|
|
continue
|
|
m = re.fullmatch(r"([A-Za-z0-9_-]+)\s+(\S.*)", line)
|
|
if not m:
|
|
bad.append(f"{path}:{n}: not `name reason`")
|
|
continue
|
|
if m.group(1) in out:
|
|
bad.append(f"{path}:{n}: {m.group(1)} is listed twice")
|
|
out[m.group(1)] = m.group(2).strip()
|
|
return out, bad
|
|
|
|
|
|
def check(root):
|
|
findings = []
|
|
named = workflow_test_names(root)
|
|
tags = workflow_tags(root)
|
|
for crate in CRATES:
|
|
tdir = os.path.join(root, crate, "tests")
|
|
files = sorted(f[:-3] for f in os.listdir(tdir) if f.endswith(".rs") and os.path.isfile(os.path.join(tdir, f)))
|
|
declared = cargo_tests(os.path.join(root, crate, "Cargo.toml"))
|
|
listed, bad = not_in_ci(os.path.join(tdir, NOT_IN_CI))
|
|
findings += bad
|
|
named = named | {t for t in files if file_tags(os.path.join(tdir, t + ".rs")) & tags}
|
|
for t in files:
|
|
if t not in named and t not in listed:
|
|
findings.append(f"{crate}/tests/{t}.rs: no workflow step names it (`--test {t}`) or takes a tag it carries, and {NOT_IN_CI} does not list it")
|
|
feats = crate_cfg_features(os.path.join(tdir, t + ".rs"))
|
|
if feats is None:
|
|
findings.append(f"{crate}/tests/{t}.rs: its #![cfg(...)] is not a feature or an all(...) of features; this check cannot tell what it needs")
|
|
elif feats:
|
|
entry = declared.get(t)
|
|
if entry is None or entry[1] is None:
|
|
findings.append(f'{crate}/tests/{t}.rs: gated on {", ".join(feats)} with no [[test]] required-features in Cargo.toml, so a step without the feature runs it empty')
|
|
elif entry[1] != feats:
|
|
findings.append(f'{crate}/Cargo.toml: [[test]] {t} requires {entry[1]}, and the file is gated on {feats}')
|
|
for t, why in sorted(listed.items()):
|
|
if t not in files:
|
|
findings.append(f"{crate}/tests/{NOT_IN_CI}: {t} is not a test file")
|
|
elif t in named:
|
|
findings.append(f"{crate}/tests/{NOT_IN_CI}: {t} is listed as not run, and a workflow names it; remove the line")
|
|
for t, (path, _) in sorted(declared.items()):
|
|
if not os.path.isfile(os.path.join(root, crate, path)):
|
|
findings.append(f"{crate}/Cargo.toml: [[test]] {t} names {path}, which does not exist")
|
|
return findings
|
|
|
|
|
|
def main(argv):
|
|
root = "."
|
|
if len(argv) >= 2 and argv[0] == "--root":
|
|
root = argv[1]
|
|
findings = check(root)
|
|
for f in findings:
|
|
print(f)
|
|
if findings:
|
|
print(f"check-tests-in-ci: {len(findings)} finding(s)")
|
|
return 1
|
|
print("check-tests-in-ci: every test file is run by a job or listed with its reason, and none can run empty.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1:]))
|