From 11a56305b50177494695c318cc04723ce5d6b6b7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 17 Sep 2026 03:22:59 +0900 Subject: [PATCH] fix(opencode-review): build PyO3/maturin extensions offline before coverage Adds maturin==1.15.0 to the hash-pinned opencode-review CI lock (hashes verified against PyPI JSON metadata) and teaches run_python_test_coverage's new build_maturin_extension_if_needed helper to run `maturin build --offline --release` with CARGO_NET_OFFLINE=true and CARGO_BUILD_JOBS=1 against the vendored base-commit Cargo dependencies before pytest, for every tracked Python project whose pyproject.toml declares build-backend = "maturin". Closes the gap materialize_base_rust_dependencies.py (#2222, #2223) left open: the base Cargo dependency graph was vendored for `cargo llvm-cov`, but nothing ever built the PyO3 extension itself, so `coverage run -m pytest` kept failing collection with `ImportError: cannot import name '_core'` on 8 of the last 10 fast-mlsirm fallbacks. tests/test_maturin_offline_build_contract.py proves both halves against a real PyO3 fixture crate: the vendored-offline build produces an importable `_core` extension, and a dependency only a pull request's head added (never seen by the base-commit materializer) is never fetched -- the offline build fails closed on the missing crate. Refs ContextualWisdomLab/fast-mlsirm#1907 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01FpnJ9UuQwdTUfhzmqueXCz --- .../workflows/opencode-review-dispatch.yml | 27 ++ ...20260917-maturin-offline-coverage-build.md | 19 ++ requirements-opencode-review-ci-hashes.txt | 179 +++++++------ requirements-opencode-review-ci.txt | 6 + tests/test_maturin_offline_build_contract.py | 239 ++++++++++++++++++ ...t_pr_review_autofix_nvidia_nim_contract.py | 2 +- 6 files changed, 399 insertions(+), 73 deletions(-) create mode 100644 CHANGELOG.d/20260917-maturin-offline-coverage-build.md create mode 100644 tests/test_maturin_offline_build_contract.py diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 76fd485dc1..9f2b2ae525 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -1272,10 +1272,37 @@ jobs: --workflow-dir "$workflow_dir" } + project_is_maturin_project() { + local pyproject="${1}/pyproject.toml" + [ -f "$pyproject" ] || return 1 + grep -Eq '^\s*build-backend\s*=\s*"maturin' "$pyproject" + } + + # Builds the PyO3/maturin extension module (e.g. `fast_mlsirm._core`) fully offline + # before pytest runs, using the Cargo vendor config baked in by + # materialize_base_rust_dependencies.py (see the /work/.opencode-sandbox-home/.cargo + # setup above). Without this, coverage collection fails with `ImportError: cannot + # import name '_core'` because nothing else in the sandbox ever builds the compiled + # extension. CARGO_BUILD_JOBS=1 keeps the offline build within the sandbox's memory + # budget. + build_maturin_extension_if_needed() { + local project_dir="$1" + project_is_maturin_project "$project_dir" || return 0 + run_and_capture "Offline PyO3/maturin extension build (${project_dir})" \ + env CARGO_NET_OFFLINE=true CARGO_BUILD_JOBS=1 \ + bash -c 'set -eu + cd "$1" + dist_dir="$(mktemp -d)" + python3 -m maturin build --offline --release -o "$dist_dir" + python3 -m pip install --user --no-index --no-deps --force-reinstall "$dist_dir"/*.whl + rm -rf "$dist_dir"' bash "$project_dir" + } + run_python_test_coverage() { local measured_projects=0 while IFS= read -r project_dir; do measured_projects=1 + build_maturin_extension_if_needed "$project_dir" configured_commands_json="$(configured_python_ci_test_commands "$project_dir")" if [ -n "$configured_commands_json" ]; then while IFS= read -r configured_command_json; do diff --git a/CHANGELOG.d/20260917-maturin-offline-coverage-build.md b/CHANGELOG.d/20260917-maturin-offline-coverage-build.md new file mode 100644 index 0000000000..3d636602f7 --- /dev/null +++ b/CHANGELOG.d/20260917-maturin-offline-coverage-build.md @@ -0,0 +1,19 @@ +### Coverage sandbox builds PyO3/maturin extensions offline before pytest + +- `maturin==1.15.0` (MIT/Apache-2.0) is added to `requirements-opencode-review-ci.txt` / + `requirements-opencode-review-ci-hashes.txt` (hashes verified against PyPI JSON metadata for the + exact release), closing the last gap `materialize_base_rust_dependencies.py` (#2222, #2223) left + open: the base commit's Cargo dependency graph was vendored for `cargo llvm-cov`, but nothing + ever built the PyO3 extension itself, so `python3 -m coverage run -m pytest` kept failing + collection with `ImportError: cannot import name '_core'` on 8 of the last 10 fast-mlsirm + fallbacks (fast-mlsirm#1907). `.github/workflows/opencode-review-dispatch.yml`'s + `run_python_test_coverage` now calls a new `build_maturin_extension_if_needed` helper for every + tracked Python project whose `pyproject.toml` declares `build-backend = "maturin"`: it runs + `maturin build --offline --release` against the vendored Cargo dependencies with + `CARGO_NET_OFFLINE=true CARGO_BUILD_JOBS=1` (the sandbox is memory-constrained), then + `pip install --user --no-index --no-deps` installs the built wheel before pytest runs, entirely + inside the existing `--network=none` sandbox. `tests/test_maturin_offline_build_contract.py` + proves both halves of the claim against a real PyO3 fixture crate: the vendored-offline build + produces an importable `_core` extension, and a dependency only a pull request's head added + (never seen by the base-commit materializer) is never fetched -- the offline build fails closed + on the missing crate instead of reaching the network. Refs fast-mlsirm#1907. diff --git a/requirements-opencode-review-ci-hashes.txt b/requirements-opencode-review-ci-hashes.txt index d8aaca3ad8..116009874b 100644 --- a/requirements-opencode-review-ci-hashes.txt +++ b/requirements-opencode-review-ci-hashes.txt @@ -4,9 +4,9 @@ attrs==26.1.0 \ --hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 \ --hash=sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32 # via interrogate -click==8.4.2 \ - --hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \ - --hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76 +click==8.5.0 \ + --hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360 \ + --hash=sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34 # via interrogate colorama==0.4.6 \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ @@ -137,69 +137,88 @@ coverage==7.15.4 \ # via # -r requirements-opencode-review-ci.txt # pytest-cov -hypothesis==6.163.0 \ - --hash=sha256:002a9709345892279fb0e81b5a05b72d08cfe81f937339827be0d588607ca9b0 \ - --hash=sha256:00d3091b28de83c5116e0ccd9a4bcb28ef61d2aace5df91093bb22434fd2350c \ - --hash=sha256:0a0c396244c13805edcb73ff467c4c8178ccefc41c4ef5ed00a68e612fd773e9 \ - --hash=sha256:0a933aca9ebf9daf951d07cf01200c94c321b6ee0b42cc7b67675c9686d914c2 \ - --hash=sha256:0cba5202f74e7e4cdb676d86f26e8cc1b4fdc88f7f58ba73c8ac45b6b22f3070 \ - --hash=sha256:213527755f0fc2b1f3721e73fd60023e2752a48f914e3e2df8d35111956ae5c8 \ - --hash=sha256:21e72e8d5818e5ef8cd6a2191c386e3fd1a6d9e3739cf97289b4d9b5dbc8e38d \ - --hash=sha256:2849c23b2e0fe2eef4c1ec336b01eac7ad7397c49fca43c264f59ec1e6046eac \ - --hash=sha256:28a6cc1c25a6cc9b6ec079eaabd32ac769994831ecddd57123ce43c9056dcf34 \ - --hash=sha256:31dc46c48aa53c3ec92d03120978ca7f19b9cf96d195ed3fc93503f1433c94a6 \ - --hash=sha256:320b076bf6436f971f1c73ee651e60001226d1b4e341f2c4a1ca87248261ca03 \ - --hash=sha256:331906cb029b6b360b8ebac3ec00c3cfa720037fe2efb294a503a1979c9a9a8f \ - --hash=sha256:34fc895691a2420595506eb17f3a104f2fa9039f013c0770a6cc2743ccaf6fed \ - --hash=sha256:3b6cee2afe6c67b31a4a64b63a876e0b020befdc61daabea80f7a0e14f19203a \ - --hash=sha256:3f3cceb4720a39127622fbf3bcebe1775b894372c53b5edddfdef10bbdeef9ec \ - --hash=sha256:40dfab6fe6a02a80abef81aebf88e53cd529e3f2f6ba3486b674a67b1f4a3512 \ - --hash=sha256:4159a1c2560e10de51b1c14956e277eb1b37526c9abef9e87c1e531760486448 \ - --hash=sha256:487ab8ec2f01a225d6a1e2ceadc5290cde2c691952bd2e7f76199cf82e06fb25 \ - --hash=sha256:4ab0dadc09c537d4ac57e564039dfe7daf09c98375306d54bfc0fd6c218efcca \ - --hash=sha256:50073f8e63c1e7d3403899755657a990d8bba7b5b5bff66b1c56796d4969bb28 \ - --hash=sha256:520480d4bd3a17557616c25923640953e360332c89d012fffcebd69857e674a9 \ - --hash=sha256:52f16840add2eb02c2416f3b83cec4f527b6c19699f2d31eff4859233c715526 \ - --hash=sha256:56ed585baab75cb98462c57ca88bbdc6a9d935a14118dd572fb476c3ecec2a06 \ - --hash=sha256:58be45d1737bf8c2e10cf29505c0f10f8a23d61bc82e4339182a6c8251cbc2d9 \ - --hash=sha256:59f5fdb8addb44c17520a60d50542d9db6ceba577bbf54efefa9c10ee20be140 \ - --hash=sha256:5a3ac6c62d49f7fe518dfe7fa924fa03aac839993702207802b0e45f9e1b0dab \ - --hash=sha256:67d1593941ede41052b4a35ec25b50d0e280358c7674ef7812d520010e7e8bdf \ - --hash=sha256:6ae63dec6d1d467b7f4737455f81a7a82f14a41c14510937fcfbc726a085b5f8 \ - --hash=sha256:7a3db868a943c814cc557104712d43bf609adfe5ea9f708f38377d366b4855f8 \ - --hash=sha256:7ca7b20bf38d51e15f7808b0239791c4792b1709ce0c63093acaff56a09c31e6 \ - --hash=sha256:7cb3d927360fe73f9a06d646e6082237142ee39c24679c7133d22bf06dd03b45 \ - --hash=sha256:7ef8954e37c80e0c46e6161eef1c72c71059b95250e620a77bd646f6c7a52a2d \ - --hash=sha256:8aac96db8a6c7ee43aba2ee0d3c43893da1fb7c38ed54790c1be2b6d8fd87b96 \ - --hash=sha256:8c5d1e6bad47edf6fb1d7406cf6d67314ac08325c63a49550d782a4596ea302b \ - --hash=sha256:9105c66ea8dbc108adc42058bb7b65bd953f53ee178bf63bf9ebb0cded6c8c96 \ - --hash=sha256:9be37b7ddf0af9e3f9112cd133afc34e78a56da1f96db5f2b4fc289fe1c4d1c3 \ - --hash=sha256:9c084749c115ea7918cf7efa144682783da17eec70d1276689182b871126e715 \ - --hash=sha256:9d23f0f3a14bb6e6f99c793d340196dba4af95ba25bfcab624d1794f540f5e27 \ - --hash=sha256:a16ebce774755a7a652bd44c62101dc914372ed1a98935969624848c9627b4a4 \ - --hash=sha256:a2a20e9835d3c4b293a709ee6ef769bcb18c6ed4ef337a9e251c1a9496d5e8be \ - --hash=sha256:a57352efa938889ea9992667a5014c0fc870d03945de71918574d1cf28276378 \ - --hash=sha256:ab34c61d9249f1a8129cb4276062c04e3e47b5be8de6446e7c7fe11362d6fe43 \ - --hash=sha256:b123b4995a7612f1130e2b2362c9a5d0568df887bf7e7bdb45c23af8cd5423c9 \ - --hash=sha256:b268211e625cd550e361fc387bf1db5deb1e9cae0ce4041116f0a0aafeef7c06 \ - --hash=sha256:b2ddcdaf6691101e06dc4a5add7b8c8fdf1e68daba599255a281f3f3550d3331 \ - --hash=sha256:b4ad2134405d5345434c22dea96bbc12c85abcfc3c253a8063dbc9ff01164555 \ - --hash=sha256:b839dfd1342bb50570cb0c66b80322307cdb468abf14faf5df4dab022bc1b9ce \ - --hash=sha256:b8f22fb8218ba6a452bf9000fc656e1ed57625d17cc8a3871a0fcea3b1b69ebf \ - --hash=sha256:bd312b15044b1c1a0920a5827a830559b2d1fa380851cedf509f8b835309c5b9 \ - --hash=sha256:c0ec3b709508ccd835d8ded1db025b7800618f2289a22a6bfd4927da5f4eb33c \ - --hash=sha256:c4f5be1482189c7b0a1dcac269fffe97a7d18cc04ac9a9a4d6613212dd87f38b \ - --hash=sha256:ca1b48bde68c528a79dec2a2859e05035802e5b1c9c3579f388c9de6ed6d0148 \ - --hash=sha256:d0838a28e9943d5b834ebae59b02adda76e2cd1e65caa808104c72102052057d \ - --hash=sha256:e165f6cc2075059b7c95dac1612bfb25494f72d90f56880e84c288b089f8a896 \ - --hash=sha256:e568a3d766b7ba8df00e0c33efc4c6530cde14fbc72daabe4824eed211ed7596 \ - --hash=sha256:ee47c2cb1be03a052ebd3549dad07f636a98b3ccfd7acbe5e17b3b7da0ab9e37 \ - --hash=sha256:f1fe222f50a1898e87a1e7323ab35f9e956278efabe4dd55a1342808206d05ad \ - --hash=sha256:f28ad27193c1fbcfb52ef2ee63d2b721563525089e80962b4268b306dac45507 \ - --hash=sha256:f2f1b67a48da86d3e41c9445367b49a49f7efdb60fc8b5e3593f05e6afb2efbe \ - --hash=sha256:f7f706df6839dcc53f20833f2933cbcd126fd2fdee7c312e053de49df4b64e44 \ - --hash=sha256:fae7305ae20fddeea09df317b920c45d3e20bfedbdb041f4db6ca5267c458189 \ - --hash=sha256:ffdda3006a383a48f71a23b4f2b3fae3fe1b09af67925d885985f7ec34d66bcb +hypothesis==6.168.0 \ + --hash=sha256:046fe4bcfce2a2fa186ba9d96bbb62c25c2f6c2e4071f0783ed6b5cc481d0669 \ + --hash=sha256:076a2096c34448931c3cfeb2eb7a6b843a56ffdce5e4e3a025bfdf8f935666d9 \ + --hash=sha256:085c9aa246487c56a40ca89003d285cbffdbb5be4097ba6d0139f9c21003c04a \ + --hash=sha256:0ba3838c4a92e0b9730d1ed7e67e4950c152ad79d0a0c7594065262db84c55c4 \ + --hash=sha256:112b0900059bf9d7d6528ed729770629ab146e0d133c4143b9bd4a01dc002bcc \ + --hash=sha256:16864797de4b024e4c6cebd44598af932f870aad811341bc5bc24c738801ff76 \ + --hash=sha256:1894782fae5d9a7bb44e6dcf848ccb09ccb5babab48d8b5c31a0a7fc025b82a1 \ + --hash=sha256:1d1aa5b3484e329295d88488a5ba06243909e65c2ab616513c2d36721de4ed1d \ + --hash=sha256:1f4cd0ff11bd470a1a846296ed5fe55e84214194850370994fd1370fe73d3099 \ + --hash=sha256:2085ee74ac3ab6b70e2f7ffae9b4cb74c246da2f574b2de81a0818a8a30f659f \ + --hash=sha256:2264f15a1c80329e3ad48e39c44bd5c9429b7b04c9ee62cdd72f4b10aaac9f29 \ + --hash=sha256:24b52a2b1c8db6e1e516f9295c8e4ef7ef63303ff24fbbc5b35f4ff71dcd732c \ + --hash=sha256:283eda952bcb1987ccba1c8b634db0e8a960e1e92e2daa7003bc2392f19cea01 \ + --hash=sha256:2a380b521b5a76a9e8917d64adcf7f861a45a4360a34b1579af14c5df8eb0377 \ + --hash=sha256:2a838218ff1eab8d7b4bf66b96037fce0a802f61f2fa5fd4b784696cac365ce7 \ + --hash=sha256:348d9b93fd4129f67f9bab94f3d70709a9372bbe0e0d22731325ce85d5eb409f \ + --hash=sha256:34e3c8b66047ba92f8b8df5e427074058d92db58038f007da4bf9d14e934ad3c \ + --hash=sha256:35f1262831b5acc74ded15f629965daffcd657f6016ee04fc9605f6eb2b334c0 \ + --hash=sha256:3b3ce1cce70b25a37ed1a38a53ce7204785726c675c0f41a0f83c338a7e47b3d \ + --hash=sha256:3bc00fd8cda04b58e37a1163e8a65389b247b4f5ee547ae37d244a4960995517 \ + --hash=sha256:3f6dcf66270278d078bed01b401f47db4e26456cd909d8e23c6b9366a6c0b131 \ + --hash=sha256:3f7486bed33225d02f6aa78a4c4ba2b6f84992a82571cdda1bf08dce41d13507 \ + --hash=sha256:4085b61e25d3dcc6c9151d4115269870aee8cdb921611ee5c989b2786449be09 \ + --hash=sha256:45fcfa05f746e253350f55f216bcef59754f5f2b85745f1fc2bb8ba81dd517a9 \ + --hash=sha256:47b89491ff02e3ae9b302c440457938e87b47a45b9a1d98ff5575b6910d779e2 \ + --hash=sha256:489d5c060f49f495b64215cae627c71730cffd5ef59dc4d7f431932e6e6d2e67 \ + --hash=sha256:4d7d29dd63ad9fdc4aa1d65fa272449e14aaf6c6bb8451091818c2945533a43a \ + --hash=sha256:527452b43e79e6dfbf9cb69145a940547a3cd177c556698a3fc939ed2354c4b3 \ + --hash=sha256:53469a1a7c4861b12c9a8622f762d7d1fd7bcf171884e1018ed5a8f063a5c063 \ + --hash=sha256:5427a3c951080c18170486f775df6a82153882b819eca6b8e7ed77693634e5ab \ + --hash=sha256:5920d267f7d8cfd376672f2bde5905cdf284d47519582e41ce7c142d48ee46c4 \ + --hash=sha256:5b54769033b84477931d2072e7133a7555e0de5c53fd5ca3bbde960762d7d31b \ + --hash=sha256:5f099b1c8fc49ec2d9d7944e661addb97d7c38e818fb8d1f78073c43895a87f6 \ + --hash=sha256:6b750390dac4429da0cb70ab3fe758457f0cea3d9c843d48c59d0690d1189fda \ + --hash=sha256:6de30e559eb151de14a5f74bceb4d97792a9315ada2a1816b5da825cd7d28edc \ + --hash=sha256:6f0dd437ec01140676192422b61f2f833b3ce6a3213da9b7e196ad6b3777e795 \ + --hash=sha256:6ff259260015f9be3756dcd4bc11c08e007314dec6b43d9a89084c4f34f94475 \ + --hash=sha256:719b45b0512e3535a6a0077c2f7c6053b02ac0e72d60693f66f98790a33855b2 \ + --hash=sha256:72af51087b7b5ab21c49f0d502f803c20897678652835596bd2a8b169a39135e \ + --hash=sha256:73084b76e4a79cd0f7883ce80fc60c9f374ce7dcad8f520b39db40470ce1852f \ + --hash=sha256:732ae5d47482f99d8028cca096729625f05690a83f5e7ce31466e266155792f4 \ + --hash=sha256:754016594fe78cef91790e0922f60d183c52f531255fbfa30dac495b813e2128 \ + --hash=sha256:76d4d36ed2fd62de11382f1d608169c1ffa9a49d3b9351146d8ff87cb81a66f7 \ + --hash=sha256:7d55562bf8d41cfa18559c33f30cadf44ceac8e517509d7a022a9feace621f28 \ + --hash=sha256:8067e6b4b48e5cfdc849a1a20c9d4972b3f532b3e3edb5e2b5dfd106045a5236 \ + --hash=sha256:812a84c4cc7f7ae4fcb39a5647cc2698e6c18254f8423126425578f1dcdac782 \ + --hash=sha256:891b2d281ede45130e7fa0a22fd65336cc77ef2f780ec3792e8de6fc274a02c8 \ + --hash=sha256:8e4b2d434e0dd134f3d31ac1efc1825bf99730dfe70fec005ff66d7211836d79 \ + --hash=sha256:9018b20acdb061b2ef4b2fa7f558ca5db97ffea316e0a528bc003a24b2ac996e \ + --hash=sha256:91e3de666a6c4f7543000d1710e25055d63ef3032c98bd2ab338b3087bdaa780 \ + --hash=sha256:92cff497b92e2285ff6a94193fdee04aba483a4115d501c1f9a570bd103fcd20 \ + --hash=sha256:93413d1b0af50a7b165d66278c529174bf2fd1773c78027735dc0b50d1d3fd27 \ + --hash=sha256:990026952d5b2eca290c88f639ac639233f47e13dae338c6dfb6e4774bcab349 \ + --hash=sha256:9a2079cd09919956dd388f1a1f8ea5a79f2b2437650fbeda31d8661217ffefef \ + --hash=sha256:9a72ed7afa1f7e30488b8a5754fca0ad9755518bdb77d6f0b003cadf7437a5f9 \ + --hash=sha256:9ba679f183c67adcb6f4ad93694beafb6da99fe691757f4e57b04ae77e581ba8 \ + --hash=sha256:9d9a8574f80fc859313aee56167d202e8625c0eedd200971130f0839f06d1c93 \ + --hash=sha256:a0d28418c104d7268fdebcc09bc49f7b6569b5eb942430c6859f53ec8d4edf63 \ + --hash=sha256:a4956f41ab1ec6e6ef9262a35970e9f3e2caaaa1cdafe0d413156c6934dd99d8 \ + --hash=sha256:a74b0945acbbd552c7c2d0a99a3b5232962b8848c8eed1829451800a9bfcf00b \ + --hash=sha256:a9650c4882fdbdd8e90bdae602a8bfa8c6f09dc5d06afec5b9b23982e8f60a04 \ + --hash=sha256:b5449a64eb37d9a4aa6ac9cd2ab0fd1a24145adf421ef1536884f73f39824887 \ + --hash=sha256:bc935a5d5f86fd8f5af951b8fbe00307f6f7c596f82a9a27c17d974f6ab0a26c \ + --hash=sha256:bfef4d46dbf1704a7b8fa3a78778651a2cb18870ca0a70da19c381646822b149 \ + --hash=sha256:c3af200b322f710c76c2189866246cdcff2039165dd77edff1a7bf1157162fb0 \ + --hash=sha256:cb10aa59b0af45badca76911f5323f40d24fdbe00d01b7b67fef8648c99411b5 \ + --hash=sha256:cd0c1dcf308e919c8ae708054d0ad61921ae87634a9aea574a9851da584cebc1 \ + --hash=sha256:d0620fa320fa66649e6bfd71e94f3f86115fffebb7e3c6dcece19d1aaff8e07f \ + --hash=sha256:d0bdb77f976740b8cd5ec697327ea343d02d052b9916d213b5d4c65d823415cd \ + --hash=sha256:db2751c27bffc8491a96d72969649089d5400115e4b7c49bf7167ebbdcc84193 \ + --hash=sha256:deb02de608268928d779aa889b0a9d67794b1cc0c54a322cf19e386be8a46ca7 \ + --hash=sha256:e21e30b76b6d3adb87c550576132a3204f4c257ec43353f6c09b9d59bb762abc \ + --hash=sha256:e2df8afacf9261070795db36db4a394e3ccdbb663fd2d38c7a9fba0c836dcecc \ + --hash=sha256:e86820053afad84677f301c0b892a226be1df49790800a65668ae7cc8a1ac571 \ + --hash=sha256:ec0886fe0be9091669937989f9a662beca42ae14a4a6dab25491c2c63365f88d \ + --hash=sha256:ecf0ab13cef899efb816ffdd7963e0679f372520884ce06756c7642f3df94213 \ + --hash=sha256:f62bdabf278db9ff61df5f3203d608949f0d893d0e30cdac3f2330e67e41ae68 \ + --hash=sha256:f77af7721ff35a58fa8797decd14c932c350a2548686c6e9b844db710a3a2441 \ + --hash=sha256:f89d8e998d3c936ffbbd1c3686c96f0378f6558aecc5967a3035a857f2bab0ad \ + --hash=sha256:fb8cdf45361e259df86e19f8cd042ce2d6c7e6ad88fa631b78a4e3a83c2e572d \ + --hash=sha256:fcc5bad4300a751804ce41f0e10d77f85272668160708ce39ec579bca8984843 # via -r requirements-opencode-review-ci.txt iniconfig==2.3.0 \ --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ @@ -209,9 +228,25 @@ interrogate==1.7.0 \ --hash=sha256:a320d6ec644dfd887cc58247a345054fc4d9f981100c45184470068f4b3719b0 \ --hash=sha256:b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12 # via -r requirements-opencode-review-ci.txt -packaging==26.2 \ - --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \ - --hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661 +maturin==1.15.0 \ + --hash=sha256:0ebf9767892725083138e671c34482c660317a2f3d6a29fc0e0f34e9d8c99136 \ + --hash=sha256:126e12e618b4db42f68c779a56d41f82a390145ba36ac3f621d057eb34f5ad9d \ + --hash=sha256:4f9d33e6c3f9615c8caceecbbbd440f8eb25a3ddeb687077682cd5eca2e9ae15 \ + --hash=sha256:552c2be4afd43fe8d5c9f3ec8d4c4756d973b8dcbe94c14084390301f50243e1 \ + --hash=sha256:653020a63525bb224e5ab0adf02e17a2e08bc86dbea7fc1399c9a56d7529b99e \ + --hash=sha256:6bf6dc62e22d4dcfd5a51244ff0d58975fa4979c48209fe84159617648956d82 \ + --hash=sha256:7ab7eebffd7b8debca2265985de4eaeb332141276d24b9560b5ad484d4b3add1 \ + --hash=sha256:7eb066372f541f8eb4909c79c5d9bd0b9e8125980bdf1ec9e8aba23c6c8d6c55 \ + --hash=sha256:94b26cc8e8aba61a5f2099715fe640e18c5f678e9a500408b38761263954228a \ + --hash=sha256:bf29beddd0c6708f112db51d5275fc28b28b9e9c9c5faae387eaef662918b176 \ + --hash=sha256:c40b4eae7bf5ef1f4b1af8d623fe4105016f93578fb15b764e741d08ec3b92dd \ + --hash=sha256:c7dc0c66c78d3debdd9c5aa807e861fbcbf07f3505d34b125df74c03986b0f48 \ + --hash=sha256:cd35772633f489841132bc8e71d6fc7f842df30b9c05cd5cdf1ee1ddcb744cc7 \ + --hash=sha256:da649988be98e87e009e51b1bf0d301b6a301bc0cecbdd60d40d8ba60748d1ca + # via -r requirements-opencode-review-ci.txt +packaging==26.3 \ + --hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \ + --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c # via pytest pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ @@ -223,9 +258,9 @@ py==1.11.0 \ --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 # via interrogate -pygments==2.20.0 \ - --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ - --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 +pygments==2.21.0 \ + --hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \ + --hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c # via pytest pytest==9.1.1 \ --hash=sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313 \ diff --git a/requirements-opencode-review-ci.txt b/requirements-opencode-review-ci.txt index 1e9a42f6a0..bf2112ed68 100644 --- a/requirements-opencode-review-ci.txt +++ b/requirements-opencode-review-ci.txt @@ -4,6 +4,12 @@ coverage==7.15.4 # collection. Matches the >=6.100 floor used by consumer repos (e.g. contextual-orchestrator). hypothesis>=6.100 interrogate==1.7.0 +# maturin (MIT/Apache-2.0, permissive) builds the PyO3 extension module for +# maturin/PyO3 projects (e.g. fast-mlsirm) inside the offline coverage sandbox, +# so `python3 -m coverage run -m pytest` can import the compiled `_core` +# module instead of failing collection with `ImportError: cannot import name +# '_core'`. See fast-mlsirm#1907. +maturin==1.15.0 pytest==9.1.1 pytest-cov==7.1.0 uv==0.12.7 diff --git a/tests/test_maturin_offline_build_contract.py b/tests/test_maturin_offline_build_contract.py new file mode 100644 index 0000000000..65e483bc72 --- /dev/null +++ b/tests/test_maturin_offline_build_contract.py @@ -0,0 +1,239 @@ +"""Contract: the coverage sandbox builds a PyO3/maturin extension fully offline. + +Reproduces the sandbox shape fast-mlsirm#1907 hit: `python3 -m coverage run -m pytest` failed +collection with `ImportError: cannot import name '_core'` because nothing in the +`--network=none` coverage container ever built the compiled extension. This exercises the same +two steps the workflow's `build_maturin_extension_if_needed` helper +(.github/workflows/opencode-review-dispatch.yml) performs -- vendor the *base* commit's Cargo +dependencies with materialize_base_rust_dependencies.py, then run +`maturin build --offline` against that vendor directory -- and proves both that the import +succeeds afterward and that a dependency only a pull request added is never fetched. +""" + +from __future__ import annotations + +import shutil +import subprocess +import sys +from pathlib import Path + +import pytest + +from scripts.ci import materialize_base_rust_dependencies as materializer + +def _maturin_importable() -> bool: + """Return whether ``sys.executable`` (the interpreter these tests run under) has maturin.""" + return ( + subprocess.run( + [sys.executable, "-c", "import maturin"], capture_output=True + ).returncode + == 0 + ) + + +pytestmark = pytest.mark.skipif( + shutil.which("cargo") is None or shutil.which("rustc") is None or not _maturin_importable(), + reason="cargo, rustc, and an importable maturin module are required to build a real PyO3 extension", +) + +_PYPROJECT_TOML = """\ +[build-system] +requires = ["maturin>=1,<2"] +build-backend = "maturin" + +[project] +name = "fixture_core" +version = "0.1.0" +requires-python = ">=3.10" + +[tool.maturin] +module-name = "fixture_core._core" +""" + +_CARGO_TOML = """\ +[package] +name = "fixture_core" +version = "0.1.0" +edition = "2021" + +[lib] +name = "_core" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = {{ version = "0.22", features = ["extension-module", "abi3-py310"] }} +{extra_dependency} +""" + +_LIB_RS = """\ +use pyo3::prelude::*; + +#[pyfunction] +fn ping() -> i64 {{ 42 }} + +#[pymodule] +fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {{ + m.add_function(wrap_pyfunction!(ping, m)?)?; + Ok(()) +}} +""" + + +def _git(repo: Path, *args: str) -> str: + return subprocess.run( + ["git", "-C", str(repo), *args], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + + +def _init_repo(repo: Path) -> None: + repo.mkdir(parents=True, exist_ok=True) + _git(repo, "init") + _git(repo, "config", "user.name", "Test") + _git(repo, "config", "user.email", "test@example.invalid") + + +def _write_fixture_project(repo: Path, *, extra_dependency: str = "") -> None: + (repo / "pyproject.toml").write_text(_PYPROJECT_TOML, encoding="utf-8") + (repo / "Cargo.toml").write_text( + _CARGO_TOML.format(extra_dependency=extra_dependency), encoding="utf-8" + ) + src_dir = repo / "src" + src_dir.mkdir(exist_ok=True) + (src_dir / "lib.rs").write_text(_LIB_RS, encoding="utf-8") + package_dir = repo / "fixture_core" + package_dir.mkdir(exist_ok=True) + (package_dir / "__init__.py").touch() + subprocess.run( + ["cargo", "generate-lockfile"], cwd=repo, check=True, capture_output=True + ) + + +def _commit_all(repo: Path, message: str) -> str: + _git(repo, "add", "-A") + _git(repo, "commit", "-m", message) + return _git(repo, "rev-parse", "HEAD") + + +def _build_offline( + repo: Path, cargo_home: Path, vendor_output: Path, final_vendor_dir: Path, dist_dir: Path +) -> subprocess.CompletedProcess[str]: + """Run the exact offline build the sandbox's coverage step performs. + + Mirrors the workflow: materialization runs on the runner at ``vendor_output``, then the + ``base-rust-dependencies`` directory is copied into the trusted image at the fixed path + the baked ``cargo-config.toml`` names (``final_vendor_dir`` here). + """ + cargo_home.mkdir(parents=True, exist_ok=True) + shutil.copyfile(vendor_output / "cargo-config.toml", cargo_home / "config.toml") + shutil.copytree(vendor_output / "vendor", final_vendor_dir) + return subprocess.run( + [sys.executable, "-m", "maturin", "build", "--offline", "--release", "-o", str(dist_dir)], + cwd=repo, + env={ + "PATH": __import__("os").environ["PATH"], + "HOME": __import__("os").environ.get("HOME", "/tmp"), + "CARGO_HOME": str(cargo_home), + "CARGO_NET_OFFLINE": "true", + "CARGO_BUILD_JOBS": "1", + }, + capture_output=True, + text=True, + timeout=600, + ) + + +def test_offline_build_and_import_of_pyo3_extension_succeeds(tmp_path: Path) -> None: + """The vendored-offline build produces an importable `_core` extension module.""" + repo = tmp_path / "fixture-repo" + _init_repo(repo) + _write_fixture_project(repo) + base_sha = _commit_all(repo, "base commit") + + vendor_output = tmp_path / "vendor-output" + final_vendor_dir = tmp_path / "final-vendor-location" + materializer.materialize( + repo, base_sha, vendor_output, vendor_dir_for_config=str(final_vendor_dir) + ) + assert (vendor_output / "vendor").is_dir() + assert final_vendor_dir.as_posix() in (vendor_output / "cargo-config.toml").read_text( + "utf-8" + ) + + cargo_home = tmp_path / "cargo-home" + dist_dir = tmp_path / "dist" + result = _build_offline(repo, cargo_home, vendor_output, final_vendor_dir, dist_dir) + assert result.returncode == 0, result.stderr + + wheels = list(dist_dir.glob("*.whl")) + assert len(wheels) == 1 + + install_root = tmp_path / "install-root" + subprocess.run( + [ + sys.executable, + "-m", + "pip", + "install", + "--no-index", + "--no-deps", + "--target", + str(install_root), + str(wheels[0]), + ], + check=True, + capture_output=True, + text=True, + ) + check = subprocess.run( + [sys.executable, "-c", "from fixture_core import _core; print(_core.ping())"], + cwd=tmp_path, + env={"PYTHONPATH": str(install_root)}, + capture_output=True, + text=True, + ) + assert check.returncode == 0, check.stderr + assert check.stdout.strip() == "42" + + +def test_pull_request_added_dependency_is_never_fetched_offline(tmp_path: Path) -> None: + """A dependency only the PR head added must not be silently fetched offline.""" + repo = tmp_path / "fixture-repo" + _init_repo(repo) + _write_fixture_project(repo) + base_sha = _commit_all(repo, "base commit") + + # Simulate a pull request that adds a new Cargo dependency the trusted base + # materializer never saw and therefore never vendored. + _write_fixture_project(repo, extra_dependency='itoa = "1"') + (repo / "src" / "lib.rs").write_text( + _LIB_RS.replace( + "fn ping() -> i64 {{ 42 }}", + 'fn ping() -> i64 {{ itoa::Buffer::new().format(42i64).len() as i64 }}', + ), + encoding="utf-8", + ) + subprocess.run(["cargo", "generate-lockfile"], cwd=repo, check=True, capture_output=True) + _commit_all(repo, "pull request adds a new Cargo dependency") + + vendor_output = tmp_path / "vendor-output" + final_vendor_dir = tmp_path / "final-vendor-location" + materializer.materialize( + repo, base_sha, vendor_output, vendor_dir_for_config=str(final_vendor_dir) + ) + + vendor_crate_names = { + entry.name.rsplit("-", 1)[0] for entry in (vendor_output / "vendor").iterdir() + } + assert "itoa" not in vendor_crate_names + + cargo_home = tmp_path / "cargo-home" + dist_dir = tmp_path / "dist" + result = _build_offline(repo, cargo_home, vendor_output, final_vendor_dir, dist_dir) + + assert result.returncode != 0 + combined_output = result.stdout + result.stderr + assert "itoa" in combined_output + assert not list(dist_dir.glob("*.whl")) diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 58dbc7728b..508ebd475d 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -17,7 +17,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "76fd485dc1095d002381bfd1b4aa746c8df7495c" +REVIEW_DISPATCH_BLOB_SHA = "9f2b2ae52588dce78542a01b9e3337b979521999" def _workflow_text(path: Path) -> str: