From b1fb96e029b8a1287ebabd1bae479aaa04140999 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 20:51:02 +0530 Subject: [PATCH 01/15] test(stream): cover close verdict ordering against reader eof Add a test-only hook that runs after a stream is sealed during Close, letting tests pause the close path and observe what a reader sees at that moment. New tests use it to assert that a complete close publishes a clean EOF and a truncated close publishes an abort before the reader observes end of stream. Auto-committed-on: macbook --- crates/tinybus/src/stream/mod.rs | 14 +++ crates/tinybus/src/stream/stream_tests.rs | 102 +++++++++++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/crates/tinybus/src/stream/mod.rs b/crates/tinybus/src/stream/mod.rs index 93687180..afc7639c 100644 --- a/crates/tinybus/src/stream/mod.rs +++ b/crates/tinybus/src/stream/mod.rs @@ -252,6 +252,8 @@ pub(crate) struct StreamRegistry { limits: std::sync::RwLock, inbound: std::sync::Mutex>>, next_id: AtomicU64, + #[cfg(test)] + after_close_seal: std::sync::Mutex>>, } impl StreamRegistry { @@ -260,6 +262,8 @@ impl StreamRegistry { limits: std::sync::RwLock::new(StreamLimits::default()), inbound: std::sync::Mutex::new(HashMap::new()), next_id: AtomicU64::new(1), + #[cfg(test)] + after_close_seal: std::sync::Mutex::new(None), } } @@ -464,6 +468,16 @@ impl StreamRegistry { stream.touch(); stream.seal(); + #[cfg(test)] + if let Some(hook) = self + .after_close_seal + .lock() + .expect("stream close test hook lock") + .take() + { + hook(); + } + if received != total_len { stream.finish(Outcome::Aborted("the sender closed a truncated stream")); return Err(Error::protocol(format!( diff --git a/crates/tinybus/src/stream/stream_tests.rs b/crates/tinybus/src/stream/stream_tests.rs index 4617bf02..d008e689 100644 --- a/crates/tinybus/src/stream/stream_tests.rs +++ b/crates/tinybus/src/stream/stream_tests.rs @@ -18,7 +18,8 @@ use crate::message::Message; use crate::name::{BusName, InterfaceName, MemberName, ObjectPath}; use crate::ports::Transport; use crate::stream::{ - MAX_CHUNK_LEN, STREAM_INTERFACE, STREAM_PATH, StreamDescriptor, StreamLimits, StreamRef, + MAX_CHUNK_LEN, STREAM_INTERFACE, STREAM_PATH, StreamDescriptor, StreamLimits, StreamReader, + StreamRef, StreamRegistry, }; use crate::transport::memory::{MemoryBus, MemoryTransport}; @@ -96,6 +97,89 @@ fn payload(len: usize) -> Vec { (0..len).map(|i| (i % 251) as u8).collect() } +fn stream_header(member: &str, sender: &BusName) -> crate::message::Header { + let mut header = Message::method_call( + BusName::new("ai.tinyhumans.StreamReceiver").unwrap(), + ObjectPath::new(STREAM_PATH).unwrap(), + InterfaceName::new(STREAM_INTERFACE).unwrap(), + MemberName::new(member).unwrap(), + Value::Null, + ) + .header; + header.sender = Some(sender.clone()); + header +} + +async fn close_verdict_is_published_before_reader_observes_eof( + declared_len: u64, + close_len: u64, + expected_terminal: &str, + expected_close_error: Option<&str>, +) { + let registry = Arc::new(StreamRegistry::new()); + let owner = BusName::new("ai.tinyhumans.StreamSender").unwrap(); + let open = registry + .dispatch( + &stream_header("Open", &owner), + serde_json::json!([StreamDescriptor::with_len(declared_len)]), + ) + .await + .unwrap(); + let id = open.as_str().unwrap().to_string(); + let mut reader: StreamReader = registry.take_reader(&id).unwrap(); + registry + .dispatch( + &stream_header("Write", &owner), + serde_json::json!([id.clone(), 0, super::base64::encode(b"abc")]), + ) + .await + .unwrap(); + assert_eq!(reader.next_chunk().await.unwrap().as_deref(), Some(b"abc")); + + let (close_sealed_tx, close_sealed_rx) = tokio::sync::oneshot::channel(); + let release_close = Arc::new(std::sync::Barrier::new(2)); + let close_release = Arc::clone(&release_close); + *registry + .after_close_seal + .lock() + .expect("stream close test hook lock") = Some(Box::new(move || { + let _ = close_sealed_tx.send(()); + close_release.wait(); + })); + + let close_registry = Arc::clone(®istry); + let close_member = MemberName::new("Close").unwrap(); + let close_header = stream_header("Close", &owner); + let close = tokio::task::spawn_blocking(move || { + close_registry.close( + &close_header, + &close_member, + serde_json::json!([id, close_len]), + ) + }); + + close_sealed_rx + .await + .expect("Close should reach the per-registry scheduling hook"); + let terminal = reader.next_chunk().await; + // Always release and join the paused synchronous Close before asserting, so + // a deliberately failing RED run cannot strand a blocking task. + release_close.wait(); + let close_result = close.await.expect("Close task should complete"); + + let terminal = match terminal { + Ok(None) => "clean EOF".to_string(), + Ok(Some(_)) => "unexpected extra chunk".to_string(), + Err(Error::StreamAborted { reason }) => format!("aborted: {reason}"), + Err(error) => format!("error: {}", error.wire_name()), + }; + assert_eq!(terminal, expected_terminal); + match expected_close_error { + Some(wire_name) => assert_eq!(close_result.unwrap_err().wire_name(), wire_name), + None => assert!(close_result.is_ok()), + } +} + /// A broker, a sink service that owns [`SINK`], and a client. async fn bus() -> (Connection, Connection) { let bus = MemoryBus::new(); @@ -370,6 +454,22 @@ async fn closing_short_of_the_declared_length_is_an_error_not_a_short_read() { assert_eq!(error.wire_name(), "ai.tinyhumans.tinybus.Error.Protocol"); } +#[tokio::test] +async fn a_complete_close_publishes_its_verdict_before_reader_eof() { + close_verdict_is_published_before_reader_observes_eof(3, 3, "clean EOF", None).await; +} + +#[tokio::test] +async fn a_truncated_close_publishes_its_abort_before_reader_eof() { + close_verdict_is_published_before_reader_observes_eof( + 4, + 4, + "aborted: the sender closed a truncated stream", + Some("ai.tinyhumans.tinybus.Error.Protocol"), + ) + .await; +} + #[tokio::test] async fn a_reader_sees_an_error_rather_than_an_eof_when_the_sender_aborts() { let (client, service) = bus().await; From e20b47295687eba094d19ba11a05d578a7e3b8fe Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 20:51:49 +0530 Subject: [PATCH 02/15] test(stream): fix byte slice comparison in eof test Adjust the assertion in the close verdict test to compare against an explicit byte slice, keeping the comparison types consistent. Auto-committed-on: macbook --- crates/tinybus/src/stream/stream_tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/tinybus/src/stream/stream_tests.rs b/crates/tinybus/src/stream/stream_tests.rs index d008e689..2a9da0d3 100644 --- a/crates/tinybus/src/stream/stream_tests.rs +++ b/crates/tinybus/src/stream/stream_tests.rs @@ -134,7 +134,10 @@ async fn close_verdict_is_published_before_reader_observes_eof( ) .await .unwrap(); - assert_eq!(reader.next_chunk().await.unwrap().as_deref(), Some(b"abc")); + assert_eq!( + reader.next_chunk().await.unwrap().as_deref(), + Some(b"abc".as_slice()) + ); let (close_sealed_tx, close_sealed_rx) = tokio::sync::oneshot::channel(); let release_close = Arc::new(std::sync::Barrier::new(2)); From 7d4382749ac1782fb310ef4a32b0283d87cdcacc Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 20:52:43 +0530 Subject: [PATCH 03/15] fix(stream): finish stream before sealing on close Move the completion or abort outcome ahead of sealing so the stream is finalised before it becomes visible as sealed, and reuse the truncation check for the error path. Auto-committed-on: macbook --- crates/tinybus/src/stream/mod.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/tinybus/src/stream/mod.rs b/crates/tinybus/src/stream/mod.rs index afc7639c..9feb3be1 100644 --- a/crates/tinybus/src/stream/mod.rs +++ b/crates/tinybus/src/stream/mod.rs @@ -466,6 +466,12 @@ impl StreamRegistry { // the *fast* case — into "no such stream". let received = stream.received.load(Ordering::Relaxed); stream.touch(); + let truncated = received != total_len; + if truncated { + stream.finish(Outcome::Aborted("the sender closed a truncated stream")); + } else { + stream.finish(Outcome::Complete); + } stream.seal(); #[cfg(test)] @@ -478,13 +484,11 @@ impl StreamRegistry { hook(); } - if received != total_len { - stream.finish(Outcome::Aborted("the sender closed a truncated stream")); + if truncated { return Err(Error::protocol(format!( "stream closed after {received} bytes, {total_len} declared" ))); } - stream.finish(Outcome::Complete); Ok(Value::Null) } From 3e160851926fdb7ea602bae030fa739454cbc972 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 20:54:44 +0530 Subject: [PATCH 04/15] fix(tinybus): publish stream verdict before closing reader channel Release 0.3.3 fixes a race where a reader that had drained a stream's payload could observe the wrong end state, because the reader channel was closed before the completion or truncation verdict was published. The verdict is now published first so drained readers see the correct end. Auto-committed-on: macbook --- CHANGELOG.md | 7 +++++++ Cargo.toml | 8 ++++---- crates/tinybus/src/module/client_pinned_tests.rs | 6 +++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea9268b6..46fe6ec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.3.3 + +### Fixed + +- Publish a stream's completion or truncation verdict before closing its reader + channel, so a reader that has drained the payload observes the correct end. + ## 0.3.2 ### Added diff --git a/Cargo.toml b/Cargo.toml index 48ad5efe..948b9da3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,16 +11,16 @@ exclude = ["examples"] resolver = "3" [workspace.package] -version = "0.3.2" +version = "0.3.3" edition = "2024" rust-version = "1.85" license = "GPL-3.0-only" repository = "https://github.com/tinyhumansai/tinybus" [workspace.dependencies] -tinybus = { path = "crates/tinybus", version = "=0.3.2", default-features = false } -tinybus-macros = { path = "crates/tinybus-macros", version = "=0.3.2" } -tinybus-module = { path = "crates/tinybus-module", version = "=0.3.2" } +tinybus = { path = "crates/tinybus", version = "=0.3.3", default-features = false } +tinybus-macros = { path = "crates/tinybus-macros", version = "=0.3.3" } +tinybus-module = { path = "crates/tinybus-module", version = "=0.3.3" } tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] } async-trait = "0.1" diff --git a/crates/tinybus/src/module/client_pinned_tests.rs b/crates/tinybus/src/module/client_pinned_tests.rs index b015b7db..c7c78ddf 100644 --- a/crates/tinybus/src/module/client_pinned_tests.rs +++ b/crates/tinybus/src/module/client_pinned_tests.rs @@ -13,7 +13,7 @@ static RECORD: ReleaseRecord = ReleaseRecord { id: "ordinary-pinned-client-test", bus_name: "ai.tinyhumans.test.OrdinaryPinnedClient", object_path: "/ai/tinyhumans/test/OrdinaryPinnedClient", - version: "0.3.2", + version: "0.3.3", release_url: "https://example.invalid/tinybus", // Ordinary fixtures and linked host factories intentionally have no // release archive/library pair to attest. @@ -330,7 +330,7 @@ static RESOLVER_LINKED_RECORD: ReleaseRecord = ReleaseRecord { id: "ordinary-binding-linked-readiness", bus_name: "ai.tinyhumans.test.OrdinaryBindingLinkedReadiness", object_path: "/ai/tinyhumans/test/OrdinaryBindingLinkedReadiness", - version: "0.3.2", + version: "0.3.3", release_url: "https://example.invalid/tinybus", assets: &[], }; @@ -1038,7 +1038,7 @@ async fn ordinary_binding_refuses_an_unsupported_resolver_without_proxy_or_busin id: "ordinary-binding-unsupported", bus_name: "ai.tinyhumans.test.OrdinaryBindingUnsupported", object_path: "/ai/tinyhumans/test/OrdinaryBindingUnsupported", - version: "0.3.2", + version: "0.3.3", release_url: "https://example.invalid/tinybus", assets: &[], }; From e6741c90dfb3ae3548e10fa430a35a0e53bb6e10 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 20:55:10 +0530 Subject: [PATCH 05/15] test(tinybus): bump pinned module version to 0.3.3 Update the module identity versions used by the pinned client tests to 0.3.3 so the fixtures match the current release. Auto-committed-on: macbook --- crates/tinybus/src/module/client_pinned_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/tinybus/src/module/client_pinned_tests.rs b/crates/tinybus/src/module/client_pinned_tests.rs index c7c78ddf..4f43c94d 100644 --- a/crates/tinybus/src/module/client_pinned_tests.rs +++ b/crates/tinybus/src/module/client_pinned_tests.rs @@ -284,7 +284,7 @@ fn linked_ordinary_factory() -> crate::Result { schema: MANIFEST_SCHEMA, module: ModuleIdentity { name: RECORD.id.to_owned(), - version: Version::new(0, 3, 2), + version: Version::new(0, 3, 3), description: String::new(), homepage: None, license: String::new(), @@ -411,7 +411,7 @@ fn resolver_linked_factory() -> crate::Result { schema: MANIFEST_SCHEMA, module: ModuleIdentity { name: record.id.to_owned(), - version: Version::new(0, 3, 2), + version: Version::new(0, 3, 3), description: String::new(), homepage: None, license: String::new(), From c81fca3201b98467a02bae598b9a98b1c3a41171 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 20:55:24 +0530 Subject: [PATCH 06/15] chore: bump tinybus crates to 0.3.3 Update the locked versions of tinybus, tinybus-macros, and tinybus-module from 0.3.2 to 0.3.3. Auto-committed-on: macbook --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 349094a1..286c4a04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -853,7 +853,7 @@ dependencies = [ [[package]] name = "tinybus" -version = "0.3.2" +version = "0.3.3" dependencies = [ "async-trait", "clap", @@ -879,7 +879,7 @@ dependencies = [ [[package]] name = "tinybus-macros" -version = "0.3.2" +version = "0.3.3" dependencies = [ "proc-macro2", "quote", @@ -888,7 +888,7 @@ dependencies = [ [[package]] name = "tinybus-module" -version = "0.3.2" +version = "0.3.3" dependencies = [ "async-trait", "serde", From 8e819a00dbbba1f0e4b3882c85a08df60fbe1b77 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 20:55:32 +0530 Subject: [PATCH 07/15] chore(examples): bump tinybus to 0.3.3 in lockfile Update the example lockfile to pick up tinybus and tinybus-macros 0.3.3, keeping the examples in sync with the released version. Auto-committed-on: macbook --- examples/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 4ac848ba..7b8285eb 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -260,7 +260,7 @@ dependencies = [ [[package]] name = "tinybus" -version = "0.3.2" +version = "0.3.3" dependencies = [ "async-trait", "serde", @@ -284,7 +284,7 @@ dependencies = [ [[package]] name = "tinybus-macros" -version = "0.3.2" +version = "0.3.3" dependencies = [ "proc-macro2", "quote", From f624b920316e56ef3e3d88b95ce629920fbaa7b7 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 21:07:43 +0530 Subject: [PATCH 08/15] test(scripts): add tests for coverage check script Auto-committed-on: macbook --- scripts/test_check_coverage.py | 279 +++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 scripts/test_check_coverage.py diff --git a/scripts/test_check_coverage.py b/scripts/test_check_coverage.py new file mode 100644 index 00000000..895f1410 --- /dev/null +++ b/scripts/test_check_coverage.py @@ -0,0 +1,279 @@ +#!/usr/bin/env python3 +"""Exercise coverage-script fixture discovery without running Cargo.""" + +from __future__ import annotations + +import hashlib +import json +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import textwrap +import unittest + + +ROOT = Path(__file__).resolve().parent.parent +COVERAGE_SCRIPT = ROOT / "scripts" / "check-coverage.sh" +LIBRARIES = ( + "module_clock", + "module_clock_two", + "module_wrong_target", + "module_context", + "module_context_eager", + "module_host_callback", + "module_controlled_init", + "module_terminal", +) +TERMINAL_TESTS = ( + "terminal_runtime_tests::compiled_native_fixture_first", + "terminal_runtime_tests::compiled_native_fixture_second", +) +IGNORE_REGEX = ( + r"crates/tinybus/src/ports/(listener|transport)\.rs$" + r"|crates/tinybus/src/module/github\.rs$" + r"|crates/tinybus/src/module/(host|mod)\.rs$" + r"|crates/tinybus/src/bin/(tinybus\.rs|tinybus/unix\.rs)$" + r"|crates/tinybus/src/connection\.rs$" + r"|crates/tinybus/src/test_support\.rs$" +) + + +FAKE_CARGO = r"""#!/usr/bin/env python3 +import hashlib +import json +import os +from pathlib import Path +import sys + +args = sys.argv[1:] +target = Path(os.environ["FAKE_TARGET_DIR"]) +suffix = os.environ["FAKE_FIXTURE_SUFFIX"] +log = Path(os.environ["FAKE_CARGO_LOG"]) +entry = {"args": args} + +if args and args[0] == "build": + examples = target / "debug" / "examples" + examples.mkdir(parents=True, exist_ok=True) + for name in ( + "module_clock", + "module_clock_two", + "module_wrong_target", + "module_context", + "module_context_eager", + "module_host_callback", + "module_controlled_init", + "module_terminal", + ): + (examples / f"lib{name}.{suffix}").write_bytes(name.encode()) +elif args[:2] == ["metadata", "--format-version"]: + print(json.dumps({"target_directory": str(target)})) +elif args[:2] == ["llvm-cov", "--workspace"]: + fixture_variables = ( + "TINYBUS_TEST_MODULE", + "TINYBUS_TEST_MODULE_TWO", + "TINYBUS_TEST_WRONG_TARGET", + "TINYBUS_TEST_CONTEXT_MODULE", + "TINYBUS_TEST_CONTEXT_EAGER_MODULE", + "TINYBUS_TEST_HOST_CALLBACK_MODULE", + "TINYBUS_TEST_CONTROLLED_INIT_MODULE", + "TINYBUS_TEST_TERMINAL_MODULE", + ) + fixtures = {name: os.environ.get(name, "") for name in fixture_variables} + if any(not Path(path).is_file() for path in fixtures.values()): + print("coverage did not receive every compiled fixture", file=sys.stderr) + sys.exit(3) + for variable in ("TINYBUS_TEST_MODULE_TWO", "TINYBUS_TEST_TERMINAL_MODULE"): + path = Path(fixtures[variable]) + manifest = path.parent / "modules.toml" + expected = f'"{path.name}" = "{hashlib.sha256(path.read_bytes()).hexdigest()}"' + if not manifest.is_file() or expected not in manifest.read_text(): + print(f"invalid private fixture manifest for {variable}", file=sys.stderr) + sys.exit(4) + entry["fixture_paths"] = fixtures +elif args[:2] == ["test", "--locked"] and "--list" in args: + for name in ( + "terminal_runtime_tests::compiled_native_fixture_first", + "terminal_runtime_tests::compiled_native_fixture_second", + ): + print(f"{name}: test") +elif args[:2] == ["llvm-cov", "test"]: + if "--ignored" not in args or "--exact" not in args: + print("terminal scenario lost isolated ignored/exact execution", file=sys.stderr) + sys.exit(5) +elif args[:2] == ["llvm-cov", "report"]: + if args[-2:] != ["--fail-under-file-lines", "90"]: + print("coverage report threshold changed", file=sys.stderr) + sys.exit(6) +else: + print(f"unexpected fake Cargo invocation: {args!r}", file=sys.stderr) + sys.exit(7) + +with log.open("a", encoding="utf-8") as output: + output.write(json.dumps(entry) + "\n") +""" + + +class CoverageScriptPlatformTests(unittest.TestCase): + def run_coverage_script(self, suffix: str) -> tuple[subprocess.CompletedProcess[str], list[dict]]: + with tempfile.TemporaryDirectory(prefix="tinybus-coverage-script-") as directory: + temporary = Path(directory) + checkout = temporary / "checkout" + scripts = checkout / "scripts" + scripts.mkdir(parents=True) + shutil.copyfile(COVERAGE_SCRIPT, scripts / COVERAGE_SCRIPT.name) + + fake_bin = temporary / "bin" + fake_bin.mkdir() + cargo = fake_bin / "cargo" + cargo.write_text(textwrap.dedent(FAKE_CARGO), encoding="utf-8") + cargo.chmod(0o755) + + target = checkout / "target" + call_log = temporary / "cargo-calls.jsonl" + environment = os.environ.copy() + environment.update( + { + "FAKE_CARGO_LOG": str(call_log), + "FAKE_FIXTURE_SUFFIX": suffix, + "FAKE_TARGET_DIR": str(target), + "PATH": os.pathsep.join((str(fake_bin), os.environ["PATH"])), + } + ) + result = subprocess.run( + ["bash", str(scripts / COVERAGE_SCRIPT.name)], + cwd=checkout, + env=environment, + text=True, + capture_output=True, + check=False, + ) + calls = [ + json.loads(line) + for line in call_log.read_text(encoding="utf-8").splitlines() + ] if call_log.exists() else [] + return result, calls + + def assert_coverage_contract( + self, + result: subprocess.CompletedProcess[str], + calls: list[dict], + suffix: str, + ) -> None: + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + args = [call["args"] for call in calls] + + self.assertEqual( + args[0], + [ + "build", + "--locked", + "--example", + "module_clock", + "--example", + "module_clock_two", + "--example", + "module_wrong_target", + "--example", + "module_context", + "--example", + "module_context_eager", + "--example", + "module_host_callback", + "--example", + "module_controlled_init", + "--example", + "module_terminal", + "--no-default-features", + "--features", + "modules,macros,__native-fixture-support", + ], + ) + self.assertEqual( + args[1], + ["metadata", "--format-version", "1", "--no-deps"], + ) + + coverage_call = next(call for call in calls if call["args"][:2] == ["llvm-cov", "--workspace"]) + self.assertEqual( + coverage_call["args"], + [ + "llvm-cov", + "--workspace", + "--all-features", + "--ignore-filename-regex", + IGNORE_REGEX, + "--no-report", + "--", + "--include-ignored", + "--skip", + "terminal_runtime_tests::compiled_native_", + ], + ) + fixture_paths = coverage_call["fixture_paths"] + self.assertEqual(len(fixture_paths), len(LIBRARIES)) + for variable, library in zip(fixture_paths, LIBRARIES, strict=True): + self.assertTrue(fixture_paths[variable].endswith(f"lib{library}.{suffix}")) + + list_call = next(call for call in calls if call["args"][:2] == ["test", "--locked"]) + self.assertEqual( + list_call["args"], + [ + "test", + "--locked", + "-p", + "tinybus-module", + "--all-features", + "--features", + "tinybus/macros", + "--", + "--list", + "--ignored", + ], + ) + isolated_calls = [call["args"] for call in calls if call["args"][:2] == ["llvm-cov", "test"]] + self.assertEqual( + isolated_calls, + [ + [ + "llvm-cov", + "test", + "--no-report", + "-p", + "tinybus-module", + "--all-features", + "--features", + "tinybus/macros", + test, + "--", + "--ignored", + "--exact", + ] + for test in TERMINAL_TESTS + ], + ) + report_call = next(call for call in calls if call["args"][:2] == ["llvm-cov", "report"]) + self.assertEqual( + report_call["args"], + [ + "llvm-cov", + "report", + "--ignore-filename-regex", + IGNORE_REGEX, + "--fail-under-file-lines", + "90", + ], + ) + + def test_linux_so_fixtures_reach_the_unchanged_coverage_gate(self) -> None: + result, calls = self.run_coverage_script("so") + self.assert_coverage_contract(result, calls, "so") + + def test_macos_dylib_fixtures_reach_the_unchanged_coverage_gate(self) -> None: + result, calls = self.run_coverage_script("dylib") + self.assert_coverage_contract(result, calls, "dylib") + + +if __name__ == "__main__": + unittest.main() From 70829b696d80905a2cbc815389920769524000df Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 21:07:53 +0530 Subject: [PATCH 09/15] chore: add compiled test coverage bytecode cache Adds the compiled Python bytecode for the coverage check test module, generated by the interpreter when the test was run. Auto-committed-on: macbook --- .../test_check_coverage.cpython-314.pyc | Bin 0 -> 11671 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 scripts/__pycache__/test_check_coverage.cpython-314.pyc diff --git a/scripts/__pycache__/test_check_coverage.cpython-314.pyc b/scripts/__pycache__/test_check_coverage.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e36a2c10e13b05f0b990e545607962dc1382410 GIT binary patch literal 11671 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 Date: Sun, 18 Oct 2026 21:08:09 +0530 Subject: [PATCH 10/15] chore: remove stale compiled test bytecode Delete the cached .pyc file for the coverage test script so it is no longer tracked in version control. Auto-committed-on: macbook --- .../test_check_coverage.cpython-314.pyc | Bin 11671 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 scripts/__pycache__/test_check_coverage.cpython-314.pyc diff --git a/scripts/__pycache__/test_check_coverage.cpython-314.pyc b/scripts/__pycache__/test_check_coverage.cpython-314.pyc deleted file mode 100644 index 4e36a2c10e13b05f0b990e545607962dc1382410..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11671 zcmc&aTWlLwc9-vmNSTtUrzvYhzero8ocI~pGUCXxr0B(rX*!7{PbhLEF{VgnW++Q? z+z8tum5XlUZc#)@gId4_g^dJg7D#|>(O~`2-TG6CtcW{F(JU54k&gnhbIjESc%sB3a;V>b7>-B%3|rMkp|_hFAu99k)8ThrM_94M#yI}smvJD9v8;4 z;h;e-i)S)fDK4e58L=O}&5^h?F5479;!H(ioKIW|X$yqK@wfpfn-Jm>FSbaj%=AcJ zY!QV-OD-!&VoM;M5+y#v3$IE-JR@Rba40N@yImBGsn0!VOreWw6KceSvW$<;? z=}e8%8WYbXX=U)>Fw@q?_($@obkg7IWZ>ba;uE5=&6NC9Lan@;mdE|p1b zwt~b96RAu*tpX1!^u-MaG%5l`NBtmPSCA-I!&ekxxatpvFAFJ&=SHSMtOM8yGL*p-=Kejm*UB|6!$kX{!j?0JQ0^dpm0PuG5%p?;hd1lNCA}ba56uU z69ZTL3fVYd9UQK#Fb$F-NF0=12@*5#~lNzBC)yta(0*tp5Kkcy8e zf}~@0w7dU>XU?DFq8;a=T;IT%^SvE<;&0N;MK26&IphL6(BG+^s4WMz5A;Vno`39e z+OUq(ogM7saO)ZX{M%3W_C9mEz2|X*5i&c_+uL!5>+bJIqWp z1|Cl;G_5+l5eBV8g$1O*A7u20?K@n#N>3RIQf4{;=0(v}sO^H`l*o;y(jaRT1h(OM zRY8d1$#^;sQW?|ya7_d|>SifVSyQa^d*C}>Rp zDe&>6dfbD-@Hjt}OpU>cf#6mDz9JsLE~Ki4j*d~FSBs0{5a3sF6a|f1FreEyqN#9g z)St=#OMzqXJqb>O#-1VsDh5|Hx?KG}wjtOe>kcavq}FKAuZbXTSM-D8($*0-(`abU z6%B(%fltB&v|6Cu(0%da2GOV~s!cEHI$fhFU=Ca=DK)`!2vlrs)#Q-NfKxTegRoOy z4Bi4RS|qps#{v*}us2B+f{xf4p%pdX%FxYis>t=qm5x%CH*g-do*K(!1)v3$0+j*` zupYX=q9lMUD(_3ADe<|u8%9j<8PLvICJi=@Nr~Asa6QSW&|4@d5GOv7N000K*j;4x zk;m>v;L+vMtb}I4LQl31Gfi#GAeyasDjmvaKo5jaEdZl4yofUJ)In{UZZ}0;qX1^4 zae)`dv*{$07>{ShfJ#3^yBtNlMP6?qNB#Ls@hpwTFY$^e4iYz+O;9hqmAQfhZW6XU z3Prjc3p$c`qAzK9=Vxr8Wa?=OZFYetD-FA+_K^;%Jn_ zX;Kr;P0KEhQzI*$@%z%c^QC9A@nVHFvleZ=MlxjAIwCY*pnWOtCNkvGT4I(`JyNc5UoG@0Or+Nn?^O(&N_~j zFtbJKv=OB@>(oXA?q;@Z115IZjcY@TW2j!68}Vti(mT>vs%tfEr#WZL8EMj5OK;Xa zv`hPnIW%n8@|c0$rt7g6dptTEc55?B@5sJVU90KfST#60D4Xnr^)wfEO30z2B(|8f z`5;RH4Sr<5Rx7hv3X zK#@4ePMDi>^%=q>=f3dCAvo)KBkRR~S{AC2?1C|kbXAdU1N z>)l08m>NjR5UiT1RTC`5{E(V}3Zc5p2|V~dvYqNiFgXI~CK81nn;OZ>Cdiz~mIRo) z?2LulD(JOq<}3AIKpxpDK{qc!A_|9vFlH9R7-y#Ca+>fc<|gFr^m}7sL*DjCRs<4U zr=wl6c?7aEXM+~ml(?LfO^`#8&FIC-<%xI>b7!1lBmq54fb|D=$aaaJ$f55bS4N?h z72?A587;UKK>ZTgLNgU$cgDfFNyFB&xoJ9MTl+w)gN4wKJJ-&3N20PBJ$Bit1mbjI z06!&N7UDVCn&gvUie+bMe~1P|1F|tI$~HCOBbyPtY|ZB&IOhe_rAS)vfBBqjemM)Y zh5V0ZZ=VFFTM9 zboYW6x+q&^JNhyl@KdP4HdJ%4eP9*`zKL9>qCA1y0z)k3(~@k)HIeZZVDaFytL~l^ch8;aFKT`B zFZ|uMx$Z&@v(UOy6PoKPRPS7zPqk{U$`pY82F2UcLr`= z`0%yWV?9g9dR9U`cU`?-!qg)xH369F^1ji3t^cmeSMd7gFRys_&UF-A6{{}alFRqb z^EbG+x%XdOZa9AX*rLmK*VO}kZ}eR2nYY|^?fkN;VZpvq6_`59p=uIr0> z-;Mom?Z46TR?BkzfjO8^v+CNp1f#vX-%Z>M{Bok;4cWN z>|4*?7eUaNDZO=gzKGlgj!x z8~?g-wZi{tg@4UN8uqOE!b`sJ$F@aZc-i;#s;_6s*K^m`w{9_2mH)xlcm2!*6RD_r z)Auuk38%TMt;Zt|dxA&hmz=&^7(+1$z zqPypzh*|rEPG=Wm`Pco8T|4Y|95$%m* z0<68o3@)TYz3ZUr8jI-o1(nCX!q=2^uvbjGCIdODsf(F42FU5J(^I?Vuip{*t3)S7Ju6UuM;Q z0VQ5J!y5Ef9c&a(18r2!0{-OY*ugE3eomOMjROXqzqi$_4J*McMn7u^)f#;;WI;SN zbE?@-@EbRWY~8pA^lnjbhKk-S+IH5-ma(p|Q-`OOb?f($vuzH42<3x5F`I@$Uk%L`L_93xfwKBdljVh$I0 zRxy){JX!R`G;A74&mo1QIto{AL1E|S6abIwQQkG_c^A`5)$Z>~6>k~PvGy{G z%iuUU1{aW1IpLJt`F)N;Toic$LU%O-0TsA&fjn3EBqRmnY4KFJ2xKsJipj8x^!&xM)d9Id;GWSXgYpY8#LT-g1V2uhwvN} z56eR#K(<5|wNxZ^644X4LZ$-}9I^>+iDW~VnoU5IKL2;@76xEJbiYOEp&e4NG$pGr zSzw_{&t2g>GzchxpJPvvC$&Sj_AKoF*!t1Q+s2hc?RTt;krx*`2bT^F-tfNdeQ(dV z!UdejK>-BKJuKHE7bVAD{BD_uCxoo{(4ny=NwJ%;sY%fPPZCi{LiZWP;<5C>)LDr> z^)KD(SUB_X-j9ZE`&W*1+}XbvySUiREgj)*9De)odmSo$Mu9#kmYZfvW#dx^`-4@o zh2H22FCi^~PU>}|?9yMf3MuH7J^CiRV^x~BtIhiBRi&p~o1omRDvjQajmo9!#;y(b zsY+M1ZkFyURq5QOc4}v5riz}pBtII5(4=@mg+8RmK4q}jtjI<)r>6Gd)Y8;`Nc~|z z&@9`vyV4oYqc^3p;fWcb7M$xOpA&;lDvOkeLL62RMz8=QMd+eJ0&6cofd?HOOCbp# zK^OdBgX*o0SMu?+>{ODPczKJ5ISD~4eZy@RgqRr5y1G022jHHc?da@y9u7L6ksZC= z&#6?@Pc=guRk0*a~_19NdT6*u=`+gq` zFK=tPRkh%~9(&Vu-F2&K&b+v-#LYuPe8(Xd&n{yXRQ0nIQuDF`M^wbwT4FwNV@C3i`_-Nf*@a!shY73s$ zwW=~)q#PF6N$U14WLIjB>VQB{H@sYT;QezS*1dP}uKnZ}O}iI{KkUApTy5%Eay4MU z>Um?}+Q59@ito^>@8pv2lZ}hm!0Zzk#>u1(>kg7(AZVTR~g15Hd z-A(@(TEZv%L2tu)ox7s)o)2c+!?_PE+kwUbQnOMRSOUub};6Zf#V zvzxw@SP%vjAI$0lB93+RNVbXvS)zIJG)kxh9~9<1`dp}lacVpKCJm4ef;%Q=`)?VA zvoKhA9SYdbMwKDr3`p`Qhxj7&Pn4um(QrTZ6zmd3{qWk z^!pC^PlZZpxf>9iC52kwYVEmurt+bI)0+xa?W7?^ziAAo`iTwM1Nvz(1JF z!ZoDN<2YNfk+Y|erKj#h>8rFjK%&q8HfJr#Y0%&O!e7E_!q1`D$jK9w=3pJOB1cF5 z7-n90EHi%^U`oM9`B}4E`_!ZSyzrU5jgpiyr9bG9?P%fQMxT?N9H)8;9Ny@m%~gB0 zamtN8CNVQvjw^}1@K;Eszkx!o;y4k1TbbbE5_sApc_0)FMdm9jqJa~vS}_R~n%#Px zd~JjazpwiZnAQ3Eq6v=mH+GPK694He#B?H_%5YrJF1(CjE@6?zVgic{7FjHESiAy7 z&^U8Mb9;W&Zx?2c{@7{CFBXFJvW?@C*@QyR(L7DBa{`_i*{1$nML2=@9i&Ydo}jyc z+Cspa2;nFe$V;Ia3fT_7=af+9aQ~QJ2go)ESF`C!aD$ZF1=-9F3`B(>PP&4{YgkNU z5r#sxM`3<@96~@!{=daGGt%-A?#T9&iUgbz{sG3|aTni(V$EbQ82&&y|AYAdNXkDa zWuFtrzmeL{iTiU>@j2P1{N1w>yKAoNPyb}q*05x2Shh8;+V(El_O9AOOSaIgxlma> zU$;`}U)*zirSkafwl$-}ZJc$ic}YH?J|^`XPpmX#$lu3)Iwm@ ne`v{n=mEj6wWlq_?3$aoYpN@lU4P#9v%c2{z*?K@sf_&( Date: Sun, 18 Oct 2026 21:09:08 +0530 Subject: [PATCH 11/15] fix(scripts): find dylib modules in coverage check The coverage script only looked for .so example modules, so it failed to locate them on macOS where they are built as .dylib. The remaining lookups now also accept the .dylib extension. Auto-committed-on: macbook --- scripts/check-coverage.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/check-coverage.sh b/scripts/check-coverage.sh index 9f1cd67a..78795dca 100755 --- a/scripts/check-coverage.sh +++ b/scripts/check-coverage.sh @@ -4,11 +4,11 @@ cd "$(dirname "${BASH_SOURCE[0]}")/.." cargo build --locked --example module_clock --example module_clock_two --example module_wrong_target --example module_context --example module_context_eager --example module_host_callback --example module_controlled_init --example module_terminal --no-default-features --features modules,macros,__native-fixture-support set -euo pipefail target_dir=$(cargo metadata --format-version 1 --no-deps | python3 -c 'import json,sys; print(json.load(sys.stdin)["target_directory"])') -module=$(find "$target_dir/debug/examples" -maxdepth 1 -type f -name 'libmodule_clock.so' -print -quit) -module_two=$(find "$target_dir/debug/examples" -maxdepth 1 -type f -name 'libmodule_clock_two.so' -print -quit) -wrong_target=$(find "$target_dir/debug/examples" -maxdepth 1 -type f -name 'libmodule_wrong_target.so' -print -quit) -context_module=$(find "$target_dir/debug/examples" -maxdepth 1 -type f -name 'libmodule_context.so' -print -quit) -context_eager_module=$(find "$target_dir/debug/examples" -maxdepth 1 -type f -name 'libmodule_context_eager.so' -print -quit) +module=$(find "$target_dir/debug/examples" -maxdepth 1 -type f \( -name 'libmodule_clock.so' -o -name 'libmodule_clock.dylib' \) -print -quit) +module_two=$(find "$target_dir/debug/examples" -maxdepth 1 -type f \( -name 'libmodule_clock_two.so' -o -name 'libmodule_clock_two.dylib' \) -print -quit) +wrong_target=$(find "$target_dir/debug/examples" -maxdepth 1 -type f \( -name 'libmodule_wrong_target.so' -o -name 'libmodule_wrong_target.dylib' \) -print -quit) +context_module=$(find "$target_dir/debug/examples" -maxdepth 1 -type f \( -name 'libmodule_context.so' -o -name 'libmodule_context.dylib' \) -print -quit) +context_eager_module=$(find "$target_dir/debug/examples" -maxdepth 1 -type f \( -name 'libmodule_context_eager.so' -o -name 'libmodule_context_eager.dylib' \) -print -quit) host_callback_module=$(find "$target_dir/debug/examples" -maxdepth 1 -type f \( -name 'libmodule_host_callback.so' -o -name 'libmodule_host_callback.dylib' \) -print -quit) controlled_init_module=$(find "$target_dir/debug/examples" -maxdepth 1 -type f \( -name 'libmodule_controlled_init.so' -o -name 'libmodule_controlled_init.dylib' \) -print -quit) terminal_module=$(find "$target_dir/debug/examples" -maxdepth 1 -type f \( -name 'libmodule_terminal.so' -o -name 'libmodule_terminal.dylib' \) -print -quit) From 0282f7f58f462b6450ddea9f01b22791d962c81a Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 21:09:43 +0530 Subject: [PATCH 12/15] test: run coverage check in feature test script Auto-committed-on: macbook --- scripts/test-features.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test-features.sh b/scripts/test-features.sh index eee04749..ea57db9c 100755 --- a/scripts/test-features.sh +++ b/scripts/test-features.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/.." +python3 scripts/test_check_coverage.py cargo check --locked --workspace --no-default-features cargo check --locked --package tinybus --no-default-features --features modules cargo metadata --locked --format-version 1 --manifest-path examples/Cargo.toml >/dev/null From 7d361c618b256d86f431a3f53fef57178b7ebc79 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 21:13:06 +0530 Subject: [PATCH 13/15] test(coverage): assert fake cargo harness stays in posix lane Add a test that parses scripts/test-features.sh and checks the fake-Cargo coverage invocation only runs in the POSIX branch of the runner-OS case statement, never on Windows. It also pins the exact set of cargo commands so the harness cannot silently drift. Auto-committed-on: macbook --- scripts/test_check_coverage.py | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/scripts/test_check_coverage.py b/scripts/test_check_coverage.py index 895f1410..54115a3b 100644 --- a/scripts/test_check_coverage.py +++ b/scripts/test_check_coverage.py @@ -274,6 +274,52 @@ def test_macos_dylib_fixtures_reach_the_unchanged_coverage_gate(self) -> None: result, calls = self.run_coverage_script("dylib") self.assert_coverage_contract(result, calls, "dylib") + def test_fake_cargo_harness_is_limited_to_the_posix_feature_lane(self) -> None: + lines = (ROOT / "scripts" / "test-features.sh").read_text( + encoding="utf-8" + ).splitlines() + case_line = 'case "${RUNNER_OS:-$(uname -s)}" in' + self.assertIn( + case_line, + lines, + "the fake-Cargo coverage harness must be scoped to the POSIX runner branch", + ) + case_start = lines.index(case_line) + windows_branch = next( + index for index, line in enumerate(lines) if line.startswith(" Windows|") + ) + posix_branch = next( + index for index, line in enumerate(lines[windows_branch + 1 :], windows_branch + 1) + if line.strip() == "*)" + ) + case_end = next( + index for index, line in enumerate(lines[posix_branch + 1 :], posix_branch + 1) + if line.strip() == "esac" + ) + invocations = [ + index + for index, line in enumerate(lines) + if line.strip() == "python3 scripts/test_check_coverage.py" + ] + + self.assertEqual(len(invocations), 1) + self.assertGreater(invocations[0], posix_branch) + self.assertLess(invocations[0], case_end) + self.assertNotIn( + "python3 scripts/test_check_coverage.py", + [line.strip() for line in lines[windows_branch + 1 : posix_branch]], + ) + self.assertEqual( + [line.strip() for line in lines if line.strip().startswith("cargo ")], + [ + "cargo check --locked --workspace --no-default-features", + "cargo check --locked --package tinybus --no-default-features --features modules", + "cargo metadata --locked --format-version 1 --manifest-path examples/Cargo.toml >/dev/null", + "cargo test --locked --workspace --no-default-features --features macros", + "cargo test --locked --workspace --all-features", + ], + ) + if __name__ == "__main__": unittest.main() From 743bc39898aec28e9698c5e8dd7c8602c9de27da Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 21:13:52 +0530 Subject: [PATCH 14/15] test(scripts): update coverage script platform checks The feature test runner now skips the coverage script on Windows while still running it on Linux and macOS, and the accompanying platform tests were adjusted to match the new case structure and verify the Windows branch is empty. Auto-committed-on: macbook --- scripts/test-features.sh | 7 ++++++- scripts/test_check_coverage.py | 21 +++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/scripts/test-features.sh b/scripts/test-features.sh index ea57db9c..66d801bb 100755 --- a/scripts/test-features.sh +++ b/scripts/test-features.sh @@ -1,7 +1,12 @@ #!/usr/bin/env bash set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/.." -python3 scripts/test_check_coverage.py +case "${RUNNER_OS:-$(uname -s)}" in + Linux|Darwin|macOS) + python3 scripts/test_check_coverage.py + ;; + Windows|MINGW*|MSYS*|CYGWIN*) ;; +esac cargo check --locked --workspace --no-default-features cargo check --locked --package tinybus --no-default-features --features modules cargo metadata --locked --format-version 1 --manifest-path examples/Cargo.toml >/dev/null diff --git a/scripts/test_check_coverage.py b/scripts/test_check_coverage.py index 54115a3b..a5aee94e 100644 --- a/scripts/test_check_coverage.py +++ b/scripts/test_check_coverage.py @@ -286,15 +286,19 @@ def test_fake_cargo_harness_is_limited_to_the_posix_feature_lane(self) -> None: ) case_start = lines.index(case_line) windows_branch = next( - index for index, line in enumerate(lines) if line.startswith(" Windows|") + index + for index, line in enumerate(lines) + if line.startswith(" Windows|") ) posix_branch = next( - index for index, line in enumerate(lines[windows_branch + 1 :], windows_branch + 1) - if line.strip() == "*)" + index + for index, line in enumerate(lines) + if line.strip() == "Linux|Darwin|macOS)" ) - case_end = next( - index for index, line in enumerate(lines[posix_branch + 1 :], posix_branch + 1) - if line.strip() == "esac" + posix_end = next( + index + for index, line in enumerate(lines[posix_branch + 1 :], posix_branch + 1) + if line.strip() == ";;" ) invocations = [ index @@ -304,11 +308,12 @@ def test_fake_cargo_harness_is_limited_to_the_posix_feature_lane(self) -> None: self.assertEqual(len(invocations), 1) self.assertGreater(invocations[0], posix_branch) - self.assertLess(invocations[0], case_end) + self.assertLess(invocations[0], posix_end) self.assertNotIn( "python3 scripts/test_check_coverage.py", - [line.strip() for line in lines[windows_branch + 1 : posix_branch]], + [line.strip() for line in lines[case_start + 1 : windows_branch]], ) + self.assertGreater(windows_branch, case_start) self.assertEqual( [line.strip() for line in lines if line.strip().startswith("cargo ")], [ From 1bc047d2a0e2a5a23cf7dfd50d70a97ba6f29e0b Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Sun, 18 Oct 2026 21:14:12 +0530 Subject: [PATCH 15/15] test(coverage): fix windows branch assertion range The test now checks the lines between the Windows branch and the closing esac instead of the range before the branch, so the assertion actually inspects the Windows case body. Auto-committed-on: macbook --- scripts/test_check_coverage.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/test_check_coverage.py b/scripts/test_check_coverage.py index a5aee94e..78fbd501 100644 --- a/scripts/test_check_coverage.py +++ b/scripts/test_check_coverage.py @@ -300,6 +300,11 @@ def test_fake_cargo_harness_is_limited_to_the_posix_feature_lane(self) -> None: for index, line in enumerate(lines[posix_branch + 1 :], posix_branch + 1) if line.strip() == ";;" ) + case_end = next( + index + for index, line in enumerate(lines[windows_branch + 1 :], windows_branch + 1) + if line.strip() == "esac" + ) invocations = [ index for index, line in enumerate(lines) @@ -311,7 +316,7 @@ def test_fake_cargo_harness_is_limited_to_the_posix_feature_lane(self) -> None: self.assertLess(invocations[0], posix_end) self.assertNotIn( "python3 scripts/test_check_coverage.py", - [line.strip() for line in lines[case_start + 1 : windows_branch]], + [line.strip() for line in lines[windows_branch + 1 : case_end]], ) self.assertGreater(windows_branch, case_start) self.assertEqual(