Skip to content

Commit e113773

Browse files
authored
ci(examples): run the Python example gates on the code they depend on (#1702)
The example tests under big-code-analysis-py (test_book_examples.py and the parity rows in test_smoke.py / test_sarif.py) and the jupyter_quickstart.ipynb nbconvert job were already wired into CI, but three things let them not run, or run against the wrong inputs: - The `py` path filter listed only the bindings crate and the root manifests, so a change to src/, the parse layer, the CLI or a vendored grammar left every python-* job `skipped` even though the extension links all of them and the parity examples byte-compare against the CLI. Add those directories to the filter. - The `bca` the parity examples compare against was built implicitly inside the `bca_binary` fixture, without `--locked` and outside the job log. Build it in an explicit step and export `BCA_BINARY` via `$GITHUB_ENV` so it reaches the PowerShell pytest step on Windows. - The notebook documents `BCA_NOTEBOOK_FIXTURES` as the contract the CI step uses, but the step never set it. Set it. The runnable-example discovery gate also only enumerated `*.py`, so a second notebook could land with nothing executing it; it now reconciles `*.ipynb` against `NON_PY_EXAMPLES` too. Verified by dropping a stray notebook into examples/ and watching exactly that test fail. Validated locally with the environments CI provisions: the dev and examples requirement exports installed with --require-hashes, maturin develop, pytest against the --locked CLI (77 passed, all cli_parity rows passing not skipping), the exact nbconvert command with the env var set (6/6 cells executed), and `make pre-commit` (BCA_GATE: pass).
1 parent 02666d8 commit e113773

2 files changed

Lines changed: 72 additions & 9 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,21 @@ env:
3838

3939
jobs:
4040
# Path-filter detection. Outputs `py: true` when the change set
41-
# touches the bindings crate, the workflow file itself, mise.toml
42-
# (toolchain pins), or the root Cargo.toml / Cargo.lock (PyO3 /
43-
# workspace dep bumps must re-run the python-test matrix even on a
44-
# Rust-only PR title). On Rust-only PRs the python jobs below are
45-
# `skipped`, which satisfies the `ci` aggregator's `needs:` without
41+
# touches the bindings crate or anything the extension and the
42+
# examples are built from: the workflow file itself, mise.toml
43+
# (toolchain pins), the root Cargo.toml / Cargo.lock (PyO3 /
44+
# workspace dep bumps), and the Rust the bindings link. The
45+
# bindings crate path-depends on the root crate
46+
# (`big-code-analysis-py/Cargo.toml`), which pulls in the parse
47+
# layer and the five vendored grammars, so a metric or grammar
48+
# change moves what `maturin develop` builds and what the example
49+
# tests assert on. `examples/cli_parity.py` and `tests/test_smoke.py`
50+
# go one step further and byte-compare the bindings against the
51+
# `bca` CLI, so the CLI crate is an input too. Before these entries
52+
# a lib-, CLI- or grammar-only change left every python-* job
53+
# `skipped` while it could have broken the examples. PRs that touch
54+
# only docs, the book, the web crate or the integration tests still
55+
# skip; `skipped` satisfies the `ci` aggregator's `needs:` without
4656
# being `failure` or `cancelled`.
4757
changes:
4858
name: detect path changes
@@ -61,6 +71,14 @@ jobs:
6171
filters: |
6272
py:
6373
- 'big-code-analysis-py/**'
74+
- 'src/**'
75+
- 'big-code-analysis-ast/**'
76+
- 'big-code-analysis-cli/**'
77+
- 'tree-sitter-ccomment/**'
78+
- 'tree-sitter-mozcpp/**'
79+
- 'tree-sitter-mozjs/**'
80+
- 'tree-sitter-preproc/**'
81+
- 'tree-sitter-tcl/**'
6482
- '.github/workflows/ci.yml'
6583
- '.pre-commit-config.yaml'
6684
- 'Makefile'
@@ -935,7 +953,8 @@ jobs:
935953
936954
# Python bindings — lint + type-check. Single Linux job; the heavier
937955
# cross-platform matrix lives in `python-test` below. Both jobs are
938-
# gated by `needs.changes.outputs.py == 'true'` so Rust-only PRs skip
956+
# gated by `needs.changes.outputs.py == 'true'` so PRs that touch
957+
# nothing the bindings are built from (see the `changes` job) skip
939958
# them entirely (verified by the `ci` aggregator job's tolerance for
940959
# `skipped` results).
941960
python-lint:
@@ -1101,6 +1120,24 @@ jobs:
11011120
run: |
11021121
.venv\Scripts\Activate.ps1
11031122
maturin develop
1123+
# The CLI-parity examples (`examples/cli_parity.py`, and the
1124+
# `test_smoke.py` parity rows) byte-compare the bindings against
1125+
# a `bca` built from this same checkout. The `bca_binary` fixture
1126+
# in tests/conftest.py builds one itself when `$BCA_BINARY` is
1127+
# unset, but that in-process `cargo build` carries no `--locked`
1128+
# and is invisible in the job log. Build it here with `--locked`
1129+
# instead and hand the path over; the fixture `pytest.fail`s
1130+
# on a path that is not a file, so a wrong value cannot degrade
1131+
# into a silently skipped parity suite. `$GITHUB_ENV` rather than
1132+
# a shell `export` because the value has to reach the next step,
1133+
# which on Windows is PowerShell.
1134+
- name: Build bca CLI for the parity examples
1135+
shell: bash
1136+
run: |
1137+
cargo build -p big-code-analysis-cli --locked
1138+
suffix=""
1139+
if [ "$RUNNER_OS" = "Windows" ]; then suffix=".exe"; fi
1140+
echo "BCA_BINARY=${GITHUB_WORKSPACE}/target/debug/bca${suffix}" >> "$GITHUB_ENV"
11041141
- name: pytest (Unix)
11051142
if: runner.os != 'Windows'
11061143
working-directory: big-code-analysis-py
@@ -1140,8 +1177,9 @@ jobs:
11401177
# A second matrix axis would only re-prove pandas works on Windows.
11411178
#
11421179
# Gated by `needs.changes.outputs.py == 'true'` same as the other
1143-
# python-* jobs; Rust-only PRs see `skipped` (not `failure`) so the
1144-
# aggregator's tolerance for skipped dependents continues to hold.
1180+
# python-* jobs; PRs outside the `changes` filter see `skipped` (not
1181+
# `failure`) so the aggregator's tolerance for skipped dependents
1182+
# continues to hold.
11451183
python-examples-nbconvert:
11461184
name: python-examples-nbconvert
11471185
needs: changes
@@ -1183,8 +1221,18 @@ jobs:
11831221
run: |
11841222
source .venv/bin/activate
11851223
maturin develop
1224+
# `BCA_NOTEBOOK_FIXTURES` is the contract the notebook's first
1225+
# code cell documents for CI: the env var is read first, and the
1226+
# fallback (`Path.cwd().parent / "tests/fixtures"`) only works
1227+
# because nbconvert sets the kernel cwd to the notebook's own
1228+
# directory. Setting the env var here keeps the job correct if
1229+
# that cwd behaviour ever changes; the cell then asserts
1230+
# `hello.rs` is present under the path, so a wrong value fails
1231+
# rather than analysing an empty tree.
11861232
- name: Execute jupyter_quickstart.ipynb
11871233
working-directory: big-code-analysis-py
1234+
env:
1235+
BCA_NOTEBOOK_FIXTURES: ${{ github.workspace }}/big-code-analysis-py/tests/fixtures
11881236
run: |
11891237
source .venv/bin/activate
11901238
# `--inplace` writes the executed outputs back to the file,

‎big-code-analysis-py/tests/test_book_examples.py‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@
6060
# Non-``.py`` example artifacts that are exercised by a different gate
6161
# (the notebook runs under CI's ``python-examples-nbconvert`` step, with
6262
# only an existence smoke check here). Listed so a future ``*.py``
63-
# companion is not accidentally waived.
63+
# companion is not accidentally waived, and reconciled against the
64+
# directory's ``*.ipynb`` files by ``test_every_example_is_covered``:
65+
# the nbconvert job names its notebook by path, so a second notebook
66+
# would otherwise exist without anything executing it.
6467
NON_PY_EXAMPLES = frozenset({"jupyter_quickstart.ipynb"})
6568

6669

@@ -347,6 +350,18 @@ def test_every_example_is_covered() -> None:
347350
for artifact in NON_PY_EXAMPLES:
348351
assert (EXAMPLES_DIR / artifact).is_file(), f"missing waived example {artifact}"
349352

353+
# And the other direction for notebooks. The ``*.py`` reconciliation
354+
# above would let a new ``examples/foo.ipynb`` through untouched,
355+
# and the CI nbconvert job executes one notebook by name, so an
356+
# unlisted notebook is one nothing runs.
357+
on_disk_nb = {p.name for p in EXAMPLES_DIR.glob("*.ipynb")}
358+
unclaimed_nb = on_disk_nb - NON_PY_EXAMPLES
359+
assert not unclaimed_nb, (
360+
f"notebooks nothing executes: {sorted(unclaimed_nb)} — add each to "
361+
"NON_PY_EXAMPLES and to the `python-examples-nbconvert` job in "
362+
".github/workflows/ci.yml"
363+
)
364+
350365

351366
@pytest.mark.parametrize("name", sorted(RUNNABLE_EXAMPLES))
352367
def test_example_file_exists_for_book_include(name: str) -> None:

0 commit comments

Comments
 (0)