//! Integration tests for tree-sitter code search use g3_core::code_search::{execute_code_search, CodeSearchRequest, SearchSpec}; use std::fs; #[tokio::test] async fn test_find_async_functions() { // Create a temporary test file let test_dir = std::env::temp_dir().join("g3_test_code_search"); fs::create_dir_all(&test_dir).unwrap(); let test_file = test_dir.join("test.rs"); fs::write( &test_file, r#" pub async fn example_async() { println!("Hello"); } fn regular_function() { println!("Regular"); } pub async fn another_async(x: i32) -> Result<(), ()> { Ok(()) } "#, ) .unwrap(); // Test 1: Find async functions let request = CodeSearchRequest { searches: vec![SearchSpec { name: "find_async_functions".to_string(), // In tree-sitter-rust, async is a token inside function_modifiers query: "(function_item (function_modifiers) name: (identifier) @name)".to_string(), language: "rust".to_string(), paths: vec![test_dir.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 100, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); let search_result = &response.searches[0]; assert_eq!(search_result.name, "find_async_functions"); assert_eq!( search_result.match_count, 2, "Should find 2 async functions" ); assert!(search_result.error.is_none()); // Check that we found the right functions let function_names: Vec = search_result .matches .iter() .filter_map(|m| m.captures.get("name").cloned()) .collect(); assert!(function_names.contains(&"example_async".to_string())); assert!(function_names.contains(&"another_async".to_string())); // Cleanup fs::remove_dir_all(&test_dir).ok(); } #[tokio::test] async fn test_find_all_functions() { // Create a temporary test file let test_dir = std::env::temp_dir().join("g3_test_code_search_2"); fs::create_dir_all(&test_dir).unwrap(); let test_file = test_dir.join("test.rs"); fs::write( &test_file, r#" pub async fn example_async() { println!("Hello"); } fn regular_function() { println!("Regular"); } pub async fn another_async(x: i32) -> Result<(), ()> { Ok(()) } "#, ) .unwrap(); // Test 2: Find all functions (async and regular) let request = CodeSearchRequest { searches: vec![SearchSpec { name: "find_all_functions".to_string(), query: "(function_item name: (identifier) @name)".to_string(), language: "rust".to_string(), paths: vec![test_dir.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 100, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); let search_result = &response.searches[0]; assert_eq!(search_result.name, "find_all_functions"); assert_eq!( search_result.match_count, 3, "Should find 3 functions total" ); assert!(search_result.error.is_none()); // Check that we found all functions let function_names: Vec = search_result .matches .iter() .filter_map(|m| m.captures.get("name").cloned()) .collect(); assert!(function_names.contains(&"example_async".to_string())); assert!(function_names.contains(&"regular_function".to_string())); assert!(function_names.contains(&"another_async".to_string())); // Cleanup fs::remove_dir_all(&test_dir).ok(); } #[tokio::test] async fn test_find_structs() { // Create a temporary test file let test_dir = std::env::temp_dir().join("g3_test_code_search_3"); fs::create_dir_all(&test_dir).unwrap(); let test_file = test_dir.join("test.rs"); fs::write( &test_file, r#" pub struct MyStruct { field: String, } struct AnotherStruct; enum MyEnum { Variant, } "#, ) .unwrap(); // Test 3: Find structs let request = CodeSearchRequest { searches: vec![SearchSpec { name: "find_structs".to_string(), query: "(struct_item name: (type_identifier) @name)".to_string(), language: "rust".to_string(), paths: vec![test_dir.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 100, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); let search_result = &response.searches[0]; assert_eq!(search_result.name, "find_structs"); assert_eq!(search_result.match_count, 2, "Should find 2 structs"); assert!(search_result.error.is_none()); // Check that we found the right structs let struct_names: Vec = search_result .matches .iter() .filter_map(|m| m.captures.get("name").cloned()) .collect(); assert!(struct_names.contains(&"MyStruct".to_string())); assert!(struct_names.contains(&"AnotherStruct".to_string())); // Cleanup fs::remove_dir_all(&test_dir).ok(); } #[tokio::test] async fn test_context_lines() { // Create a temporary test file let test_dir = std::env::temp_dir().join("g3_test_code_search_4"); fs::create_dir_all(&test_dir).unwrap(); let test_file = test_dir.join("test.rs"); fs::write( &test_file, r#" // Line 1 // Line 2 pub fn target_function() { // Line 4 println!("target"); } // Line 7 // Line 8 "#, ) .unwrap(); // Test 4: Context lines let request = CodeSearchRequest { searches: vec![SearchSpec { name: "find_with_context".to_string(), query: "(function_item name: (identifier) @name)".to_string(), language: "rust".to_string(), paths: vec![test_dir.to_string_lossy().to_string()], context_lines: 2, }], max_concurrency: 4, max_matches_per_search: 100, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); let search_result = &response.searches[0]; assert_eq!(search_result.match_count, 1); let match_result = &search_result.matches[0]; assert!(match_result.context.is_some()); let context = match_result.context.as_ref().unwrap(); assert!(context.contains("Line 2"), "Should include 2 lines before"); assert!( context.contains("target_function"), "Should include the function" ); // Note: context_lines=2 means 2 lines before and after the match line (line 4) // So we get lines 2-6, which includes up to println but not the closing brace assert!( context.contains("println"), "Should include 2 lines after the match" ); // Cleanup fs::remove_dir_all(&test_dir).ok(); } #[tokio::test] async fn test_multiple_searches() { // Create a temporary test file let test_dir = std::env::temp_dir().join("g3_test_code_search_5"); fs::create_dir_all(&test_dir).unwrap(); let test_file = test_dir.join("test.rs"); fs::write( &test_file, r#" pub async fn async_func() {} fn regular_func() {} pub struct MyStruct; "#, ) .unwrap(); // Test 5: Multiple searches in one request let request = CodeSearchRequest { searches: vec![ SearchSpec { name: "async_functions".to_string(), query: "(function_item (function_modifiers) name: (identifier) @name)".to_string(), language: "rust".to_string(), paths: vec![test_dir.to_string_lossy().to_string()], context_lines: 0, }, SearchSpec { name: "structs".to_string(), query: "(struct_item name: (type_identifier) @name)".to_string(), language: "rust".to_string(), paths: vec![test_dir.to_string_lossy().to_string()], context_lines: 0, }, ], max_concurrency: 4, max_matches_per_search: 100, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 2); assert_eq!(response.total_matches, 2); // 1 async function + 1 struct // Check first search (async functions) let async_search = &response.searches[0]; assert_eq!(async_search.name, "async_functions"); assert_eq!(async_search.match_count, 1); // Check second search (structs) let struct_search = &response.searches[1]; assert_eq!(struct_search.name, "structs"); assert_eq!(struct_search.match_count, 1); // Cleanup fs::remove_dir_all(&test_dir).ok(); } #[tokio::test] async fn test_python_search() { // Create a temporary Python test file let test_dir = std::env::temp_dir().join("g3_test_code_search_python"); fs::create_dir_all(&test_dir).unwrap(); let test_file = test_dir.join("test.py"); fs::write( &test_file, r#" def regular_function(): pass async def async_function(): pass class MyClass: def method(self): pass "#, ) .unwrap(); // Test 6: Python async functions let request = CodeSearchRequest { searches: vec![SearchSpec { name: "python_async".to_string(), // Note: tree-sitter-python doesn't expose 'async' as a queryable node // For now, we'll just find all functions (async detection would need text matching) query: "(function_definition name: (identifier) @name)".to_string(), language: "python".to_string(), paths: vec![test_dir.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 100, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); let search_result = &response.searches[0]; assert_eq!( search_result.match_count, 3, "Should find 3 functions in Python (2 regular + 1 async + 1 method)" ); let function_names: Vec = search_result .matches .iter() .filter_map(|m| m.captures.get("name").cloned()) .collect(); assert!(function_names.contains(&"regular_function".to_string())); assert!(function_names.contains(&"async_function".to_string())); assert!(function_names.contains(&"method".to_string())); // Cleanup fs::remove_dir_all(&test_dir).ok(); } #[tokio::test] async fn test_javascript_search() { // Create a temporary JavaScript test file let test_dir = std::env::temp_dir().join("g3_test_code_search_js"); fs::create_dir_all(&test_dir).unwrap(); let test_file = test_dir.join("test.js"); fs::write( &test_file, r#" function regularFunction() { console.log("regular"); } async function asyncFunction() { console.log("async"); } class MyClass { constructor() {} } "#, ) .unwrap(); // Test 7: JavaScript functions let request = CodeSearchRequest { searches: vec![SearchSpec { name: "js_functions".to_string(), query: "(function_declaration name: (identifier) @name)".to_string(), language: "javascript".to_string(), paths: vec![test_dir.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 100, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); let search_result = &response.searches[0]; assert_eq!( search_result.match_count, 2, "Should find 2 functions in JavaScript" ); let function_names: Vec = search_result .matches .iter() .filter_map(|m| m.captures.get("name").cloned()) .collect(); assert!(function_names.contains(&"regularFunction".to_string())); assert!(function_names.contains(&"asyncFunction".to_string())); // Cleanup fs::remove_dir_all(&test_dir).ok(); } #[tokio::test] async fn test_go_search() { // Get the workspace root (where Cargo.toml is) let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let workspace_root = std::path::Path::new(&manifest_dir) .parent() .and_then(|p| p.parent()) .unwrap(); let test_code_path = workspace_root.join("examples/test_code"); let request = CodeSearchRequest { searches: vec![SearchSpec { name: "go_functions".to_string(), query: "(function_declaration name: (identifier) @name)".to_string(), language: "go".to_string(), paths: vec![test_code_path.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 500, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); eprintln!("Go search result: {:?}", response.searches[0]); eprintln!("Match count: {}", response.searches[0].matches.len()); eprintln!("Error: {:?}", response.searches[0].error); assert!( response.searches[0].matches.len() > 0, "No matches found for Go search" ); // Should find main and greet functions let names: Vec<&str> = response.searches[0] .matches .iter() .filter_map(|m| m.captures.get("name").map(|s| s.as_str())) .collect(); assert!(names.contains(&"main")); assert!(names.contains(&"greet")); } #[tokio::test] async fn test_java_search() { // Get the workspace root (where Cargo.toml is) let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let workspace_root = std::path::Path::new(&manifest_dir) .parent() .and_then(|p| p.parent()) .unwrap(); let test_code_path = workspace_root.join("examples/test_code"); let request = CodeSearchRequest { searches: vec![SearchSpec { name: "java_classes".to_string(), query: "(class_declaration name: (identifier) @name)".to_string(), language: "java".to_string(), paths: vec![test_code_path.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 500, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); assert!(response.searches[0].matches.len() > 0); // Should find Example class let names: Vec<&str> = response.searches[0] .matches .iter() .filter_map(|m| m.captures.get("name").map(|s| s.as_str())) .collect(); assert!(names.contains(&"Example")); } #[tokio::test] async fn test_c_search() { // Get the workspace root (where Cargo.toml is) let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let workspace_root = std::path::Path::new(&manifest_dir) .parent() .and_then(|p| p.parent()) .unwrap(); let test_code_path = workspace_root.join("examples/test_code"); let request = CodeSearchRequest { searches: vec![SearchSpec { name: "c_functions".to_string(), query: "(function_definition declarator: (function_declarator declarator: (identifier) @name))".to_string(), language: "c".to_string(), paths: vec![test_code_path.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 500, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); assert!(response.searches[0].matches.len() > 0); // Should find greet, add, and main functions let names: Vec<&str> = response.searches[0] .matches .iter() .filter_map(|m| m.captures.get("name").map(|s| s.as_str())) .collect(); assert!(names.contains(&"greet")); assert!(names.contains(&"add")); assert!(names.contains(&"main")); } #[tokio::test] async fn test_cpp_search() { // Get the workspace root (where Cargo.toml is) let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let workspace_root = std::path::Path::new(&manifest_dir) .parent() .and_then(|p| p.parent()) .unwrap(); let test_code_path = workspace_root.join("examples/test_code"); let request = CodeSearchRequest { searches: vec![SearchSpec { name: "cpp_classes".to_string(), query: "(class_specifier name: (type_identifier) @name)".to_string(), language: "cpp".to_string(), paths: vec![test_code_path.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 500, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); assert!(response.searches[0].matches.len() > 0); // Should find Person class let names: Vec<&str> = response.searches[0] .matches .iter() .filter_map(|m| m.captures.get("name").map(|s| s.as_str())) .collect(); assert!(names.contains(&"Person")); } #[tokio::test] async fn test_racket_search() { // Get the workspace root (where Cargo.toml is) let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let workspace_root = std::path::Path::new(&manifest_dir) .parent() .and_then(|p| p.parent()) .unwrap(); let test_code_path = workspace_root.join("examples/test_code"); let request = CodeSearchRequest { searches: vec![SearchSpec { name: "racket_functions".to_string(), query: r#"(list . (symbol) @kw (#eq? @kw "define") . (list . (symbol) @name))"#.to_string(), language: "racket".to_string(), paths: vec![test_code_path.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 500, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); assert!(response.searches[0].matches.len() > 0); // Should find greet, add, factorial functions let names: Vec<&str> = response.searches[0] .matches .iter() .filter_map(|m| m.captures.get("name").map(|s| s.as_str())) .collect(); assert!(names.contains(&"greet")); assert!(names.contains(&"add")); assert!(names.contains(&"factorial")); assert!(names.contains(&"person-greet")); assert!(names.contains(&"describe-list")); assert!(names.contains(&"sum-squares")); } #[tokio::test] async fn test_racket_structs() { // Get the workspace root (where Cargo.toml is) let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let workspace_root = std::path::Path::new(&manifest_dir) .parent() .and_then(|p| p.parent()) .unwrap(); let test_code_path = workspace_root.join("examples/test_code"); let request = CodeSearchRequest { searches: vec![SearchSpec { name: "racket_structs".to_string(), query: r#"(list . (symbol) @kw (#eq? @kw "struct") . (symbol) @name)"#.to_string(), language: "racket".to_string(), paths: vec![test_code_path.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 500, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); assert!(response.searches[0].matches.len() > 0); // Should find person and point structs let names: Vec<&str> = response.searches[0] .matches .iter() .filter_map(|m| m.captures.get("name").map(|s| s.as_str())) .collect(); assert!(names.contains(&"person"), "Should find 'person' struct, found: {:?}", names); assert!(names.contains(&"point"), "Should find 'point' struct, found: {:?}", names); } #[tokio::test] async fn test_racket_macros() { // Get the workspace root (where Cargo.toml is) let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let workspace_root = std::path::Path::new(&manifest_dir) .parent() .and_then(|p| p.parent()) .unwrap(); let test_code_path = workspace_root.join("examples/test_code"); let request = CodeSearchRequest { searches: vec![SearchSpec { name: "racket_macros".to_string(), query: r#"(list . (symbol) @kw (#eq? @kw "define-syntax-rule") . (list . (symbol) @name))"#.to_string(), language: "racket".to_string(), paths: vec![test_code_path.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 500, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); assert!(response.searches[0].matches.len() > 0, "Should find macros, error: {:?}", response.searches[0].error); // Should find swap! and unless macros let names: Vec<&str> = response.searches[0] .matches .iter() .filter_map(|m| m.captures.get("name").map(|s| s.as_str())) .collect(); assert!(names.contains(&"swap!"), "Should find 'swap!' macro, found: {:?}", names); assert!(names.contains(&"unless"), "Should find 'unless' macro, found: {:?}", names); } #[tokio::test] async fn test_racket_contracts() { // Get the workspace root (where Cargo.toml is) let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let workspace_root = std::path::Path::new(&manifest_dir) .parent() .and_then(|p| p.parent()) .unwrap(); let test_code_path = workspace_root.join("examples/test_code"); let request = CodeSearchRequest { searches: vec![SearchSpec { name: "racket_contracts".to_string(), query: r#"(list . (symbol) @kw (#eq? @kw "define/contract") . (list . (symbol) @name))"#.to_string(), language: "racket".to_string(), paths: vec![test_code_path.to_string_lossy().to_string()], context_lines: 0, }], max_concurrency: 4, max_matches_per_search: 500, }; let response = execute_code_search(request).await.unwrap(); assert_eq!(response.searches.len(), 1); assert!(response.searches[0].matches.len() > 0, "Should find contract functions, error: {:?}", response.searches[0].error); // Should find safe-divide and non-negative-add let names: Vec<&str> = response.searches[0] .matches .iter() .filter_map(|m| m.captures.get("name").map(|s| s.as_str())) .collect(); assert!(names.contains(&"safe-divide"), "Should find 'safe-divide', found: {:?}", names); assert!(names.contains(&"non-negative-add"), "Should find 'non-negative-add', found: {:?}", names); }