use sniff::callgraph::build_callee_context;
use sniff::parser::{parse_file_checked, parse_file_symbols_checked};
use sniff::symbol_graph::SymbolGraph;
use std::fs;
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
fn resolved_callee_context_reaches_the_calling_method() {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock before unix epoch")
.as_nanos();
let root = std::env::temp_dir().join(format!("sniff-callee-context-{nonce}"));
let package = root.join("src");
fs::create_dir_all(&package).expect("create fixture directory");
let caller_path = package.join("caller.py");
let callee_path = package.join("callee.py");
fs::write(
&caller_path,
"from .callee import compute\n\ndef run(value):\n return compute(value)\n",
)
.expect("write caller fixture");
fs::write(&callee_path, "def compute(value):\n return value + 1\n")
.expect("write callee fixture");
let caller_path = caller_path.to_string_lossy().to_string();
let callee_path = callee_path.to_string_lossy().to_string();
let files = vec![
parse_file_checked(&caller_path).expect("parse caller fixture"),
parse_file_checked(&callee_path).expect("parse callee fixture"),
];
let mut graph = SymbolGraph::new(&root.to_string_lossy());
graph.add_file(parse_file_symbols_checked(&caller_path).expect("index caller fixture"));
graph.add_file(parse_file_symbols_checked(&callee_path).expect("index callee fixture"));
graph.resolve_all();
let contexts = build_callee_context(&files, &graph);
let context = contexts
.get(&(caller_path.clone(), "run".to_string(), 3))
.expect("run should have a resolved callee");
assert_eq!(context.len(), 1);
assert_eq!(context[0].file_path, callee_path);
assert_eq!(context[0].line, 1);
assert!(context[0].snippet.contains("Callee Method: compute"));
fs::remove_dir_all(root).ok();
}
#[test]
fn module_qualified_python_reference_counts_as_a_real_use() {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock before unix epoch")
.as_nanos();
let root = std::env::temp_dir().join(format!("sniff-qualified-reference-{nonce}"));
let src = root.join("src");
let analysis = src.join("bumpkin").join("analysis");
fs::create_dir_all(&analysis).expect("create fixture directory");
let callee_path = analysis.join("diff_git.py");
let caller_path = src.join("diff.py");
fs::write(
&callee_path,
"def run_command(args):\n return args\n\ndef run_git(args):\n return args\n",
)
.expect("write callee fixture");
fs::write(
&caller_path,
"from bumpkin.analysis import diff_git\n\ndef run_command_adapter(args):\n return diff_git.run_command(args)\n",
)
.expect("write caller fixture");
let caller_path = caller_path.to_string_lossy().to_string();
let callee_path = callee_path.to_string_lossy().to_string();
let mut files = vec![
parse_file_checked(&caller_path).expect("parse caller fixture"),
parse_file_checked(&callee_path).expect("parse callee fixture"),
];
let mut graph = SymbolGraph::new(&root.to_string_lossy());
graph.add_file(parse_file_symbols_checked(&caller_path).expect("index caller fixture"));
graph.add_file(parse_file_symbols_checked(&callee_path).expect("index callee fixture"));
graph.resolve_all();
sniff::callgraph::build_references(&mut files, &graph);
let callee = files[1]
.methods
.iter()
.find(|method| method.name == "run_command")
.expect("run_command should be parsed");
assert_eq!(callee.real_ref_count, 1);
assert_eq!(callee.references[0].file_path, caller_path);
fs::remove_dir_all(root).ok();
}
#[test]
fn same_file_javascript_calls_count_as_real_references() {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock before unix epoch")
.as_nanos();
let root = std::env::temp_dir().join(format!("sniff-js-local-reference-{nonce}"));
fs::create_dir_all(&root).expect("create fixture directory");
let path = root.join("extractor.ts");
fs::write(
&path,
"function push_method(methods: unknown[], method: unknown) {\n methods.push(method);\n}\n\nfunction visit() {\n push_method([], {});\n}\n",
)
.expect("write fixture");
let path = path.to_string_lossy().to_string();
let mut files = vec![parse_file_checked(&path).expect("parse fixture")];
let mut graph = SymbolGraph::new(&root.to_string_lossy());
graph.add_file(parse_file_symbols_checked(&path).expect("index fixture"));
graph.resolve_all();
sniff::callgraph::build_references(&mut files, &graph);
let helper = files[0]
.methods
.iter()
.find(|method| method.name == "push_method")
.expect("push_method should be parsed");
assert_eq!(helper.real_ref_count, 1);
assert!(!helper.references[0].snippet.is_empty());
fs::remove_dir_all(root).ok();
}
#[test]
fn sniff_javascript_extractor_helper_has_resolved_local_callers() {
let path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/parser_impl/js_ts_extractor.rs");
let path = path.to_string_lossy().to_string();
let mut files = vec![parse_file_checked(&path).expect("parse Sniff extractor")];
let mut graph = SymbolGraph::new(env!("CARGO_MANIFEST_DIR"));
graph.add_file(parse_file_symbols_checked(&path).expect("index Sniff extractor"));
graph.resolve_all();
sniff::callgraph::build_references(&mut files, &graph);
let helper = files[0]
.methods
.iter()
.find(|method| method.name == "push_method")
.expect("push_method should be parsed in Sniff itself");
assert!(
helper.real_ref_count >= 2,
"expected both local calls to resolve, got {}",
helper.real_ref_count
);
}