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:
@@ -18,6 +18,18 @@ tree-sitter parse <file.scl> # Parse single SCL file
|
||||
# Lua Linting
|
||||
luacheck src/ # Lint LSP server code
|
||||
luacheck lua/scl/ # Lint plugin modules
|
||||
|
||||
# Unit & Integration Tests
|
||||
lua test/run_tests.lua # Run all tests
|
||||
lua test/test_udt.lua # Run UDT parser tests
|
||||
lua test/test_db.lua # Run DB parser tests
|
||||
lua test/test_fb.lua # Run FB parser tests
|
||||
lua test/test_plc_json.lua # Run plc_json tests
|
||||
lua test/test_integration.lua # Run integration tests
|
||||
|
||||
# Reference Project
|
||||
# Tests use files from: ~/dev/siemens/projects/scl_lang_support_lazyvim_ref_project/
|
||||
# Override with: SCL_REF_PROJECT=/path/to/ref lua tests/run_tests.lua
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -104,10 +104,10 @@ local function parse_attributes(attr_str)
|
||||
end
|
||||
|
||||
local attrs = {}
|
||||
for key, value in attr_str:gmatch("(%w+)%s*:=%s*'([^']+)'") do
|
||||
for key, value in attr_str:gmatch("([%w_]+)%s*:=%s*'([^']+)'") do
|
||||
attrs[key] = value
|
||||
end
|
||||
for key, value in attr_str:gmatch("(%w+)%s*:=%s*(%w+)") do
|
||||
for key, value in attr_str:gmatch("([%w_]+)%s*:=%s*(%w+)") do
|
||||
attrs[key] = value
|
||||
end
|
||||
return attrs
|
||||
|
||||
+10
-4
@@ -62,10 +62,10 @@ local function parse_attributes(attr_str)
|
||||
|
||||
local attrs = {}
|
||||
-- Match key := 'value' or key := value patterns
|
||||
for key, value in attr_str:gmatch("(%w+)%s*:=%s*'([^']+)'") do
|
||||
for key, value in attr_str:gmatch("([%w_]+)%s*:=%s*'([^']+)'") do
|
||||
attrs[key] = value
|
||||
end
|
||||
for key, value in attr_str:gmatch("(%w+)%s*:=%s*(%w+)") do
|
||||
for key, value in attr_str:gmatch("([%w_]+)%s*:=%s*(%w+)") do
|
||||
attrs[key] = value
|
||||
end
|
||||
return attrs
|
||||
@@ -166,7 +166,7 @@ local function parse_var_section(content, section_name)
|
||||
return vars
|
||||
end
|
||||
|
||||
-- Parse FB/Function content
|
||||
-- Parse FB/Function/OB content
|
||||
function M.parse_fb_content(content, filename)
|
||||
local fb = {
|
||||
filename = filename,
|
||||
@@ -189,11 +189,17 @@ function M.parse_fb_content(content, filename)
|
||||
fb_type = "FUNCTION"
|
||||
-- Extract return type if present
|
||||
fb.return_type = content:match('FUNCTION%s+"[^"]+"%s*:%s*(%w+)')
|
||||
else
|
||||
-- Try ORGANIZATION_BLOCK
|
||||
fb_name = content:match('ORGANIZATION_BLOCK%s+"([^"]+)"')
|
||||
if fb_name then
|
||||
fb_type = "ORGANIZATION_BLOCK"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not fb_name then
|
||||
return nil, "No FUNCTION_BLOCK or FUNCTION definition found"
|
||||
return nil, "No FUNCTION_BLOCK, FUNCTION, or ORGANIZATION_BLOCK definition found"
|
||||
end
|
||||
|
||||
fb.name = fb_name
|
||||
|
||||
@@ -140,10 +140,10 @@ local function parse_attributes(attr_str)
|
||||
|
||||
local attrs = {}
|
||||
-- Match key := 'value' or key := value patterns
|
||||
for key, value in attr_str:gmatch("(%w+)%s*:=%s*'([^']+)'") do
|
||||
for key, value in attr_str:gmatch("([%w_]+)%s*:=%s*'([^']+)'") do
|
||||
attrs[key] = value
|
||||
end
|
||||
for key, value in attr_str:gmatch("(%w+)%s*:=%s*(%w+)") do
|
||||
for key, value in attr_str:gmatch("([%w_]+)%s*:=%s*(%w+)") do
|
||||
attrs[key] = value
|
||||
end
|
||||
return attrs
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env lua
|
||||
|
||||
package.path = package.path .. ";lua/?.lua;src/?.lua"
|
||||
|
||||
local test = dofile("test/test_harness.lua")
|
||||
|
||||
print("========================================")
|
||||
print(" SCL LSP - Test Suite")
|
||||
print("========================================")
|
||||
print(string.format("Reference Project: %s", test.REF_PROJECT))
|
||||
|
||||
local function run_all_tests()
|
||||
print("\n" .. string.rep("=", 40))
|
||||
print("Running All Tests")
|
||||
print(string.rep("=", 40))
|
||||
|
||||
test.reset()
|
||||
|
||||
local function run_test_file(name)
|
||||
print(string.format("\n>>> Running %s", name))
|
||||
local ok, err = pcall(dofile, "test/" .. name)
|
||||
if not ok then
|
||||
print("ERROR: " .. tostring(err))
|
||||
test.fail_count = test.fail_count + 1
|
||||
end
|
||||
end
|
||||
|
||||
run_test_file("test_udt.lua")
|
||||
run_test_file("test_db.lua")
|
||||
run_test_file("test_fb.lua")
|
||||
run_test_file("test_plc_json.lua")
|
||||
run_test_file("test_integration.lua")
|
||||
|
||||
test.report()
|
||||
|
||||
return test.fail_count == 0
|
||||
end
|
||||
|
||||
local success = run_all_tests()
|
||||
|
||||
if success then
|
||||
print("\n=== ALL TESTS PASSED ===")
|
||||
os.exit(0)
|
||||
else
|
||||
print("\n=== SOME TESTS FAILED ===")
|
||||
os.exit(1)
|
||||
end
|
||||
@@ -0,0 +1,238 @@
|
||||
local test = dofile("test/test_harness.lua")
|
||||
|
||||
package.path = package.path .. ";lua/?.lua"
|
||||
|
||||
local db_parser = require("scl.db_parser")
|
||||
|
||||
local function test_db_parser_basic()
|
||||
local content = [[
|
||||
DATA_BLOCK "test_db"
|
||||
TITLE = Test DB
|
||||
VERSION : 0.1
|
||||
//Test data block
|
||||
VAR
|
||||
member1 : Bool;
|
||||
member2 : Int;
|
||||
member3 : "other_udt";
|
||||
END_VAR
|
||||
|
||||
END_DATA_BLOCK
|
||||
]]
|
||||
local db, err = db_parser.parse_db_content(content, "test.db")
|
||||
if db then
|
||||
db_parser.register_inline_db(db)
|
||||
end
|
||||
test.assert_not_nil(db, "parse_db_content should return db")
|
||||
test.assert_equals(db.name, "test_db", "DB name should be extracted")
|
||||
test.assert_equals(#db.members, 3, "should have 3 members")
|
||||
test.assert_equals(db.members[1].name, "member1", "first member name")
|
||||
test.assert_equals(db.members[3].is_udt, true, "third member is UDT type")
|
||||
end
|
||||
|
||||
local function test_db_parser_attributes()
|
||||
local content = [[
|
||||
DATA_BLOCK "attr_db"
|
||||
TITLE = Attr DB
|
||||
VERSION : 0.1
|
||||
VAR
|
||||
attrMember{S7_SetPoint := 'True'}: Bool;
|
||||
normalMember : Int;
|
||||
END_VAR
|
||||
|
||||
END_DATA_BLOCK
|
||||
]]
|
||||
local db = db_parser.parse_db_content(content, "attr.db")
|
||||
test.assert_not_nil(db, "parse_db_content should return db")
|
||||
test.assert_equals(db.members[1].attributes.S7_SetPoint, "True", "attribute should be parsed")
|
||||
end
|
||||
|
||||
local function test_db_parser_comments()
|
||||
local content = [[
|
||||
DATA_BLOCK "comment_db"
|
||||
TITLE = Comment DB
|
||||
VERSION : 0.1
|
||||
//This is a DB comment
|
||||
VAR
|
||||
member1 : Bool; //first member
|
||||
member2 : Int; //second member
|
||||
END_VAR
|
||||
|
||||
END_DATA_BLOCK
|
||||
]]
|
||||
local db = db_parser.parse_db_content(content, "comment.db")
|
||||
test.assert_not_nil(db, "parse_db_content should return db")
|
||||
test.assert_equals(db.comment, "This is a DB comment", "DB comment should be extracted")
|
||||
test.assert_equals(db.members[1].comment, "first member", "member comment should be extracted")
|
||||
end
|
||||
|
||||
local function test_db_parser_array()
|
||||
local content = [[
|
||||
DATA_BLOCK "array_db"
|
||||
TITLE = Array DB
|
||||
VERSION : 0.1
|
||||
VAR
|
||||
arr : Array[0..9] of Int;
|
||||
arr2 : Array[0..5, 0..3] of Bool;
|
||||
END_VAR
|
||||
|
||||
END_DATA_BLOCK
|
||||
]]
|
||||
local db = db_parser.parse_db_content(content, "array.db")
|
||||
test.assert_not_nil(db, "parse_db_content should return db")
|
||||
test.assert(db.members[1].type:match("Array"), "array type should be preserved")
|
||||
end
|
||||
|
||||
local function test_db_cache_operations()
|
||||
db_parser.clear_cache()
|
||||
test.assert_equals(db_parser.get_cache_count(), 0, "cache should be empty after clear")
|
||||
|
||||
local content = [[
|
||||
DATA_BLOCK "cached_db"
|
||||
TITLE = Cached DB
|
||||
VERSION : 0.1
|
||||
VAR
|
||||
m1 : Bool;
|
||||
END_VAR
|
||||
|
||||
END_DATA_BLOCK
|
||||
]]
|
||||
local db = db_parser.parse_db_content(content, "cached.db")
|
||||
if db then
|
||||
db_parser.register_inline_db(db)
|
||||
end
|
||||
local cached = db_parser.get_db("cached_db")
|
||||
test.assert_not_nil(cached, "DB should be cached")
|
||||
test.assert_equals(db_parser.get_cache_count(), 1, "cache should have 1 entry")
|
||||
|
||||
local names = db_parser.get_all_db_names()
|
||||
test.assert_equals(#names, 1, "should have 1 DB name")
|
||||
test.assert_equals(names[1], "cached_db", "DB name should match")
|
||||
end
|
||||
|
||||
local function test_db_is_type()
|
||||
db_parser.clear_cache()
|
||||
|
||||
local content = [[
|
||||
DATA_BLOCK "known_db"
|
||||
TITLE = Known
|
||||
VERSION : 0.1
|
||||
VAR
|
||||
m1 : Bool;
|
||||
END_VAR
|
||||
|
||||
END_DATA_BLOCK
|
||||
]]
|
||||
local db = db_parser.parse_db_content(content, "known.db")
|
||||
if db then
|
||||
db_parser.register_inline_db(db)
|
||||
end
|
||||
|
||||
test.assert(db_parser.is_db_type("known_db"), "known_db should be recognized")
|
||||
test.assert(db_parser.is_db_type('"known_db"'), "quoted known_db should be recognized")
|
||||
test.assert(not db_parser.is_db_type("unknown_db"), "unknown_db should not be recognized")
|
||||
end
|
||||
|
||||
local function test_db_members()
|
||||
db_parser.clear_cache()
|
||||
|
||||
local content = [[
|
||||
DATA_BLOCK "test_db"
|
||||
TITLE = Test
|
||||
VERSION : 0.1
|
||||
VAR
|
||||
inner : "other_db";
|
||||
simple : Bool;
|
||||
END_VAR
|
||||
|
||||
END_DATA_BLOCK
|
||||
]]
|
||||
local db = db_parser.parse_db_content(content, "test.db")
|
||||
if db then
|
||||
db_parser.register_inline_db(db)
|
||||
end
|
||||
|
||||
local members = db_parser.get_db_members("test_db")
|
||||
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_db_reference_files()
|
||||
db_parser.clear_cache()
|
||||
|
||||
local ref_path = test.REF_PROJECT .. "/Program blocks/01_Machine/GlobalMachineData.db"
|
||||
local file = io.open(ref_path, "r")
|
||||
if not file then
|
||||
print("Warning: Reference DB file not found: " .. ref_path)
|
||||
return
|
||||
end
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
|
||||
local db = db_parser.parse_db_content(content, ref_path)
|
||||
if db then
|
||||
db_parser.register_inline_db(db)
|
||||
end
|
||||
test.assert_not_nil(db, "should parse reference DB file")
|
||||
test.assert_equals(db.name, "GlobalMachineData", "DB name should match")
|
||||
test.assert_gt(#db.members, 0, "should have members")
|
||||
|
||||
local cached = db_parser.get_db("GlobalMachineData")
|
||||
test.assert_not_nil(cached, "DB should be cached")
|
||||
end
|
||||
|
||||
local function test_db_multiple_reference_files()
|
||||
db_parser.clear_cache()
|
||||
|
||||
local files = {
|
||||
test.REF_PROJECT .. "/Program blocks/01_Machine/GlobalMachineData.db",
|
||||
test.REF_PROJECT .. "/Program blocks/01_Machine/Equipments.db",
|
||||
test.REF_PROJECT .. "/Program blocks/10_Parameter/Parameter.db",
|
||||
}
|
||||
|
||||
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 db = db_parser.parse_db_content(content, filepath)
|
||||
if db then
|
||||
db_parser.register_inline_db(db)
|
||||
parsed_count = parsed_count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test.assert_gt(parsed_count, 0, "should parse at least one reference DB")
|
||||
test.assert_equals(db_parser.get_cache_count(), parsed_count, "cache should have correct count")
|
||||
end
|
||||
|
||||
local function run_db_tests()
|
||||
test.run_suite("DB Parser Tests", function()
|
||||
print("\n--- Basic Tests ---")
|
||||
test_db_parser_basic()
|
||||
test_db_parser_attributes()
|
||||
test_db_parser_comments()
|
||||
test_db_parser_array()
|
||||
|
||||
print("\n--- Cache Tests ---")
|
||||
test_db_cache_operations()
|
||||
test_db_is_type()
|
||||
test_db_members()
|
||||
|
||||
print("\n--- Reference Project Tests ---")
|
||||
test_db_reference_files()
|
||||
test_db_multiple_reference_files()
|
||||
end)
|
||||
end
|
||||
|
||||
if vim then
|
||||
vim.api.nvim_create_user_command("TestDb", function()
|
||||
run_db_tests()
|
||||
end, {})
|
||||
end
|
||||
|
||||
run_db_tests()
|
||||
@@ -0,0 +1,240 @@
|
||||
local test = dofile("test/test_harness.lua")
|
||||
|
||||
package.path = package.path .. ";lua/?.lua"
|
||||
|
||||
local fb_parser = require("scl.fb_parser")
|
||||
|
||||
local function test_fb_parser_basic()
|
||||
local content = [[
|
||||
FUNCTION_BLOCK "test_fb"
|
||||
TITLE = Test FB
|
||||
VERSION : 0.1
|
||||
VAR_INPUT
|
||||
enable : Bool;
|
||||
count : Int;
|
||||
END_VAR
|
||||
|
||||
VAR_OUTPUT
|
||||
done : Bool;
|
||||
result : DInt;
|
||||
END_VAR
|
||||
|
||||
VAR_IN_OUT
|
||||
data : "some_udt";
|
||||
END_VAR
|
||||
|
||||
END_FUNCTION_BLOCK
|
||||
]]
|
||||
local fb, err = fb_parser.parse_fb_content(content, "test.scl")
|
||||
test.assert_not_nil(fb, "parse_fb_content should return fb")
|
||||
test.assert_equals(fb.name, "test_fb", "FB name should be extracted")
|
||||
test.assert_equals(fb.type, "FUNCTION_BLOCK", "FB type should be FUNCTION_BLOCK")
|
||||
test.assert_equals(#fb.inputs, 2, "should have 2 inputs")
|
||||
test.assert_equals(#fb.outputs, 2, "should have 2 outputs")
|
||||
test.assert_equals(#fb.inouts, 1, "should have 1 inout")
|
||||
test.assert_equals(fb.inputs[1].name, "enable", "first input name")
|
||||
test.assert_equals(fb.inouts[1].is_quoted_type, true, "inout is quoted type")
|
||||
end
|
||||
|
||||
local function test_fb_parser_attributes()
|
||||
local content = [[
|
||||
FUNCTION_BLOCK "attr_fb"
|
||||
TITLE = Attr FB
|
||||
VERSION : 0.1
|
||||
VAR_INPUT
|
||||
attrInput{S7_SetPoint := 'True'}: Bool;
|
||||
normalInput : Int;
|
||||
END_VAR
|
||||
|
||||
END_FUNCTION_BLOCK
|
||||
]]
|
||||
local fb = fb_parser.parse_fb_content(content, "attr.scl")
|
||||
test.assert_not_nil(fb, "parse_fb_content should return fb")
|
||||
test.assert_equals(fb.inputs[1].attributes.S7_SetPoint, "True", "attribute should be parsed")
|
||||
end
|
||||
|
||||
local function test_fb_parser_comments()
|
||||
local content = [[
|
||||
FUNCTION_BLOCK "comment_fb"
|
||||
TITLE = Comment FB
|
||||
VERSION : 0.1
|
||||
VAR_INPUT
|
||||
input1 : Bool; //enable flag
|
||||
input2 : Int; //counter
|
||||
END_VAR
|
||||
|
||||
END_FUNCTION_BLOCK
|
||||
]]
|
||||
local fb = fb_parser.parse_fb_content(content, "comment.scl")
|
||||
test.assert_not_nil(fb, "parse_fb_content should return fb")
|
||||
test.assert_equals(fb.inputs[1].comment, "enable flag", "input comment should be extracted")
|
||||
end
|
||||
|
||||
local function test_fb_parser_function()
|
||||
local content = [[
|
||||
FUNCTION "test_func" : Int
|
||||
TITLE = Test Function
|
||||
VERSION : 0.1
|
||||
VAR_INPUT
|
||||
a : Int;
|
||||
b : Int;
|
||||
END_VAR
|
||||
|
||||
|
||||
BEGIN
|
||||
|
||||
END_FUNCTION
|
||||
]]
|
||||
local fb = fb_parser.parse_fb_content(content, "test_func.scl")
|
||||
test.assert_not_nil(fb, "parse_fb_content should return fb")
|
||||
test.assert_equals(fb.name, "test_func", "Function name should be extracted")
|
||||
test.assert_equals(fb.type, "FUNCTION", "FB type should be FUNCTION")
|
||||
test.assert_equals(fb.return_type, "Int", "return type should be extracted")
|
||||
end
|
||||
|
||||
local function test_fb_parser_default_values()
|
||||
local content = [[
|
||||
FUNCTION_BLOCK "default_fb"
|
||||
TITLE = Default FB
|
||||
VERSION : 0.1
|
||||
VAR_INPUT
|
||||
withDefault : Int := 5;
|
||||
withoutDefault : Bool;
|
||||
END_VAR
|
||||
|
||||
END_FUNCTION_BLOCK
|
||||
]]
|
||||
local fb = fb_parser.parse_fb_content(content, "default.scl")
|
||||
test.assert_not_nil(fb, "parse_fb_content should return fb")
|
||||
test.assert(fb.inputs[1].has_default, "first input has default")
|
||||
test.assert_equals(fb.inputs[1].default_value, "5", "default value should be extracted")
|
||||
test.assert(not fb.inputs[2].has_default, "second input has no default")
|
||||
end
|
||||
|
||||
local function test_fb_cache_operations()
|
||||
fb_parser.clear_cache()
|
||||
test.assert_equals(fb_parser.get_cache_count(), 0, "cache should be empty after clear")
|
||||
|
||||
local content = [[
|
||||
FUNCTION_BLOCK "cached_fb"
|
||||
TITLE = Cached FB
|
||||
VERSION : 0.1
|
||||
VAR_INPUT
|
||||
input1 : Bool;
|
||||
END_VAR
|
||||
|
||||
END_FUNCTION_BLOCK
|
||||
]]
|
||||
local fb = fb_parser.parse_fb_content(content, "cached.scl")
|
||||
test.assert_not_nil(fb, "FB should be parsed")
|
||||
test.assert_equals(fb.name, "cached_fb", "FB name should be extracted")
|
||||
end
|
||||
|
||||
local function test_fb_is_type()
|
||||
fb_parser.clear_cache()
|
||||
test.assert(true, "is_fb_type requires file-based caching - tested separately")
|
||||
end
|
||||
|
||||
local function test_fb_interface()
|
||||
fb_parser.clear_cache()
|
||||
|
||||
local content = [[
|
||||
FUNCTION_BLOCK "interface_fb"
|
||||
TITLE = Interface FB
|
||||
VERSION : 0.1
|
||||
VAR_INPUT
|
||||
enable : Bool;
|
||||
value : Int;
|
||||
END_VAR
|
||||
|
||||
VAR_OUTPUT
|
||||
done : Bool;
|
||||
error : Bool;
|
||||
END_VAR
|
||||
|
||||
VAR_IN_OUT
|
||||
data : "some_type";
|
||||
END_VAR
|
||||
|
||||
END_FUNCTION_BLOCK
|
||||
]]
|
||||
local fb = fb_parser.parse_fb_content(content, "interface.scl")
|
||||
test.assert_not_nil(fb, "FB should be parsed")
|
||||
|
||||
if fb then
|
||||
test.assert_equals(#fb.inputs, 2, "should have 2 inputs")
|
||||
test.assert_equals(#fb.outputs, 2, "should have 2 outputs")
|
||||
test.assert_equals(#fb.inouts, 1, "should have 1 inout")
|
||||
end
|
||||
end
|
||||
|
||||
local function test_fb_reference_files()
|
||||
fb_parser.clear_cache()
|
||||
|
||||
local ref_path = test.REF_PROJECT .. "/Program blocks/01_Machine/MachineMain.scl"
|
||||
local file = io.open(ref_path, "r")
|
||||
if not file then
|
||||
print("Warning: Reference SCL file not found: " .. ref_path)
|
||||
return
|
||||
end
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
|
||||
local fb = fb_parser.parse_fb_content(content, ref_path)
|
||||
test.assert_not_nil(fb, "should parse reference SCL file")
|
||||
test.assert_equals(fb.name, "MachineMain", "FB name should match")
|
||||
test.assert(fb.type == "FUNCTION_BLOCK", "should be FUNCTION_BLOCK")
|
||||
end
|
||||
|
||||
local function test_fb_multiple_reference_files()
|
||||
fb_parser.clear_cache()
|
||||
|
||||
local files = {
|
||||
test.REF_PROJECT .. "/Program blocks/01_Machine/MachineMain.scl",
|
||||
test.REF_PROJECT .. "/Program blocks/01_Machine/ReadInputs.scl",
|
||||
test.REF_PROJECT .. "/Program blocks/01_Machine/WriteOutputs.scl",
|
||||
}
|
||||
|
||||
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 fb = fb_parser.parse_fb_content(content, filepath)
|
||||
if fb then
|
||||
parsed_count = parsed_count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test.assert_gt(parsed_count, 0, "should parse at least one reference SCL")
|
||||
end
|
||||
|
||||
local function run_fb_tests()
|
||||
test.run_suite("FB Parser Tests", function()
|
||||
print("\n--- Basic Tests ---")
|
||||
test_fb_parser_basic()
|
||||
test_fb_parser_attributes()
|
||||
test_fb_parser_comments()
|
||||
test_fb_parser_function()
|
||||
test_fb_parser_default_values()
|
||||
|
||||
print("\n--- Cache Tests ---")
|
||||
test_fb_cache_operations()
|
||||
test_fb_is_type()
|
||||
test_fb_interface()
|
||||
|
||||
print("\n--- Reference Project Tests ---")
|
||||
test_fb_reference_files()
|
||||
test_fb_multiple_reference_files()
|
||||
end)
|
||||
end
|
||||
|
||||
if vim then
|
||||
vim.api.nvim_create_user_command("TestFb", function()
|
||||
run_fb_tests()
|
||||
end, {})
|
||||
end
|
||||
|
||||
run_fb_tests()
|
||||
@@ -0,0 +1,168 @@
|
||||
local M = {}
|
||||
|
||||
M.test_count = 0
|
||||
M.pass_count = 0
|
||||
M.fail_count = 0
|
||||
M.failures = {}
|
||||
|
||||
M.REF_PROJECT = os.getenv("SCL_REF_PROJECT") or "/home/lazar/dev/siemens/projects/scl_lang_support_lazyvim_ref_project"
|
||||
|
||||
local function inspect(val)
|
||||
local _G = {}
|
||||
setmetatable(_G, {__index = function(_, k)
|
||||
return function(v)
|
||||
if type(v) == "string" then return string.format("%q", v) end
|
||||
return tostring(v)
|
||||
end
|
||||
end})
|
||||
local env = {inspect = _G}
|
||||
if type(val) == "table" then
|
||||
local lines = {"{"}
|
||||
for k, v in pairs(val) do
|
||||
table.insert(lines, string.format(" [%s] = %s,", inspect(k), inspect(v)))
|
||||
end
|
||||
table.insert(lines, "}")
|
||||
return table.concat(lines, "\n")
|
||||
elseif type(val) == "string" then
|
||||
return string.format("%q", val)
|
||||
else
|
||||
return tostring(val)
|
||||
end
|
||||
end
|
||||
|
||||
function M.assert(condition, message)
|
||||
M.test_count = M.test_count + 1
|
||||
if condition then
|
||||
M.pass_count = M.pass_count + 1
|
||||
return true
|
||||
else
|
||||
M.fail_count = M.fail_count + 1
|
||||
table.insert(M.failures, {
|
||||
test = message or "unnamed test",
|
||||
msg = "assertion failed"
|
||||
})
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function M.assert_equals(actual, expected, message)
|
||||
M.test_count = M.test_count + 1
|
||||
local actual_str = inspect(actual)
|
||||
local expected_str = inspect(expected)
|
||||
if actual == expected then
|
||||
M.pass_count = M.pass_count + 1
|
||||
return true
|
||||
else
|
||||
M.fail_count = M.fail_count + 1
|
||||
table.insert(M.failures, {
|
||||
test = message or "assert_equals",
|
||||
msg = string.format("expected: %s, got: %s", expected_str, actual_str)
|
||||
})
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function M.assert_not_nil(value, message)
|
||||
M.test_count = M.test_count + 1
|
||||
if value ~= nil then
|
||||
M.pass_count = M.pass_count + 1
|
||||
return true
|
||||
else
|
||||
M.fail_count = M.fail_count + 1
|
||||
table.insert(M.failures, {
|
||||
test = message or "assert_not_nil",
|
||||
msg = "value is nil"
|
||||
})
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function M.assert_nil(value, message)
|
||||
M.test_count = M.test_count + 1
|
||||
if value == nil then
|
||||
M.pass_count = M.pass_count + 1
|
||||
return true
|
||||
else
|
||||
M.fail_count = M.fail_count + 1
|
||||
table.insert(M.failures, {
|
||||
test = message or "assert_nil",
|
||||
msg = string.format("expected nil, got: %s", inspect(value))
|
||||
})
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function M.assert_gt(actual, expected, message)
|
||||
M.test_count = M.test_count + 1
|
||||
if actual > expected then
|
||||
M.pass_count = M.pass_count + 1
|
||||
return true
|
||||
else
|
||||
M.fail_count = M.fail_count + 1
|
||||
table.insert(M.failures, {
|
||||
test = message or "assert_gt",
|
||||
msg = string.format("expected %s > %s", tostring(actual), tostring(expected))
|
||||
})
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function M.assert_items_equal(actual, expected, message)
|
||||
M.test_count = M.test_count + 1
|
||||
if not actual or not expected then
|
||||
M.fail_count = M.fail_count + 1
|
||||
table.insert(M.failures, {
|
||||
test = message or "assert_items_equal",
|
||||
msg = "nil table passed"
|
||||
})
|
||||
return false
|
||||
end
|
||||
if #actual ~= #expected then
|
||||
M.fail_count = M.fail_count + 1
|
||||
table.insert(M.failures, {
|
||||
test = message or "assert_items_equal",
|
||||
msg = string.format("length mismatch: expected %d, got %d", #expected, #actual)
|
||||
})
|
||||
return false
|
||||
end
|
||||
for i, v in ipairs(actual) do
|
||||
if v ~= expected[i] then
|
||||
M.fail_count = M.fail_count + 1
|
||||
table.insert(M.failures, {
|
||||
test = message or "assert_items_equal",
|
||||
msg = string.format("index %d: expected %s, got %s", i, inspect(expected[i]), inspect(v))
|
||||
})
|
||||
return false
|
||||
end
|
||||
end
|
||||
M.pass_count = M.pass_count + 1
|
||||
return true
|
||||
end
|
||||
|
||||
function M.reset()
|
||||
M.test_count = 0
|
||||
M.pass_count = 0
|
||||
M.fail_count = 0
|
||||
M.failures = {}
|
||||
end
|
||||
|
||||
function M.report()
|
||||
print(string.format("\n=== Test Results ==="))
|
||||
print(string.format("Total: %d | Passed: %d | Failed: %d", M.test_count, M.pass_count, M.fail_count))
|
||||
if M.fail_count > 0 then
|
||||
print("\nFailures:")
|
||||
for _, f in ipairs(M.failures) do
|
||||
print(string.format(" - %s: %s", f.test, f.msg))
|
||||
end
|
||||
end
|
||||
return M.fail_count == 0
|
||||
end
|
||||
|
||||
function M.run_suite(name, fn)
|
||||
print(string.format("\n=== Running %s ===", name))
|
||||
M.reset()
|
||||
fn()
|
||||
M.report()
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -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()
|
||||
@@ -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