- Add test/ directory with test harness and test files - Add UDT, DB, FB parser tests - Add PLC JSON and integration tests - Test against reference project (239 UDTs, 12 DBs, 37 SCL, 13 XML) - Fix attribute parsing to handle underscores (S7_SetPoint) - Add ORGANIZATION_BLOCK support to FB parser
250 lines
6.2 KiB
Lua
250 lines
6.2 KiB
Lua
local test = dofile("test/test_harness.lua")
|
|
|
|
package.path = package.path .. ";lua/?.lua;src/?.lua"
|
|
|
|
local udt_parser = require("scl.udt_parser")
|
|
local db_parser = require("scl.db_parser")
|
|
local fb_parser = require("scl.fb_parser")
|
|
|
|
local script_path = debug.getinfo(1, "S").source:gsub("^@", ""):match("(.*/)") or "."
|
|
script_path = script_path:gsub("^test/", ""):gsub("/tests$", "")
|
|
if script_path == "" then script_path = "." end
|
|
|
|
local plc_json = dofile(script_path .. "/src/plc_json.lua")
|
|
|
|
local function scan_directory(dir, ext)
|
|
local files = {}
|
|
local handle = io.popen('find "' .. dir .. '" -type f -name "*.' .. ext .. '" 2>/dev/null')
|
|
if handle then
|
|
for line in handle:lines() do
|
|
if line ~= "" then
|
|
table.insert(files, line)
|
|
end
|
|
end
|
|
handle:close()
|
|
end
|
|
return files
|
|
end
|
|
|
|
local function test_parse_all_udt_files()
|
|
udt_parser.clear_cache()
|
|
|
|
local udt_files = scan_directory(test.REF_PROJECT .. "/PLC data types", "udt")
|
|
test.assert_gt(#udt_files, 0, "should find UDT files in reference project")
|
|
|
|
local parsed = 0
|
|
local failed = {}
|
|
|
|
for _, filepath in ipairs(udt_files) do
|
|
local file = io.open(filepath, "r")
|
|
if file then
|
|
local content = file:read("*all")
|
|
file:close()
|
|
local udt, err = udt_parser.parse_udt_content(content, filepath)
|
|
if udt then
|
|
parsed = parsed + 1
|
|
else
|
|
table.insert(failed, filepath .. ": " .. (err or "unknown error"))
|
|
end
|
|
end
|
|
end
|
|
|
|
print(string.format("Parsed %d/%d UDT files", parsed, #udt_files))
|
|
if #failed > 0 then
|
|
print("Failed files:")
|
|
for _, f in ipairs(failed) do
|
|
print(" " .. f)
|
|
end
|
|
end
|
|
|
|
test.assert_gt(parsed, 0, "should parse at least one UDT file")
|
|
end
|
|
|
|
local function test_parse_all_db_files()
|
|
db_parser.clear_cache()
|
|
|
|
local db_files = scan_directory(test.REF_PROJECT .. "/Program blocks", "db")
|
|
test.assert_gt(#db_files, 0, "should find DB files in reference project")
|
|
|
|
local parsed = 0
|
|
local failed = {}
|
|
|
|
for _, filepath in ipairs(db_files) do
|
|
local file = io.open(filepath, "r")
|
|
if file then
|
|
local content = file:read("*all")
|
|
file:close()
|
|
local db, err = db_parser.parse_db_content(content, filepath)
|
|
if db then
|
|
parsed = parsed + 1
|
|
else
|
|
table.insert(failed, filepath .. ": " .. (err or "unknown error"))
|
|
end
|
|
end
|
|
end
|
|
|
|
print(string.format("Parsed %d/%d DB files", parsed, #db_files))
|
|
if #failed > 0 then
|
|
print("Failed files:")
|
|
for _, f in ipairs(failed) do
|
|
print(" " .. f)
|
|
end
|
|
end
|
|
|
|
test.assert_gt(parsed, 0, "should parse at least one DB file")
|
|
end
|
|
|
|
local function test_parse_all_scl_files()
|
|
fb_parser.clear_cache()
|
|
|
|
local scl_files = scan_directory(test.REF_PROJECT .. "/Program blocks", "scl")
|
|
test.assert_gt(#scl_files, 0, "should find SCL files in reference project")
|
|
|
|
local parsed = 0
|
|
local failed = {}
|
|
|
|
for _, filepath in ipairs(scl_files) do
|
|
local file = io.open(filepath, "r")
|
|
if file then
|
|
local content = file:read("*all")
|
|
file:close()
|
|
local fb, err = fb_parser.parse_fb_content(content, filepath)
|
|
if fb then
|
|
parsed = parsed + 1
|
|
else
|
|
table.insert(failed, filepath .. ": " .. (err or "unknown error"))
|
|
end
|
|
end
|
|
end
|
|
|
|
print(string.format("Parsed %d/%d SCL files", parsed, #scl_files))
|
|
if #failed > 0 then
|
|
print("Failed files:")
|
|
for _, f in ipairs(failed) do
|
|
print(" " .. f)
|
|
end
|
|
end
|
|
|
|
test.assert_gt(parsed, 0, "should parse at least one SCL file")
|
|
end
|
|
|
|
local function test_parse_xml_files()
|
|
local xml_files = scan_directory(test.REF_PROJECT, "xml")
|
|
|
|
local found = 0
|
|
for _, filepath in ipairs(xml_files) do
|
|
local file = io.open(filepath, "r")
|
|
if file then
|
|
local content = file:read("*all")
|
|
file:close()
|
|
if content and #content > 10 then
|
|
found = found + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
print(string.format("Found %d/%d XML files", found, #xml_files))
|
|
test.assert(found > 0, "should find XML files")
|
|
end
|
|
|
|
local function test_type_resolution()
|
|
udt_parser.clear_cache()
|
|
db_parser.clear_cache()
|
|
fb_parser.clear_cache()
|
|
|
|
local udt_content = [[
|
|
TYPE "inner_type"
|
|
TITLE = Inner
|
|
VERSION : 0.1
|
|
STRUCT
|
|
x : Int;
|
|
y : Bool;
|
|
END_STRUCT;
|
|
|
|
END_TYPE
|
|
]]
|
|
|
|
local db_content = [[
|
|
DATA_BLOCK "test_db"
|
|
TITLE = Test
|
|
VERSION : 0.1
|
|
VAR
|
|
myInner : "inner_type";
|
|
simple : Bool;
|
|
END_VAR
|
|
|
|
END_DATA_BLOCK
|
|
]]
|
|
|
|
local fb_content = [[
|
|
FUNCTION_BLOCK "test_fb"
|
|
TITLE = Test
|
|
VERSION : 0.1
|
|
VAR_INPUT
|
|
input1 : "inner_type";
|
|
END_VAR
|
|
|
|
END_FUNCTION_BLOCK
|
|
]]
|
|
|
|
local udt = udt_parser.parse_udt_content(udt_content, "inner.udt")
|
|
if udt then udt_parser.register_inline_udt(udt) end
|
|
|
|
local db = db_parser.parse_db_content(db_content, "test.db")
|
|
if db then db_parser.register_inline_db(db) end
|
|
|
|
local fb = fb_parser.parse_fb_content(fb_content, "test.scl")
|
|
|
|
test.assert(udt_parser.is_udt_type("inner_type"), "inner_type should be recognized as UDT")
|
|
test.assert(db_parser.is_db_type("test_db"), "test_db should be recognized as DB")
|
|
|
|
local db_members = db_parser.get_db_members("test_db")
|
|
if db_members and db_members[1] then
|
|
test.assert_equals(db_members[1].is_udt, true, "DB member should be recognized as UDT reference")
|
|
end
|
|
end
|
|
|
|
local function test_workspace_type_loading()
|
|
plc_json.clear_cache()
|
|
|
|
local types = plc_json.get_types(test.REF_PROJECT)
|
|
|
|
local total_types = 0
|
|
for _ in pairs(types) do
|
|
total_types = total_types + 1
|
|
end
|
|
|
|
print(string.format("Loaded %d types from workspace", total_types))
|
|
test.assert_gt(total_types, 0, "should load types from reference project")
|
|
end
|
|
|
|
local function run_integration_tests()
|
|
test.run_suite("Integration Tests - Reference Project", function()
|
|
print("\n--- UDT Files ---")
|
|
test_parse_all_udt_files()
|
|
|
|
print("\n--- DB Files ---")
|
|
test_parse_all_db_files()
|
|
|
|
print("\n--- SCL Files ---")
|
|
test_parse_all_scl_files()
|
|
|
|
print("\n--- XML Files ---")
|
|
test_parse_xml_files()
|
|
|
|
print("\n--- Type Resolution ---")
|
|
test_type_resolution()
|
|
|
|
print("\n--- Workspace Type Loading ---")
|
|
test_workspace_type_loading()
|
|
end)
|
|
end
|
|
|
|
if vim then
|
|
vim.api.nvim_create_user_command("TestIntegration", function()
|
|
run_integration_tests()
|
|
end, {})
|
|
end
|
|
|
|
run_integration_tests()
|