use std::path::Path;
use super::cross_file_types::{CallSite, CallType, FuncDef, VarType, FileIR};
use super::var_types::extract_python_definitions;
use super::resolution::apply_type_resolution;
use crate::types::Language;
#[test]
fn h1a_scanner_creates_method_calls_with_receiver() {
let source = r#"
class User:
def save(self):
pass
class Order:
def calculate(self):
pass
def process():
user = User()
user.save()
order = Order()
order.calculate()
"#;
let result = extract_python_definitions(source, Path::new("test.py"));
let all_calls: Vec<&CallSite> = result.calls.values().flat_map(|v| v.iter()).collect();
let method_calls: Vec<&CallSite> = all_calls
.iter()
.filter(|c| c.call_type == CallType::Method)
.copied()
.collect();
assert!(
method_calls.len() >= 2,
"Expected at least 2 method calls, got {}. All calls: {:?}",
method_calls.len(),
all_calls
);
let with_receiver = method_calls.iter().filter(|c| c.receiver.is_some()).count();
assert_eq!(
with_receiver,
method_calls.len(),
"All method calls should have receiver field populated"
);
let with_receiver_type_at_scan = method_calls
.iter()
.filter(|c| c.receiver_type.is_some())
.count();
eprintln!(
"H1a: {}/{} method calls have receiver_type at scan time (expected 0)",
with_receiver_type_at_scan,
method_calls.len()
);
}
#[test]
fn h1b_resolution_populates_receiver_type_from_var_types() {
let source = r#"
class User:
def save(self):
pass
class Order:
def calculate(self):
pass
def process():
user = User()
user.save()
order = Order()
order.calculate()
"#;
let parse_result = extract_python_definitions(source, Path::new("test.py"));
let mut file_ir = FileIR::new("test.py".into());
file_ir.funcs = parse_result.funcs;
file_ir.classes = parse_result.classes;
file_ir.imports = parse_result.imports;
file_ir.calls = parse_result.calls;
file_ir.var_types = parse_result.var_types;
apply_type_resolution(&mut file_ir, source, Language::Python);
let all_calls: Vec<&CallSite> = file_ir.calls.values().flat_map(|v| v.iter()).collect();
let method_calls: Vec<&CallSite> = all_calls
.iter()
.filter(|c| c.call_type == CallType::Method)
.copied()
.collect();
let with_receiver_type = method_calls
.iter()
.filter(|c| c.receiver_type.is_some())
.count();
let total_method = method_calls.len();
eprintln!("H1b: {}/{} method calls have receiver_type after resolution", with_receiver_type, total_method);
for call in &method_calls {
eprintln!(
" call: {}.{} | receiver={:?} | receiver_type={:?}",
call.caller, call.target, call.receiver, call.receiver_type
);
}
assert!(
with_receiver_type > 0,
"FALSIFIED: No method calls have receiver_type after resolution. \
Feature Envy / Inappropriate Intimacy will be blind. \
Total method calls: {}, with receiver_type: {}",
total_method,
with_receiver_type
);
let ratio = with_receiver_type as f64 / total_method.max(1) as f64;
eprintln!(
"H1b: receiver_type population ratio: {:.1}% ({}/{})",
ratio * 100.0,
with_receiver_type,
total_method
);
}
#[test]
fn h1c_self_calls_get_receiver_type() {
let source = r#"
class Calculator:
def add(self, x, y):
return x + y
def compute(self):
result = self.add(1, 2)
return result
"#;
let parse_result = extract_python_definitions(source, Path::new("test.py"));
let mut file_ir = FileIR::new("test.py".into());
file_ir.funcs = parse_result.funcs;
file_ir.classes = parse_result.classes;
file_ir.imports = parse_result.imports;
file_ir.calls = parse_result.calls;
file_ir.var_types = parse_result.var_types;
apply_type_resolution(&mut file_ir, source, Language::Python);
let all_calls: Vec<&CallSite> = file_ir.calls.values().flat_map(|v| v.iter()).collect();
let self_calls: Vec<&CallSite> = all_calls
.iter()
.filter(|c| c.receiver.as_deref() == Some("self"))
.copied()
.collect();
eprintln!("H1c: Found {} self.X() calls", self_calls.len());
for call in &self_calls {
eprintln!(
" self.{} -> receiver_type={:?}",
call.target, call.receiver_type
);
}
let resolved_self = self_calls
.iter()
.filter(|c| c.receiver_type.is_some())
.count();
assert!(
resolved_self > 0,
"self.method() calls should have receiver_type set to enclosing class"
);
let calc_calls: Vec<&&CallSite> = self_calls
.iter()
.filter(|c| c.receiver_type.as_deref() == Some("Calculator"))
.collect();
eprintln!("H1c: {} self calls resolved to Calculator", calc_calls.len());
}
#[test]
fn h2_classdef_bases_populated() {
let source = r#"
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof"
class GuideDog(Dog, Animal):
def guide(self):
pass
"#;
let result = extract_python_definitions(source, Path::new("test.py"));
eprintln!("H2: Found {} classes", result.classes.len());
for class in &result.classes {
eprintln!(
" class {} | bases={:?} | methods={:?}",
class.name, class.bases, class.methods
);
}
let animal = result.classes.iter().find(|c| c.name == "Animal");
assert!(animal.is_some(), "Animal class not found");
assert!(
animal.unwrap().bases.is_empty(),
"Animal should have no bases"
);
let dog = result.classes.iter().find(|c| c.name == "Dog");
assert!(dog.is_some(), "Dog class not found");
let dog = dog.unwrap();
assert!(
dog.bases.contains(&"Animal".to_string()),
"Dog should have Animal as base. Actual bases: {:?}",
dog.bases
);
let guide_dog = result.classes.iter().find(|c| c.name == "GuideDog");
assert!(guide_dog.is_some(), "GuideDog class not found");
let guide_dog = guide_dog.unwrap();
assert!(
guide_dog.bases.contains(&"Dog".to_string()),
"GuideDog should have Dog as base"
);
assert!(
guide_dog.bases.contains(&"Animal".to_string()),
"GuideDog should have Animal as base"
);
}
#[test]
fn h2b_inheritance_report_from_file() {
use crate::inheritance::{extract_inheritance, InheritanceOptions};
use tempfile::TempDir;
let dir = TempDir::new().unwrap();
let code = r#"
class Parent:
def parent_method(self):
pass
class Child(Parent):
def child_method(self):
pass
class GrandChild(Child):
def gc_method(self):
pass
"#;
std::fs::write(dir.path().join("classes.py"), code).unwrap();
let options = InheritanceOptions::default();
let report = extract_inheritance(dir.path(), Some(Language::Python), &options).unwrap();
eprintln!("H2b: InheritanceReport nodes={}, edges={}", report.nodes.len(), report.edges.len());
for edge in &report.edges {
eprintln!(
" edge: {} -> {} (kind={:?}, resolution={:?})",
edge.child, edge.parent, edge.kind, edge.resolution
);
}
for node in &report.nodes {
eprintln!(
" node: {} | bases={:?}",
node.name, node.bases
);
}
assert_eq!(report.count, 3, "Should find 3 classes");
assert_eq!(report.nodes.len(), 3, "Should have 3 nodes");
assert!(
report.edges.len() >= 2,
"Should have at least 2 edges. Got: {}",
report.edges.len()
);
let child_parent = report
.edges
.iter()
.find(|e| e.child == "Child" && e.parent == "Parent");
assert!(
child_parent.is_some(),
"Child -> Parent edge should exist"
);
let gc_child = report
.edges
.iter()
.find(|e| e.child == "GrandChild" && e.parent == "Child");
assert!(
gc_child.is_some(),
"GrandChild -> Child edge should exist"
);
let child_node = report.nodes.iter().find(|n| n.name == "Child");
assert!(child_node.is_some(), "Child node should exist");
assert!(
child_node.unwrap().bases.contains(&"Parent".to_string()),
"Child node should have Parent in bases"
);
}
#[test]
fn h3_classdef_methods_and_funcdef_class_name() {
let source = r#"
class UserService:
def __init__(self):
self.users = []
def add_user(self, user):
self.users.append(user)
def remove_user(self, user):
self.users.remove(user)
def get_all_users(self):
return self.users
"#;
let result = extract_python_definitions(source, Path::new("test.py"));
eprintln!("H3: Found {} classes, {} functions", result.classes.len(), result.funcs.len());
let user_service = result.classes.iter().find(|c| c.name == "UserService");
assert!(user_service.is_some(), "UserService class not found");
let user_service = user_service.unwrap();
eprintln!("H3: UserService.methods = {:?}", user_service.methods);
let expected_methods = vec!["__init__", "add_user", "remove_user", "get_all_users"];
for method_name in &expected_methods {
assert!(
user_service.methods.contains(&method_name.to_string()),
"ClassDef.methods should contain '{}'. Actual: {:?}",
method_name,
user_service.methods
);
}
assert_eq!(
user_service.methods.len(),
expected_methods.len(),
"ClassDef.methods should have exactly {} methods. Actual: {:?}",
expected_methods.len(),
user_service.methods
);
let method_funcs: Vec<&FuncDef> = result
.funcs
.iter()
.filter(|f| f.class_name.as_deref() == Some("UserService"))
.collect();
eprintln!("H3: {} FuncDefs have class_name=UserService", method_funcs.len());
for func in &method_funcs {
eprintln!(
" func: {} | is_method={} | class_name={:?}",
func.name, func.is_method, func.class_name
);
}
assert_eq!(
method_funcs.len(),
expected_methods.len(),
"Should have {} FuncDefs with class_name=UserService",
expected_methods.len()
);
for func in &method_funcs {
assert!(
func.is_method,
"FuncDef '{}' should have is_method=true",
func.name
);
}
}
#[test]
fn h4a_vartype_constructor_assignments() {
let source = r#"
class User:
pass
class Order:
pass
def process():
user = User()
order = Order()
data = {}
items = []
name = "Alice"
count = 42
"#;
let result = extract_python_definitions(source, Path::new("test.py"));
eprintln!("H4a: Found {} VarType entries", result.var_types.len());
for vt in &result.var_types {
eprintln!(
" var={} type={} source={} line={} scope={:?}",
vt.var_name, vt.type_name, vt.source, vt.line, vt.scope
);
}
let user_vt = result
.var_types
.iter()
.find(|vt| vt.var_name == "user" && vt.type_name == "User");
assert!(
user_vt.is_some(),
"VarType for 'user = User()' should exist. All VarTypes: {:?}",
result.var_types
);
assert_eq!(user_vt.unwrap().source, "assignment");
let order_vt = result
.var_types
.iter()
.find(|vt| vt.var_name == "order" && vt.type_name == "Order");
assert!(
order_vt.is_some(),
"VarType for 'order = Order()' should exist"
);
let data_vt = result
.var_types
.iter()
.find(|vt| vt.var_name == "data" && vt.type_name == "dict");
assert!(
data_vt.is_some(),
"VarType for 'data = {{}}' should exist"
);
let items_vt = result
.var_types
.iter()
.find(|vt| vt.var_name == "items" && vt.type_name == "list");
assert!(
items_vt.is_some(),
"VarType for 'items = []' should exist"
);
}
#[test]
fn h4b_vartype_parameter_annotations() {
let source = r#"
class User:
pass
def process(user: User, name: str, count: int):
user.save()
"#;
let result = extract_python_definitions(source, Path::new("test.py"));
eprintln!("H4b: Found {} VarType entries", result.var_types.len());
for vt in &result.var_types {
eprintln!(
" var={} type={} source={} scope={:?}",
vt.var_name, vt.type_name, vt.source, vt.scope
);
}
let user_param = result
.var_types
.iter()
.find(|vt| vt.var_name == "user" && vt.type_name == "User" && vt.source == "parameter");
assert!(
user_param.is_some(),
"VarType for parameter 'user: User' should exist"
);
assert_eq!(
user_param.unwrap().scope.as_deref(),
Some("process"),
"Parameter VarType scope should be the function name"
);
}
#[test]
fn h4c_self_field_patterns() {
let source = r#"
class Service:
def __init__(self):
self.repo = Repository()
self.cache = Cache()
self.logger = Logger()
name = "default"
"#;
let result = extract_python_definitions(source, Path::new("test.py"));
eprintln!("H4c: Found {} VarType entries", result.var_types.len());
for vt in &result.var_types {
eprintln!(
" var={} type={} source={} scope={:?}",
vt.var_name, vt.type_name, vt.source, vt.scope
);
}
let self_field_vts: Vec<&VarType> = result
.var_types
.iter()
.filter(|vt| vt.var_name.starts_with("self."))
.collect();
eprintln!(
"H4c: {} VarType entries have self.field pattern (out of {} total)",
self_field_vts.len(),
result.var_types.len()
);
if self_field_vts.is_empty() {
eprintln!(
"H4c RESULT: self.field patterns NOT captured in VarType. \
Feature Envy will need to use receiver=='self' heuristic instead."
);
} else {
eprintln!("H4c RESULT: self.field patterns ARE captured in VarType.");
}
let name_vt = result
.var_types
.iter()
.find(|vt| vt.var_name == "name");
eprintln!("H4c: Simple var 'name' captured: {}", name_vt.is_some());
}
#[test]
fn h5_delegation_pattern_visible() {
let source = r#"
class RealService:
def do_thing(self):
return "done"
def do_other(self):
return "other"
class Proxy:
def __init__(self):
self.delegate = RealService()
def do_thing(self):
return self.delegate.do_thing()
def do_other(self):
return self.delegate.do_other()
def do_complex(self):
a = self.delegate.do_thing()
b = self.delegate.do_other()
return a + b
"#;
let result = extract_python_definitions(source, Path::new("test.py"));
eprintln!("H5: Calls map has {} entries", result.calls.len());
for (caller, calls) in &result.calls {
eprintln!(" caller '{}' has {} calls:", caller, calls.len());
for call in calls {
eprintln!(
" -> {}.{} (type={:?}, receiver={:?}, receiver_type={:?})",
call.caller, call.target, call.call_type, call.receiver, call.receiver_type
);
}
}
let proxy_do_thing_calls = result.calls.get("Proxy.do_thing");
assert!(
proxy_do_thing_calls.is_some(),
"Proxy.do_thing should have calls. Available callers: {:?}",
result.calls.keys().collect::<Vec<_>>()
);
let proxy_do_thing_calls = proxy_do_thing_calls.unwrap();
let method_calls: Vec<&CallSite> = proxy_do_thing_calls
.iter()
.filter(|c| matches!(c.call_type, CallType::Method | CallType::Attr))
.collect();
eprintln!(
"H5: Proxy.do_thing has {} method calls (delegation detection needs exactly 1)",
method_calls.len()
);
assert_eq!(
method_calls.len(),
1,
"Proxy.do_thing should have exactly 1 method call (pure delegation). Got: {:?}",
method_calls
);
let delegation_call = method_calls[0];
assert_eq!(delegation_call.target, "do_thing");
eprintln!(
"H5: Delegation call receiver = {:?} (need to check if this includes 'self.delegate' or just 'delegate')",
delegation_call.receiver
);
let proxy_do_complex = result.calls.get("Proxy.do_complex");
assert!(proxy_do_complex.is_some(), "Proxy.do_complex should have calls");
let complex_method_calls: Vec<&CallSite> = proxy_do_complex
.unwrap()
.iter()
.filter(|c| matches!(c.call_type, CallType::Method | CallType::Attr))
.collect();
eprintln!(
"H5: Proxy.do_complex has {} method calls (should be 2, not pure delegation)",
complex_method_calls.len()
);
assert!(
complex_method_calls.len() > 1,
"Proxy.do_complex should have multiple calls (not pure delegation)"
);
}
#[test]
fn h5b_delegation_receiver_type_resolution() {
let source = r#"
class RealService:
def do_thing(self):
return "done"
class Proxy:
def __init__(self):
self.delegate = RealService()
def do_thing(self):
return self.delegate.do_thing()
"#;
let parse_result = extract_python_definitions(source, Path::new("test.py"));
let mut file_ir = FileIR::new("test.py".into());
file_ir.funcs = parse_result.funcs;
file_ir.classes = parse_result.classes;
file_ir.imports = parse_result.imports;
file_ir.calls = parse_result.calls;
file_ir.var_types = parse_result.var_types;
apply_type_resolution(&mut file_ir, source, Language::Python);
let proxy_calls = file_ir.calls.get("Proxy.do_thing");
assert!(proxy_calls.is_some(), "Proxy.do_thing should have calls");
let method_calls: Vec<&CallSite> = proxy_calls
.unwrap()
.iter()
.filter(|c| c.call_type == CallType::Method)
.collect();
eprintln!("H5b: Proxy.do_thing method calls after resolution:");
for call in &method_calls {
eprintln!(
" -> target={} receiver={:?} receiver_type={:?}",
call.target, call.receiver, call.receiver_type
);
}
let with_type = method_calls
.iter()
.filter(|c| c.receiver_type.is_some())
.count();
eprintln!(
"H5b: {}/{} delegation calls have receiver_type after resolution",
with_type,
method_calls.len()
);
}
#[test]
fn hypotheses_summary() {
eprintln!("\n=== Discriminative Hypothesis Summary ===\n");
eprintln!("H1: receiver_type population");
eprintln!(" H1a: Scanner creates Method calls with receiver (tested above)");
eprintln!(" H1b: Resolution populates receiver_type from VarType (tested above)");
eprintln!(" H1c: self.method() gets receiver_type=EnclosingClass (tested above)");
eprintln!();
eprintln!("H2: Inheritance data");
eprintln!(" H2a: ClassDef.bases populated from source (tested above)");
eprintln!(" H2b: InheritanceReport with real edges (tested above)");
eprintln!();
eprintln!("H3: ClassDef.methods populated");
eprintln!(" ClassDef.methods + FuncDef.class_name (tested above)");
eprintln!();
eprintln!("H4: VarType tracking");
eprintln!(" H4a: Constructor assignments tracked (tested above)");
eprintln!(" H4b: Parameter annotations tracked (tested above)");
eprintln!(" H4c: self.field patterns (tested above - informational)");
eprintln!();
eprintln!("H5: Delegation patterns");
eprintln!(" H5a: Single-call method visible in call graph (tested above)");
eprintln!(" H5b: Delegation receiver_type resolution (tested above)");
eprintln!("\n=== End Summary ===\n");
}