#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[tokio::test]
async fn test_rust_borrow_checker_creation() {
let checker = RustBorrowChecker::new();
assert!(checker.is_ok());
}
#[tokio::test]
async fn test_rust_borrow_checker_collect() {
let temp_dir = TempDir::new().unwrap();
let rust_file = temp_dir.path().join("test.rs");
std::fs::write(
&rust_file,
r#"
fn safe_function() {
let x = 42;
println!("{}", x);
}
"#,
)
.unwrap();
let checker = RustBorrowChecker::default();
let cache = Arc::new(RwLock::new(ProofCache::new()));
let symbol_table = Arc::new(SymbolTable::new());
let result = checker
.collect(temp_dir.path(), &cache, &symbol_table)
.await;
assert!(result.is_ok());
let collection_result = result.unwrap();
assert_eq!(collection_result.metrics.files_processed, 1);
assert!(!collection_result.annotations.is_empty());
}
fn seed_location() -> Location {
Location::new(std::path::PathBuf::from("src/seed.rs"), 0, 100)
}
#[cfg(feature = "rust-ast")]
#[tokio::test]
async fn test_annotation_spans_are_measured_not_the_constant_0_to_100() {
let temp_dir = TempDir::new().unwrap();
let rust_file = temp_dir.path().join("spans.rs");
let source = concat!(
"fn alpha() {\n",
" let x = 1;\n",
"}\n",
"\n",
"fn beta() {\n",
" let y = 2;\n",
"}\n",
);
std::fs::write(&rust_file, source).unwrap();
let checker = RustBorrowChecker::default();
let cache = Arc::new(RwLock::new(ProofCache::new()));
let symbol_table = Arc::new(SymbolTable::new());
let result = checker
.collect(temp_dir.path(), &cache, &symbol_table)
.await
.unwrap();
let spans: std::collections::BTreeSet<(u32, u32)> = result
.annotations
.iter()
.map(|(loc, _)| (loc.span.start.0, loc.span.end.0))
.collect();
assert!(
!spans.contains(&(0, 100)),
"the placeholder span 0..100 must not survive; got {spans:?}"
);
assert_eq!(
spans.len(),
2,
"two functions must produce two distinct spans, got {spans:?}"
);
for (start, end) in spans {
let slice = &source[start as usize..end as usize];
assert!(
slice.starts_with("fn "),
"span {start}..{end} does not start at a fn: {slice:?}"
);
assert!(
slice.ends_with('}'),
"span {start}..{end} does not end at the closing brace: {slice:?}"
);
}
}
#[cfg(feature = "rust-ast")]
#[tokio::test]
async fn test_each_safe_function_yields_its_own_annotation() {
let temp_dir = TempDir::new().unwrap();
std::fs::write(
temp_dir.path().join("many.rs"),
"fn a() {}\nfn b() {}\nfn c() {}\nfn d() {}\n",
)
.unwrap();
let checker = RustBorrowChecker::default();
let cache = Arc::new(RwLock::new(ProofCache::new()));
let symbol_table = Arc::new(SymbolTable::new());
let result = checker
.collect(temp_dir.path(), &cache, &symbol_table)
.await
.unwrap();
let distinct: std::collections::BTreeSet<(u32, u32)> = result
.annotations
.iter()
.map(|(loc, _)| (loc.span.start.0, loc.span.end.0))
.collect();
assert_eq!(
distinct.len(),
4,
"four functions must have four distinct locations, got {distinct:?}"
);
}
#[test]
fn test_memory_safety_annotation() {
let checker = RustBorrowChecker::default();
let annotation = checker.memory_safety_annotation(&seed_location());
assert_eq!(annotation.property_proven, PropertyType::MemorySafety);
assert_eq!(annotation.method, VerificationMethod::BorrowChecker);
assert_eq!(annotation.confidence_level, ConfidenceLevel::Medium);
assert!(
!annotation.assumptions.is_empty(),
"a claim that rests on assumptions cannot be a machine-checked proof"
);
assert_eq!(annotation.tool_name, "pmat-syn-static-analysis");
assert!(
!annotation.tool_name.contains("rustc"),
"must not attribute findings to a compiler pmat never ran"
);
}
#[test]
fn test_thread_safety_annotation() {
let checker = RustBorrowChecker::default();
let annotation = checker.create_thread_safety_annotation(&seed_location());
assert_eq!(annotation.property_proven, PropertyType::ThreadSafety);
assert_eq!(annotation.method, VerificationMethod::BorrowChecker);
assert_eq!(annotation.confidence_level, ConfidenceLevel::Low);
}
#[test]
fn annotation_ids_are_derived_from_content_not_random() {
let checker = RustBorrowChecker::default();
let here = seed_location();
let elsewhere = Location::new(std::path::PathBuf::from("src/other.rs"), 0, 100);
let a = checker.memory_safety_annotation(&here);
for _ in 0..5 {
assert_eq!(
checker.memory_safety_annotation(&here).annotation_id,
a.annotation_id,
"identical input must produce an identical annotationId"
);
}
assert_ne!(
checker.memory_safety_annotation(&elsewhere).annotation_id,
a.annotation_id,
"a different file must not collide"
);
assert_ne!(
checker
.create_thread_safety_annotation(&here)
.annotation_id,
a.annotation_id,
"a different property at the same site must not collide"
);
assert_ne!(
checker.const_fn_termination(&here).annotation_id,
a.annotation_id,
"a different property at the same site must not collide"
);
}
#[test]
fn confidence_is_not_one_hardcoded_level_for_every_annotation() {
let checker = RustBorrowChecker::default();
let loc = seed_location();
let levels = [
checker.memory_safety_annotation(&loc).confidence_level,
checker.create_thread_safety_annotation(&loc).confidence_level,
checker.const_fn_termination(&loc).confidence_level,
];
assert!(
!levels.contains(&ConfidenceLevel::High),
"pmat never runs a checker, so it cannot claim a machine-checkable proof: {levels:?}"
);
let distinct: std::collections::BTreeSet<_> =
levels.iter().map(|l| format!("{l:?}")).collect();
assert!(
distinct.len() > 1,
"confidence must follow the evidence, not be one constant: {levels:?}"
);
}
}