use std::path::{Path, PathBuf};
const SERIAL_WITHOUT_ENV_LOCK: &[(&str, &str)] = &[
("src/bm25/tests.rs", "TRUSTY_BM25_CORPUS_CAP"),
("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 PRIVATE_ENV_MUTEXES: &[(&str, &str, &str)] = &[
(
"src/embedder/test_env.rs",
"ENV_LOCK",
"embedder-tree lock over TRUSTY_EMBEDDER_*, TRUSTY_DEVICE and FASTEMBED_*. Its tests \
hold it across ONNX loads and #4940's poison test poisons it on purpose, so moving \
them onto the crate-wide lock is its own change. Its one HOME reader \
(`resolve_fastembed_cache_dir_prefers_env_vars`) takes the crate-wide lock first",
),
(
"src/error_capture/mod.rs",
"BUG_CAPTURE_ENV_TEST_LOCK",
"`pub` (doc-hidden) lock over TRUSTY_NO_BUG_CAPTURE; retiring it changes the public \
surface. Guards no HOME access",
),
(
"src/inference/bedrock/tests.rs",
"REGION_ENV_LOCK",
"tokio mutex over the AWS region variables, held across `.await`s a std mutex cannot \
span. Guards no HOME access",
),
];
const SELF_BASENAME: &str = "env_lock_ratchet_tests.rs";
const SHARED_LOCK_PATH: &str = "crate::data_dir::ENV_LOCK";
const SHARED_LOCK_NAME: &str = "ENV_LOCK";
const SHARED_LOCK_MODULE: &str = "crate::data_dir::";
const SHARED_LOCK_OWNER: &str = "src/data_dir.rs";
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 takes_shared_lock(&code) && private_env_mutexes(&code).is_empty() {
Verdict::Compliant
} else {
Verdict::Offender
}
}
fn takes_shared_lock(code: &str) -> bool {
if code.contains(SHARED_LOCK_PATH) {
return true;
}
let group_open = format!("use {SHARED_LOCK_MODULE}{{");
let mut from = 0;
while let Some(rel) = code[from..].find(&group_open) {
let body_start = from + rel + group_open.len();
let Some(len) = code[body_start..].find('}') else {
return false;
};
let body = &code[body_start..body_start + len];
if body
.split(|c: char| !(c.is_alphanumeric() || c == '_'))
.any(|tok| tok == SHARED_LOCK_NAME)
{
return true;
}
from = body_start + len;
}
false
}
fn private_env_mutexes(code: &str) -> Vec<String> {
let writes_env = mutates_env(code);
let mut found = Vec::new();
let mut from = 0;
while let Some(rel) = code[from..].find("static ") {
let at = from + rel;
from = at + "static ".len();
let preceded_by_word = code[..at]
.chars()
.next_back()
.is_some_and(|c| c.is_alphanumeric() || c == '_' || c == '\'');
if preceded_by_word {
continue;
}
let rest = code[from..].trim_start();
let rest = rest.strip_prefix("mut ").unwrap_or(rest).trim_start();
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
let Some(ty_rest) = rest[name.len()..].trim_start().strip_prefix(':') else {
continue;
};
let ty_end = ty_rest.find(['=', ';']).unwrap_or(ty_rest.len());
let ty: String = ty_rest[..ty_end].split_whitespace().collect();
if !(ty.contains("Mutex") || ty.contains("RwLock")) {
continue;
}
if name.contains("ENV") || (writes_env && ty.contains("<()>")) {
found.push(name);
}
}
found
}
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 rel == SHARED_LOCK_OWNER
|| 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 `crate::data_dir::ENV_LOCK` by that path, or they declare an env mutex of their \
own (#7253, #5937). `#[serial]`, a private mutex and `ENV_LOCK` are different locks \
and exclude nothing of each other, so such a test still runs inside another test's \
`setenv`. Take `crate::data_dir::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 lib_target_declares_no_private_env_mutex() {
let mut unlisted: Vec<String> = Vec::new();
let mut matched: Vec<(&str, &str)> = Vec::new();
for path in lib_target_sources() {
let rel = relative(&path);
if rel == SHARED_LOCK_OWNER {
continue;
}
let Ok(text) = std::fs::read_to_string(&path) else {
unlisted.push(format!("{rel}: unreadable"));
continue;
};
for name in private_env_mutexes(&strip_noncode(&text)) {
match PRIVATE_ENV_MUTEXES
.iter()
.find(|(suffix, listed, _)| rel.ends_with(suffix) && *listed == name)
{
Some((suffix, listed, _)) => matched.push((suffix, listed)),
None => unlisted.push(format!("{rel}: static {name}")),
}
}
}
let stale: Vec<String> = PRIVATE_ENV_MUTEXES
.iter()
.filter(|(suffix, name, _)| !matched.contains(&(*suffix, *name)))
.map(|(suffix, name, _)| format!("{suffix}: static {name}"))
.collect();
assert!(
unlisted.is_empty(),
"these lib-target files declare an env mutex of their own (#5937). A private mutex \
excludes only the tests that take it, so the variable it guards still races every \
test holding `crate::data_dir::ENV_LOCK` or nothing. Delete it and take \
`crate::data_dir::ENV_LOCK`; if a file genuinely cannot, add it to \
`PRIVATE_ENV_MUTEXES` in this file with the reason.\n {}",
unlisted.join("\n ")
);
assert!(
stale.is_empty(),
"`PRIVATE_ENV_MUTEXES` is stale — these rows name declarations that are gone. Delete \
the rows so the ratchet keeps its grip.\n {}",
stale.join("\n ")
);
}
#[test]
fn the_ratchet_requires_the_shared_lock_path() {
let body = concat!(
"#[test]\n#[serial(g)]\n",
"fn t() { let _g = ENV_LOCK.lock(); unsafe { std::env::set_var(\"HOME\", \"/x\") } }\n",
);
let file_private = format!("static ENV_LOCK: Mutex<()> = Mutex::new(());\n{body}");
assert_eq!(
classify(Some(&file_private)),
Verdict::Offender,
"a file-private ENV_LOCK is not the shared lock"
);
let imported_elsewhere = format!("use super::test_env::{{EnvVarGuard, ENV_LOCK}};\n{body}");
assert_eq!(
classify(Some(&imported_elsewhere)),
Verdict::Offender,
"an ENV_LOCK imported from another module is not the shared lock"
);
let longer_name = concat!(
"#[test]\n#[serial]\n",
"fn t() { let _g = REGION_ENV_LOCK.lock(); unsafe { std::env::remove_var(\"K\") } }\n",
);
assert_eq!(
classify(Some(longer_name)),
Verdict::Offender,
"REGION_ENV_LOCK merely contains the name"
);
let use_group = format!("use crate::data_dir::{{DATA_DIR_OVERRIDE_ENV, ENV_LOCK}};\n{body}");
assert_eq!(classify(Some(&use_group)), Verdict::Compliant);
let use_single = format!("use crate::data_dir::ENV_LOCK;\n{body}");
assert_eq!(classify(Some(&use_single)), Verdict::Compliant);
let both = format!(
"static ENV_LOCK: Mutex<()> = Mutex::new(());\n\
fn f() {{ let _ = crate::data_dir::ENV_LOCK.lock(); }}\n{body}"
);
assert_eq!(
classify(Some(&both)),
Verdict::Offender,
"naming the shared lock does not excuse declaring a private one"
);
}
#[test]
fn the_ratchet_flags_a_private_env_mutex() {
let scan = |s: &str| private_env_mutexes(&strip_noncode(s));
assert_eq!(
scan("static ENV_LOCK: Mutex<()> = Mutex::new(());"),
["ENV_LOCK"]
);
assert_eq!(
scan("pub static BUG_ENV_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());"),
["BUG_ENV_TEST_LOCK"]
);
assert_eq!(
scan("static REGION_ENV_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());"),
["REGION_ENV_LOCK"]
);
let unnamed_lock = concat!(
"fn l() { static LOCK: OnceLock<Mutex<()>> = OnceLock::new(); }\n",
"fn w() { unsafe { std::env::set_var(\"K\", \"v\") } }\n",
);
assert_eq!(
scan(unnamed_lock),
["LOCK"],
"a unit lock in a file that writes env is an env lock, whatever its name"
);
assert!(
scan("fn l() { static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); }")
.is_empty(),
"a unit lock in a file that writes no env guards something else"
);
let cache = concat!(
"static CACHE: OnceLock<Mutex<HashMap<PathBuf, Weak<S>>>> = OnceLock::new();\n",
"fn w() { unsafe { std::env::set_var(\"K\", \"v\") } }\n",
);
assert!(scan(cache).is_empty(), "a data cache is not an env lock");
assert!(
scan("fn f(g: &'static Mutex<()>) { unsafe { std::env::set_var(\"K\", \"v\") } }")
.is_empty(),
"a `'static` lifetime is not a declaration"
);
assert!(
scan("// static ENV_LOCK: Mutex<()> = Mutex::new(());").is_empty(),
"a commented-out declaration is prose"
);
}
#[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(SHARED_LOCK_NAME),
"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());
}
}