Skip to main content

tests_bridge_macro/
lib.rs

1//! `tests_bridge` — compile-time "top-level anchor" for module tests.
2//!
3//! Rust's `#[path]` attribute only accepts string literals (relative to the
4//! containing source file), and the attribute evaluator rejects macro calls
5//! (`#[path = concat!(...)]` → `malformed path attribute input`; RFC 2320
6//! "eager macro expansion" was closed unmerged). This macro sidesteps the
7//! limitation by expanding — at macro-expansion time, before attribute
8//! evaluation — into a `#[path = "<absolute>"] mod tests;` where the path is
9//! anchored at `CARGO_MANIFEST_DIR` (the *calling* crate's root, which is the
10//! environment the proc-macro runs in). That is the TS `@/`-equivalent Rust
11//! does not provide natively.
12//!
13//! Usage (in the crate whose `tests/<mirror>/mod.rs` holds the module tests):
14//!
15//! ```ignore
16//! #[cfg(test)]
17//! tests_bridge_macro::tests_bridge!("runtime/multiagent/graph/engine");
18//! // expands to:
19//! //   #[cfg(test)] #[path = "/abs/<CARGO_MANIFEST_DIR>/tests/runtime/multiagent/graph/engine/mod.rs"] mod tests;
20//! ```
21//!
22//! The call site keeps the `#[cfg(test)]` prefix so plain `cargo build` skips
23//! the expansion entirely (the macro is a dev-dependency and unavailable to
24//! non-test builds). Mirror path is relative to the crate root, `..` rejected.
25
26use proc_macro::TokenStream;
27use std::env;
28use std::path::PathBuf;
29
30#[proc_macro]
31pub fn tests_bridge(input: TokenStream) -> TokenStream {
32    // Only bridge tests when compiling the package's own library or binary
33    // unit-test target.
34    // Integration-test crates often path-include source modules (to reach private
35    // code / macros); in that context `cfg(test)` is also true, but the bridge's
36    // `crate::...` paths refer to the test-crate root instead of the lib, and the
37    // bridged tests run a second time in a binary that already has its own
38    // integration tests. That duplicate execution also races on process-global
39    // state (console sinks, registries, env locks). Compare CARGO_CRATE_NAME to
40    // CARGO_PKG_NAME or CARGO_BIN_NAME so the bridge is emitted for the owning
41    // target, not for `tests/*.rs` integration binaries.
42    let crate_name = env::var("CARGO_CRATE_NAME").unwrap_or_default();
43    let package_name = env::var("CARGO_PKG_NAME")
44        .unwrap_or_default()
45        .replace('-', "_");
46    let bin_name = env::var("CARGO_BIN_NAME")
47        .unwrap_or_default()
48        .replace('-', "_");
49    if crate_name != package_name && crate_name != bin_name {
50        return TokenStream::new();
51    }
52
53    let lit = input.to_string().trim().to_string();
54    let mirror = lit.trim_matches('"').to_string();
55    assert!(
56        !mirror.is_empty() && !mirror.contains(".."),
57        "tests_bridge!: mirror path must be a non-empty literal without `..`, got `{lit}`"
58    );
59    let manifest =
60        PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set"));
61    // `tests/<mirror>/mod.rs`, anchored at the calling crate root. Forward slashes
62    // keep the generated literal portable across platforms.
63    let abs = manifest
64        .join("tests")
65        .join(&mirror)
66        .join("mod.rs")
67        .to_string_lossy()
68        .replace('\\', "/");
69    format!(r#"#[path = "{abs}"] mod tests;"#)
70        .parse()
71        .expect("generated bridge tokens must parse")
72}