This commit is contained in:
2026-08-09 11:53:13 +07:00
parent ed5415d92a
commit 8cde269c49
7 changed files with 291 additions and 275 deletions
+49 -69
View File
@@ -3,7 +3,6 @@ using YiemAgent
using YiemAgent.toolRegistry
using YiemAgent.type
# Path to the real tools directory
TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
@testset "loadTools" begin
@@ -15,95 +14,77 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
# ------------------------------------------------------------------ #
# 2. loadTools throws if a .jl file does not define getTool() #
# Must run BEFORE any other loadTools call (getTool binding #
# persists in module scope after include()). #
# ------------------------------------------------------------------ #
bad_dir = mktempdir()
write(joinpath(bad_dir, "noTool.jl"), "x = 42\n")
@test_throws ArgumentError loadTools(bad_dir)
# ------------------------------------------------------------------ #
# 3. loadTools loads actual tool files from src/tools/ #
# 3. Load all tools from src/tools/ #
# ------------------------------------------------------------------ #
clearTools()
loaded = loadTools(TOOLS_DIR)
@test !isempty(loaded)
@test length(loaded) == 3
names = [t.name for t in loaded]
@test "getTime" in names
@test "getWeather" in names
@test "writeTool" in names
@test length(loaded) == 3 # getTime.jl, getWeather.jl, writeTool.jl
# ------------------------------------------------------------------ #
# 4. loadTools returns tools sorted alphabetically by filename #
# (getTime.jl < getWeather.jl < writeTool.jl) #
# because 'T' < 'W' in ASCII #
# 4. Each loaded tool has an isolated _tool_module #
# ------------------------------------------------------------------ #
@test loaded[1].name == "getTime"
@test loaded[2].name == "getWeather"
@test loaded[3].name == "writeTool"
for tool in loaded
@test tool._tool_module isa Module
end
# Verify modules are unique (not shared)
modules = [t._tool_module for t in loaded]
@test length(unique(modules)) == length(modules)
# ------------------------------------------------------------------ #
# 5. Verify loaded tool fields are correct #
# 5. Each tool's executeTool is callable #
# ------------------------------------------------------------------ #
# getTime
time_tool = loaded[1]
@test time_tool.name == "getTime"
@test time_tool.label == "Time Lookup"
@test time_tool.validateRequiredArgs !== nothing
@test time_tool.parallelToolExecute == false
@test time_tool.inputSchema["required"] == Any[]
function run_all_execution_tests(loaded)
sig = nothing
op = x -> x # no-op partial result callback
# getWeather
weather = loaded[2]
@test weather.name == "getWeather"
@test weather.label == "Weather Lookup"
@test weather.execute !== nothing
@test weather.parallelToolExecute == false
@test weather.inputSchema["required"] == ["city"]
for tool in loaded
@test tool.execute !== nothing
@test tool._tool_module isa Module
# writeTool
wt = loaded[3]
@test wt.name == "writeTool"
@test wt.label == "Create Tool"
@test wt.execute !== nothing
@test "name" in wt.inputSchema["required"]
@test "executeCode" in wt.inputSchema["required"]
# Verify the module actually defines executeTool
@test isdefined(tool._tool_module, :executeTool)
# Execute the tool — must return agentToolResult without throwing
result = try
tool.execute("call-$(tool.name)", Dict{String,Any}("city" => "Tokyo"), sig, op)
catch err
tool.execute("call-$(tool.name)", Dict{String,Any}(), sig, op)
end
@test result isa agentToolResult
@test result.content[1] isa textContent
end
end
run_all_execution_tests(loaded)
# ------------------------------------------------------------------ #
# 6. Tool execution returns valid results #
# 6. Specific tool assertions based on known tool names #
# ------------------------------------------------------------------ #
sig = nothing
op = x -> x # no-op partial result callback
time_tool = filter(t -> t.name == "getTime", loaded)
@test !isempty(time_tool)
@test time_tool[1].validateRequiredArgs !== nothing
# execute getTime
result_t = time_tool.execute("call-1", Dict{String,Any}("city" => "Tokyo"), sig, op)
@test result_t isa agentToolResult
@test result_t.content[1] isa textContent
@test occursin("Tokyo", result_t.content[1].text)
getWeather_tool = filter(t -> t.name == "getWeather", loaded)
@test !isempty(getWeather_tool)
@test getWeather_tool[1].inputSchema["required"] == ["city"]
# execute getTime with timezone
result_tz = time_tool.execute("call-2", Dict{String,Any}("timezone" => "America/New_York"), sig, op)
@test result_tz isa agentToolResult
@test occursin("America/New_York", result_tz.content[1].text)
# execute getWeather
result_w = weather.execute("call-3", Dict{String,Any}("city" => "Bangkok"), sig, op)
@test result_w isa agentToolResult
@test result_w.content[1] isa textContent
@test occursin("Bangkok", result_w.content[1].text)
# execute getWeather with units
result_w2 = weather.execute("call-4", Dict{String,Any}("city" => "London", "units" => "fahrenheit"), sig, op)
@test occursin("72°F", result_w2.content[1].text)
write_tool = filter(t -> t.name == "writeTool", loaded)
@test !isempty(write_tool)
@test "name" in write_tool[1].inputSchema["required"]
# ------------------------------------------------------------------ #
# 7. getTools / registerTool / clearTools #
# ------------------------------------------------------------------ #
registry_tools = getTools()
@test !isempty(registry_tools)
@test any(t -> t.name == "getTime", registry_tools)
@test any(t -> t.name == "getWeather", registry_tools)
@test length(registry_tools) == 3 # only the loaded tools
clearTools()
@test isempty(getTools())
@@ -117,20 +98,19 @@ TOOLS_DIR = joinpath(@__DIR__, "..", "src", "tools")
agentToolResult([textContent("manual")], Dict{Any,Any}(), nothing, false),
prepareArguments = nothing,
validateRequiredArgs = nothing,
parallelToolExecute = true
parallelToolExecute = true,
_tool_module = nothing
)
registerTool(test_tool)
reg = getTools()
@test any(t -> t.name == "manualTool", reg)
@test count(t -> t.name == "manualTool", reg) == 1
@test reg[1].parallelToolExecute == true
@test reg[1]._tool_module === nothing
# ------------------------------------------------------------------ #
# 8. getTools returns deep copy (mutations don't affect registry) #
# 8. getTools returns new vector (mutations don't affect registry) #
# ------------------------------------------------------------------ #
copy1 = getTools()
copy2 = getTools()
@test copy1 !== copy2
empty!(copy1)
@test !isempty(getTools())
clearTools()
@test isempty(getTools())
end