Files
yvgude--lean-ctx/rust/tests/lock_ordering_check.rs
T
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

147 lines
5.1 KiB
Rust

//! CI check: every `static ... Mutex` / `static ... RwLock` in `src/` must be
//! referenced in `LOCK_ORDERING.md`. Prevents undocumented locks from accumulating.
use std::collections::HashSet;
use std::path::Path;
fn collect_lock_files_from_source(src_dir: &Path) -> Vec<(String, String)> {
let mut results = Vec::new();
visit_dir(src_dir, &mut results);
results
}
fn visit_dir(dir: &Path, results: &mut Vec<(String, String)>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
visit_dir(&path, results);
} else if path.extension().is_some_and(|e| e == "rs") {
check_file(&path, results);
}
}
}
fn check_file(path: &Path, results: &mut Vec<(String, String)>) {
let Ok(content) = std::fs::read_to_string(path) else {
return;
};
let rel = path
.strip_prefix(env!("CARGO_MANIFEST_DIR"))
.unwrap_or(path);
// Test-only serialization locks (e.g. `static TEST_LOCK: Mutex<()>`) live inside
// inline `#[cfg(test)] mod tests { ... }` blocks and are not part of the
// production lock-ordering graph, so they must not require documentation. We
// track brace depth to skip such blocks. An external `#[cfg(test)] mod tests;`
// (semicolon, no body) does NOT open a skip scope, so production locks declared
// after it (common in large modules) are still checked.
let mut depth: i32 = 0;
let mut in_test = false;
let mut test_depth: i32 = 0;
let mut armed = false; // saw `#[cfg(test)]`, awaiting the gated item's `{`
for (i, line) in content.lines().enumerate() {
let trimmed = line.trim();
if !in_test && trimmed.contains("#[cfg(test)]") {
armed = true;
}
let opens = line.matches('{').count() as i32;
let closes = line.matches('}').count() as i32;
// Whether THIS line is the item a preceding `#[cfg(test)]` gates: a
// `#[cfg(test)] static LOCK: Mutex<…>` is test-only by definition and
// must not require documentation, exactly like locks inside
// `#[cfg(test)] mod tests { … }` blocks.
let gated_by_cfg_test = armed;
if armed {
if opens > 0 {
// The gated item has a body — enter the test scope.
in_test = true;
test_depth = depth;
armed = false;
} else if trimmed.contains(';') {
// Gated item without a body (external `mod x;`, `use ...;`,
// a gated `static …;`) — no scope.
armed = false;
}
}
let is_static_decl =
!(trimmed.starts_with("//") || trimmed.starts_with("///") || trimmed.starts_with("#["))
&& (trimmed.starts_with("static ") || trimmed.starts_with("pub static "))
&& (trimmed.contains("Mutex<") || trimmed.contains("RwLock<"));
if is_static_decl && !in_test && !gated_by_cfg_test {
let lock_name = extract_lock_name(trimmed);
let location = format!("{}:{}", rel.display(), i + 1);
results.push((lock_name, location));
}
depth += opens - closes;
if in_test && depth <= test_depth {
in_test = false;
}
}
}
fn extract_lock_name(line: &str) -> String {
let line = line.trim_start_matches("pub ");
if let Some(rest) = line.strip_prefix("static ")
&& let Some(colon) = rest.find(':')
{
return rest[..colon].trim().to_string();
}
for part in line.split_whitespace() {
if part.chars().all(|c| c.is_uppercase() || c == '_') && part.len() > 1 {
return part.trim_end_matches(':').to_string();
}
}
line.chars().take(60).collect()
}
fn load_lock_ordering_doc() -> String {
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
let doc_path = manifest.join("LOCK_ORDERING.md");
std::fs::read_to_string(&doc_path)
.unwrap_or_else(|e| panic!("Cannot read {}: {e}", doc_path.display()))
}
#[test]
fn all_static_locks_documented() {
let src_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let locks = collect_lock_files_from_source(&src_dir);
let doc = load_lock_ordering_doc();
let doc_referenced: HashSet<&str> = doc
.lines()
.filter(|l| l.contains('|') && (l.contains("Mutex") || l.contains("RwLock")))
.flat_map(|l| l.split('|'))
.map(|s| s.trim().trim_start_matches('`').trim_end_matches('`'))
.filter(|s| !s.is_empty())
.collect();
let mut undocumented = Vec::new();
for (name, location) in &locks {
let name_clean = name.trim();
let is_documented = doc_referenced
.iter()
.any(|entry| entry.contains(name_clean) || doc.contains(&location.replace("src/", "")));
if !is_documented {
undocumented.push(format!(" {name_clean} at {location}"));
}
}
assert!(
undocumented.is_empty(),
"Undocumented static Mutex/RwLock found! Add them to LOCK_ORDERING.md:\n{}",
undocumented.join("\n")
);
}