use std::path::Path;
use proptest::prelude::*;
use super::parse_file;
use crate::analysis::{Language, test_support::load_fixture};
#[test]
fn test_parse_functions() {
let source = load_fixture("rust/parser/parse_functions.txt");
let parsed = parse_file(Path::new("src/main.rs"), &source)
.expect("rust source should parse successfully");
assert_eq!(parsed.language, Language::Rust);
assert_eq!(parsed.package_name.as_deref(), Some("main"));
assert!(!parsed.syntax_error);
assert_eq!(parsed.functions.len(), 2);
assert_eq!(parsed.functions[0].fingerprint.name, "sum_pair");
assert_eq!(parsed.functions[1].fingerprint.kind, "method");
assert_eq!(
parsed.functions[1].fingerprint.receiver_type.as_deref(),
Some("Runner")
);
assert!(!parsed.functions[0].is_test_function);
assert_eq!(parsed.symbols.len(), 2);
}
#[test]
fn test_extract_evidence() {
let source = load_fixture("rust/parser/extract_evidence.txt");
let parsed = parse_file(Path::new("src/lib.rs"), &source)
.expect("rust source should parse successfully");
assert_eq!(parsed.imports.len(), 3);
assert!(
parsed
.imports
.iter()
.any(|import| { import.alias == "fmt" && import.path.contains("std::fmt") })
);
assert!(parsed.imports.iter().any(|import| {
import.alias == "FmtDisplay"
&& import.path == "std::fmt::Display"
&& import.namespace_path.as_deref() == Some("std::fmt")
&& import.imported_name.as_deref() == Some("Display")
}));
assert!(
parsed
.imports
.iter()
.any(|import| { import.alias == "*" && import.path.contains("crate::config") })
);
assert_eq!(parsed.pkg_strings.len(), 1);
assert_eq!(parsed.pkg_strings[0].name, "API_TOKEN");
assert!(parsed.symbols.iter().any(|symbol| {
symbol.name == "Runner" && matches!(symbol.kind, crate::model::SymbolKind::Struct)
}));
assert!(parsed.symbols.iter().any(|symbol| {
symbol.name == "Mode" && matches!(symbol.kind, crate::model::SymbolKind::Type)
}));
assert!(parsed.symbols.iter().any(|symbol| {
symbol.name == "Render" && matches!(symbol.kind, crate::model::SymbolKind::Interface)
}));
assert!(parsed.symbols.iter().any(|symbol| {
symbol.name == "Output" && matches!(symbol.kind, crate::model::SymbolKind::Type)
}));
let execute = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "execute")
.expect("execute should be parsed");
assert!(!execute.is_test_function);
assert!(
execute
.local_binding_names
.iter()
.any(|name| name == "self")
);
assert_eq!(execute.local_strings.len(), 1);
assert_eq!(execute.local_strings[0].name, "password");
assert!(
execute
.local_binding_names
.iter()
.any(|name| name == "password")
);
assert!(execute.calls.iter().any(|call| call.name == "dbg!"));
assert!(execute.calls.iter().any(|call| call.name == "todo!"));
assert!(
execute
.calls
.iter()
.any(|call| { call.receiver.as_deref() == Some("value") && call.name == "unwrap" })
);
assert!(execute.rust_evidence().safety_comment_lines.is_empty());
assert_eq!(execute.rust_evidence().unsafe_lines.len(), 2);
let test_fn = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "detects_test_only_code")
.expect("test function should be parsed");
assert!(test_fn.is_test_function);
}
#[test]
fn test_collects_advanceplan2_rust_summaries() {
let source = load_fixture("rust/parser/advanceplan2_summaries.txt");
let parsed = parse_file(Path::new("src/lib.rs"), &source)
.expect("rust source should parse successfully");
let config = parsed
.structs()
.iter()
.find(|summary| summary.name == "RequestConfig")
.expect("RequestConfig should be summarized");
assert!(
config
.attributes
.iter()
.any(|attribute| attribute.contains("deny_unknown_fields"))
);
assert!(
config.fields[0]
.attributes
.iter()
.any(|attribute| attribute.contains("serde(default)"))
);
assert!(
config.fields[1]
.attributes
.iter()
.any(|attribute| attribute.contains("serde(flatten)"))
);
assert_eq!(parsed.rust_enums().len(), 1);
assert_eq!(parsed.rust_enums()[0].name, "WireValue");
assert_eq!(parsed.rust_enums()[0].variant_count, 2);
assert!(
parsed.rust_enums()[0]
.attributes
.iter()
.any(|attribute| attribute.contains("untagged"))
);
assert_eq!(parsed.rust_statics().len(), 1);
assert_eq!(parsed.rust_statics()[0].name, "CACHE");
assert!(parsed.rust_statics()[0].type_text.contains("OnceLock"));
}
proptest! {
#[test]
fn parses_valid_function_names(name in "[a-z][a-z0-9_]{0,12}") {
let template = load_fixture("rust/parser/valid_function_template.txt");
let source = template.replace("__FUNCTION_NAME__", &name);
let parsed = parse_file(Path::new("src/lib.rs"), &source)
.expect("generated Rust source should parse successfully");
prop_assert_eq!(parsed.functions.len(), 1);
prop_assert_eq!(&parsed.functions[0].fingerprint.name, &name);
prop_assert!(!parsed.syntax_error);
}
}
#[test]
fn test_syntax_error() {
let source = load_fixture("rust/parser/syntax_error.txt");
let parsed = parse_file(Path::new("src/lib.rs"), &source)
.expect("tree-sitter should recover from syntax errors");
assert!(parsed.syntax_error);
assert_eq!(parsed.functions.len(), 1);
}