use std::path::Path;
use crate::model::SymbolKind;
use super::{comments::extract_doc_comment, general::alias_from_path, parse_file};
macro_rules! go_parser_fixture {
($name:literal) => {
include_str!(concat!(
"../../../../tests/fixtures/go/parser/",
$name,
".txt"
))
};
}
#[test]
fn test_import_alias() {
assert_eq!(alias_from_path("github.com/acme/utils"), "utils");
assert_eq!(alias_from_path("github.com/labstack/echo/v4"), "echo");
assert_eq!(alias_from_path("github.com/gofiber/fiber/v2"), "fiber");
}
#[test]
fn test_alias_symbols() {
let source = go_parser_fixture!("alias_symbols");
let parsed = parse_file(Path::new("sample.go"), source).expect("parse should work");
assert!(parsed.symbols.iter().any(|symbol| {
symbol.name == "IsCustomFont" && matches!(symbol.kind, SymbolKind::Function)
}));
assert!(
!parsed
.symbols
.iter()
.any(|symbol| symbol.name == "PlainValue")
);
}
#[test]
fn test_doc_comment() {
let source = go_parser_fixture!("doc_comment");
let comment = extract_doc_comment(source, 2).expect("doc comment should exist");
assert_eq!(
comment,
"Run Processes The Input\nThis function does X by doing Y because Z"
);
}
#[test]
fn test_error_handling() {
let source = go_parser_fixture!("error_handling");
let parsed = parse_file(Path::new("sample.go"), source).expect("parse should work");
let run = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "Run")
.expect("Run should be parsed");
let log_only = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "LogOnly")
.expect("LogOnly should be parsed");
assert_eq!(run.go_evidence().dropped_errors, vec![9]);
assert_eq!(run.go_evidence().panic_errors, vec![10]);
assert_eq!(run.go_evidence().errorf_calls.len(), 1);
assert!(run.go_evidence().errorf_calls[0].mentions_err);
assert!(!run.go_evidence().errorf_calls[0].uses_percent_w);
assert_eq!(log_only.go_evidence().panic_errors, vec![17]);
}
#[test]
fn test_ctx_sleep() {
let source = go_parser_fixture!("ctx_sleep");
let parsed = parse_file(Path::new("sample.go"), source).expect("parse should work");
let poll = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "Poll")
.expect("Poll should be parsed");
assert!(poll.go_evidence().has_context_parameter);
assert_eq!(poll.go_evidence().sleep_loops, vec![10]);
}
#[test]
fn test_ctx_busy_json() {
let source = go_parser_fixture!("ctx_busy_json");
let parsed = parse_file(Path::new("sample.go"), source).expect("parse should work");
let run = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "Run")
.expect("Run should be parsed");
assert_eq!(run.go_evidence().context_factory_calls.len(), 1);
assert_eq!(
run.go_evidence().context_factory_calls[0].cancel_name,
"cancel"
);
assert_eq!(
run.go_evidence().context_factory_calls[0].factory_name,
"WithTimeout"
);
assert_eq!(run.go_evidence().busy_wait_lines, vec![14]);
assert_eq!(run.go_evidence().json_loops, vec![21]);
}
#[test]
fn test_concurrency_db() {
let source = go_parser_fixture!("concurrency_db");
let parsed = parse_file(Path::new("sample.go"), source).expect("parse should work");
let run = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "Run")
.expect("Run should be parsed");
assert_eq!(run.go_evidence().unmanaged_goroutines, vec![11]);
assert_eq!(run.go_evidence().mutex_loops, vec![18]);
assert_eq!(run.go_evidence().alloc_loops, vec![23]);
assert_eq!(run.go_evidence().fmt_loops, vec![21]);
assert_eq!(run.go_evidence().reflect_loops, vec![22]);
assert_eq!(run.go_evidence().db_query_calls.len(), 1);
assert!(run.go_evidence().db_query_calls[0].in_loop);
assert_eq!(
run.go_evidence().db_query_calls[0].query_text.as_deref(),
Some("SELECT * FROM widgets WHERE name LIKE '%foo%'")
);
}
#[test]
fn test_concat_goroutine() {
let source = go_parser_fixture!("concat_goroutine");
let parsed = parse_file(Path::new("sample.go"), source).expect("parse should work");
let build = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "Build")
.expect("Build should be parsed");
assert_eq!(build.go_evidence().concat_loops, vec![6]);
assert_eq!(build.go_evidence().goroutines, vec![7]);
assert_eq!(build.go_evidence().loop_goroutines, vec![7]);
}
#[test]
fn test_pkg_literals() {
let source = go_parser_fixture!("pkg_literals");
let parsed = parse_file(Path::new("sample_test.go"), source).expect("parse should work");
let test_fn = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "TestUser")
.expect("TestUser should be parsed");
assert!(parsed.is_test_file);
assert_eq!(parsed.pkg_strings.len(), 1);
assert_eq!(parsed.pkg_strings[0].name, "apiToken");
assert_eq!(parsed.struct_tags().len(), 1);
assert_eq!(parsed.struct_tags()[0].field_name, "Name");
assert!(parsed.symbols.iter().any(|symbol| {
symbol.name == "NameValue"
&& symbol.receiver_type.as_deref() == Some("User")
&& symbol.receiver_is_pointer == Some(false)
}));
assert!(parsed.symbols.iter().any(|symbol| {
symbol.name == "SetName"
&& symbol.receiver_type.as_deref() == Some("User")
&& symbol.receiver_is_pointer == Some(true)
}));
assert_eq!(test_fn.local_strings.len(), 1);
assert_eq!(test_fn.local_strings[0].name, "token");
assert!(test_fn.test_summary.is_some());
assert_eq!(
test_fn
.test_summary
.as_ref()
.map(|summary| summary.production_calls),
Some(2)
);
}
#[test]
fn test_imports_preserve_source_order_and_lines() {
let source = go_parser_fixture!("imports_preserve_source_order");
let parsed = parse_file(Path::new("sample.go"), source).expect("parse should work");
let imports = parsed
.imports
.iter()
.map(|import| (import.path.as_str(), import.line, import.group_line))
.collect::<Vec<_>>();
assert_eq!(
imports,
vec![
("github.com/acme/widgets", 4, 3),
("fmt", 5, 3),
("strings", 6, 3),
]
);
}
#[test]
fn test_collects_package_vars_interfaces_structs_and_signature_text() {
let source = go_parser_fixture!("package_vars_interfaces_structs_signature");
let parsed = parse_file(Path::new("sample.go"), source).expect("parse should work");
assert_eq!(parsed.package_vars().len(), 2);
assert_eq!(parsed.package_vars()[0].name, "DefaultStore");
assert_eq!(parsed.package_vars()[1].type_text.as_deref(), Some("int"));
assert_eq!(parsed.interfaces().len(), 1);
assert_eq!(parsed.interfaces()[0].name, "Store");
assert_eq!(parsed.interfaces()[0].methods, vec!["Save", "Load"]);
assert_eq!(parsed.go_structs().len(), 1);
assert_eq!(parsed.go_structs()[0].name, "Service");
assert_eq!(parsed.go_structs()[0].fields.len(), 2);
assert_eq!(parsed.go_structs()[0].fields[0].type_text, "Store");
let run = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "Run")
.expect("Run should be parsed");
assert!(run.signature_text.contains("dryRun bool"));
assert_eq!(run.body_start_line, 16);
}
#[test]
fn test_collects_gorm_query_chain_summaries() {
let source = go_parser_fixture!("gorm_query_chains");
let parsed = parse_file(Path::new("sample.go"), source).expect("parse should work");
let handle = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "Handle")
.expect("Handle should be parsed");
assert_eq!(handle.go_evidence().gorm_query_chains.len(), 2);
assert_eq!(
handle.go_evidence().gorm_query_chains[0].terminal_method,
"Count"
);
assert_eq!(
handle.go_evidence().gorm_query_chains[1].terminal_method,
"Find"
);
assert_eq!(
handle.go_evidence().gorm_query_chains[1]
.steps
.iter()
.map(|step| step.method_name.as_str())
.collect::<Vec<_>>(),
vec!["Model", "Preload", "Offset", "Limit", "Find"]
);
assert!(!handle.go_evidence().gorm_query_chains[1].in_loop);
}
#[test]
fn test_collects_gin_calls_and_parse_input_summaries() {
let source = go_parser_fixture!("gin_calls_and_parse_input_summaries");
let parsed = parse_file(Path::new("sample.go"), source).expect("parse should work");
let handle = parsed
.functions
.iter()
.find(|function| function.fingerprint.name == "Handle")
.expect("Handle should be parsed");
assert_eq!(
handle
.go_evidence()
.gin_calls
.iter()
.map(|call| call.operation.as_str())
.collect::<Vec<_>>(),
vec![
"get_raw_data",
"read_request_body",
"should_bind_json",
"should_bind_query",
"parse_multipart_form",
"form_file",
"indented_json",
"copy",
]
);
assert_eq!(
handle.go_evidence().gin_calls[0]
.assigned_binding
.as_deref(),
Some("raw")
);
assert_eq!(
handle.go_evidence().gin_calls[1]
.assigned_binding
.as_deref(),
Some("payload")
);
assert_eq!(
handle
.go_evidence()
.gin_calls
.iter()
.find(|call| call.operation == "parse_multipart_form")
.and_then(|call| call.argument_texts.first())
.map(String::as_str),
Some("64 << 20")
);
assert_eq!(
handle
.go_evidence()
.gin_calls
.iter()
.find(|call| call.operation == "form_file")
.and_then(|call| call.assigned_binding.as_deref()),
Some("fileHeader")
);
assert!(
handle
.go_evidence()
.gin_calls
.iter()
.find(|call| call.operation == "copy")
.is_some_and(|call| call.in_loop)
);
assert_eq!(handle.go_evidence().parse_input_calls.len(), 2);
assert!(
handle
.go_evidence()
.parse_input_calls
.iter()
.all(|call| call.input_binding.as_deref() == Some("body"))
);
}