From 3f1c5bac86cc47f58f14da3015a8c0bc2fa7feb5 Mon Sep 17 00:00:00 2001 From: Leynos Date: Sun, 29 Jun 2025 22:18:06 +0100 Subject: [PATCH 1/4] Add advanced tests for streams and pushes --- .github/workflows/advanced-tests.yml | 23 + Cargo.lock | 544 +++++++++++++++++- Cargo.toml | 5 + ...asynchronous-outbound-messaging-roadmap.md | 2 +- tests/advanced/concurrency_loom.rs | 55 ++ tests/advanced/interaction_fuzz.rs | 83 +++ 6 files changed, 707 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/advanced-tests.yml create mode 100644 tests/advanced/concurrency_loom.rs create mode 100644 tests/advanced/interaction_fuzz.rs diff --git a/.github/workflows/advanced-tests.yml b/.github/workflows/advanced-tests.yml new file mode 100644 index 00000000..5a41ad27 --- /dev/null +++ b/.github/workflows/advanced-tests.yml @@ -0,0 +1,23 @@ +name: Advanced Tests + +on: + workflow_dispatch: + schedule: + - cron: '0 17 * * *' + +jobs: + advanced: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - name: Install rust + uses: actions-rust-lang/setup-rust-toolchain@9d7e65c320fdb52dcd45ffaa68deb6c02c8754d9 + with: + override: true + components: rustfmt, clippy + - name: Cache cargo + uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + - name: Run advanced tests + run: RUSTFLAGS="--cfg loom" cargo test --features advanced-tests diff --git a/Cargo.lock b/Cargo.lock index 1db20b76..3905a0aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,6 +78,21 @@ dependencies = [ "virtue", ] +[[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.9.1" @@ -90,6 +105,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.1" @@ -109,6 +133,28 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "futures" version = "0.3.31" @@ -204,6 +250,32 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "gimli" version = "0.31.1" @@ -234,6 +306,12 @@ version = "0.2.173" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "lock_api" version = "0.4.13" @@ -263,6 +341,28 @@ dependencies = [ "log", ] +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.5" @@ -285,10 +385,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -304,6 +423,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot_core" version = "0.9.11" @@ -329,6 +454,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -338,6 +472,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.5", + "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.40" @@ -347,6 +507,50 @@ 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 = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[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 = "redox_syscall" version = "0.5.13" @@ -364,8 +568,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -376,9 +589,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -435,6 +654,43 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -467,6 +723,21 @@ dependencies = [ "syn", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -512,6 +783,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tokio" version = "1.45.1" @@ -553,6 +846,61 @@ dependencies = [ "tokio", ] +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[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.18" @@ -565,6 +913,12 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" version = "1.11.1" @@ -577,12 +931,154 @@ version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -617,6 +1113,15 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -676,6 +1181,8 @@ dependencies = [ "futures", "log", "logtest", + "loom", + "proptest", "rstest", "serde", "tokio", @@ -693,3 +1200,32 @@ dependencies = [ "tokio", "wireframe", ] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index a57b95b7..b21a3abc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,11 @@ dashmap = "5" rstest = "0.18.2" wireframe_testing = { path = "./wireframe_testing" } logtest = "^2.0" +proptest = "1" +loom = "0.7" + +[features] +advanced-tests = [] [lints.clippy] pedantic = "warn" diff --git a/docs/asynchronous-outbound-messaging-roadmap.md b/docs/asynchronous-outbound-messaging-roadmap.md index 9e8927a9..15fef8c4 100644 --- a/docs/asynchronous-outbound-messaging-roadmap.md +++ b/docs/asynchronous-outbound-messaging-roadmap.md @@ -33,7 +33,7 @@ design documents. ([Roadmap #2.4][roadmap-2-4]). - [ ] **Example handler using `async-stream`** demonstrating `Response::Stream` generation in the examples directory. -- [ ] **Tests covering streams and push delivery** drawing on +- [x] **Tests covering streams and push delivery** drawing on [Testing Guide ยง4][testing-guide-advanced]. ## 3. Production Hardening diff --git a/tests/advanced/concurrency_loom.rs b/tests/advanced/concurrency_loom.rs new file mode 100644 index 00000000..31a5bce7 --- /dev/null +++ b/tests/advanced/concurrency_loom.rs @@ -0,0 +1,55 @@ +#![cfg(feature = "advanced-tests")] + +use loom::model; +use tokio::runtime::Builder; +use tokio_util::sync::CancellationToken; +use wireframe::{ + connection::ConnectionActor, + push::PushQueues, +}; + +#[test] +fn concurrent_push_delivery() { + model(|| { + let rt = Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + rt.block_on(async { + let (queues, handle) = PushQueues::bounded(1, 1); + let token = CancellationToken::new(); + + let out = loom::sync::Arc::new(loom::sync::Mutex::new(Vec::new())); + let out_clone = out.clone(); + let mut actor: ConnectionActor<_, ()> = + ConnectionActor::new(queues, handle.clone(), None, token.clone()); + + let actor_task = tokio::spawn(async move { + let mut buf = Vec::new(); + actor.run(&mut buf).await.unwrap(); + out_clone.lock().unwrap().extend(buf); + }); + + let h1 = handle.clone(); + let t1 = tokio::spawn(async move { + h1.push_high_priority(1u8).await.unwrap(); + }); + + let h2 = handle.clone(); + let t2 = tokio::spawn(async move { + h2.push_low_priority(2u8).await.unwrap(); + }); + + t1.await.unwrap(); + t2.await.unwrap(); + token.cancel(); + actor_task.await.unwrap(); + + let buf = out.lock().unwrap(); + assert!(buf.contains(&1)); + assert!(buf.contains(&2)); + }); + }); +} + diff --git a/tests/advanced/interaction_fuzz.rs b/tests/advanced/interaction_fuzz.rs new file mode 100644 index 00000000..426b0550 --- /dev/null +++ b/tests/advanced/interaction_fuzz.rs @@ -0,0 +1,83 @@ +#![cfg(feature = "advanced-tests")] + +use futures::stream; +use proptest::prelude::*; +use tokio_util::sync::CancellationToken; +use wireframe::{ + connection::ConnectionActor, + push::PushQueues, + response::FrameStream, +}; + +#[derive(Debug, Clone)] +enum Action { + High(u8), + Low(u8), + Stream(Vec), +} + +prop_compose! { + fn actions_strategy() + ( + high in proptest::collection::vec(any::(), 0..5), + low in proptest::collection::vec(any::(), 0..5), + stream_frames in proptest::collection::vec(any::(), 0..5) + ) -> Vec { + let mut actions = Vec::new(); + for n in high { actions.push(Action::High(n)); } + for n in low { actions.push(Action::Low(n)); } + if !stream_frames.is_empty() { + actions.push(Action::Stream(stream_frames)); + } + actions + } +} + +proptest! { + #[test] + fn random_push_and_stream(actions in actions_strategy()) { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + rt.block_on(async { + let (queues, handle) = PushQueues::bounded(16, 16); + let shutdown = CancellationToken::new(); + + let mut stream: Option> = None; + for act in &actions { + match act { + Action::High(f) => handle.push_high_priority(*f).await.unwrap(), + Action::Low(f) => handle.push_low_priority(*f).await.unwrap(), + Action::Stream(frames) => { + let s = stream::iter(frames.clone().into_iter().map(Ok)); + stream = Some(Box::pin(s)); + } + } + } + + let mut actor: ConnectionActor<_, ()> = + ConnectionActor::new(queues, handle, stream, shutdown); + let mut out = Vec::new(); + actor.run(&mut out).await.unwrap(); + + let mut expected_high = Vec::new(); + let mut expected_low = Vec::new(); + let mut expected_stream = Vec::new(); + for act in actions { + match act { + Action::High(f) => expected_high.push(f), + Action::Low(f) => expected_low.push(f), + Action::Stream(v) => expected_stream = v, + } + } + let mut expected = expected_high; + expected.extend(expected_low); + expected.extend(expected_stream); + + prop_assert_eq!(out, expected); + }); + } +} + From 0144ac6ff271e7d78060d453977c6a22b6db05c9 Mon Sep 17 00:00:00 2001 From: Leynos Date: Mon, 30 Jun 2025 00:48:14 +0100 Subject: [PATCH 2/4] Add boundary tests for push-stream logic --- Cargo.toml | 4 +- tests/advanced/interaction_fuzz.rs | 97 +++++++++++++++++++----------- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b21a3abc..7775b619 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,8 @@ dashmap = "5" rstest = "0.18.2" wireframe_testing = { path = "./wireframe_testing" } logtest = "^2.0" -proptest = "1" -loom = "0.7" +proptest = "^1.0" +loom = "^0.7" [features] advanced-tests = [] diff --git a/tests/advanced/interaction_fuzz.rs b/tests/advanced/interaction_fuzz.rs index 426b0550..29306040 100644 --- a/tests/advanced/interaction_fuzz.rs +++ b/tests/advanced/interaction_fuzz.rs @@ -16,6 +16,46 @@ enum Action { Stream(Vec), } +async fn run_actions(actions: &[Action]) -> Vec { + let (queues, handle) = PushQueues::bounded(16, 16); + let shutdown = CancellationToken::new(); + + let mut stream: Option> = None; + for act in actions { + match act { + Action::High(f) => handle.push_high_priority(*f).await.unwrap(), + Action::Low(f) => handle.push_low_priority(*f).await.unwrap(), + Action::Stream(frames) => { + let s = stream::iter(frames.clone().into_iter().map(Ok)); + stream = Some(Box::pin(s)); + } + } + } + + let mut actor: ConnectionActor<_, ()> = + ConnectionActor::new(queues, handle, stream, shutdown); + let mut out = Vec::new(); + actor.run(&mut out).await.unwrap(); + out +} + +fn expected_from(actions: &[Action]) -> Vec { + let mut high = Vec::new(); + let mut low = Vec::new(); + let mut stream = Vec::new(); + for act in actions { + match act { + Action::High(f) => high.push(*f), + Action::Low(f) => low.push(*f), + Action::Stream(v) => stream = v.clone(), + } + } + let mut expected = high; + expected.extend(low); + expected.extend(stream); + expected +} + prop_compose! { fn actions_strategy() ( @@ -42,42 +82,31 @@ proptest! { .unwrap(); rt.block_on(async { - let (queues, handle) = PushQueues::bounded(16, 16); - let shutdown = CancellationToken::new(); - - let mut stream: Option> = None; - for act in &actions { - match act { - Action::High(f) => handle.push_high_priority(*f).await.unwrap(), - Action::Low(f) => handle.push_low_priority(*f).await.unwrap(), - Action::Stream(frames) => { - let s = stream::iter(frames.clone().into_iter().map(Ok)); - stream = Some(Box::pin(s)); - } - } - } - - let mut actor: ConnectionActor<_, ()> = - ConnectionActor::new(queues, handle, stream, shutdown); - let mut out = Vec::new(); - actor.run(&mut out).await.unwrap(); - - let mut expected_high = Vec::new(); - let mut expected_low = Vec::new(); - let mut expected_stream = Vec::new(); - for act in actions { - match act { - Action::High(f) => expected_high.push(f), - Action::Low(f) => expected_low.push(f), - Action::Stream(v) => expected_stream = v, - } - } - let mut expected = expected_high; - expected.extend(expected_low); - expected.extend(expected_stream); - + let out = run_actions(&actions).await; + let expected = expected_from(&actions); prop_assert_eq!(out, expected); }); } } +#[tokio::test] +async fn test_empty_actions_vector() { + let actions: Vec = Vec::new(); + let out = run_actions(&actions).await; + let expected = expected_from(&actions); + assert_eq!(out, expected); +} + +#[tokio::test] +async fn test_maximal_actions_vector() { + let mut actions = Vec::new(); + for n in 0u8..5 { actions.push(Action::High(n)); } + for n in 5u8..10 { actions.push(Action::Low(n)); } + let stream_frames = (10u8..15).collect::>(); + actions.push(Action::Stream(stream_frames)); + + let out = run_actions(&actions).await; + let expected = expected_from(&actions); + assert_eq!(out, expected); +} + From 9a671770a4265afb539587e6696ac5b29408ebd6 Mon Sep 17 00:00:00 2001 From: Leynos Date: Mon, 30 Jun 2025 07:15:58 +0100 Subject: [PATCH 3/4] Refactor boundary tests with rstest --- tests/advanced/interaction_fuzz.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/advanced/interaction_fuzz.rs b/tests/advanced/interaction_fuzz.rs index 29306040..f9153bf2 100644 --- a/tests/advanced/interaction_fuzz.rs +++ b/tests/advanced/interaction_fuzz.rs @@ -2,6 +2,7 @@ use futures::stream; use proptest::prelude::*; +use rstest::rstest; use tokio_util::sync::CancellationToken; use wireframe::{ connection::ConnectionActor, @@ -89,22 +90,18 @@ proptest! { } } -#[tokio::test] -async fn test_empty_actions_vector() { - let actions: Vec = Vec::new(); - let out = run_actions(&actions).await; - let expected = expected_from(&actions); - assert_eq!(out, expected); -} - -#[tokio::test] -async fn test_maximal_actions_vector() { +#[rstest] +#[case::empty(Vec::new())] +#[case::maximal({ let mut actions = Vec::new(); for n in 0u8..5 { actions.push(Action::High(n)); } for n in 5u8..10 { actions.push(Action::Low(n)); } let stream_frames = (10u8..15).collect::>(); actions.push(Action::Stream(stream_frames)); - + actions +})] +#[tokio::test] +async fn test_boundary_cases(#[case] actions: Vec) { let out = run_actions(&actions).await; let expected = expected_from(&actions); assert_eq!(out, expected); From 0cf1363fd91ef9e5e2ef8a29846326619440113b Mon Sep 17 00:00:00 2001 From: Leynos Date: Mon, 30 Jun 2025 23:43:20 +0100 Subject: [PATCH 4/4] Refine advanced tests --- tests/advanced/concurrency_loom.rs | 34 ++++++++++++++++++++++-------- tests/advanced/interaction_fuzz.rs | 22 +++++++++++++++---- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/tests/advanced/concurrency_loom.rs b/tests/advanced/concurrency_loom.rs index 31a5bce7..ff997bad 100644 --- a/tests/advanced/concurrency_loom.rs +++ b/tests/advanced/concurrency_loom.rs @@ -1,4 +1,8 @@ #![cfg(feature = "advanced-tests")] +//! Concurrency tests for push delivery using loom. +//! +//! These tests model concurrent push execution to validate fairness and +//! correct shutdown behaviour under various interleavings. use loom::model; use tokio::runtime::Builder; @@ -14,7 +18,7 @@ fn concurrent_push_delivery() { let rt = Builder::new_current_thread() .enable_all() .build() - .unwrap(); + .expect("failed to build tokio runtime"); rt.block_on(async { let (queues, handle) = PushQueues::bounded(1, 1); @@ -27,26 +31,38 @@ fn concurrent_push_delivery() { let actor_task = tokio::spawn(async move { let mut buf = Vec::new(); - actor.run(&mut buf).await.unwrap(); - out_clone.lock().unwrap().extend(buf); + actor + .run(&mut buf) + .await + .expect("connection actor failed to run"); + out_clone + .lock() + .expect("mutex poisoned") + .extend(buf); }); let h1 = handle.clone(); let t1 = tokio::spawn(async move { - h1.push_high_priority(1u8).await.unwrap(); + h1 + .push_high_priority(1u8) + .await + .expect("failed to push high priority frame"); }); let h2 = handle.clone(); let t2 = tokio::spawn(async move { - h2.push_low_priority(2u8).await.unwrap(); + h2 + .push_low_priority(2u8) + .await + .expect("failed to push low priority frame"); }); - t1.await.unwrap(); - t2.await.unwrap(); + t1.await.expect("high priority task join failed"); + t2.await.expect("low priority task join failed"); token.cancel(); - actor_task.await.unwrap(); + actor_task.await.expect("actor task join failed"); - let buf = out.lock().unwrap(); + let buf = out.lock().expect("mutex poisoned"); assert!(buf.contains(&1)); assert!(buf.contains(&2)); }); diff --git a/tests/advanced/interaction_fuzz.rs b/tests/advanced/interaction_fuzz.rs index f9153bf2..99d71632 100644 --- a/tests/advanced/interaction_fuzz.rs +++ b/tests/advanced/interaction_fuzz.rs @@ -1,4 +1,9 @@ #![cfg(feature = "advanced-tests")] +//! Advanced property-based fuzzing tests for push and stream handling. +//! +//! This module provides comprehensive fuzzing tests using proptest to verify +//! the correctness of push queue priorities and stream frame handling in +//! various randomised scenarios. use futures::stream; use proptest::prelude::*; @@ -24,8 +29,14 @@ async fn run_actions(actions: &[Action]) -> Vec { let mut stream: Option> = None; for act in actions { match act { - Action::High(f) => handle.push_high_priority(*f).await.unwrap(), - Action::Low(f) => handle.push_low_priority(*f).await.unwrap(), + Action::High(f) => handle + .push_high_priority(*f) + .await + .expect("failed to push high priority frame"), + Action::Low(f) => handle + .push_low_priority(*f) + .await + .expect("failed to push low priority frame"), Action::Stream(frames) => { let s = stream::iter(frames.clone().into_iter().map(Ok)); stream = Some(Box::pin(s)); @@ -36,7 +47,10 @@ async fn run_actions(actions: &[Action]) -> Vec { let mut actor: ConnectionActor<_, ()> = ConnectionActor::new(queues, handle, stream, shutdown); let mut out = Vec::new(); - actor.run(&mut out).await.unwrap(); + actor + .run(&mut out) + .await + .expect("connection actor failed to run"); out } @@ -80,7 +94,7 @@ proptest! { let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() - .unwrap(); + .expect("failed to build tokio runtime"); rt.block_on(async { let out = run_actions(&actions).await;