Merge branch 'feat/testing_framework' into develop
This commit is contained in:
@@ -18,6 +18,18 @@ tree-sitter parse <file.scl> # Parse single SCL file
|
|||||||
# Lua Linting
|
# Lua Linting
|
||||||
luacheck src/ # Lint LSP server code
|
luacheck src/ # Lint LSP server code
|
||||||
luacheck lua/scl/ # Lint plugin modules
|
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
|
## Project Structure
|
||||||
|
|||||||
@@ -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,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,124 @@
|
|||||||
|
local test = dofile("test/test_harness.lua")
|
||||||
|
|
||||||
|
package.path = package.path .. ";src/?.lua"
|
||||||
|
|
||||||
|
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 test_plc_json_load_types()
|
||||||
|
local root_dir = test.REF_PROJECT
|
||||||
|
local types = plc_json.load_types_from_workspace(root_dir)
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(types) do count = count + 1 end
|
||||||
|
test.assert_gt(count, 0, "should load types from workspace")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_plc_json_struct_type()
|
||||||
|
local content = [[
|
||||||
|
{
|
||||||
|
"dataTypes": [
|
||||||
|
{
|
||||||
|
"name": "TestStruct",
|
||||||
|
"struct": [
|
||||||
|
{ "name": "field1", "dataTypeName": "Bool" },
|
||||||
|
{ "name": "field2", "dataTypeName": "Int" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
local json = dofile(script_path .. "/src/json.lua")
|
||||||
|
local data = json.decode(content)
|
||||||
|
test.assert_not_nil(data, "should decode JSON")
|
||||||
|
|
||||||
|
local types = plc_json.load_types_from_workspace("")
|
||||||
|
test.assert_not_nil(types, "should return types table")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_plc_json_alias_type()
|
||||||
|
local content = [[
|
||||||
|
{
|
||||||
|
"dataTypes": [
|
||||||
|
{
|
||||||
|
"name": "MyInt",
|
||||||
|
"baseType": "Int"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
local json = dofile(script_path .. "/src/json.lua")
|
||||||
|
local data = json.decode(content)
|
||||||
|
test.assert_not_nil(data, "should decode JSON alias")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_plc_json_cache()
|
||||||
|
plc_json.clear_cache()
|
||||||
|
|
||||||
|
local types1 = plc_json.get_types(test.REF_PROJECT)
|
||||||
|
local count1 = 0
|
||||||
|
for _ in pairs(types1) do count1 = count1 + 1 end
|
||||||
|
test.assert_gt(count1, 0, "should load types")
|
||||||
|
|
||||||
|
local types2 = plc_json.get_types(test.REF_PROJECT)
|
||||||
|
local count2 = 0
|
||||||
|
for _ in pairs(types2) do count2 = count2 + 1 end
|
||||||
|
test.assert_gt(count2, 0, "should return cached types")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_plc_json_scl_parsing()
|
||||||
|
local root_dir = test.REF_PROJECT
|
||||||
|
local types = plc_json.load_types_from_workspace(root_dir)
|
||||||
|
|
||||||
|
local found_udt = false
|
||||||
|
for name, typ in pairs(types) do
|
||||||
|
if typ.source == "udt_file" or typ.source == "scl_file" then
|
||||||
|
found_udt = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not found_udt then
|
||||||
|
print("Note: No UDT/SCL types found in reference project (may be normal)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function test_plc_json_db_parsing()
|
||||||
|
local root_dir = test.REF_PROJECT
|
||||||
|
local types = plc_json.load_types_from_workspace(root_dir)
|
||||||
|
|
||||||
|
local found_db = false
|
||||||
|
for name, typ in pairs(types) do
|
||||||
|
if typ.source == "db_file" then
|
||||||
|
found_db = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not found_db then
|
||||||
|
print("Note: No DB types found in reference project (may be normal)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run_plc_json_tests()
|
||||||
|
test.run_suite("PLC JSON Tests", function()
|
||||||
|
print("\n--- Workspace Tests ---")
|
||||||
|
test_plc_json_load_types()
|
||||||
|
test_plc_json_scl_parsing()
|
||||||
|
test_plc_json_db_parsing()
|
||||||
|
|
||||||
|
print("\n--- Cache Tests ---")
|
||||||
|
test_plc_json_cache()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
if vim then
|
||||||
|
vim.api.nvim_create_user_command("TestPlcJson", function()
|
||||||
|
run_plc_json_tests()
|
||||||
|
end, {})
|
||||||
|
end
|
||||||
|
|
||||||
|
run_plc_json_tests()
|
||||||
Reference in New Issue
Block a user