diff --git a/xtask/src/gates/kind_isolation.rs b/xtask/src/gates/kind_isolation.rs index b0dae1606b..f86c02b4fb 100644 --- a/xtask/src/gates/kind_isolation.rs +++ b/xtask/src/gates/kind_isolation.rs @@ -873,6 +873,18 @@ const BATTERY_MARKER: &str = "testkit"; /// A per-crate battery file matches this, under the crate's own `tests/`. const CONFORMANCE_MARKER: &str = "conformance"; +/// THE PUBLISHED CONFORMANCE SUITE'S INVOCATION. A plugin runs busbar's published suite (OWNER +/// 2026-10-03: plugins test themselves against busbar's published suite) by invoking +/// `busbar_plugin_loader::conformance_suite!` in its `tests/conformance.rs`, and the macro expands +/// to the suite's `#[test]` entries (`crates/plugin-loader/src/conformance/mod.rs`). The file +/// itself spells no `#[test]`, so a counter that reads only attributes scored a running battery as +/// one with no entry. +const PUBLISHED_SUITE_MACRO: &str = "conformance_suite!"; + +/// The key an invocation of [`PUBLISHED_SUITE_MACRO`] names its subject with. An invocation that +/// names no `door:` expands to no entry against any subject, so it is not counted. +const PUBLISHED_SUITE_SUBJECT: &str = "door:"; + // ------------------------------------------------------------------------------------------------ // the vocabulary bans // ------------------------------------------------------------------------------------------------ @@ -4020,9 +4032,68 @@ fn live_battery_entries(text: &str) -> (usize, usize) { live += 1; } } + live += published_suite_invocations(&lines); (live, ignored) } +/// How many invocations of the published suite ([`PUBLISHED_SUITE_MACRO`]) a battery file makes +/// that name a subject — each one is the suite's entries run against that subject. +/// +/// Read off the BLANKED lines, so the macro's name in prose or in a string is not an invocation. +/// The invocation runs from the macro's name to the delimiter that closes the one it opened, and it +/// counts only if that span names [`PUBLISHED_SUITE_SUBJECT`]: `conformance_suite! {}` expands to +/// nothing and stays a file with no entry. A file that DEFINES its own `macro_rules! +/// conformance_suite` is not invoking the published suite, whatever its invocation looks like, so +/// none of its invocations count. +fn published_suite_invocations(lines: &[String]) -> usize { + let shadowed = lines.iter().any(|l| { + l.contains("macro_rules!") && l.contains(PUBLISHED_SUITE_MACRO.trim_end_matches('!')) + }); + if shadowed { + return 0; + } + let mut count = 0usize; + for (i, l) in lines.iter().enumerate() { + let Some(at) = l.find(PUBLISHED_SUITE_MACRO) else { + continue; + }; + // The macro's name must be a path's last segment, not the tail of a longer identifier. + let before = l[..at].chars().next_back(); + if before.is_some_and(|c| c.is_ascii_alphanumeric() || c == '_') { + continue; + } + let mut span = String::new(); + let mut depth = 0i32; + let mut opened = false; + 'walk: for (j, line) in lines[i..].iter().enumerate() { + let text = if j == 0 { + &line[at + PUBLISHED_SUITE_MACRO.len()..] + } else { + line.as_str() + }; + for ch in text.chars() { + match ch { + '{' | '(' | '[' => { + depth += 1; + opened = true; + } + '}' | ')' | ']' => depth -= 1, + _ => {} + } + span.push(ch); + if opened && depth <= 0 { + break 'walk; + } + } + span.push('\n'); + } + if span.contains(PUBLISHED_SUITE_SUBJECT) { + count += 1; + } + } + count +} + /// The `<…>` immediately after `impl`, skipped as a BALANCED group: `impl` and /// `impl<'a, T: Into>>` both end at the `>` that closes the one this opened, not at the /// first `>` in the line. @@ -9686,6 +9757,81 @@ impl Gate for KindIsolationGate { &["battery-ignored", "busbar-store-ignored"], )); + // THE PUBLISHED SUITE IS A BATTERY. A plugin's `tests/conformance.rs` that invokes + // `busbar_plugin_loader::conformance_suite!` over its door runs the suite's entries — the + // macro expands to them — and spells no `#[test]` of its own. It is a live battery, and the + // row must not call it ignored. + let mut ov = manifest_plant( + "crates/busbar-store-suite", + "busbar-store-suite", + &["busbar-contract"], + ); + ov.set( + "crates/busbar-store-suite/src/lib.rs", + "pub struct P;\nimpl Store for P {}\n", + ); + ov.set( + "crates/busbar-store-suite/tests/conformance.rs", + "busbar_plugin_loader::conformance_suite! {\n door: busbar_store_suite::door,\n \ + cdylib: \"store_suite_door\",\n inputs: include_str!(\"conformance.json\"),\n}\n", + ); + report.push(prove_rows_green( + cx, + subject, + "a battery that invokes the published suite over its door is a live battery", + &[ROW_TESTKIT], + ov, + )); + + // AN INVOCATION THAT EXPANDS TO NOTHING IS NOT A BATTERY. The same file with the subject + // taken out names the published macro and runs no entry against anything. + let mut ov = manifest_plant( + "crates/busbar-store-emptysuite", + "busbar-store-emptysuite", + &["busbar-contract"], + ); + ov.set( + "crates/busbar-store-emptysuite/src/lib.rs", + "pub struct P;\nimpl Store for P {}\n", + ); + ov.set( + "crates/busbar-store-emptysuite/tests/conformance.rs", + "busbar_plugin_loader::conformance_suite! {}\n", + ); + report.push(prove_rows_red( + cx, + subject, + "a published-suite invocation that names no subject is a file with no entry", + &[ROW_TESTKIT], + ov, + &["battery-ignored", "busbar-store-emptysuite"], + )); + + // A LOCAL MACRO BY THE SAME NAME IS NOT THE PUBLISHED SUITE. A file that defines its own + // `conformance_suite` to expand to nothing, then invokes it with a subject, runs nothing. + let mut ov = manifest_plant( + "crates/busbar-store-shadowsuite", + "busbar-store-shadowsuite", + &["busbar-contract"], + ); + ov.set( + "crates/busbar-store-shadowsuite/src/lib.rs", + "pub struct P;\nimpl Store for P {}\n", + ); + ov.set( + "crates/busbar-store-shadowsuite/tests/conformance.rs", + "macro_rules! conformance_suite {\n ($($t:tt)*) => {};\n}\n\ + conformance_suite! {\n door: busbar_store_shadowsuite::door,\n}\n", + ); + report.push(prove_rows_red( + cx, + subject, + "a file-local conformance_suite macro is not the published suite", + &[ROW_TESTKIT], + ov, + &["battery-ignored", "busbar-store-shadowsuite"], + )); + // A BATTERY WITH NO SUBJECT. The same crate, with a live battery and no implementor of its // kind's trait anywhere in shipped source: the file compiles, the battery passes, and it is // evidence about nothing this crate ships.