use std::path::{Path, PathBuf};
const WRITERS_WITHOUT_ENV_TEST_LOCK: &[(&str, &str)] = &[
("src/chat/tools_tests.rs", SKIP_ENFORCEMENT_1),
("src/commands/kg_rebuild.rs", SKIP_ENFORCEMENT_1),
("src/commands/kg_twin_merge.rs", SKIP_ENFORCEMENT_1),
("src/console_metrics/mod.rs", SKIP_ENFORCEMENT_1),
("src/service/core_kg_tests.rs", SKIP_ENFORCEMENT_1),
("src/service/core_tests.rs", SKIP_ENFORCEMENT_1),
("src/test_daemon.rs", SKIP_ENFORCEMENT_1),
("src/tools/tests.rs", SKIP_ENFORCEMENT_1),
("src/transport/methods/health_tests.rs", SKIP_ENFORCEMENT_1),
(
"src/startup_task_tests.rs",
"HOME and TRUSTY_SKIP_PALACE_ENFORCEMENT; PR #8791 (fix/5937-pin-map-test-hang) \
rewrites this file and removes this row",
),
];
const SKIP_ENFORCEMENT_1: &str =
"only writes TRUSTY_SKIP_PALACE_ENFORCEMENT=\"1\", an idempotent constant";
const SELF_BASENAME: &str = "env_lock_ratchet_tests.rs";
const LOCK_OWNER: &str = "src/commands/mod.rs";
const ENV_WRITE_CALLS: &[&str] = &["set_var", "remove_var", "set_current_dir"];
fn takes_env_test_lock(code: &str) -> bool {
contains_call(code, "::env_test_lock")
}
fn is_offender(rel: &str, code: &str) -> bool {
let tests = test_code(rel, code);
writes_env(tests) && !takes_env_test_lock(tests)
}
#[test]
fn env_writing_tests_take_env_test_lock() {
let mut unlisted = Vec::new();
let mut matched = Vec::new();
for path in lib_sources() {
let rel = relative(&path);
if rel == LOCK_OWNER {
continue;
}
let offender = match std::fs::read_to_string(&path) {
Ok(text) => is_offender(&rel, &strip_noncode(&text)),
Err(_) => true,
};
if !offender {
continue;
}
match WRITERS_WITHOUT_ENV_TEST_LOCK
.iter()
.find(|(suffix, _)| rel.ends_with(suffix))
{
Some((suffix, _)) => matched.push(*suffix),
None => unlisted.push(rel),
}
}
let stale: Vec<&str> = WRITERS_WITHOUT_ENV_TEST_LOCK
.iter()
.map(|(suffix, _)| *suffix)
.filter(|suffix| !matched.contains(suffix))
.collect();
assert!(
unlisted.is_empty(),
"these files write the process environment in test code without holding \
`crate::commands::env_test_lock()` (#5937). A `#[serial]` group or a private mutex \
excludes nothing of it, so the write races every test that reads the variable under \
the lock. Hold the lock for the whole window the variable is changed, or add the file \
to `WRITERS_WITHOUT_ENV_TEST_LOCK` with the reason.\n {}",
unlisted.join("\n ")
);
assert!(
stale.is_empty(),
"`WRITERS_WITHOUT_ENV_TEST_LOCK` is stale — delete these rows:\n {}",
stale.join("\n ")
);
}
#[test]
fn the_ratchet_detects_an_unlocked_env_writer() {
let serial_only = "#[test]\n#[serial]\nfn t() { unsafe { std::env::set_var(\"K\", \"1\") } }\n";
assert!(is_offender("src/a_tests.rs", &strip_noncode(serial_only)));
let locked = concat!(
"#[test]\nfn t() { let _g = crate::commands::env_test_lock().blocking_lock();\n",
"unsafe { std::env::remove_var(\"K\") } }\n",
);
assert!(!is_offender("src/a_tests.rs", &strip_noncode(locked)));
let local_fn = concat!(
"fn env_test_lock() -> M { M }\n",
"#[test]\nfn t() { let _g = env_test_lock(); std::env::set_current_dir(\"/\").unwrap(); }\n",
);
assert!(
is_offender("src/a_tests.rs", &strip_noncode(local_fn)),
"a file-local lock of the same name is not the crate lock"
);
let prose = "// call crate::commands::env_test_lock() before set_var(\"K\")\nfn f() {}\n";
assert!(!is_offender("src/a_tests.rs", &strip_noncode(prose)));
let production = concat!(
"fn setup() { unsafe { std::env::set_var(\"K\", \"v\") } }\n",
"#[cfg(test)]\nmod tests { #[test] fn t() {} }\n",
);
assert!(
!is_offender("src/setup.rs", &strip_noncode(production)),
"a production write above `#[cfg(test)]` is not a test's"
);
}
fn lib_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 scan found only {} files — a broken walk reports a clean crate",
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("/")
}
fn test_code<'a>(rel: &str, code: &'a str) -> &'a str {
let basename = rel.rsplit('/').next().unwrap_or(rel);
if basename.contains("test") || rel.split('/').any(|seg| seg == "tests") {
return code;
}
code.find("#[cfg(test)]").map_or("", |at| &code[at..])
}
fn writes_env(code: &str) -> bool {
ENV_WRITE_CALLS.iter().any(|c| contains_call(code, c))
}
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)
}