use decy_llm::{AnalysisContext, ContextBuilder};
#[test]
fn test_create_context_builder() {
let builder = ContextBuilder::new();
let context = builder.build();
assert!(context.functions.is_empty(), "Empty builder should have no functions");
}
#[test]
fn test_add_function_to_context() {
let mut builder = ContextBuilder::new();
builder.add_function("process", "void process(int* data, size_t len)");
let context = builder.build();
assert_eq!(context.functions.len(), 1);
assert_eq!(context.functions[0].name, "process");
assert_eq!(context.functions[0].c_signature, "void process(int* data, size_t len)");
}
#[test]
fn test_add_ownership_inference() {
let mut builder = ContextBuilder::new();
builder
.add_function("transfer", "void transfer(int* dest, int* src)")
.add_ownership("transfer", "dest", "mutable_borrow", 0.95, "Mutated via assignment")
.add_ownership("transfer", "src", "immutable_borrow", 0.9, "Read-only access");
let context = builder.build();
let func = &context.functions[0];
assert!(func.ownership.contains_key("dest"));
assert!(func.ownership.contains_key("src"));
let dest_ownership = &func.ownership["dest"];
assert_eq!(dest_ownership.kind, "mutable_borrow");
assert!((dest_ownership.confidence - 0.95).abs() < 0.01);
let src_ownership = &func.ownership["src"];
assert_eq!(src_ownership.kind, "immutable_borrow");
}
#[test]
fn test_add_lifetime_info() {
let mut builder = ContextBuilder::new();
builder.add_function("create", "int* create()").add_lifetime("create", "result", 0, true);
let context = builder.build();
let func = &context.functions[0];
assert_eq!(func.lifetimes.len(), 1);
assert_eq!(func.lifetimes[0].variable, "result");
assert_eq!(func.lifetimes[0].scope_depth, 0);
assert!(func.lifetimes[0].escapes);
}
#[test]
fn test_add_lock_mapping() {
let mut builder = ContextBuilder::new();
builder.add_function("sync_update", "void sync_update()").add_lock_mapping(
"sync_update",
"counter_mutex",
vec!["counter".to_string(), "total".to_string()],
);
let context = builder.build();
let func = &context.functions[0];
assert!(func.lock_mappings.contains_key("counter_mutex"));
let protected = &func.lock_mappings["counter_mutex"];
assert!(protected.contains(&"counter".to_string()));
assert!(protected.contains(&"total".to_string()));
}
#[test]
fn test_serialize_to_json() {
let mut builder = ContextBuilder::new();
builder.add_function("example", "int example(int* ptr)").add_ownership(
"example",
"ptr",
"owning",
0.85,
"Allocated via malloc",
);
let json = builder.to_json().expect("JSON serialization failed");
let parsed: serde_json::Value = serde_json::from_str(&json).expect("Invalid JSON");
assert!(parsed["functions"].is_array());
assert_eq!(parsed["functions"][0]["name"], "example");
assert_eq!(parsed["functions"][0]["ownership"]["ptr"]["kind"], "owning");
}
#[test]
fn test_multiple_functions() {
let mut builder = ContextBuilder::new();
builder
.add_function("init", "void init()")
.add_function("cleanup", "void cleanup()")
.add_function("process", "int process(int* data)");
let context = builder.build();
assert_eq!(context.functions.len(), 3);
let names: Vec<&str> = context.functions.iter().map(|f| f.name.as_str()).collect();
assert!(names.contains(&"init"));
assert!(names.contains(&"cleanup"));
assert!(names.contains(&"process"));
}
#[test]
fn test_complex_function_context() {
let mut builder = ContextBuilder::new();
builder
.add_function("thread_safe_update", "void thread_safe_update(Counter* c)")
.add_ownership("thread_safe_update", "c", "mutable_borrow", 0.9, "Modified under lock")
.add_lifetime("thread_safe_update", "c", 0, false)
.add_lock_mapping("thread_safe_update", "mutex", vec!["c".to_string()]);
let context = builder.build();
let func = &context.functions[0];
assert!(!func.ownership.is_empty());
assert!(!func.lifetimes.is_empty());
assert!(!func.lock_mappings.is_empty());
}
#[test]
fn test_json_schema_structure() {
let mut builder = ContextBuilder::new();
builder
.add_function("test_fn", "void test_fn(int* ptr)")
.add_ownership("test_fn", "ptr", "immutable_borrow", 0.8, "Read only")
.add_lifetime("test_fn", "ptr", 1, false)
.add_lock_mapping("test_fn", "lock", vec!["data".to_string()]);
let json = builder.to_json().expect("Serialization failed");
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
let func = &parsed["functions"][0];
assert!(func["name"].is_string());
assert!(func["c_signature"].is_string());
assert!(func["ownership"].is_object());
assert!(func["lifetimes"].is_array());
assert!(func["lock_mappings"].is_object());
let ownership = &func["ownership"]["ptr"];
assert!(ownership["kind"].is_string());
assert!(ownership["confidence"].is_number());
assert!(ownership["reason"].is_string());
let lifetime = &func["lifetimes"][0];
assert!(lifetime["variable"].is_string());
assert!(lifetime["scope_depth"].is_number());
assert!(lifetime["escapes"].is_boolean());
}
#[test]
fn test_json_roundtrip() {
let mut builder = ContextBuilder::new();
builder
.add_function("roundtrip", "int roundtrip(int* x, int* y)")
.add_ownership("roundtrip", "x", "mutable_borrow", 0.95, "Modified")
.add_ownership("roundtrip", "y", "immutable_borrow", 0.88, "Read only")
.add_lifetime("roundtrip", "x", 0, false)
.add_lifetime("roundtrip", "y", 0, false);
let json = builder.to_json().expect("Serialization failed");
let deserialized: AnalysisContext =
serde_json::from_str(&json).expect("Deserialization failed");
assert_eq!(deserialized.functions.len(), 1);
assert_eq!(deserialized.functions[0].name, "roundtrip");
assert_eq!(deserialized.functions[0].ownership.len(), 2);
assert_eq!(deserialized.functions[0].lifetimes.len(), 2);
}
#[test]
fn test_add_ownership_nonexistent_function() {
let mut builder = ContextBuilder::new();
builder.add_function("real_func", "void real_func()");
builder.add_ownership("nonexistent", "ptr", "owning", 0.9, "Should be ignored");
let context = builder.build();
assert_eq!(context.functions.len(), 1);
assert!(context.functions[0].ownership.is_empty());
}
#[test]
fn test_add_lifetime_nonexistent_function() {
let mut builder = ContextBuilder::new();
builder.add_function("real_func", "void real_func()");
builder.add_lifetime("nonexistent", "ptr", 0, true);
let context = builder.build();
assert_eq!(context.functions.len(), 1);
assert!(context.functions[0].lifetimes.is_empty());
}
#[test]
fn test_add_lock_mapping_nonexistent_function() {
let mut builder = ContextBuilder::new();
builder.add_function("real_func", "void real_func()");
builder.add_lock_mapping("nonexistent", "mutex", vec!["data".to_string()]);
let context = builder.build();
assert_eq!(context.functions.len(), 1);
assert!(context.functions[0].lock_mappings.is_empty());
}
#[test]
fn test_add_ownership_overwrites_same_variable() {
let mut builder = ContextBuilder::new();
builder
.add_function("func", "void func(int* ptr)")
.add_ownership("func", "ptr", "owning", 0.5, "First inference")
.add_ownership("func", "ptr", "mutable_borrow", 0.95, "Refined inference");
let context = builder.build();
let func = &context.functions[0];
assert_eq!(func.ownership.len(), 1);
assert_eq!(func.ownership["ptr"].kind, "mutable_borrow");
assert!((func.ownership["ptr"].confidence - 0.95).abs() < 0.01);
}
#[test]
fn test_add_multiple_lifetimes_same_variable() {
let mut builder = ContextBuilder::new();
builder
.add_function("func", "void func(int* ptr)")
.add_lifetime("func", "ptr", 0, false)
.add_lifetime("func", "ptr", 1, true);
let context = builder.build();
let func = &context.functions[0];
assert_eq!(func.lifetimes.len(), 2);
assert_eq!(func.lifetimes[0].scope_depth, 0);
assert!(!func.lifetimes[0].escapes);
assert_eq!(func.lifetimes[1].scope_depth, 1);
assert!(func.lifetimes[1].escapes);
}
#[test]
fn test_builder_chaining() {
let mut builder = ContextBuilder::new();
let result = builder
.add_function("f1", "void f1()")
.add_function("f2", "void f2()")
.add_ownership("f1", "x", "owning", 0.9, "test")
.add_lifetime("f1", "x", 0, false)
.add_lock_mapping("f2", "mtx", vec!["data".to_string()])
.build();
assert_eq!(result.functions.len(), 2);
assert!(!result.functions[0].ownership.is_empty());
assert!(!result.functions[0].lifetimes.is_empty());
assert!(!result.functions[1].lock_mappings.is_empty());
}