feat: add testing framework with reference project integration
- 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
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user