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,252 @@
|
||||
local test = dofile("test/test_harness.lua")
|
||||
|
||||
package.path = package.path .. ";lua/?.lua"
|
||||
|
||||
local udt_parser = require("scl.udt_parser")
|
||||
|
||||
local function test_udt_parser_basic()
|
||||
local content = [[
|
||||
TYPE "test_udt"
|
||||
TITLE = Test UDT
|
||||
VERSION : 0.1
|
||||
//Test type definition
|
||||
STRUCT
|
||||
member1 : Bool;
|
||||
member2 : Int;
|
||||
member3 : "other_udt";
|
||||
END_STRUCT;
|
||||
|
||||
END_TYPE
|
||||
]]
|
||||
local udt, err = udt_parser.parse_udt_content(content, "test.udt")
|
||||
if udt then
|
||||
udt_parser.register_inline_udt(udt)
|
||||
end
|
||||
test.assert_not_nil(udt, "parse_udt_content should return udt")
|
||||
test.assert_equals(udt.name, "test_udt", "type name should be extracted")
|
||||
test.assert_equals(#udt.members, 3, "should have 3 members")
|
||||
test.assert_equals(udt.members[1].name, "member1", "first member name")
|
||||
test.assert_equals(udt.members[1].type, "Bool", "first member type")
|
||||
test.assert_equals(udt.members[3].is_udt, true, "third member is UDT type")
|
||||
end
|
||||
|
||||
local function test_udt_parser_attributes()
|
||||
local content = [[
|
||||
TYPE "attr_test"
|
||||
TITLE = Attr Test
|
||||
VERSION : 0.1
|
||||
STRUCT
|
||||
attrMember{S7_SetPoint := 'True'}: Bool;
|
||||
normalMember : Int;
|
||||
END_STRUCT;
|
||||
|
||||
END_TYPE
|
||||
]]
|
||||
local udt = udt_parser.parse_udt_content(content, "attr.udt")
|
||||
test.assert_not_nil(udt, "parse_udt_content should return udt")
|
||||
test.assert_equals(udt.members[1].attributes.S7_SetPoint, "True", "attribute should be parsed")
|
||||
test.assert(udt.members[2].attributes == nil or next(udt.members[2].attributes) == nil, "normal member has no attributes")
|
||||
end
|
||||
|
||||
local function test_udt_parser_comments()
|
||||
local content = [[
|
||||
TYPE "comment_test"
|
||||
TITLE = Comment Test
|
||||
VERSION : 0.1
|
||||
//This is a type comment
|
||||
STRUCT
|
||||
member1 : Bool; //first member
|
||||
member2 : Int; //second member
|
||||
END_STRUCT;
|
||||
|
||||
END_TYPE
|
||||
]]
|
||||
local udt = udt_parser.parse_udt_content(content, "comment.udt")
|
||||
test.assert_not_nil(udt, "parse_udt_content should return udt")
|
||||
test.assert_equals(udt.comment, "This is a type comment", "type comment should be extracted")
|
||||
test.assert_equals(udt.members[1].comment, "first member", "member comment should be extracted")
|
||||
end
|
||||
|
||||
local function test_udt_parser_array()
|
||||
local content = [[
|
||||
TYPE "array_test"
|
||||
TITLE = Array Test
|
||||
VERSION : 0.1
|
||||
STRUCT
|
||||
arr : Array[0..9] of Int;
|
||||
arr2 : Array[0..5, 0..3] of Bool;
|
||||
END_STRUCT;
|
||||
|
||||
END_TYPE
|
||||
]]
|
||||
local udt = udt_parser.parse_udt_content(content, "array.udt")
|
||||
test.assert_not_nil(udt, "parse_udt_content should return udt")
|
||||
test.assert(udt.members[1].type:match("Array"), "array type should be preserved")
|
||||
end
|
||||
|
||||
local function test_udt_cache_operations()
|
||||
udt_parser.clear_cache()
|
||||
test.assert_equals(udt_parser.get_cache_count(), 0, "cache should be empty after clear")
|
||||
|
||||
local content = [[
|
||||
TYPE "cached_udt"
|
||||
TITLE = Cached UDT
|
||||
VERSION : 0.1
|
||||
STRUCT
|
||||
m1 : Bool;
|
||||
END_STRUCT;
|
||||
|
||||
END_TYPE
|
||||
]]
|
||||
local udt = udt_parser.parse_udt_content(content, "cached.udt")
|
||||
if udt then
|
||||
udt_parser.register_inline_udt(udt)
|
||||
end
|
||||
local cached = udt_parser.get_udt("cached_udt")
|
||||
test.assert_not_nil(cached, "UDT should be cached")
|
||||
test.assert_equals(udt_parser.get_cache_count(), 1, "cache should have 1 entry")
|
||||
|
||||
local names = udt_parser.get_all_udt_names()
|
||||
test.assert_equals(#names, 1, "should have 1 UDT name")
|
||||
test.assert_equals(names[1], "cached_udt", "UDT name should match")
|
||||
end
|
||||
|
||||
local function test_udt_is_type()
|
||||
udt_parser.clear_cache()
|
||||
|
||||
local content = [[
|
||||
TYPE "known_type"
|
||||
TITLE = Known
|
||||
VERSION : 0.1
|
||||
STRUCT
|
||||
m1 : Bool;
|
||||
END_STRUCT;
|
||||
|
||||
END_TYPE
|
||||
]]
|
||||
local udt = udt_parser.parse_udt_content(content, "known.udt")
|
||||
if udt then
|
||||
udt_parser.register_inline_udt(udt)
|
||||
end
|
||||
|
||||
test.assert(udt_parser.is_udt_type("known_type"), "known_type should be recognized")
|
||||
test.assert(udt_parser.is_udt_type('"known_type"'), "quoted known_type should be recognized")
|
||||
test.assert(not udt_parser.is_udt_type("unknown_type"), "unknown_type should not be recognized")
|
||||
end
|
||||
|
||||
local function test_udt_members()
|
||||
udt_parser.clear_cache()
|
||||
|
||||
local content1 = [[
|
||||
TYPE "inner_type"
|
||||
TITLE = Inner
|
||||
VERSION : 0.1
|
||||
STRUCT
|
||||
x : Int;
|
||||
END_STRUCT;
|
||||
|
||||
END_TYPE
|
||||
]]
|
||||
local content2 = [[
|
||||
TYPE "outer_type"
|
||||
TITLE = Outer
|
||||
VERSION : 0.1
|
||||
STRUCT
|
||||
inner : "inner_type";
|
||||
simple : Bool;
|
||||
END_STRUCT;
|
||||
|
||||
END_TYPE
|
||||
]]
|
||||
local udt1 = udt_parser.parse_udt_content(content1, "inner.udt")
|
||||
local udt2 = udt_parser.parse_udt_content(content2, "outer.udt")
|
||||
if udt1 then udt_parser.register_inline_udt(udt1) end
|
||||
if udt2 then udt_parser.register_inline_udt(udt2) end
|
||||
|
||||
local members = udt_parser.get_udt_members("outer_type")
|
||||
test.assert_not_nil(members, "members should not be nil")
|
||||
if members and #members > 0 then
|
||||
test.assert_equals(members[1].name, "inner", "first member name")
|
||||
test.assert_equals(members[1].is_udt, true, "first member is UDT type")
|
||||
end
|
||||
end
|
||||
|
||||
local function test_udt_reference_files()
|
||||
udt_parser.clear_cache()
|
||||
|
||||
local ref_path = test.REF_PROJECT .. "/PLC data types/99_Library/Manz Siemens Standard/01_Machine/LMS_typePartCounter.udt"
|
||||
local file = io.open(ref_path, "r")
|
||||
if not file then
|
||||
print("Warning: Reference UDT file not found: " .. ref_path)
|
||||
return
|
||||
end
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
|
||||
local udt = udt_parser.parse_udt_content(content, ref_path)
|
||||
if udt then
|
||||
udt_parser.register_inline_udt(udt)
|
||||
end
|
||||
test.assert_not_nil(udt, "should parse reference UDT file")
|
||||
test.assert_equals(udt.name, "LMS_typePartCounter", "UDT name should match")
|
||||
test.assert_gt(#udt.members, 0, "should have members")
|
||||
|
||||
local cached = udt_parser.get_udt("LMS_typePartCounter")
|
||||
test.assert_not_nil(cached, "UDT should be cached")
|
||||
end
|
||||
|
||||
local function test_udt_multiple_reference_files()
|
||||
udt_parser.clear_cache()
|
||||
|
||||
local base_path = test.REF_PROJECT .. "/PLC data types/99_Library/Manz Siemens Standard/"
|
||||
|
||||
local files = {
|
||||
base_path .. "01_Machine/LMS_typePartCounter.udt",
|
||||
base_path .. "01_Machine/LMS_typePartCounterCommands.udt",
|
||||
base_path .. "01_Machine/LMS_typePartCounterStatus.udt",
|
||||
}
|
||||
|
||||
local parsed_count = 0
|
||||
for _, filepath in ipairs(files) do
|
||||
local file = io.open(filepath, "r")
|
||||
if file then
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
local udt = udt_parser.parse_udt_content(content, filepath)
|
||||
if udt then
|
||||
udt_parser.register_inline_udt(udt)
|
||||
parsed_count = parsed_count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test.assert_gt(parsed_count, 0, "should parse at least one reference UDT")
|
||||
test.assert_equals(udt_parser.get_cache_count(), parsed_count, "cache should have correct count")
|
||||
end
|
||||
|
||||
local function run_udt_tests()
|
||||
test.run_suite("UDT Parser Tests", function()
|
||||
print("\n--- Basic Tests ---")
|
||||
test_udt_parser_basic()
|
||||
test_udt_parser_attributes()
|
||||
test_udt_parser_comments()
|
||||
test_udt_parser_array()
|
||||
|
||||
print("\n--- Cache Tests ---")
|
||||
test_udt_cache_operations()
|
||||
test_udt_is_type()
|
||||
test_udt_members()
|
||||
|
||||
print("\n--- Reference Project Tests ---")
|
||||
test_udt_reference_files()
|
||||
test_udt_multiple_reference_files()
|
||||
end)
|
||||
end
|
||||
|
||||
if vim then
|
||||
vim.api.nvim_create_user_command("TestUdt", function()
|
||||
run_udt_tests()
|
||||
end, {})
|
||||
end
|
||||
|
||||
run_udt_tests()
|
||||
Reference in New Issue
Block a user