use std::path::PathBuf;
use std::process::Command;
fn cargo_macra_bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_cargo-macra"))
}
fn debug_target_manifest() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/debug_target/Cargo.toml")
}
#[derive(Debug)]
struct ExpansionBlock {
caller: String,
input: String,
output: String,
}
fn parse_expansion_blocks(stdout: &str) -> Vec<ExpansionBlock> {
let lines: Vec<&str> = stdout.lines().collect();
let mut blocks = Vec::new();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
if let Some(caller) = line.strip_prefix("== ").and_then(|s| s.strip_suffix(" ==")) {
let caller = caller.to_string();
i += 1;
let mut input_lines = Vec::new();
while i < lines.len() && lines[i] != "---" {
input_lines.push(lines[i]);
i += 1;
}
if i < lines.len() && lines[i] == "---" {
i += 1;
}
let mut output_lines = Vec::new();
while i < lines.len() {
let l = lines[i];
if l.starts_with("== ") && l.ends_with(" ==") {
break;
}
output_lines.push(l);
i += 1;
}
while output_lines.last().is_some_and(|l| l.is_empty()) {
output_lines.pop();
}
blocks.push(ExpansionBlock {
caller,
input: input_lines.join("\n"),
output: output_lines.join("\n"),
});
} else {
i += 1;
}
}
blocks
}
fn find_blocks<'a>(blocks: &'a [ExpansionBlock], caller: &str) -> Vec<&'a ExpansionBlock> {
blocks.iter().filter(|b| b.caller == caller).collect()
}
#[test]
fn fnlike_alpha_and_beta_are_distinct() {
assert_eq!(
debug_target::case_fnlike::__TRACE_FNLIKE_alpha,
"fnlike:alpha"
);
assert_eq!(
debug_target::case_fnlike::__TRACE_FNLIKE_beta,
"fnlike:beta"
);
}
#[test]
fn fnlike_generated_macro_rules_invoked() {
assert_eq!(
debug_target::case_fnlike::__TRACE_GENERATED_alpha,
"generated:alpha:from_alpha"
);
assert_eq!(
debug_target::case_fnlike::__TRACE_GENERATED_beta,
"generated:beta:from_beta"
);
}
#[test]
fn attr_different_targets_are_distinct() {
assert_eq!(
debug_target::case_attr_different_targets::__TRACE_ATTR_alpha_first_FOR_A1,
"attr:alpha:first:A1"
);
assert_eq!(
debug_target::case_attr_different_targets::__TRACE_ATTR_alpha_first_FOR_A2,
"attr:alpha:first:A2"
);
}
#[test]
fn attr_same_target_different_instances() {
assert_eq!(
debug_target::case_attr_same_target::__TRACE_ATTR_alpha_first_FOR_A,
"attr:alpha:first:A"
);
assert_eq!(
debug_target::case_attr_same_target::__TRACE_ATTR_alpha_second_FOR_A,
"attr:alpha:second:A"
);
}
#[test]
fn derive_different_targets_are_distinct() {
assert_eq!(
debug_target::case_derive::__TRACE_DERIVE_alpha_first_FOR_D1,
"derive:alpha:first:D1"
);
assert_eq!(
debug_target::case_derive::__TRACE_DERIVE_alpha_second_FOR_D2,
"derive:alpha:second:D2"
);
}
#[test]
fn mixed_all_kinds_coexist() {
assert_eq!(
debug_target::case_mixed::__TRACE_FNLIKE_gamma,
"fnlike:gamma"
);
assert_eq!(
debug_target::case_mixed::__TRACE_GENERATED_gamma,
"generated:gamma:from_gamma"
);
assert_eq!(
debug_target::case_mixed::__TRACE_FNLIKE_delta,
"fnlike:delta"
);
assert_eq!(
debug_target::case_mixed::__TRACE_GENERATED_delta,
"generated:delta:from_delta"
);
assert_eq!(
debug_target::case_mixed::__TRACE_ATTR_beta_one_FOR_M1,
"attr:beta:one:M1"
);
assert_eq!(
debug_target::case_mixed::__TRACE_DERIVE_beta_two_FOR_M2,
"derive:beta:two:M2"
);
}
#[test]
fn decl_macro_baseline() {
assert_eq!(debug_target::case_decl_macro::__TRACE_DECL, "decl:alpha");
assert_eq!(
debug_target::case_decl_macro::sub::__TRACE_DECL,
"decl:beta"
);
}
fn run_show_expansion() -> Vec<ExpansionBlock> {
let output = Command::new(cargo_macra_bin())
.arg("--show-expansion")
.arg("--manifest-path")
.arg(debug_target_manifest())
.output()
.expect("failed to run cargo-macra");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"cargo-macra failed.\nstderr:\n{}",
stderr
);
let blocks = parse_expansion_blocks(&stdout);
assert!(
!blocks.is_empty(),
"Expected at least one expansion block.\nstdout:\n{}\nstderr:\n{}",
stdout,
stderr
);
blocks
}
#[test]
fn show_expansion_generated_macro_rules_alpha() {
let blocks = run_show_expansion();
let alpha = find_blocks(&blocks, "__trace_generated_alpha!");
assert!(
!alpha.is_empty(),
"Expected __trace_generated_alpha! block.\nall callers: {:?}",
blocks.iter().map(|b| &b.caller).collect::<Vec<_>>()
);
assert!(
alpha[0].input.contains("from_alpha"),
"__trace_generated_alpha! input should contain 'from_alpha'.\nblock: {:?}",
alpha[0]
);
assert!(
alpha[0].output.contains("__TRACE_GENERATED_alpha")
&& alpha[0].output.contains("generated:")
&& alpha[0].output.contains("alpha"),
"__trace_generated_alpha! output should contain the generated const.\nblock: {:?}",
alpha[0]
);
}
#[test]
fn show_expansion_generated_macro_rules_beta() {
let blocks = run_show_expansion();
let beta = find_blocks(&blocks, "__trace_generated_beta!");
assert!(
!beta.is_empty(),
"Expected __trace_generated_beta! block.\nall callers: {:?}",
blocks.iter().map(|b| &b.caller).collect::<Vec<_>>()
);
assert!(
beta[0].input.contains("from_beta"),
"__trace_generated_beta! input should contain 'from_beta'.\nblock: {:?}",
beta[0]
);
assert!(
beta[0].output.contains("__TRACE_GENERATED_beta")
&& beta[0].output.contains("generated:")
&& beta[0].output.contains("beta"),
"__trace_generated_beta! output should contain the generated const.\nblock: {:?}",
beta[0]
);
}
#[test]
fn show_expansion_generated_macro_rules_are_distinct() {
let blocks = run_show_expansion();
let alpha = find_blocks(&blocks, "__trace_generated_alpha!");
let beta = find_blocks(&blocks, "__trace_generated_beta!");
assert!(!alpha.is_empty(), "Missing __trace_generated_alpha! block");
assert!(!beta.is_empty(), "Missing __trace_generated_beta! block");
assert!(
alpha[0].input.contains("from_alpha"),
"alpha input should contain from_alpha"
);
assert!(
beta[0].input.contains("from_beta"),
"beta input should contain from_beta"
);
assert_ne!(
alpha[0].input, beta[0].input,
"alpha and beta inputs should differ"
);
assert_ne!(
alpha[0].output, beta[0].output,
"alpha and beta generated macro outputs should differ"
);
}
#[test]
fn show_expansion_generated_macro_rules_gamma_delta() {
let blocks = run_show_expansion();
let gamma = find_blocks(&blocks, "__trace_generated_gamma!");
let delta = find_blocks(&blocks, "__trace_generated_delta!");
assert!(!gamma.is_empty(), "Missing __trace_generated_gamma! block");
assert!(!delta.is_empty(), "Missing __trace_generated_delta! block");
assert!(
gamma[0].output.contains("__TRACE_GENERATED_gamma"),
"gamma output should contain its const name.\nblock: {:?}",
gamma[0]
);
assert!(
delta[0].output.contains("__TRACE_GENERATED_delta"),
"delta output should contain its const name.\nblock: {:?}",
delta[0]
);
assert_ne!(
gamma[0].output, delta[0].output,
"gamma and delta outputs should differ"
);
}
#[test]
fn show_expansion_decl_trace_alpha_and_beta() {
let blocks = run_show_expansion();
let decl = find_blocks(&blocks, "decl_trace!");
assert!(
decl.len() >= 2,
"Expected at least 2 decl_trace! blocks, found {}.\nall callers: {:?}",
decl.len(),
blocks.iter().map(|b| &b.caller).collect::<Vec<_>>()
);
let decl_alpha = decl.iter().find(|b| b.input.trim() == "alpha");
let decl_beta = decl.iter().find(|b| b.input.trim() == "beta");
assert!(
decl_alpha.is_some(),
"Expected a decl_trace! block with input 'alpha'.\ndecl blocks: {:?}",
decl
);
assert!(
decl_beta.is_some(),
"Expected a decl_trace! block with input 'beta'.\ndecl blocks: {:?}",
decl
);
let alpha_out = &decl_alpha.unwrap().output;
let beta_out = &decl_beta.unwrap().output;
assert!(
alpha_out.contains("__TRACE_DECL"),
"decl_trace!(alpha) output should contain __TRACE_DECL.\noutput: {}",
alpha_out
);
assert!(
beta_out.contains("__TRACE_DECL"),
"decl_trace!(beta) output should contain __TRACE_DECL.\noutput: {}",
beta_out
);
assert_ne!(
decl_alpha.unwrap().input.trim(),
decl_beta.unwrap().input.trim(),
"decl_trace alpha and beta should have different inputs"
);
}
#[test]
fn show_expansion_all_generated_macros_present() {
let blocks = run_show_expansion();
let expected = [
"__trace_generated_alpha!",
"__trace_generated_beta!",
"__trace_generated_gamma!",
"__trace_generated_delta!",
];
for name in &expected {
let found = find_blocks(&blocks, name);
assert!(
!found.is_empty(),
"Missing expansion block for {}.\nall callers: {:?}",
name,
blocks.iter().map(|b| &b.caller).collect::<Vec<_>>()
);
}
}