From b1a6d8d7b956f84970f6894f8382c2412771e522 Mon Sep 17 00:00:00 2001 From: Yuval Kogman Date: Fri, 22 May 2026 01:41:47 +0000 Subject: [PATCH 1/6] add proptest dev-dependency --- Cargo.lock | 620 ++++++++++++++++++++++++++++++ crates/concurrent-psbt/Cargo.toml | 3 + 2 files changed, 623 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0de2366..edc39b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,10 +2,630 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c61cd05405eb1d0f3a4660f802bad76ece84b6e722426342ba5dd511f724e97" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "concurrent-psbt" version = "0.1.0" +dependencies = [ + "proptest", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] [[package]] name = "scrubber" version = "0.1.0" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zerocopy" +version = "0.8.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/crates/concurrent-psbt/Cargo.toml b/crates/concurrent-psbt/Cargo.toml index e6855c5..da685d6 100644 --- a/crates/concurrent-psbt/Cargo.toml +++ b/crates/concurrent-psbt/Cargo.toml @@ -7,3 +7,6 @@ edition = "2024" default = ["unit-tests", "prop-tests"] unit-tests = [] prop-tests = [] + +[dev-dependencies] +proptest = "1" From 3f9a31274cb9d4d9cf5ac53e2b99d58b98dd5dcb Mon Sep 17 00:00:00 2001 From: Yuval Kogman Date: Thu, 28 May 2026 22:53:25 +0000 Subject: [PATCH 2/6] nix: coverage via cargo-llvm-cov nextest w/ --no-tests=warn Use cargo-llvm-cov nextest --no-tests=warn instead of the exit-status fallback that silently swallowed build failures. --- nix/checks.nix | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/nix/checks.nix b/nix/checks.nix index fd340f9..4d6d505 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -54,18 +54,17 @@ // { cargoArtifacts = cargoArtifactsDev; pnameSuffix = "-coverage"; - nativeBuildInputs = [ pkgs.cargo-llvm-cov ]; + nativeBuildInputs = with pkgs; [ + cargo-llvm-cov + cargo-nextest + ]; buildPhaseCargoCommand = '' + cargo llvm-cov nextest --no-report --no-tests=warn --all-features + mkdir -p $out - cargo llvm-cov --all-features --lcov --output-path $out/coverage.lcov || { - # no coverage data when there are no tests yet - if [ ! -s $out/coverage.lcov ]; then - echo "no coverage data (no tests), skipping assertion" - exit 0 - fi - exit 1 - } - cargo llvm-cov report --fail-under-regions 100 + if [ -f target/llvm-cov-target/source.profdata ]; then + cargo llvm-cov report --lcov --output-path $out/coverage.lcov --fail-under-regions 100 + fi ''; installPhase = "true"; } From 57777f97dbef9237bfa1d263cf3a5f57811425bb Mon Sep 17 00:00:00 2001 From: Yuval Kogman Date: Thu, 28 May 2026 22:53:25 +0000 Subject: [PATCH 3/6] enable #[coverage(off)] feature on nightly This is conditional on coverage_nightly cfg_attr (set by cargo llvm-cov on nightly toolchains), this cfg_attr was allowed in nrrq --- crates/concurrent-psbt/Cargo.toml | 3 +++ crates/concurrent-psbt/src/lib.rs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/crates/concurrent-psbt/Cargo.toml b/crates/concurrent-psbt/Cargo.toml index da685d6..e29fc4a 100644 --- a/crates/concurrent-psbt/Cargo.toml +++ b/crates/concurrent-psbt/Cargo.toml @@ -10,3 +10,6 @@ prop-tests = [] [dev-dependencies] proptest = "1" + +[lints] +workspace = true diff --git a/crates/concurrent-psbt/src/lib.rs b/crates/concurrent-psbt/src/lib.rs index 45278f2..d3a543f 100644 --- a/crates/concurrent-psbt/src/lib.rs +++ b/crates/concurrent-psbt/src/lib.rs @@ -1 +1,3 @@ #![forbid(unsafe_code)] +#![allow(unused_features)] +#![cfg_attr(coverage_nightly, feature(coverage_attribute))] From 19f4fd0cfc094a0f945a3260a335027c414ebd89 Mon Sep 17 00:00:00 2001 From: Yuval Kogman Date: Sat, 16 May 2026 09:25:11 +0000 Subject: [PATCH 4/6] introduce Join and JoinMut traits Join: infallible binary merge (semilattice operation). Implementors must satisfy idempotent, commutative, and associative laws. JoinMut: in-place variant; blanket impl provides Join automatically. assert_join_laws! is a crate-internal macro (gated behind prop-tests feature, scoped via #[macro_use]). Given an arbitrary strategy, it generates three proptests verifying the semilattice laws for any Join + Clone + PartialEq + Debug type. Used by downstream commits to validate collection and domain-type Join implementations. --- crates/concurrent-psbt/src/lattice/join.rs | 104 +++++++++++++++++++++ crates/concurrent-psbt/src/lattice/mod.rs | 2 + crates/concurrent-psbt/src/lib.rs | 5 + 3 files changed, 111 insertions(+) create mode 100644 crates/concurrent-psbt/src/lattice/join.rs create mode 100644 crates/concurrent-psbt/src/lattice/mod.rs diff --git a/crates/concurrent-psbt/src/lattice/join.rs b/crates/concurrent-psbt/src/lattice/join.rs new file mode 100644 index 0000000..773d288 --- /dev/null +++ b/crates/concurrent-psbt/src/lattice/join.rs @@ -0,0 +1,104 @@ +/// Trait for infallible join operation. +pub trait Join: Sized { + /// Merge two values of a given type into a new value of the same type + /// incorporating the information of both inputs. + /// + /// This operation should be associative, commutative and idempotent. + fn join(self, other: Self) -> Self; +} + +/// In-place variant of [`Join`]. +/// +/// Implementing `JoinMut` provides `Join` automatically via blanket impl. +pub trait JoinMut: Sized { + /// Merge `other` into `self` in place. + fn join_mut(&mut self, other: Self); +} + +impl Join for T { + fn join(mut self, other: Self) -> Self { + self.join_mut(other); + self + } +} + +/// Proptest macros for verifying lattice laws. Available when `prop-tests` +/// feature is enabled. +/// +/// # `assert_join_laws!($strategy)` +/// +/// Generates idempotent, commutative, and associative property tests for +/// any type implementing [`Join`] + `Clone` + `PartialEq` + `Debug`. +#[cfg(feature = "prop-tests")] +#[allow(unused_macros)] // used by downstream commits (partial.rs, collection modules) +macro_rules! assert_join_laws { + ($strategy:expr) => { + proptest! { + #[test] + fn idempotent(a in $strategy) { + prop_assert_eq!(a.clone().join(a.clone()), a); + } + + #[test] + fn commutative(a in $strategy, b in $strategy) { + prop_assert_eq!(a.clone().join(b.clone()), b.join(a)); + } + + #[test] + fn associative(a in $strategy, b in $strategy, c in $strategy) { + prop_assert_eq!( + a.clone().join(b.clone()).join(c.clone()), + a.join(b.join(c)), + ); + } + } + }; +} + +#[cfg(test)] +#[cfg_attr(coverage_nightly, coverage(off))] +mod tests { + use super::*; + + impl JoinMut for u32 { + fn join_mut(&mut self, other: Self) { + *self = (*self).max(other); + } + } + + #[cfg(feature = "unit-tests")] + mod unit { + use super::*; + + impl Join for () { + fn join(self, _other: Self) -> Self { + self + } + } + + #[test] + fn unit_type_join() { + assert_eq!(Join::join((), ()), ()); + } + + #[test] + fn join_mut_in_place() { + let mut a = 1u32; + a.join_mut(2); + assert_eq!(a, 2); + } + + #[test] + fn blanket_join_from_join_mut() { + assert_eq!(Join::join(1u32, 2), 2); + } + } + + #[cfg(feature = "prop-tests")] + mod prop { + use super::*; + use proptest::prelude::*; + + assert_join_laws!(any::()); + } +} diff --git a/crates/concurrent-psbt/src/lattice/mod.rs b/crates/concurrent-psbt/src/lattice/mod.rs new file mode 100644 index 0000000..eea0426 --- /dev/null +++ b/crates/concurrent-psbt/src/lattice/mod.rs @@ -0,0 +1,2 @@ +#[macro_use] +pub mod join; diff --git a/crates/concurrent-psbt/src/lib.rs b/crates/concurrent-psbt/src/lib.rs index d3a543f..356423a 100644 --- a/crates/concurrent-psbt/src/lib.rs +++ b/crates/concurrent-psbt/src/lib.rs @@ -1,3 +1,8 @@ #![forbid(unsafe_code)] #![allow(unused_features)] #![cfg_attr(coverage_nightly, feature(coverage_attribute))] + +#[macro_use] +mod lattice; + +pub use lattice::join::{Join, JoinMut}; From a0fb98234828675ccb84732981bc13cdec4d6126 Mon Sep 17 00:00:00 2001 From: Yuval Kogman Date: Sat, 16 May 2026 09:25:11 +0000 Subject: [PATCH 5/6] PartialJoin trait abstracting fallible merge operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PartialJoin::try_join returns JoinResult, either Ok(v), where v the least upper bound, or a Err(Conflict) where the conflict contains values that could not be joined. Conflict is a multiset with set-equality semantics (order-independent). It implements JoinMut to merge conflict sets (union of distinct values). JoinResult itself implements Join: (Ok(a), Ok(b)) => a.try_join(b) (delegate to PartialJoin on V) (Ok(v), Err(c)) => Err(c ∪ {v}) (absorb into conflict) (Err(c), Ok(v)) => Err(c ∪ {v}) (absorb into conflict) (Err(a), Err(b)) => Err(a ∪ b) (join conflict sets) Containers or product types that wrap their fields in JoinResult can implement Join recursively to compute the field-by-field merge without early exit. assert_partial_join_laws! is a crate-internal macro (gated behind prop-tests feature, scoped via #[macro_use]). Given clean-value and result strategies, it generates try_join law tests, JoinResult law tests (via assert_join_laws!), and a wrap roundtrip test. ## Rationale for Conflict as flat, order preserving & order insensitive Conflicts represent a formal completion of the partial semilattice `V : PartialJoin`. If `a` and `b` are conflicts, commutativity requires that `a.join(b) == b.join(a)`, and idempotence requires that `a.join(a) == a`. This is somewhat at odds with keeping track of where each conflicting value originated from. The main purpose of preserving the order is to allow provenance to be tracked. If `a` and `b` are both conflict free, and `let c = a.join(b)`, then `c.conflicted_field.len() == 2` and the first value is from `a` whereas the second is from `b`, which makes reporting this as an error with clear diagnostics easier, without requiring the provenance be tracked by some kind of surrogate ID. This does not generalize to n > 2, because if `a.join(b).join(c).some_conflicted_field.len() == 2`, the values could originate from `(a, b)`, `(b, c)`, or `(a, c)`. This compromise keeps the interface and implementation simple, allows provenance to be tracked as long as it's done one pair of values at a time in a straightforward way, but imposes no additional burdens on users that do not care about provenance (for instance if computing something like `vs.reduce(|a, b| a.join(b))`) ### Alternatives considered Several alternative approaches were tried, of which the compromise of making `Conflict` just a thin wrapper around Vec seemed the best. #### HashSet or BTreeSet based This alternative is very close to what is implemented. The differences are that with a Vec, the order is preserved, the implementation of equality and `join` has quadratic complexity. We expect `n` to be very small so this shouldn't make a difference in practice. Using a lookup based set requires `V : Hash` or `V : Ord` which the current `Conflict` does not require (unfortunately adding it later would be semver breaking, as would be changing the return value from `iter()` or the associated `IntoIter` type of the `IntoIterator` impl). #### Recursive data type The following definition could in principle shadow the `join` structure: ```rs enum Conflict { Value(V), Pair([Box; 2]) } ``` In this case, if `a.join(b).join(c.join(d))` has 4 conflicting values, they would take the form `Pair([ Pair([ Value(x), Value(y) ]), Pair([ Value(z), Value(w) ]) ])`, which is arguably more informative. Unfortunately this is still imprecise because if `a.join(b).join(c)` has a binary conflict `Pair([ Value(x), Value(y) ])`, it's still ambiguous in the same way. In order for this approach to be workable it has to shadow the syntax tree of the join operation for the Ok branch too, in which case this entire abstraction kind of only computing the transpose, going from e.g. a list of structs with values, to a struct of lists of values, but not reducing any of the complexity unless there are no conflicts anywhere. The purpose of these abstractions is to take the problem of merging two or more compound values into the a series of simpler problems, merging two or more elements of a simpler type. Tracking provenance with perfect fidelity means that if there is any conflict the structure is not simplified at all. #### n-ary join The final option considered was defining join not as a binary operation but n-ary. This is no different than the other options in terms of expressive power, it could be just a simple transpose step followed by some kind of collapse-compatible-values-to-LUB step. Presumably this could be impl on `SomeNewType(Vec)`, with a transpose operation to a `VecPsbt` (analog of `ResultPsbt`) which internally contains `Vec<>` wrappers (instead of `JoinResult` wrappers) for each field. Then this transposed structure would be joined field-wise, attempting to collapse the n items to their LUB. The current implementation can kinda represent this operation by mapping *everything* to a conflict, joining and then reducing the conflicts, at the cost of no longer having the special meaning of a unary conflict representing a global invariant violation. ### Associated error type or generics The above alternatives imply a "one size fits all" approach. However, PartialJoin could have an Error type, where `JoinResult = Result::Error>`. Ostensibly this would allow some choice, but with associated types the choice is fixed per implementation of the trait and so would not afford users the choice of whether to opt out of provenance tracking for simpler errors or opt in and deal with the added complexity. Making the error type fully generic would make that possible with even more complexity and syntactic overhead. However, no generality would be gained for this additional complexity. Thinking of Conflict as just "deferred arguments for a join" (i.e. a formal product), any arbitrary merge operation can be expressed by just taking those arguments. More formally, Conflict is the free semilattice (sets under union) over V. Since every semilattice is a quotient of the free semilattice, so there is no operation that can be expressed by setting Error to some type that merges V's according to some rules (e.g. taking the max of integers) that can't be expressed by simply processing the conflict after the fact. ### Conclusion For these reasons, making Conflict a thin wrapper around `Vec` seems like the best compromise: has the same expressive power but results in a simpler interface than all the alternatives, and makes provenance tracking possible and even relatively straightforward without forcing it on all users. --- crates/concurrent-psbt/src/lattice/mod.rs | 2 + crates/concurrent-psbt/src/lattice/partial.rs | 382 ++++++++++++++++++ crates/concurrent-psbt/src/lib.rs | 1 + 3 files changed, 385 insertions(+) create mode 100644 crates/concurrent-psbt/src/lattice/partial.rs diff --git a/crates/concurrent-psbt/src/lattice/mod.rs b/crates/concurrent-psbt/src/lattice/mod.rs index eea0426..fb450d6 100644 --- a/crates/concurrent-psbt/src/lattice/mod.rs +++ b/crates/concurrent-psbt/src/lattice/mod.rs @@ -1,2 +1,4 @@ #[macro_use] pub mod join; +#[macro_use] +pub mod partial; diff --git a/crates/concurrent-psbt/src/lattice/partial.rs b/crates/concurrent-psbt/src/lattice/partial.rs new file mode 100644 index 0000000..baeb6c0 --- /dev/null +++ b/crates/concurrent-psbt/src/lattice/partial.rs @@ -0,0 +1,382 @@ +//! Some types have a natural merge operation but that isn't defined for all pairs of values. +//! [`PartialJoin::try_join`] captures this. +//! +//! It returns [`JoinResult`], an alias for `Result>`, where [`Conflict`], +//! collects all the distinct values that couldn't be merged. +//! +//! [`JoinResult`] implements [`Join`], which is an infallible join operation (the free +//! semilattice over `V`, more or less), in terms of [`Conflict`]'s [`Join`] implementation. + +use crate::lattice::join::{Join, JoinMut}; + +/// Fallible join for scalar value types where merge may produce a conflict. +/// +/// Containers should not implement this, instead they should implement [`Join`] by wrapping +/// contained values in [`JoinResult`]. +pub trait PartialJoin: Sized + PartialEq { + /// Attempt to join two values. + /// + /// Returns `Ok(lub)` where `lub` is the least upper bound if one exists, or `Err(Conflict)` + /// containing both values. + /// + /// Although `Conflict` has set semantics (equality treats it as an unordered collection), + /// insertion order is preserved during iteration: when two `Ok` values are joined and produce + /// a binary conflict, the first value is the left operand and the second is the right. + /// + /// Provenance is only reliable when both operands were `Ok` before the join. When an existing + /// `Conflict` is joined with another value, new entries are appended, so a multi-way fold does + /// not preserve per-operand attribution. + fn try_join(self, other: Self) -> JoinResult; + + /// Lift a value into the result domain as `Ok(self)`. + fn wrap(self) -> JoinResult { + Ok(self) + } +} + +/// Result of a fallible join: `Ok(v)` where `v` is the least upper bound if one exists, +/// `Err(Conflict)` otherwise. +/// +/// `JoinResult` implements [`Join`]. +pub type JoinResult = Result>; + +impl Join for JoinResult +where + V: PartialJoin, +{ + fn join(self, other: Self) -> Self { + match (self, other) { + (Ok(a), Ok(b)) => a.try_join(b), + (Err(a), Err(b)) => Err(a.join(b)), + (Err(a), Ok(b)) => Err(a.join(Conflict::singleton(b))), + (Ok(a), Err(b)) => Err(Conflict::singleton(a).join(b)), + } + } +} + +/// A set of conflicting values, produced when a join for those values does not exist. +/// +/// When two participants set the same field to different values, neither can be chosen without +/// losing information. `Conflict` preserves the set of distinct conflicting values allowing the +/// caller to inspect or resolve the disagreement. +/// +/// The inner values are [`PartialJoin`] with equality based on [`PartialEq`]. Duplicate values +/// are omitted. +#[derive(Debug, Clone)] +pub struct Conflict(Vec); + +impl Conflict { + /// Wrap a single value into a conflict. + fn singleton(v: V) -> Self { + Self(vec![v]) + } + + /// Build from an iterator, deduplicating values. + #[allow(dead_code)] // production caller: tspwkqxz (values.rs IdempotentValue::try_join) + pub(crate) fn from_values(iter: impl IntoIterator) -> Self { + let mut vals = Vec::new(); + for v in iter { + if !vals.contains(&v) { + vals.push(v); + } + } + Self(vals) + } + + /// Iterates over references to the conflicted values. + fn iter(&self) -> std::slice::Iter<'_, V> { + self.0.iter() + } + + /// Number of distinct conflicting values. + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl JoinMut for Conflict { + fn join_mut(&mut self, other: Self) { + for value in other.0 { + if !self.0.contains(&value) { + self.0.push(value) + } + } + } +} + +/// Set equality: same elements, regardless of order. +/// +/// $O(n^2)$ but $n$ is tiny in practice (typically 2). +impl PartialEq for Conflict { + fn eq(&self, other: &Self) -> bool { + self.0.len() == other.0.len() && self.iter().all(|v| other.0.contains(v)) + } +} + +impl Eq for Conflict {} + +impl IntoIterator for Conflict { + type Item = V; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, V: PartialJoin> IntoIterator for &'a Conflict { + type Item = &'a V; + type IntoIter = std::slice::Iter<'a, V>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// Proptest macros for verifying `PartialJoin` laws and the `JoinResult` completion. Available +/// when `prop-tests` feature is enabled. +/// +/// # `assert_partial_join_laws!($arb_clean, $arb_result)` +/// +/// Generates: +/// - `try_join` laws on clean values: idempotent, commutative, associative (via wrap/join) +/// - `JoinResult` laws on pre-built Ok/Err values: idempotent, commutative, associative +/// - `wrap_roundtrip`: wrapping a clean value always produces `Ok` +/// +/// The `$arb_clean` strategy produces clean (pre-join) values. +/// The `$arb_result` strategy produces `JoinResult` values (both Ok and Err). +#[cfg(feature = "prop-tests")] +#[allow(unused_macros)] // used by downstream commits (collection modules, domain types) +macro_rules! assert_partial_join_laws { + ($arb_clean:expr, $arb_result:expr) => { + proptest! { + #[test] + fn try_join_idempotent(a in $arb_clean) { + prop_assert_eq!(a.clone().try_join(a.clone()), Ok(a)); + } + + #[test] + fn try_join_commutative(a in $arb_clean, b in $arb_clean) { + prop_assert_eq!(a.clone().try_join(b.clone()), b.try_join(a)); + } + + #[test] + fn try_join_associative(a in $arb_clean, b in $arb_clean, c in $arb_clean) { + prop_assert_eq!( + a.clone().try_join(b.clone()).join(c.clone().wrap()), + a.wrap().join(b.try_join(c)), + ); + } + + #[test] + fn wrap_roundtrip(a in $arb_clean) { + let wrapped = a.clone().wrap(); + prop_assert!(wrapped.is_ok()); + prop_assert_eq!(wrapped.expect("should be Ok"), a); + } + } + + mod join_result { + use super::*; + assert_join_laws!($arb_result); + } + }; +} + +#[cfg(all(test, any(feature = "unit-tests", feature = "prop-tests")))] +#[cfg_attr(coverage_nightly, coverage(off))] +mod tests { + use super::*; + + #[derive(Debug, Clone, Copy, PartialEq)] + struct Foo(u8); + + impl PartialJoin for Foo { + fn try_join(self, other: Self) -> JoinResult { + if self == other { + Ok(self) + } else { + Err(Conflict::from_values([self, other])) + } + } + } + + #[cfg(feature = "unit-tests")] + mod unit { + use super::*; + + #[test] + fn result_ok_ok_different_produces_conflict() { + assert_eq!( + Foo(0).wrap().join(Foo(1).wrap()), + Err(Conflict(vec![Foo(0), Foo(1)])), + ); + } + + #[test] + fn result_err_ok_absorbs() { + let err: JoinResult = Err(Conflict(vec![Foo(0), Foo(1)])); + assert_eq!( + err.join(Ok(Foo(2))), + Err(Conflict(vec![Foo(0), Foo(1), Foo(2)])) + ); + } + + #[test] + fn result_ok_err_absorbs() { + let err: JoinResult = Err(Conflict(vec![Foo(0), Foo(1)])); + assert_eq!( + Ok(Foo(2)).join(err), + Err(Conflict(vec![Foo(2), Foo(0), Foo(1)])) + ); + } + + #[test] + fn result_err_err_merges_conflicts() { + let a: JoinResult = Err(Conflict(vec![Foo(0), Foo(1)])); + let b: JoinResult = Err(Conflict(vec![Foo(1), Foo(2)])); + assert_eq!(a.join(b), Err(Conflict(vec![Foo(0), Foo(1), Foo(2)]))); + } + + #[test] + fn different_conflicts_are_not_equal() { + let a = Conflict::from_values([Foo(0), Foo(1)]); + let b = Conflict::from_values([Foo(0), Foo(2)]); + assert_ne!(a, b); + } + + #[test] + fn different_length_conflicts_are_not_equal() { + let a = Conflict::from_values([Foo(0), Foo(1)]); + let b = Conflict::from_values([Foo(0), Foo(1), Foo(2)]); + assert_ne!(a, b); + } + + #[test] + fn conflict_from_equal_pair_deduplicates() { + let c = Conflict::from_values([Foo(0), Foo(0)]); + assert_eq!(c.len(), 1); + } + + #[test] + fn len_distinct_values() { + let c = Conflict::from_values([Foo(0), Foo(1)]); + assert_eq!(c.len(), 2); + } + + #[test] + fn is_empty() { + assert!(Conflict::::from_values([]).is_empty()); + assert!(!Conflict::from_values([Foo(0)]).is_empty()); + } + + #[test] + fn into_iter_yields_elements() { + let c = Conflict::from_values([Foo(0), Foo(1)]); + let v: Vec<_> = c.into_iter().collect(); + assert_eq!(v, vec![Foo(0), Foo(1)]); + } + + #[test] + fn borrowed_iteration() { + let c = Conflict::from_values([Foo(0), Foo(1)]); + let v: Vec<_> = (&c).into_iter().collect(); + assert_eq!(v, vec![&Foo(0), &Foo(1)]); + } + + #[test] + fn conflict_preserves_order() { + let x = Conflict::from_values([Foo(0)]); + let y = Conflict::from_values([Foo(1)]); + + let xy = x.clone().join(y.clone()); + let yx = y.clone().join(x.clone()); + + assert_eq!(xy, yx); + assert_ne!(xy.iter().collect::>(), yx.iter().collect::>()); + assert_ne!( + xy.into_iter().collect::>(), + yx.into_iter().collect::>() + ); + } + } + + // Small domain (4 values): high collision rate exercises the + // equal-value (Ok) path while still covering the conflict (Err) path. + #[cfg(feature = "prop-tests")] + mod prop { + use super::*; + use proptest::prelude::*; + + pub fn arb_foo() -> impl Strategy { + (0u8..4).prop_map(Foo) + } + + /// Generate a JoinResult which is either Ok(v) or Err(Conflict) with 1–5 values. + /// Exercises all arms of JoinResult::join. + pub fn arb_join_result() -> impl Strategy> { + prop_oneof![ + arb_foo().prop_map(Ok), + proptest::collection::vec(arb_foo(), 1..=5) + .prop_map(|v| Err(Conflict::from_values(v))), + ] + } + + assert_partial_join_laws!(arb_foo(), arb_join_result()); + + proptest! { + #[test] + fn conflict_into_iter_roundtrips(a in arb_foo(), b in arb_foo()) { + let c = Conflict::from_values([a, b]); + let vals = c.clone().into_iter(); + let rebuilt = Conflict::from_values(vals); + prop_assert_eq!(c, rebuilt); + } + + #[test] + fn borrowed_iter_matches_owned(a in arb_foo(), b in arb_foo()) { + let c = Conflict::from_values([a, b]); + let owned: Vec<_> = c.clone().into_iter().collect(); + let borrowed: Vec<_> = (&c).into_iter().copied().collect(); + prop_assert_eq!(owned, borrowed); + } + + #[test] + fn len_matches_iter_count(a in arb_foo(), b in arb_foo(), c in arb_foo()) { + let conflict = Conflict::from_values([a, b, c]); + prop_assert_eq!(conflict.len(), conflict.into_iter().count()); + } + + #[test] + fn non_empty_after_construction(a in arb_foo()) { + let c = Conflict::from_values([a]); + prop_assert!(!c.is_empty()); + } + + #[test] + fn empty_from_empty(a in arb_foo()) { + let _ = a; // use the parameter to satisfy proptest + let c = Conflict::::from_values([]); + prop_assert!(c.is_empty()); + prop_assert_eq!(c.len(), 0); + } + + #[test] + fn conflict_different_lengths_not_equal( + a in arb_foo(), + b in arb_foo(), + c in arb_foo(), + ) { + let short = Conflict::from_values([a, b]); + let long = Conflict::from_values([a, b, c]); + if short.len() != long.len() { + prop_assert_ne!(short, long); + } + } + } + } +} diff --git a/crates/concurrent-psbt/src/lib.rs b/crates/concurrent-psbt/src/lib.rs index 356423a..c4cc31d 100644 --- a/crates/concurrent-psbt/src/lib.rs +++ b/crates/concurrent-psbt/src/lib.rs @@ -6,3 +6,4 @@ mod lattice; pub use lattice::join::{Join, JoinMut}; +pub use lattice::partial::{Conflict, JoinResult, PartialJoin}; From 7ca5400efec23fbae75d3dff5aec21241793e0ca Mon Sep 17 00:00:00 2001 From: Yuval Kogman Date: Sat, 16 May 2026 09:25:11 +0000 Subject: [PATCH 6/6] Remove --no-tests=warn workarounds --- .github/workflows/cargo.yml | 2 +- Justfile | 2 +- nix/checks.nix | 8 ++------ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index 7f9306f..85593e7 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -17,7 +17,7 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-nextest - - run: cargo nextest run --no-tests=warn + - run: cargo nextest run # Warn-only: this toolchain is rolling (not pinned), which may cause # unrelated PRs to start failing due to nightly containing new lints. # Nix clippy checks in ci.yml, which pins the toolchain via a flake.lock, diff --git a/Justfile b/Justfile index 61b20be..1590cba 100644 --- a/Justfile +++ b/Justfile @@ -20,7 +20,7 @@ fmt: # Run tests via cargo-nextest test: - cargo nextest run --no-tests=warn + cargo nextest run # Run clippy lints clippy: diff --git a/nix/checks.nix b/nix/checks.nix index 4d6d505..ff8213c 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -34,7 +34,6 @@ // { cargoArtifacts = deps; CARGO_PROFILE = profile; - cargoNextestExtraArgs = "--no-tests=warn"; } ); @@ -59,12 +58,9 @@ cargo-nextest ]; buildPhaseCargoCommand = '' - cargo llvm-cov nextest --no-report --no-tests=warn --all-features - mkdir -p $out - if [ -f target/llvm-cov-target/source.profdata ]; then - cargo llvm-cov report --lcov --output-path $out/coverage.lcov --fail-under-regions 100 - fi + cargo llvm-cov nextest --all-features --lcov --output-path $out/coverage.lcov + cargo llvm-cov report --fail-under-regions 100 ''; installPhase = "true"; }