test: add formatter tests
- Add comprehensive formatter test suite with 38 tests - Test multiline assignments, FB calls, control structures - Test IF, FOR, WHILE, CASE, REPEAT statements - Test regions, structs, var sections - Update AGENTS.md with formatter test command
This commit is contained in:
@@ -24,6 +24,7 @@ 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_formatter.lua # Run formatter tests
|
||||
lua test/test_plc_json.lua # Run plc_json tests
|
||||
lua test/test_integration.lua # Run integration tests
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ local function run_all_tests()
|
||||
run_test_file("test_udt.lua")
|
||||
run_test_file("test_db.lua")
|
||||
run_test_file("test_fb.lua")
|
||||
run_test_file("test_formatter.lua")
|
||||
run_test_file("test_plc_json.lua")
|
||||
run_test_file("test_integration.lua")
|
||||
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
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("/test$", "")
|
||||
if script_path == "" then script_path = "." end
|
||||
|
||||
local formatter = dofile(script_path .. "/src/formatter.lua")
|
||||
|
||||
local function test_formatter_basic()
|
||||
local content = [[FUNCTION "TestFunc" : Int
|
||||
VAR_INPUT
|
||||
a : Int;
|
||||
END_VAR
|
||||
BEGIN
|
||||
|
||||
END_FUNCTION
|
||||
]]
|
||||
local result = formatter.format_document(content, {})
|
||||
test.assert_not_nil(result, "should return formatting result")
|
||||
test.assert(result[1].newText, "should have newText")
|
||||
end
|
||||
|
||||
local function test_formatter_function_block()
|
||||
local content = [[FUNCTION_BLOCK "TestFB"
|
||||
VAR_INPUT
|
||||
enable : Bool;
|
||||
value : Int;
|
||||
END_VAR
|
||||
|
||||
VAR_OUTPUT
|
||||
done : Bool;
|
||||
END_VAR
|
||||
|
||||
BEGIN
|
||||
#done := #enable;
|
||||
END_FUNCTION_BLOCK
|
||||
]]
|
||||
local result = formatter.format_document(content, {})
|
||||
test.assert_not_nil(result, "should return formatting result")
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("FUNCTION_BLOCK"), "should preserve FUNCTION_BLOCK")
|
||||
test.assert(formatted:match("VAR_INPUT"), "should preserve VAR_INPUT")
|
||||
end
|
||||
|
||||
local function test_formatter_if_statement()
|
||||
local content = [[IF #x > 5 THEN
|
||||
#y := 1;
|
||||
ELSIF #x > 3 THEN
|
||||
#y := 2;
|
||||
ELSE
|
||||
#y := 0;
|
||||
END_IF;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("IF"), "should preserve IF")
|
||||
test.assert(formatted:match("THEN"), "should preserve THEN")
|
||||
test.assert(formatted:match("END_IF"), "should preserve END_IF")
|
||||
end
|
||||
|
||||
local function test_formatter_for_loop()
|
||||
local content = [[FOR #i := 0 TO 10 BY 1 DO
|
||||
#arr[#i] := #i;
|
||||
END_FOR;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("FOR"), "should preserve FOR")
|
||||
test.assert(formatted:match("END_FOR"), "should preserve END_FOR")
|
||||
end
|
||||
|
||||
local function test_formatter_case_statement()
|
||||
local content = [[CASE #x OF
|
||||
1: #y := 10;
|
||||
2: #y := 20;
|
||||
ELSE
|
||||
#y := 0;
|
||||
END_CASE;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("CASE"), "should preserve CASE")
|
||||
test.assert(formatted:match("END_CASE"), "should preserve END_CASE")
|
||||
end
|
||||
|
||||
local function test_formatter_comments()
|
||||
local content = [[// This is a comment
|
||||
(* This is a block comment *)
|
||||
(* Another block comment *)
|
||||
// Another line comment
|
||||
#x := 1;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("This is a comment"), "should preserve line comments")
|
||||
end
|
||||
|
||||
local function test_formatter_multiline_assignment_simple()
|
||||
local content = [[#x := 1 AND
|
||||
2 AND
|
||||
3;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("#x := 1 AND"), "should preserve assignment start")
|
||||
end
|
||||
|
||||
local function test_formatter_multiline_assignment_with_hash()
|
||||
local content = [[#statWarning := #instTurn.warning OR
|
||||
#instPusher.warning OR
|
||||
#instClamp.warning;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("#statWarning :="), "should preserve assignment start")
|
||||
test.assert(formatted:match("#instClamp.warning;$"), "should have continuation")
|
||||
end
|
||||
|
||||
local function test_formatter_multiline_assignment_complex()
|
||||
local content = [[#interface.status.main.inBasePosition := #equipmentData.turn.status.homePos AND
|
||||
#equipmentData.pusher.status.homePos AND
|
||||
NOT #equipmentData.conveyor.status.forward AND
|
||||
NOT #equipmentData.conveyor.status.backward;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("#interface.status.main.inBasePosition :="), "should preserve first line")
|
||||
test.assert(formatted:match("NOT #equipmentData"), "should preserve NOT continuation")
|
||||
end
|
||||
|
||||
local function test_formatter_multiline_assignment_ends_properly()
|
||||
local content = [[#first := 1 AND
|
||||
2 AND
|
||||
3;
|
||||
|
||||
#second := 10;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("#first := 1"), "should preserve first assignment")
|
||||
test.assert(formatted:match("#second := 10"), "should start new assignment properly")
|
||||
end
|
||||
|
||||
local function test_formatter_fb_call_single_line()
|
||||
local content = [[#instMyFB(enable := #x, done => #y);]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("#instMyFB"), "should preserve FB call")
|
||||
end
|
||||
|
||||
local function test_formatter_fb_call_multiline()
|
||||
local content = [[#instMyFB(
|
||||
enable := #x,
|
||||
done => #y
|
||||
);]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("#instMyFB"), "should preserve FB call")
|
||||
end
|
||||
|
||||
local function test_formatter_fb_call_with_long_params()
|
||||
local content = [[#instPartCounter(enable := #unitStatus.currentSubMode <> "DRY_RUN",
|
||||
incPartIn := #equipmentData.partCounter.commands.incPartIn,
|
||||
incGoodPart := #equipmentData.partCounter.commands.incGoodPart,
|
||||
resetCntr := #equipmentData.partCounter.commands.resetCntr,
|
||||
status => #equipmentData.partCounter.status);]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("#instPartCounter"), "should preserve FB call")
|
||||
test.assert(formatted:match("status =>"), "should preserve output param")
|
||||
end
|
||||
|
||||
local function test_formatter_region()
|
||||
local content = [[REGION MyRegion
|
||||
// code
|
||||
#x := 1;
|
||||
END_REGION;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("REGION"), "should preserve REGION")
|
||||
test.assert(formatted:match("END_REGION"), "should preserve END_REGION")
|
||||
end
|
||||
|
||||
local function test_formatter_struct()
|
||||
local content = [[TYPE MyStruct
|
||||
STRUCT
|
||||
x : Int;
|
||||
y : Bool;
|
||||
END_STRUCT;
|
||||
END_TYPE
|
||||
]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("TYPE"), "should preserve TYPE")
|
||||
test.assert(formatted:match("STRUCT"), "should preserve STRUCT")
|
||||
end
|
||||
|
||||
local function test_formatter_var_sections()
|
||||
local content = [[VAR_INPUT
|
||||
in1 : Bool;
|
||||
in2 : Int;
|
||||
END_VAR
|
||||
|
||||
VAR_OUTPUT
|
||||
out1 : Bool;
|
||||
END_VAR
|
||||
|
||||
VAR
|
||||
stat1 : Int;
|
||||
END_VAR
|
||||
]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("VAR_INPUT"), "should preserve VAR_INPUT")
|
||||
test.assert(formatted:match("VAR_OUTPUT"), "should preserve VAR_OUTPUT")
|
||||
test.assert(formatted:match("\nVAR\n"), "should preserve VAR")
|
||||
end
|
||||
|
||||
local function test_formatter_attribute_block()
|
||||
local content = [[VAR
|
||||
stat1{ S7_SetPoint := 'True' } : Int;
|
||||
stat2 : Bool;
|
||||
END_VAR
|
||||
]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("stat1"), "should preserve variable name")
|
||||
end
|
||||
|
||||
local function test_formatter_while_loop()
|
||||
local content = [[WHILE #x < 10 DO
|
||||
#x := #x + 1;
|
||||
END_WHILE;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("WHILE"), "should preserve WHILE")
|
||||
test.assert(formatted:match("END_WHILE"), "should preserve END_WHILE")
|
||||
end
|
||||
|
||||
local function test_formatter_repeat_loop()
|
||||
local content = [[REPEAT
|
||||
#x := #x + 1;
|
||||
UNTIL #x >= 10;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("REPEAT"), "should preserve REPEAT")
|
||||
test.assert(formatted:match("UNTIL"), "should preserve UNTIL")
|
||||
end
|
||||
|
||||
local function test_formatter_assignment_with_string()
|
||||
local content = [[#str := "Hello World";]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match('"Hello World"'), "should preserve string")
|
||||
end
|
||||
|
||||
local function test_formatter_assignment_with_array()
|
||||
local content = [[#arr[0] := 1;
|
||||
#arr[1] := 2;]]
|
||||
local result = formatter.format_document(content, {})
|
||||
local formatted = result[1].newText
|
||||
test.assert(formatted:match("#arr%[0%]"), "should preserve array access")
|
||||
end
|
||||
|
||||
local function run_formatter_tests()
|
||||
test.run_suite("Formatter Tests", function()
|
||||
print("\n--- Basic Tests ---")
|
||||
test_formatter_basic()
|
||||
test_formatter_function_block()
|
||||
test_formatter_if_statement()
|
||||
test_formatter_for_loop()
|
||||
test_formatter_case_statement()
|
||||
test_formatter_comments()
|
||||
test_formatter_while_loop()
|
||||
test_formatter_repeat_loop()
|
||||
test_formatter_region()
|
||||
test_formatter_struct()
|
||||
test_formatter_var_sections()
|
||||
test_formatter_attribute_block()
|
||||
test_formatter_assignment_with_string()
|
||||
test_formatter_assignment_with_array()
|
||||
|
||||
print("\n--- Multiline Assignment Tests ---")
|
||||
test_formatter_multiline_assignment_simple()
|
||||
test_formatter_multiline_assignment_with_hash()
|
||||
test_formatter_multiline_assignment_complex()
|
||||
test_formatter_multiline_assignment_ends_properly()
|
||||
|
||||
print("\n--- FB Call Tests ---")
|
||||
test_formatter_fb_call_single_line()
|
||||
test_formatter_fb_call_multiline()
|
||||
test_formatter_fb_call_with_long_params()
|
||||
end)
|
||||
end
|
||||
|
||||
if vim then
|
||||
vim.api.nvim_create_user_command("TestFormatter", function()
|
||||
run_formatter_tests()
|
||||
end, {})
|
||||
end
|
||||
|
||||
run_formatter_tests()
|
||||
Reference in New Issue
Block a user