Skip to content

feat(assets): records travel with what needs them, open locations through adapters, the tier per asset, the guard, the AR4 out of the library #807

feat(assets): records travel with what needs them, open locations through adapters, the tier per asset, the guard, the AR4 out of the library

feat(assets): records travel with what needs them, open locations through adapters, the tier per asset, the guard, the AR4 out of the library #807

Workflow file for this run

name: CI
# Four test lanes and a typing gate. `default` installs the locked PyPI releases on every supported Python and
# blocks. The other three leave the run's conclusion alone (job-level `continue-on-error`: a failure there marks its
# own job red): `openmaterica-main` and `maddening-main` swap one upstream for its `main`, and `usd-core-newest`
# re-resolves usd-core to the newest release. v0.3's branch protection requires `default` on each Python,
# `maddening-main`, `openmaterica-main`, `typecheck` and `confidential` by these jobs' names, so a red one of them blocks
# a merge, and tests/test_ci_wiring.py holds the names and the checks' wiring. Each lane names itself in MIME_CI_LANE,
# and tests/test_ci_lane.py asserts from the installed metadata that it installed what it claims.
# Every lane also builds and installs lab_fixture/, which tests/test_lab_fixture.py and the registry walk require
# under CI. `uv sync` installs the `dev` dependency group (pytest, Hypothesis, pyright, hatchling) by default.
# `typecheck` blocks: pyright, pinned, strict over src/mime at zero errors, on a run that must be trustworthy.
# `confidential` blocks: the confidentiality scan over the tracked tree and its paths, the commits' diffs and messages
# and the branch's name.
# `default`, `openmaterica-main` and `maddening-main` each run their tests as shard jobs (`<lane>-tests`), ten unless
# the repository variable MIME_CI_SHARDS or a manual run's `shards` input says otherwise (the `shards` job,
# scripts/shard_matrix.py, CONTRIBUTING.md). The split is in tests/_sharding.py, by file and stable. The job under the
# lane's own name, which branch protection requires, passes only when every shard passed, their selections partition
# one collection and the shards' JUnit
# reports agree (every test has an outcome in one report, none failed, none skipped; scripts/check_shards.py); it runs
# after a red shard too, since a job skipped by its `if` passes a required check.
# A shard job's report is named by the run attempt (`...-shard-<n>-attempt-<k>`), so that re-running one failed shard job
# adds a report beside the first attempt's instead of a second one under the same name, and the lane's job keeps each
# shard's newest attempt before it checks (scripts/latest_attempt.py): a re-run of that shard alone clears the lane when
# it passes and leaves it red when it fails again. `default` also reads the run's jobs at their latest attempts and requires
# its Python's shard jobs to have concluded success (scripts/check_shard_jobs.py), which a re-run that failed before it
# uploaded would otherwise hide behind the first attempt's report.
# Each test's child processes get a compilation cache of their own where this workflow sets the shared one
# (tests/_child_cache.py).
# `usd-core-newest`, weekly, runs as one job. Each shard runs its tests on pytest-xdist workers and writes JUnit XML,
# and its `Test durations` step holds MADDENING's budget (scripts/report_test_durations.py, tests/README.md): an
# unlisted test over 5 s gets a warning, and one over 20 s, once re-run alone, fails the job; the lane's job writes
# where the time went to the run page. Each test's JAX tracing, lowering and compiling are recorded beside its times
# (MIME_TEST_JAX_TIMING, tests/_jax_timing.py), and each shard's report is uploaded as a run artifact. Each shard keeps
# JAX's persistent compilation cache, per lane, shard, jaxlib and runner CPU model: a pull request restores its base's
# and never saves, a push starts empty and saves (scripts/compilation_cache_mode.py). setup-uv's own cache is saved the
# same way, by pushes only: pull requests and branches each saved a copy of about 0.5 GB, which filled GitHub's 10 GB
# per-repository limit and evicted the compilation cache. Every shard and lane job has a `timeout-minutes`; a shard's is
# 20. The longest of the 45 shard jobs of run 41724784021 (a push at nine shards, so every shard cold) took 18.7 min, a
# margin of 1.07 (the limit was set at about 3.5 times the 5.7 min measured then); the tenth shard was added to move
# that margin, and tests/shard_weights.json is rebuilt from a full run's reports (scripts/shard_weights.py) when the
# suite has grown. A pull request's newer push cancels its older run
# (`concurrency` below).
# Two modes (CONTRIBUTING.md, "CI: the `ready` label"). The full matrix (every lane and Python, 58 jobs) runs on a push to
# a branch, a manual run, and a pull request that carries the label `ready`. A pull request without the label runs a
# reduced set: `typecheck`, `no private name or path`, the docs check and the default lane on Python 3.12 with all its
# shards (18 jobs), and the required jobs it does not run for (`default` on 3.13 and 3.14, `maddening-main`,
# `openmaterica-main`) still run and fail with "the full matrix has not run on this head: add the `ready` label", so
# that no required check passes by being skipped. An agent adds `ready` when its head is final.
# The mode is read when the run starts, from the pull request's labels as they are then (the `shards` job), not from the
# event: so a re-run reads the label too. This workflow does not listen to `labeled`: a label event would start a run
# whose jobs are all skipped, and a wholly skipped run, the newest of this workflow on the head, left v0.3's branch
# protection reporting the head blocked with every required check green. Adding `ready` starts ready.yml instead, which
# re-runs the head's newest run of this workflow, now in full: one check suite per head, its newest attempt counting.
# tests/test_ci_ready_label.py reads this.
# Every test job stores the asset `unit_tube_R0.997` (209 MB, the tube wall kind's table) before its tests, by the local
# action .github/actions/unit-tube: one actions/cache entry keyed by the asset's sha256, shared by every lane, Python and
# shard, and `mime assets fetch` into MIME_ASSETS_DIR. The tests never download; a missing asset fails by name.
on:
push:
branches: [main, v0.3]
# No `labeled`: a label starts no run here (ready.yml re-runs this one for `ready`).
pull_request:
branches: [main, v0.3]
types: [opened, synchronize, reopened]
# Weekly, for usd-core-newest. GitHub runs schedules from the default branch's workflow file only.
schedule:
- cron: "23 5 * * 1"
workflow_dispatch:
inputs:
plant_failure:
description: "Plant a failing canary test in every lane: the control that no lane is silently skipped"
type: boolean
default: false
plant_slow_tests:
description: "Plant an unlisted 21 s test and an unlisted 6 s test in every lane: the control that the time budget fails the job and warns"
type: boolean
default: false
plant_shard_failure:
description: "Fail this shard number of the Python 3.12 default lane on the run's first attempt only: the control that re-running just that shard clears the lane (scripts/latest_attempt.py)"
type: string
default: ""
shards:
description: "Shards per lane for this run, 1 to 12 (empty: the repository variable MIME_CI_SHARDS, else 10; scripts/shard_matrix.py)"
type: string
default: ""
compilation_cache:
description: "cold: start from an empty compilation cache and save it; warm: restore the branch's last saved cache (scripts/compilation_cache_mode.py)"
type: choice
options: [cold, warm]
default: cold
permissions:
contents: read
# A pull request's newer push cancels its older run, which would otherwise hold up to 37 of the organisation's 60
# concurrent jobs for nothing; a re-run is in its pull request's group too. A push to a branch and a manual or scheduled
# run each get a group of their own (the run's id), so none is ever cancelled: v0.3's pushes build the compilation cache
# and are the cold run that holds the time budget.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
JAX_PLATFORMS: cpu
UV_VERSION: "0.13.0"
MIME_CI_PLANT_FAILURE: ${{ inputs.plant_failure && '1' || '0' }}
MIME_CI_PLANT_SLOW_TESTS: ${{ inputs.plant_slow_tests && '1' || '0' }}
MIME_CI_PLANT_SHARD_FAILURE: ${{ inputs.plant_shard_failure }}
jobs:
# How many shards each lane runs as: the repository variable MIME_CI_SHARDS, a manual run's `shards` input over it, or 10
# (scripts/shard_matrix.py, CONTRIBUTING.md). A count outside 1 to 12 fails this job by name, and every job that needs
# it, the lanes' required jobs included, then fails too.
# And the run's mode (`full`): the full matrix on a push or a manual run, and on a pull request whose labels, read
# now and not from the event, hold `ready`. The step after it runs only in the full matrix, so the run's jobs say which
# mode it took (scripts/ci_ready.py reads it). A label that cannot be read fails this job, and the lanes with it.
shards:
name: shard count
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
outputs:
count: ${{ steps.count.outputs.count }}
list: ${{ steps.count.outputs.list }}
full: ${{ steps.mode.outputs.full }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: The shard count
id: count
env:
VARIABLE: ${{ vars.MIME_CI_SHARDS }}
INPUT: ${{ inputs.shards }}
run: python3 scripts/shard_matrix.py --variable "$VARIABLE" --input "$INPUT"
- name: The run's mode, from the pull request's labels as they are now
id: mode
env:
GH_TOKEN: ${{ github.token }}
EVENT: ${{ github.event_name }}
PULL_REQUEST: repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}
run: |
full=true
if [ "$EVENT" = pull_request ]; then
full=$(gh api "$PULL_REQUEST" --jq 'any(.labels[].name; ascii_downcase == "ready")')
fi
case "$full" in
true|false) echo "full=$full" >> "$GITHUB_OUTPUT" ;;
*) echo "::error::the pull request's labels could not be read ($full)"; exit 1 ;;
esac
- name: The full matrix runs
if: steps.mode.outputs.full == 'true'
run: echo "the full matrix runs on this head"
default-tests:
name: default tests (Python ${{ matrix.python-version }}, shard ${{ matrix.shard }} of ${{ needs.shards.outputs.count }})
needs: shards
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
python-version: ["3.12", "3.13", "3.14"]
shard: ${{ fromJSON(needs.shards.outputs.list) }}
# A reduced run (a pull request without `ready`) runs Python 3.12 alone.
exclude: ${{ fromJSON(needs.shards.outputs.full == 'true' && '[]' || '[{"python-version":"3.13"},{"python-version":"3.14"}]') }}
env:
MIME_CI_LANE: default
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 2
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: ${{ env.UV_VERSION }}
python-version: ${{ matrix.python-version }}
enable-cache: true
cache-dependency-glob: uv.lock
save-cache: ${{ github.event_name != 'pull_request' }}
- name: Install the locked PyPI releases
run: uv sync --locked --extra verify
- name: Build and install the lab fixture package, registered from outside MIME
run: uv pip install --no-deps ./lab_fixture
- name: Install the shells the completion tests ask (zsh and fish; bash is on the runner)
run: sudo apt-get install -y --no-install-recommends zsh fish
- name: The unit tube table, in the cache shared by every job
uses: ./.github/actions/unit-tube
- name: Report what this lane installed
run: uv run --no-sync python tests/test_ci_lane.py | tee -a "$GITHUB_STEP_SUMMARY"
- name: Compilation cache mode
id: cc
env:
EVENT: ${{ github.event_name }}
CHOICE: ${{ inputs.compilation_cache }}
run: >-
python3 scripts/compilation_cache_mode.py --event "$EVENT" --choice "$CHOICE" --lane "default-py${{ matrix.python-version }}"
--jaxlib "$(uv run --no-sync python -c 'import jaxlib; print(jaxlib.__version__)')"
--shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}"
- name: Restore the compilation cache
id: cc-restore
if: steps.cc.outputs.mode == 'warm'
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ${{ runner.temp }}/jax-cache
key: ${{ steps.cc.outputs.key }}-${{ github.sha }}
restore-keys: ${{ steps.cc.outputs.key }}-
- name: Tests
id: tests
env:
MIME_TEST_JAX_TIMING: "1"
JAX_COMPILATION_CACHE_DIR: ${{ runner.temp }}/jax-cache
JAX_PERSISTENT_CACHE_MIN_COMPILE_TIME_SECS: "0"
JAX_PERSISTENT_CACHE_MIN_ENTRY_SIZE_BYTES: "-1"
run: |
mkdir -p "$RUNNER_TEMP/report"
uv run --no-sync pytest -m "not slow" -n auto --dist worksteal --mime-shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}" \
--mime-shard-record "$RUNNER_TEMP/report/shard.json" \
--junitxml="$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml"
- name: Test durations
if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }}
run: |
echo "${{ (steps.cc.outputs.mode == 'warm' && steps.cc-restore.outputs.cache-matched-key != '') && 'warm' || 'cold' }}" \
> "$RUNNER_TEMP/report/cache-mode-shard${{ matrix.shard }}.txt"
uv run --no-sync python scripts/report_test_durations.py "$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml" --allowlist tests/duration_allowlist.txt --retry-outliers --cache-mode mixed --markdown /dev/null
- name: Plant a failing shard (a manual run's first attempt only)
if: ${{ !cancelled() && env.MIME_CI_PLANT_SHARD_FAILURE == format('{0}', matrix.shard) && matrix.python-version == '3.12' && github.run_attempt == 1 }}
run: |
echo "::error::planted: shard ${{ matrix.shard }} fails on its first attempt, to be re-run alone"
exit 1
- name: Record the shard's outcome
if: ${{ !cancelled() }}
run: |
mkdir -p "$RUNNER_TEMP/report"
echo "${{ job.status }}" > "$RUNNER_TEMP/report/outcome.txt"
- name: Drop unreadable compilation-cache entries
id: prune-cache
if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' }}
run: |
uv run --no-sync python scripts/prune_jax_cache.py "$RUNNER_TEMP/jax-cache"
du -sh "$RUNNER_TEMP/jax-cache"
- name: Save the compilation cache
if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' && steps.prune-cache.outcome == 'success' }}
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ${{ runner.temp }}/jax-cache
key: ${{ steps.cc.outputs.key }}-${{ github.sha }}
- name: Upload the shard's report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: durations-default-python-${{ matrix.python-version }}-shard-${{ matrix.shard }}-attempt-${{ github.run_attempt }}
path: ${{ runner.temp }}/report
if-no-files-found: error
# The lane's result, under the name branch protection requires: the documentation's checks and pages, then every
# shard passed and their selections partition one collection, and the lane's durations summary. It runs after a red
# shard too (a job skipped by its `if` passes a required check), and fails then.
default:
name: default (Python ${{ matrix.python-version }})
needs: [shards, default-tests]
if: ${{ !cancelled() && github.event_name != 'schedule' }}
# contents for the checkout, actions to read the run's own jobs (scripts/check_shard_jobs.py).
permissions:
contents: read
actions: read
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
python-version: ["3.12", "3.13", "3.14"]
env:
MIME_CI_LANE: default
# Python 3.12's job runs in every mode; the others run the lane only when the full matrix runs, and otherwise
# their first step fails the job (a required check never passes by being skipped).
MIME_CI_RUNS: ${{ needs.shards.outputs.full == 'true' || matrix.python-version == '3.12' }}
steps:
- name: Refuse a head the full matrix has not run on
if: ${{ env.MIME_CI_RUNS != 'true' }}
run: |
echo '::error::the full matrix has not run on this head: add the `ready` label'
exit 1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: ${{ env.UV_VERSION }}
python-version: ${{ matrix.python-version }}
enable-cache: true
cache-dependency-glob: uv.lock
save-cache: ${{ github.event_name != 'pull_request' }}
- name: Install the locked PyPI releases
run: uv sync --locked --extra verify
- name: Build and install the lab fixture package, registered from outside MIME
run: uv pip install --no-deps ./lab_fixture
# The default lane's reports come first: the evidence counts page (docs build, below) reads each cited test's outcome
# in them, and they are uploaded as the lane's JUnit artifact, the very files the page read.
- name: Download the shards' reports
if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }}
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: durations-default-python-${{ matrix.python-version }}-shard-*
path: ${{ runner.temp }}/shards
- name: Keep each shard's newest attempt
if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }}
run: python3 scripts/latest_attempt.py "$RUNNER_TEMP"/shards
- name: Gather the default lane's JUnit reports
if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }}
run: |
mkdir -p "$RUNNER_TEMP/junit"
cp "$RUNNER_TEMP"/shards/*/test-results-shard*.xml "$RUNNER_TEMP/junit/"
- name: Upload the default lane's JUnit reports
if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: junit-default-python-${{ matrix.python-version }}
path: ${{ runner.temp }}/junit
if-no-files-found: error
# Every check of the documentation's sources (src/mime/docs/README.md): the sources' forms, the docs walk and
# its ledger, the number rule, the citation check over the built pages, then scripts/check_anomalies.py,
# unchanged. Its summary, each list's length, the entries whose derivative is not exact and the word-numeral
# warnings among it, goes to the run page, and so does its wall time, the check's budget. The README start list
# and the none-declared list are held against origin/v0.3's, fetched when the checkout lacks it.
- name: Docs check
if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }}
run: |
status=0
git rev-parse --verify --quiet origin/v0.3 > /dev/null \
|| git fetch --no-tags --depth=1 origin +refs/heads/v0.3:refs/remotes/origin/v0.3
started=$(date +%s%N)
uv run --no-sync mime docs check --base origin/v0.3 > "$RUNNER_TEMP/docs-check.txt" 2>&1 || status=$?
echo "the command's wall time, start-up included: $(( ($(date +%s%N) - started) / 1000000 )) ms" \
>> "$RUNNER_TEMP/docs-check.txt"
cat "$RUNNER_TEMP/docs-check.txt"
{ echo "### mime docs check (Python ${{ matrix.python-version }})"; echo '```'
cat "$RUNNER_TEMP/docs-check.txt"; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
exit "$status"
# The pages, built and never committed (src/mime/docs/README.md), then scanned for a private name or path by the
# confidentiality script copied here, over the built directory, with the same key and the same fork exception as
# the tracked tree's scan (the pages hold text found in no tracked file), then uploaded as a run artifact. The scan
# fails the job and the upload runs only if every step before it passed, which tests/test_ci_docs_artifact.py
# reads. The build's wall time, start-up included, is the generation budget.
- name: Build the documentation pages
run: |
failed=0
started=$(date +%s%N)
uv run --no-sync mime docs build --out "$RUNNER_TEMP/docs-pages" --junit "$RUNNER_TEMP/junit" \
> "$RUNNER_TEMP/docs-build.txt" 2>&1 \
|| failed=$?
echo "the command's wall time, start-up included: $(( ($(date +%s%N) - started) / 1000000 )) ms" \
>> "$RUNNER_TEMP/docs-build.txt"
cat "$RUNNER_TEMP/docs-build.txt"
{ echo "### mime docs build (Python ${{ matrix.python-version }})"; echo '```'
cat "$RUNNER_TEMP/docs-build.txt"; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
exit "$failed"
- name: No private name or path in the built pages
env:
CONFIDENTIAL_SCAN_KEY: ${{ secrets.CONFIDENTIAL_SCAN_KEY }}
run: python3 scripts/check_confidential.py --directory "$RUNNER_TEMP/docs-pages"
- name: Upload the built pages
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: docs-pages-python-${{ matrix.python-version }}
path: ${{ runner.temp }}/docs-pages
if-no-files-found: error
- name: Every shard job, at its latest attempt, passed
if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
JOBS_URL: repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs?filter=latest&per_page=100
run: |
set -o pipefail
gh api "$JOBS_URL" --paginate --jq '.jobs[] | {name, conclusion}' \
| python3 scripts/check_shard_jobs.py --prefix "default tests (Python ${{ matrix.python-version }}, " --shards "${{ needs.shards.outputs.count }}"
- name: Every shard passed, and together they ran the lane
if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }}
run: python3 scripts/check_shards.py --shards "${{ needs.shards.outputs.count }}" --max-skipped 0 "$RUNNER_TEMP"/shards/*/
- name: Test durations
if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }}
env:
TITLE: "Test durations: default, Python ${{ matrix.python-version }}, ${{ needs.shards.outputs.count }} shards"
run: >-
python3 scripts/report_test_durations.py "$RUNNER_TEMP"/shards/*/test-results-shard*.xml
--allowlist tests/duration_allowlist.txt --fail-over 0 --cache-mode mixed --title "$TITLE"
openmaterica-main-tests:
name: openmaterica-main tests (shard ${{ matrix.shard }} of ${{ needs.shards.outputs.count }})
needs: shards
if: ${{ github.event_name != 'schedule' && needs.shards.outputs.full == 'true' }}
continue-on-error: true
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
shard: ${{ fromJSON(needs.shards.outputs.list) }}
env:
MIME_CI_LANE: openmaterica-main
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 2
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: ${{ env.UV_VERSION }}
python-version: "3.12"
enable-cache: true
cache-dependency-glob: uv.lock
save-cache: ${{ github.event_name != 'pull_request' }}
- name: Install the locked releases, then OpenMaterica from its main
run: |
uv sync --locked --extra verify
uv pip install --reinstall-package openmaterica \
"openmaterica @ git+https://github.com/OpenMaterica/OpenMaterica@main"
- name: Build and install the lab fixture package, registered from outside MIME
run: uv pip install --no-deps ./lab_fixture
- name: Install the shells the completion tests ask (zsh and fish; bash is on the runner)
run: sudo apt-get install -y --no-install-recommends zsh fish
- name: The unit tube table, in the cache shared by every job
uses: ./.github/actions/unit-tube
- name: Report what this lane installed
run: uv run --no-sync python tests/test_ci_lane.py | tee -a "$GITHUB_STEP_SUMMARY"
- name: Compilation cache mode
id: cc
env:
EVENT: ${{ github.event_name }}
CHOICE: ${{ inputs.compilation_cache }}
run: >-
python3 scripts/compilation_cache_mode.py --event "$EVENT" --choice "$CHOICE" --lane "openmaterica-main"
--jaxlib "$(uv run --no-sync python -c 'import jaxlib; print(jaxlib.__version__)')"
--shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}"
- name: Restore the compilation cache
id: cc-restore
if: steps.cc.outputs.mode == 'warm'
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ${{ runner.temp }}/jax-cache
key: ${{ steps.cc.outputs.key }}-${{ github.sha }}
restore-keys: ${{ steps.cc.outputs.key }}-
- name: Tests
id: tests
env:
MIME_TEST_JAX_TIMING: "1"
JAX_COMPILATION_CACHE_DIR: ${{ runner.temp }}/jax-cache
JAX_PERSISTENT_CACHE_MIN_COMPILE_TIME_SECS: "0"
JAX_PERSISTENT_CACHE_MIN_ENTRY_SIZE_BYTES: "-1"
run: |
mkdir -p "$RUNNER_TEMP/report"
uv run --no-sync pytest -m "not slow" -n auto --dist worksteal --mime-shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}" \
--mime-shard-record "$RUNNER_TEMP/report/shard.json" \
--junitxml="$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml"
- name: Test durations
if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }}
run: |
echo "${{ (steps.cc.outputs.mode == 'warm' && steps.cc-restore.outputs.cache-matched-key != '') && 'warm' || 'cold' }}" \
> "$RUNNER_TEMP/report/cache-mode-shard${{ matrix.shard }}.txt"
uv run --no-sync python scripts/report_test_durations.py "$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml" --allowlist tests/duration_allowlist.txt --retry-outliers --cache-mode mixed --markdown /dev/null
- name: Record the shard's outcome
if: ${{ !cancelled() }}
run: |
mkdir -p "$RUNNER_TEMP/report"
echo "${{ job.status }}" > "$RUNNER_TEMP/report/outcome.txt"
- name: Drop unreadable compilation-cache entries
id: prune-cache
if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' }}
run: |
uv run --no-sync python scripts/prune_jax_cache.py "$RUNNER_TEMP/jax-cache"
du -sh "$RUNNER_TEMP/jax-cache"
- name: Save the compilation cache
if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' && steps.prune-cache.outcome == 'success' }}
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ${{ runner.temp }}/jax-cache
key: ${{ steps.cc.outputs.key }}-${{ github.sha }}
- name: Upload the shard's report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: durations-openmaterica-main-shard-${{ matrix.shard }}-attempt-${{ github.run_attempt }}
path: ${{ runner.temp }}/report
if-no-files-found: error
openmaterica-main:
name: openmaterica-main
needs: [shards, openmaterica-main-tests]
if: ${{ !cancelled() && github.event_name != 'schedule' }}
continue-on-error: true
runs-on: ubuntu-latest
timeout-minutes: 10
env:
MIME_CI_RUNS: ${{ needs.shards.outputs.full == 'true' }}
steps:
- name: Refuse a head the full matrix has not run on
if: ${{ env.MIME_CI_RUNS != 'true' }}
run: |
echo '::error::the full matrix has not run on this head: add the `ready` label'
exit 1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download the shards' reports
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: durations-openmaterica-main-shard-*
path: ${{ runner.temp }}/shards
- name: Keep each shard's newest attempt
run: python3 scripts/latest_attempt.py "$RUNNER_TEMP"/shards
- name: Every shard passed, and together they ran the lane
run: python3 scripts/check_shards.py --shards "${{ needs.shards.outputs.count }}" --max-skipped 0 "$RUNNER_TEMP"/shards/*/
- name: Test durations
if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }}
env:
TITLE: "Test durations: openmaterica-main, ${{ needs.shards.outputs.count }} shards"
run: >-
python3 scripts/report_test_durations.py "$RUNNER_TEMP"/shards/*/test-results-shard*.xml
--allowlist tests/duration_allowlist.txt --fail-over 0 --cache-mode mixed --title "$TITLE"
maddening-main-tests:
name: maddening-main tests (shard ${{ matrix.shard }} of ${{ needs.shards.outputs.count }})
needs: shards
if: ${{ github.event_name != 'schedule' && needs.shards.outputs.full == 'true' }}
continue-on-error: true
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
shard: ${{ fromJSON(needs.shards.outputs.list) }}
env:
MIME_CI_LANE: maddening-main
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 2
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: ${{ env.UV_VERSION }}
python-version: "3.12"
enable-cache: true
cache-dependency-glob: uv.lock
save-cache: ${{ github.event_name != 'pull_request' }}
# MADDENING is private: git reads it with MADDENING_TOKEN (Contents: read), through a URL rewrite, so the
# install records the plain repository URL and the token reaches no metadata.
- name: Install the locked releases, then MADDENING from its main
env:
MADDENING_TOKEN: ${{ secrets.MADDENING_TOKEN }}
run: |
uv sync --locked --extra verify
rewrite="url.https://x-access-token:${MADDENING_TOKEN}@github.com/Microrobotics-Simulation-Framework/MADDENING"
git config --global "${rewrite}.insteadOf" https://github.com/Microrobotics-Simulation-Framework/MADDENING
uv pip install --reinstall-package maddening \
"maddening[verify] @ git+https://github.com/Microrobotics-Simulation-Framework/MADDENING@main"
git config --global --remove-section "${rewrite}"
- name: Build and install the lab fixture package, registered from outside MIME
run: uv pip install --no-deps ./lab_fixture
- name: Install the shells the completion tests ask (zsh and fish; bash is on the runner)
run: sudo apt-get install -y --no-install-recommends zsh fish
- name: The unit tube table, in the cache shared by every job
uses: ./.github/actions/unit-tube
- name: Report what this lane installed
run: uv run --no-sync python tests/test_ci_lane.py | tee -a "$GITHUB_STEP_SUMMARY"
- name: Compilation cache mode
id: cc
env:
EVENT: ${{ github.event_name }}
CHOICE: ${{ inputs.compilation_cache }}
run: >-
python3 scripts/compilation_cache_mode.py --event "$EVENT" --choice "$CHOICE" --lane "maddening-main"
--jaxlib "$(uv run --no-sync python -c 'import jaxlib; print(jaxlib.__version__)')"
--shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}"
- name: Restore the compilation cache
id: cc-restore
if: steps.cc.outputs.mode == 'warm'
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ${{ runner.temp }}/jax-cache
key: ${{ steps.cc.outputs.key }}-${{ github.sha }}
restore-keys: ${{ steps.cc.outputs.key }}-
- name: Tests
id: tests
env:
MIME_TEST_JAX_TIMING: "1"
JAX_COMPILATION_CACHE_DIR: ${{ runner.temp }}/jax-cache
JAX_PERSISTENT_CACHE_MIN_COMPILE_TIME_SECS: "0"
JAX_PERSISTENT_CACHE_MIN_ENTRY_SIZE_BYTES: "-1"
run: |
mkdir -p "$RUNNER_TEMP/report"
uv run --no-sync pytest -m "not slow" -n auto --dist worksteal --mime-shard "${{ matrix.shard }}/${{ needs.shards.outputs.count }}" \
--mime-shard-record "$RUNNER_TEMP/report/shard.json" \
--junitxml="$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml"
- name: Test durations
if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }}
run: |
echo "${{ (steps.cc.outputs.mode == 'warm' && steps.cc-restore.outputs.cache-matched-key != '') && 'warm' || 'cold' }}" \
> "$RUNNER_TEMP/report/cache-mode-shard${{ matrix.shard }}.txt"
uv run --no-sync python scripts/report_test_durations.py "$RUNNER_TEMP/report/test-results-shard${{ matrix.shard }}.xml" --allowlist tests/duration_allowlist.txt --retry-outliers --cache-mode mixed --markdown /dev/null
- name: Record the shard's outcome
if: ${{ !cancelled() }}
run: |
mkdir -p "$RUNNER_TEMP/report"
echo "${{ job.status }}" > "$RUNNER_TEMP/report/outcome.txt"
- name: Drop unreadable compilation-cache entries
id: prune-cache
if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' }}
run: |
uv run --no-sync python scripts/prune_jax_cache.py "$RUNNER_TEMP/jax-cache"
du -sh "$RUNNER_TEMP/jax-cache"
- name: Save the compilation cache
if: ${{ !cancelled() && steps.cc.outputs.save == 'true' && steps.tests.outcome == 'success' && steps.prune-cache.outcome == 'success' }}
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: ${{ runner.temp }}/jax-cache
key: ${{ steps.cc.outputs.key }}-${{ github.sha }}
- name: Upload the shard's report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: durations-maddening-main-shard-${{ matrix.shard }}-attempt-${{ github.run_attempt }}
path: ${{ runner.temp }}/report
if-no-files-found: error
maddening-main:
name: maddening-main
needs: [shards, maddening-main-tests]
if: ${{ !cancelled() && github.event_name != 'schedule' }}
continue-on-error: true
runs-on: ubuntu-latest
timeout-minutes: 10
env:
MIME_CI_RUNS: ${{ needs.shards.outputs.full == 'true' }}
steps:
- name: Refuse a head the full matrix has not run on
if: ${{ env.MIME_CI_RUNS != 'true' }}
run: |
echo '::error::the full matrix has not run on this head: add the `ready` label'
exit 1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download the shards' reports
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: durations-maddening-main-shard-*
path: ${{ runner.temp }}/shards
- name: Keep each shard's newest attempt
run: python3 scripts/latest_attempt.py "$RUNNER_TEMP"/shards
- name: Every shard passed, and together they ran the lane
run: python3 scripts/check_shards.py --shards "${{ needs.shards.outputs.count }}" --max-skipped 0 "$RUNNER_TEMP"/shards/*/
- name: Test durations
if: ${{ !cancelled() && env.MIME_CI_RUNS == 'true' }}
env:
TITLE: "Test durations: maddening-main, ${{ needs.shards.outputs.count }} shards"
run: >-
python3 scripts/report_test_durations.py "$RUNNER_TEMP"/shards/*/test-results-shard*.xml
--allowlist tests/duration_allowlist.txt --fail-over 0 --cache-mode mixed --title "$TITLE"
usd-core-newest:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
continue-on-error: true
runs-on: ubuntu-latest
env:
MIME_CI_LANE: usd-core-newest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: ${{ env.UV_VERSION }}
python-version: "3.12"
enable-cache: true
cache-dependency-glob: uv.lock
save-cache: ${{ github.event_name != 'pull_request' }}
- name: Install the locked releases, with usd-core re-resolved to the newest
run: uv sync --upgrade-package usd-core --extra verify
- name: Build and install the lab fixture package, registered from outside MIME
run: uv pip install --no-deps ./lab_fixture
- name: Install the shells the completion tests ask (zsh and fish; bash is on the runner)
run: sudo apt-get install -y --no-install-recommends zsh fish
- name: The unit tube table, in the cache shared by every job
uses: ./.github/actions/unit-tube
- name: Report what this lane installed
run: uv run --no-sync python tests/test_ci_lane.py | tee -a "$GITHUB_STEP_SUMMARY"
- name: Tests
id: tests
env:
MIME_TEST_JAX_TIMING: "1"
run: |
mkdir -p "$RUNNER_TEMP/report"
uv run --no-sync pytest -m "not slow" -n auto --dist worksteal --junitxml="$RUNNER_TEMP/report/test-results.xml"
- name: Test durations
if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }}
env:
TITLE: "Test durations: ${{ github.job }}${{ matrix.python-version && format(', Python {0}', matrix.python-version) || '' }}"
run: >-
uv run --no-sync python scripts/report_test_durations.py "$RUNNER_TEMP/report/test-results.xml"
--allowlist tests/duration_allowlist.txt --retry-outliers --cache-mode off --title "$TITLE"
- name: Upload the test durations
if: ${{ !cancelled() && steps.tests.outcome != 'skipped' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: durations-${{ github.job }}${{ matrix.python-version && format('-python-{0}', matrix.python-version) || '' }}
path: ${{ runner.temp }}/report
if-no-files-found: error
# scripts/check_confidential.py, OpenMaterica's scan copied (CONTRIBUTING.md, "Confidentiality"), on the standard
# library alone: the tracked files and their paths, the diffs and messages of the commits this event brings (on a
# push, every commit's when the commit before it is unknown), and a pull request's branch name, public from its
# push. A change into main leaves out v0.3's commits, public already and scanned on their way into v0.3. Without
# the secret the scan fails, so it cannot pass having checked nothing; a pull request from a fork, which GitHub
# gives no secrets, is the one exception, told apart by the event's payload: it checks the private paths and warns
# that the names were not, and the push after its merge scans with the key. A manual run scans every commit of its
# branch that v0.3 does not hold, and the branch's name: what a pull request of that head into v0.3 scans, and more
# than one into main (which leaves out v0.3's commits), so it is never weaker than a pull request's scan of the head.
confidential:
name: no private name or path
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: No private name or path in the tracked files, the commits or the branch's name
env:
CONFIDENTIAL_SCAN_KEY: ${{ secrets.CONFIDENTIAL_SCAN_KEY }}
EVENT: ${{ github.event_name }}
HEAD_REF: ${{ github.head_ref }}
REF_NAME: ${{ github.ref_name }}
TARGET: ${{ github.base_ref || github.ref_name }}
PR_BASE: ${{ github.event.pull_request.base.sha }}
PR_HEAD: ${{ github.event.pull_request.head.sha }}
PUSH_BEFORE: ${{ github.event.before }}
PUSH_AFTER: ${{ github.sha }}
run: |
public=""
if [ "$TARGET" = main ]; then public="origin/v0.3"; fi
name="$HEAD_REF"
case "$EVENT" in
pull_request) range="$PR_BASE..$PR_HEAD" ;;
push)
range="$PUSH_AFTER"
if git cat-file -e "$PUSH_BEFORE^{commit}" 2>/dev/null; then range="$PUSH_BEFORE..$PUSH_AFTER"; fi ;;
*) range="HEAD"; public="origin/v0.3"; name="$REF_NAME" ;;
esac
python3 scripts/check_confidential.py ${range:+--messages "$range" --diffs "$range"} \
${public:+--public "$public"} ${name:+--ref-name "$name"}
typecheck:
name: typecheck (pyright, Python 3.12)
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: ${{ env.UV_VERSION }}
# The floor, which pyrightconfig.json's pythonVersion also names.
python-version: "3.12"
enable-cache: true
cache-dependency-glob: uv.lock
save-cache: ${{ github.event_name != 'pull_request' }}
- name: Install the locked releases, pyright among them
run: uv sync --locked --extra verify
- name: Plant a type error in src/mime (the planted-failure run only)
if: env.MIME_CI_PLANT_FAILURE == '1'
run: |
printf 'planted: int = "a planted type error"\n' > src/mime/_planted_type_error.py
# scripts/typing_check.py exits 2 when the run cannot be trusted (pyright missing or another release than the
# pin, an exit code other than 0 or 1, no JSON, too few files analysed, a core import unresolved), 1 on any
# error, 0 otherwise; either of the first two fails the job.
- name: pyright, strict over src/mime, at zero errors
run: |
set -o pipefail
uv run --no-sync pyright --version
uv run --no-sync python scripts/typing_check.py --markdown | tee -a "$GITHUB_STEP_SUMMARY"
- name: Control, a planted type error in src/mime fails the check
run: |
printf 'planted: int = "a planted type error"\n' > src/mime/_planted_type_error.py
if uv run --no-sync python scripts/typing_check.py; then
echo "::error::the typing gate passed a planted type error, so it cannot fail"
exit 1
fi
rm src/mime/_planted_type_error.py
- name: Build the wheel
run: uv build --wheel
# The guard (mime assets audit): neither distribution, nor the tree a release's source archive and Zenodo's
# snapshot of a tag hold, carries a file whose terms let no copy be hosted (the AR4's meshes among them, by the
# manifest's hashes), a mesh baked from one, or a mesh with no record. A plugin's CI runs the same command.
- name: No file that may not be redistributed in the wheel, the sdist or the committed tree
run: |
uv build --sdist
git archive --format=tar -o "$RUNNER_TEMP/tree.tar" HEAD
uv run --no-sync mime assets audit dist/*.whl dist/*.tar.gz "$RUNNER_TEMP/tree.tar" \
--manifest examples/descriptions/ar4/visuals.json | tee -a "$GITHUB_STEP_SUMMARY"
- name: Control, a made mesh planted in a copy of the wheel fails the guard
run: |
cp dist/*.whl "$RUNNER_TEMP/planted.whl"
uv run --no-sync python -c "
import struct, sys, zipfile
with zipfile.ZipFile(sys.argv[1], 'a') as wheel:
wheel.writestr('mime/data/planted.STL', b'planted'.ljust(80, b'\0') + struct.pack('<I', 1) + bytes(50))
" "$RUNNER_TEMP/planted.whl"
if uv run --no-sync mime assets audit "$RUNNER_TEMP/planted.whl"; then
echo "::error::the guard passed a wheel holding a planted mesh, so it cannot fail"
exit 1
fi
- name: A consumer's code type-checks against the installed wheel
run: |
uv venv "$RUNNER_TEMP/consumer" --python 3.12
uv pip install --python "$RUNNER_TEMP/consumer/bin/python" dist/*.whl
mkdir "$RUNNER_TEMP/snippet"
cp tests/typing/consumer_snippet.py tests/typing/pyrightconfig.json "$RUNNER_TEMP/snippet/"
cd "$RUNNER_TEMP/snippet"
uv run --project "$GITHUB_WORKSPACE" --no-sync pyright --pythonpath "$RUNNER_TEMP/consumer/bin/python" consumer_snippet.py
- name: Control, the snippet is refused once a write into the read-only lock is not ignored
run: |
cd "$RUNNER_TEMP/snippet"
sed 's/ # type: ignore$//' consumer_snippet.py > writes_the_lock.py
if uv run --project "$GITHUB_WORKSPACE" --no-sync pyright --pythonpath "$RUNNER_TEMP/consumer/bin/python" writes_the_lock.py; then
echo "::error::pyright accepted a write into the read-only lock, so the snippet's check cannot fail"
exit 1
fi
- name: Control, the snippet is refused once py.typed is gone
run: |
rm "$("$RUNNER_TEMP/consumer/bin/python" -c 'import mime, pathlib; print(pathlib.Path(mime.__file__).parent / "py.typed")')"
cd "$RUNNER_TEMP/snippet"
if uv run --project "$GITHUB_WORKSPACE" --no-sync pyright --pythonpath "$RUNNER_TEMP/consumer/bin/python" consumer_snippet.py; then
echo "::error::the snippet type-checked without py.typed, so the check above cannot fail"
exit 1
fi