use std::path::{Path, PathBuf};
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("repo root")
.to_path_buf()
}
fn read(rel: &str) -> String {
let p = repo_root().join(rel);
std::fs::read_to_string(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()))
}
fn code_only(src: &str) -> String {
src.lines()
.map(|line| match line.find("//") {
Some(i) => &line[..i],
None => line,
})
.collect::<Vec<_>>()
.join("\n")
}
fn is_gating_cfg(attr: &str) -> bool {
let squashed: String = attr.chars().filter(|c| !c.is_whitespace()).collect();
if squashed.starts_with("#[cfg(") {
return true;
}
squashed
.strip_prefix("#[cfg_attr(")
.is_some_and(|added| added.contains("cfg("))
}
fn unix_gated_items(src: &str) -> Vec<String> {
let code = code_only(src);
let mut gated = Vec::new();
let mut cfg_pending = false;
for line in code.lines() {
let t = line.trim();
if t.is_empty() {
continue;
}
if is_gating_cfg(t) {
cfg_pending = true;
continue;
}
if t.starts_with('#') {
continue;
}
if cfg_pending {
if let Some(name) = item_name(t) {
gated.push(name);
}
cfg_pending = false;
}
}
gated.sort();
gated
}
fn push_group_names(group: &str, refs: &mut Vec<String>) {
for item in group.split(',') {
let name: String = item
.trim()
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if !name.is_empty() && name != "self" {
refs.push(name);
}
}
}
fn item_name(line: &str) -> Option<String> {
let after_kw = |kw: &str| -> Option<String> {
let rest = line.split_once(kw)?.1;
let name: String = rest
.trim_start()
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
(!name.is_empty()).then_some(name)
};
for kw in ["fn ", "struct ", "enum ", "const ", "static ", "use "] {
if let Some(n) = after_kw(kw) {
return Some(n);
}
}
None
}
fn unconditional_resim_refs(src: &str) -> Vec<String> {
let code = code_only(src);
let mut refs = Vec::new();
let mut cfg_pending = false;
let mut in_gated_fn = false;
let mut depth: i32 = 0;
let mut pending_group: Option<String> = None;
for line in code.lines() {
let t = line.trim();
if in_gated_fn {
depth += line.matches('{').count() as i32;
depth -= line.matches('}').count() as i32;
if depth <= 0 {
in_gated_fn = false;
}
continue;
}
if is_gating_cfg(t) {
cfg_pending = true;
continue;
}
if t.starts_with('#') || t.starts_with("///") {
continue;
}
if cfg_pending && !t.is_empty() {
cfg_pending = false;
in_gated_fn = true;
depth = line.matches('{').count() as i32 - line.matches('}').count() as i32;
if depth <= 0 {
in_gated_fn = false;
}
continue;
}
if let Some(open) = pending_group.as_mut() {
match line.split_once('}') {
Some((head, _)) => {
open.push_str(head);
let group = std::mem::take(open);
pending_group = None;
push_group_names(&group, &mut refs);
}
None => {
open.push_str(line);
open.push(',');
}
}
continue;
}
let mut rest = line;
while let Some(i) = rest.find("resim_cmd::") {
rest = &rest[i + "resim_cmd::".len()..];
if let Some(inner) = rest.strip_prefix('{') {
match inner.split_once('}') {
Some((group, tail)) => {
push_group_names(group, &mut refs);
rest = tail;
continue;
}
None => {
pending_group = Some(format!("{inner},"));
break;
}
}
}
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if !name.is_empty() {
refs.push(name);
}
}
}
refs.sort();
refs.dedup();
refs
}
const ENGINE_LIB: &str = "cerulion_cli_engine/src/lib.rs";
const RESIM: &str = "cerulion_cli_engine/src/resim_cmd.rs";
const MAIN: &str = "cerulion_cli/src/main.rs";
#[test]
fn the_resim_module_declaration_carries_no_cfg() {
let lib = code_only(&read(ENGINE_LIB));
let (before, _) = lib
.split_once("pub mod resim_cmd;")
.expect("`pub mod resim_cmd;` must be declared in the engine's lib.rs");
let tail = before
.lines()
.rev()
.take_while(|l| {
let t = l.trim();
t.starts_with('#') || t.is_empty()
})
.collect::<Vec<_>>();
assert!(
!tail.iter().any(|l| l.contains("cfg(")),
"`resim_cmd` must NOT be cfg-gated — `main` reaches its removal notice \
on every platform. Gate the individual engine-touching functions \
instead. Attributes found: {tail:?}"
);
}
#[test]
fn every_unconditional_reference_names_an_ungated_item() {
let gated = unix_gated_items(&read(RESIM));
let refs = unconditional_resim_refs(&read(MAIN));
let broken: Vec<&String> = refs.iter().filter(|r| gated.contains(r)).collect();
assert!(
broken.is_empty(),
"`main.rs` names {broken:?} unconditionally, but `resim_cmd` gates \
it with `#[cfg(unix)]` — that is an E0433 on every non-Unix build, \
and a Unix host's build cannot catch it. Either un-gate the \
item or move the reference into a `#[cfg(unix)]` function with a \
`#[cfg(not(unix))]` twin.\n gated: {gated:?}\n unconditional refs: {refs:?}"
);
}
#[test]
fn the_walk_still_sees_the_code_it_claims_to_read() {
let gated = unix_gated_items(&read(RESIM));
assert!(
gated.contains(&"run_resim".to_string()) && gated.contains(&"run_play_resim".to_string()),
"the two engine-touching functions must be seen as gated; got {gated:?}"
);
let refs = unconditional_resim_refs(&read(MAIN));
for want in ["resolve_play_mode", "PlayFlags", "EXIT_USAGE"] {
assert!(
refs.contains(&want.to_string()),
"the pre-auth usage refusal must be seen as an UNCONDITIONAL \
reference to {want:?} — it is what this contract now protects; \
got {refs:?}"
);
}
let code = code_only("let x = 1; // #[cfg(unix)] in a comment\n#[cfg(unix)]\nfn real() {}");
assert!(!code.contains("in a comment"));
assert!(code.contains("#[cfg(unix)]"));
assert!(code.contains("fn real()"));
}
#[test]
fn a_cfg_predicate_cannot_be_respelled_to_evade_the_gate_walk() {
for attr in [
"#[cfg(unix)]",
"#[cfg(any(unix))]",
"#[cfg(all(unix))]",
"#[cfg(any(all(unix)))]",
"#[cfg(target_family = \"unix\")]",
"#[cfg( unix )]",
"#[cfg(not(windows))]",
"#[cfg_attr(not(unix), cfg(unix))]",
"#[cfg_attr(feature = \"x\", cfg(unix))]",
"#[cfg_attr(a, cfg_attr(b, cfg(unix)))]",
] {
let src = format!("{attr}\npub fn gated_item() {{}}\n");
assert_eq!(
unix_gated_items(&src),
vec!["gated_item".to_string()],
"{attr} must be seen as a gate"
);
}
for src in [
"pub fn plain() {}\n",
"#[derive(Debug)]\npub struct Plain;\n",
"#[cfg_attr(unix, allow(dead_code))]\npub fn conditioned() {}\n",
] {
assert!(
unix_gated_items(src).is_empty(),
"must read as UNGATED: {src:?} -> {:?}",
unix_gated_items(src)
);
}
}
#[test]
fn a_grouped_import_does_not_hide_its_items_from_the_symmetry_walk() {
let src = "use cerulion_cli_engine::resim_cmd::{run_resim, PlayFlags};\n";
assert_eq!(
unconditional_resim_refs(src),
vec!["PlayFlags".to_string(), "run_resim".to_string()],
"both grouped names must be recorded"
);
let src = "use cerulion_cli_engine::resim_cmd::{self, run_play_resim as go};\n";
assert_eq!(
unconditional_resim_refs(src),
vec!["run_play_resim".to_string()]
);
let src = "use cerulion_cli_engine::resim_cmd::{EXIT_USAGE, PlayFlags};\n\
let c = resim_cmd::EXIT_PASS;\n";
let refs = unconditional_resim_refs(src);
for want in ["EXIT_USAGE", "PlayFlags", "EXIT_PASS"] {
assert!(refs.contains(&want.to_string()), "missing {want}: {refs:?}");
}
let src = "#[cfg(unix)]\nfn gated() {\n use cerulion_cli_engine::resim_cmd::{run_resim, PlayFlags};\n}\n";
assert!(
unconditional_resim_refs(src).is_empty(),
"a gated fn's grouped import is not an unconditional ref: {:?}",
unconditional_resim_refs(src)
);
}
#[test]
fn a_wrapped_grouped_import_is_carried_across_lines() {
let wrapped = "use cerulion_cli_engine::resim_cmd::{\n\
\x20 run_resim,\n\
\x20 run_play_resim,\n\
\x20 PlayFlags,\n\
};\n";
let refs = unconditional_resim_refs(wrapped);
for want in ["PlayFlags", "run_play_resim", "run_resim"] {
assert!(
refs.contains(&want.to_string()),
"a wrapped group must record {want}: {refs:?}"
);
}
let tight = "use cerulion_cli_engine::resim_cmd::{\n\
\x20 EXIT_USAGE,\n\
\x20 EXIT_PASS};\n";
let refs = unconditional_resim_refs(tight);
assert!(refs.contains(&"EXIT_USAGE".to_string()), "{refs:?}");
assert!(
refs.contains(&"EXIT_PASS".to_string()),
"the last item without a trailing comma must survive: {refs:?}"
);
let mixed = "use cerulion_cli_engine::resim_cmd::{\n\
\x20 self, run_resim, EXIT_USAGE as USAGE,\n\
};\n";
let refs = unconditional_resim_refs(mixed);
assert_eq!(
refs,
vec!["EXIT_USAGE".to_string(), "run_resim".to_string()]
);
let after = "use cerulion_cli_engine::resim_cmd::{\n\
\x20 run_resim,\n\
};\n\
let x = resim_cmd::EXIT_PASS;\n\
let y = something_else::NotAResimItem;\n";
let refs = unconditional_resim_refs(after);
assert_eq!(
refs,
vec!["EXIT_PASS".to_string(), "run_resim".to_string()],
"the carry must close at `}}` and invent nothing: {refs:?}"
);
}