use sqry_core::graph::unified::StagingGraph;
use sqry_core::graph::unified::build::staging::StagingOp;
use sqry_core::graph::unified::edge::EdgeKind;
use sqry_core::graph::unified::node::NodeKind;
use sqry_core::graph::{GraphBuilder, Language};
use sqry_lang_lua::LuaGraphBuilder;
use std::path::Path;
fn parse_lua(source: &str) -> tree_sitter::Tree {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(&tree_sitter_lua::LANGUAGE.into())
.expect("failed to set Lua language");
parser
.parse(source.as_bytes(), None)
.expect("failed to parse Lua code")
}
fn count_edges_of_kind(staging: &StagingGraph, kind_check: impl Fn(&EdgeKind) -> bool) -> usize {
staging
.operations()
.iter()
.filter(|op| {
if let StagingOp::AddEdge { kind, .. } = op {
kind_check(kind)
} else {
false
}
})
.count()
}
fn count_call_edges(staging: &StagingGraph) -> usize {
count_edges_of_kind(staging, |k| matches!(k, EdgeKind::Calls { .. }))
}
fn count_export_edges(staging: &StagingGraph) -> usize {
count_edges_of_kind(staging, |k| matches!(k, EdgeKind::Exports { .. }))
}
fn has_interned_string_containing(staging: &StagingGraph, pattern: &str) -> bool {
staging.operations().iter().any(|op| {
if let StagingOp::InternString { value, .. } = op {
value.contains(pattern)
} else {
false
}
})
}
fn has_node_of_kind(staging: &StagingGraph, kind: NodeKind) -> bool {
staging.operations().iter().any(|op| {
if let StagingOp::AddNode { entry, .. } = op {
entry.kind == kind
} else {
false
}
})
}
#[test]
fn test_global_function_extraction() {
let source = r"
function greet(name)
return 'Hello, ' .. name
end
function add(a, b)
return a + b
end
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
builder
.build_graph(
&tree,
source.as_bytes(),
Path::new("test.lua"),
&mut staging,
)
.unwrap();
let stats = staging.stats();
assert!(
stats.nodes_staged >= 2,
"Expected at least 2 function nodes, got {}",
stats.nodes_staged
);
assert!(
has_interned_string_containing(&staging, "greet"),
"Expected 'greet' function"
);
assert!(
has_interned_string_containing(&staging, "add"),
"Expected 'add' function"
);
}
#[test]
fn test_local_function_extraction() {
let source = r"
local function helper()
return 42
end
local function compute(x)
return helper() * x
end
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
builder
.build_graph(
&tree,
source.as_bytes(),
Path::new("local.lua"),
&mut staging,
)
.unwrap();
let stats = staging.stats();
assert!(
stats.nodes_staged >= 2,
"Expected at least 2 local function nodes, got {}",
stats.nodes_staged
);
}
#[test]
fn test_module_method_extraction() {
let source = r"
local M = {}
function M.greet(name)
return 'Hello, ' .. name
end
function M.farewell(name)
return 'Goodbye, ' .. name
end
return M
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
builder
.build_graph(
&tree,
source.as_bytes(),
Path::new("module.lua"),
&mut staging,
)
.unwrap();
let stats = staging.stats();
assert!(
stats.nodes_staged >= 2,
"Expected at least 2 module method nodes, got {}",
stats.nodes_staged
);
}
#[test]
fn test_function_nodes_have_function_kind() {
let source = r"
function standalone()
return true
end
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
builder
.build_graph(
&tree,
source.as_bytes(),
Path::new("test.lua"),
&mut staging,
)
.unwrap();
assert!(
has_node_of_kind(&staging, NodeKind::Function),
"Expected at least one Function-kind node"
);
}
#[test]
fn test_call_edge_detection() {
let source = r"
function helper()
print('helper')
end
function caller()
helper()
end
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
builder
.build_graph(
&tree,
source.as_bytes(),
Path::new("test.lua"),
&mut staging,
)
.unwrap();
let call_count = count_call_edges(&staging);
assert!(
call_count >= 1,
"Expected at least 1 call edge, got {call_count}"
);
}
#[test]
fn test_method_call_detection() {
let source = r"
local obj = {}
function obj:method1()
return 1
end
function obj:method2()
return self:method1() + 1
end
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
let result = builder.build_graph(&tree, source.as_bytes(), Path::new("obj.lua"), &mut staging);
assert!(result.is_ok(), "Method calls should succeed");
}
#[test]
fn test_nested_calls() {
let source = r"
function level3()
return 1
end
function level2()
return level3()
end
function level1()
return level2()
end
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
builder
.build_graph(
&tree,
source.as_bytes(),
Path::new("nested.lua"),
&mut staging,
)
.unwrap();
let call_count = count_call_edges(&staging);
assert!(
call_count >= 2,
"Expected at least 2 call edges, got {call_count}"
);
}
#[test]
fn test_module_export() {
let source = r"
local M = {}
function M.public_api()
return 'hello'
end
return M
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
builder
.build_graph(
&tree,
source.as_bytes(),
Path::new("module.lua"),
&mut staging,
)
.unwrap();
let export_count = count_export_edges(&staging);
assert!(
export_count >= 1,
"Expected at least 1 export edge for module method, got {export_count}"
);
}
#[test]
fn test_luajit_ffi_detection() {
let source = r"
local ffi = require('ffi')
ffi.cdef[[
int printf(const char *fmt, ...);
void *malloc(size_t size);
]]
local function use_c()
ffi.C.printf('hello\n')
end
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
let result = builder.build_graph(&tree, source.as_bytes(), Path::new("ffi.lua"), &mut staging);
assert!(result.is_ok(), "LuaJIT FFI should succeed");
}
#[test]
fn test_builder_language() {
let builder = LuaGraphBuilder::default();
assert_eq!(builder.language(), Language::Lua);
}
#[test]
fn test_builder_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<LuaGraphBuilder>();
}
#[test]
fn test_empty_file() {
let source = "";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
let result = builder.build_graph(
&tree,
source.as_bytes(),
Path::new("empty.lua"),
&mut staging,
);
assert!(result.is_ok(), "Empty Lua file should succeed");
let stats = staging.stats();
assert_eq!(stats.nodes_staged, 0, "Empty file should produce no nodes");
}
#[test]
fn test_malformed_lua() {
let source = r"
function broken(
"; let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
let result = builder.build_graph(
&tree,
source.as_bytes(),
Path::new("broken.lua"),
&mut staging,
);
let _ = result;
}
#[test]
fn test_comments_only() {
let source = r"
-- This is a comment
--[[
Multi-line comment
]]
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
let result = builder.build_graph(
&tree,
source.as_bytes(),
Path::new("comments.lua"),
&mut staging,
);
assert!(result.is_ok(), "Comments-only Lua file should succeed");
}
#[test]
fn test_table_functions() {
let source = r"
local MyClass = {}
MyClass.__index = MyClass
function MyClass.new(value)
local self = setmetatable({}, MyClass)
self.value = value
return self
end
function MyClass:getValue()
return self.value
end
function MyClass:double()
return self:getValue() * 2
end
";
let tree = parse_lua(source);
let mut staging = StagingGraph::new();
let builder = LuaGraphBuilder::default();
builder
.build_graph(
&tree,
source.as_bytes(),
Path::new("class.lua"),
&mut staging,
)
.unwrap();
let stats = staging.stats();
assert!(
stats.nodes_staged >= 2,
"Expected at least 2 function nodes, got {}",
stats.nodes_staged
);
}