use std::path::{Path, PathBuf};
const SERIAL_WITHOUT_ENV_LOCK: &[(&str, &str)] = &[
("src/bm25/tests.rs", "TRUSTY_BM25_CORPUS_CAP"),
("src/catchup/mod.rs", "TRUSTY_MEMORY_PALACE"),
("src/credentials/authority.rs", "provider API keys"),
("src/credentials/dotenv.rs", "bulk republish of a .env file"),
(
"src/credentials/env_guard.rs",
"TRUSTY_COMMON_ENV_GUARD_TEST_*, the guard's own tests",
),
("src/credentials/resolver.rs", "provider API keys"),
("src/inference/configurator/mod.rs", "provider API keys"),
(
"src/inference/configurator/resolver.rs",
"provider API keys",
),
("src/memory_core/dream/tests.rs", "provider API keys"),
(
"src/memory_core/semantic_consolidation/mod.rs",
"provider API keys",
),
(
"src/daemon_guard.rs",
"TRUSTY_TEST_ADDR_DIR_EMPTY and a caller-named data-dir variable",
),
("src/daemon_token.rs", "TRUSTY_TEST_DAEMON_TOKEN"),
(
"src/embedder_client/supervisor_tests.rs",
"TRUSTY_EMBEDDERD_*",
),
(
"src/inference/providers/local.rs",
"OLLAMA_HOST, TRUSTY_LOCAL_API_KEY",
),
("src/local_probe.rs", "OLLAMA_HOST"),
(
"src/memory_core/registry_tests.rs",
"TRUSTY_MEMORY_MAX_OPEN_PALACES",
),
("src/palace_resolve_tests.rs", "TRUSTY_MEMORY_PALACE"),
("src/uds/on_demand_tests.rs", "TRUSTY_ANALYZE_EXTERNAL"),
(
"src/uds/supervisor/tests.rs",
"TRUSTY_TEST_SUPERVISOR_EXTERNAL",
),
];
const SELF_BASENAME: &str = "env_lock_ratchet_tests.rs";
const ENV_LOCK_IDENT: &str = "ENV_LOCK";
const SERIAL_ATTRIBUTES: &[&str] = &["#[serial", "#[file_serial"];
const ENV_MUTATION_CALLS: &[&str] = &["set_var", "remove_var"];
const ENV_MUTATION_TYPES: &[&str] = &["EnvVarGuard"];
#[derive(Debug, PartialEq, Eq)]
enum Verdict {
OutOfScope,
Compliant,
Offender,
}
fn classify(source: Option<&str>) -> Verdict {
let Some(text) = source else {
return Verdict::Offender;
};
let code = strip_noncode(text);
if !uses_serial_attribute(&code) || !mutates_env(&code) {
return Verdict::OutOfScope;
}
if code.contains(ENV_LOCK_IDENT) {
Verdict::Compliant
} else {
Verdict::Offender
}
}
fn uses_serial_attribute(code: &str) -> bool {
SERIAL_ATTRIBUTES.iter().any(|a| code.contains(a))
}
fn mutates_env(code: &str) -> bool {
ENV_MUTATION_CALLS.iter().any(|c| contains_call(code, c))
|| ENV_MUTATION_TYPES.iter().any(|t| code.contains(t))
}
fn contains_call(code: &str, name: &str) -> bool {
let mut from = 0;
while let Some(rel) = code[from..].find(name) {
let at = from + rel;
if code[at + name.len()..].trim_start().starts_with('(') {
return true;
}
from = at + name.len();
}
false
}
fn strip_noncode(text: &str) -> String {
let chars: Vec<char> = text.chars().collect();
let n = chars.len();
let mut out = String::with_capacity(text.len());
let mut i = 0usize;
while i < n {
let c = chars[i];
let next = chars.get(i + 1).copied();
if c == '/' && next == Some('/') {
while i < n && chars[i] != '\n' {
i += 1;
}
out.push(' ');
} else if c == '/' && next == Some('*') {
let mut depth = 1usize;
i += 2;
while i < n && depth > 0 {
if chars[i] == '/' && chars.get(i + 1) == Some(&'*') {
depth += 1;
i += 2;
} else if chars[i] == '*' && chars.get(i + 1) == Some(&'/') {
depth -= 1;
i += 2;
} else {
i += 1;
}
}
out.push(' ');
} else if let Some(hashes) = raw_string_open(&chars, i) {
i += 1 + hashes + 1;
while i < n {
if chars[i] == '"' && (1..=hashes).all(|k| chars.get(i + k) == Some(&'#')) {
i += 1 + hashes;
break;
}
i += 1;
}
out.push(' ');
} else if c == '"' {
i += 1;
while i < n {
match chars[i] {
'\\' => i += 2,
'"' => {
i += 1;
break;
}
_ => i += 1,
}
}
out.push(' ');
} else if let Some(len) = char_literal_len(&chars, i) {
i += len;
out.push(' ');
} else {
out.push(c);
i += 1;
}
}
out
}
fn raw_string_open(chars: &[char], i: usize) -> Option<usize> {
if chars.get(i) != Some(&'r') {
return None;
}
if i > 0 {
let prev = chars[i - 1];
if (prev.is_alphanumeric() || prev == '_') && prev != 'b' {
return None;
}
}
let mut hashes = 0usize;
while chars.get(i + 1 + hashes) == Some(&'#') {
hashes += 1;
}
(chars.get(i + 1 + hashes) == Some(&'"')).then_some(hashes)
}
fn char_literal_len(chars: &[char], i: usize) -> Option<usize> {
if chars.get(i) != Some(&'\'') {
return None;
}
if chars.get(i + 1) == Some(&'\\') {
let end = (i + 2..chars.len().min(i + 12)).find(|k| chars[*k] == '\'')?;
return Some(end - i + 1);
}
(chars.get(i + 2) == Some(&'\'')).then_some(3)
}
fn lib_target_sources() -> Vec<PathBuf> {
fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
let entries = std::fs::read_dir(dir)
.unwrap_or_else(|e| panic!("read {}: {e}", dir.display()))
.filter_map(Result::ok);
for entry in entries {
let path = entry.path();
if path.is_dir() {
walk(&path, out);
} else if path.extension().is_some_and(|e| e == "rs")
&& path.file_name().is_some_and(|n| n != SELF_BASENAME)
{
out.push(path);
}
}
}
let mut out = Vec::new();
walk(&Path::new(env!("CARGO_MANIFEST_DIR")).join("src"), &mut out);
assert!(
out.len() > 100,
"the lib-target scan found only {} files — the walk is broken, and a broken walk \
reports a clean target regardless of its contents",
out.len()
);
out.sort();
out
}
fn relative(path: &Path) -> String {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
path.strip_prefix(root)
.unwrap_or(path)
.components()
.map(|c| c.as_os_str().to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("/")
}
#[test]
fn serial_env_mutating_files_take_env_lock() {
let mut unlisted: Vec<String> = Vec::new();
let mut matched: Vec<&str> = Vec::new();
for path in lib_target_sources() {
let rel = relative(&path);
if classify(std::fs::read_to_string(&path).ok().as_deref()) != Verdict::Offender {
continue;
}
match SERIAL_WITHOUT_ENV_LOCK
.iter()
.find(|(suffix, _)| rel.ends_with(suffix))
{
Some((suffix, _)) => matched.push(suffix),
None => unlisted.push(rel),
}
}
let stale: Vec<&str> = SERIAL_WITHOUT_ENV_LOCK
.iter()
.map(|(suffix, _)| *suffix)
.filter(|suffix| !matched.contains(suffix))
.collect();
assert!(
unlisted.is_empty(),
"these files mutate the process environment under a `serial_test` attribute without \
taking `data_dir::ENV_LOCK` (#7253). `#[serial]` and `ENV_LOCK` are different mutexes \
and exclude nothing of each other, so such a test still runs inside another test's \
`setenv`. Take `ENV_LOCK` for the whole window the variable is changed — \
`http_client.rs`'s `with_http_proxy` is the pattern. If a file genuinely cannot, add \
it to `SERIAL_WITHOUT_ENV_LOCK` in this file with the reason.\n {}",
unlisted.join("\n ")
);
assert!(
stale.is_empty(),
"`SERIAL_WITHOUT_ENV_LOCK` is stale — these rows name files that are now compliant (or \
gone). Delete the rows so the ratchet keeps its grip.\n {}",
stale.join("\n ")
);
}
#[test]
fn the_ratchet_detects_a_serial_mutation_without_env_lock() {
let offender = concat!(
"#[test]\n#[serial(dotenv_credential_env)]\n",
"fn t() { unsafe { std::env::set_var(\"TRUSTY_X\", \"1\") } }\n",
);
assert_eq!(classify(Some(offender)), Verdict::Offender);
let compliant = concat!(
"#[test]\n#[serial(dotenv_credential_env)]\n",
"fn t() {\n let _g = crate::data_dir::ENV_LOCK.lock().unwrap();\n",
" unsafe { std::env::set_var(\"TRUSTY_X\", \"1\") }\n}\n",
);
assert_eq!(classify(Some(compliant)), Verdict::Compliant);
let prose_only = concat!(
"// This test would need ENV_LOCK if it mutated anything shared.\n",
"#[test]\n#[serial]\nfn t() { unsafe { std::env::remove_var(\"TRUSTY_X\") } }\n",
);
assert_eq!(
classify(Some(prose_only)),
Verdict::Offender,
"a comment naming ENV_LOCK is documentation, not the lock"
);
let guard_wrapper = "#[test]\n#[serial]\nfn t() { let _g = EnvVarGuard::remove(\"K\"); }\n";
assert_eq!(
classify(Some(guard_wrapper)),
Verdict::Offender,
"the crate's RAII env guard is an environment mutation"
);
let no_env = "#[test]\n#[serial]\nfn t() { assert!(true); }\n";
assert_eq!(classify(Some(no_env)), Verdict::OutOfScope);
let no_serial = "#[test]\nfn t() { unsafe { std::env::set_var(\"TRUSTY_X\", \"1\") } }\n";
assert_eq!(
classify(Some(no_serial)),
Verdict::OutOfScope,
"#7253 is about reaching for the wrong lock; reaching for none is a different defect"
);
}
#[test]
fn an_unreadable_source_counts_as_an_offender() {
assert_eq!(classify(None), Verdict::Offender);
}
#[test]
fn the_stripper_removes_prose_and_string_literals() {
let sample = concat!(
"let url = \"http://127.0.0.1/health\"; set_var(\"A\", \"b\");\n",
"/* outer /* inner mentions ENV_LOCK */ still comment */ let q = '\"';\n",
"let raw = r#\"#[serial] set_var(\"#; let keep: &'static str = \"x\";\n",
"// trailing prose about #[serial] and ENV_LOCK\n",
);
let code = strip_noncode(sample);
assert!(
contains_call(&code, "set_var"),
"the real call after the URL literal must survive:\n{code}"
);
assert!(
!code.contains(ENV_LOCK_IDENT),
"every ENV_LOCK here is prose or a literal:\n{code}"
);
assert!(
!uses_serial_attribute(&code),
"the only #[serial] spellings here are inside a raw string and a comment:\n{code}"
);
assert!(
code.contains("'static"),
"a lifetime is code, not a char literal:\n{code}"
);
}
#[test]
fn the_scanner_survives_every_real_source() {
for path in lib_target_sources() {
let text = std::fs::read_to_string(&path).ok();
let _ = classify(text.as_deref());
}
}