Files
tia-lsp/test/test_builtin_instructions.lua
lazar f7f1062b90 feat: test suite expansion + case-insensitive builtin lookup (Phase 6)
Add 3 new test files:

test_grammar_spec.lua (36 tests):
- Block structure: RETURN, GOTO, AUTHOR/FAMILY, VAR DB_SPECIFIC, typed
  DATA_BLOCK, END_STRUCT; semicolon, case-insensitive keywords
- Data types: REF_TO, VARIANT, anonymous STRUCT, ARRAY[*], multidim
  ARRAY, all type_builtin aliases (LWORD, LINT, ULINT, UDINT, DATE,
  TIME_OF_DAY, DATE_AND_TIME, S5TIME, LTIME, DTL, LTOD, LDT)
- Literals: binary (2#), octal (8#), BOOL#TRUE, typed int (INT#),
  combined time (T#1h30m), WSTRING#, compound assignments, CASE labels
- Parser: case-insensitive var_temp, VAR DB_SPECIFIC, lowercase blocks

test_diagnostics_spec.lua (6 tests):
- Unknown attribute warning (SCL005: ExternalWriteable typo)
- Valid attributes pass (ExternalWritable, S7_SetPoint)
- Unknown type warning (SCL003)
- Known types pass (VARIANT, LWORD, DTL)
- IEC types pass (IEC_COUNTER, IEC_TIMER)

test_builtin_instructions.lua (5 tests):
- All 221 spec instructions are known
- All spec data types are known
- Key instructions have parameter signatures
- Total count > 300 functions, > 35 FBs

Fix builtin_instructions.lua:
- Make is_known_function/is_known_function_block/get_parameters
  case-insensitive (check both name:upper() and name) — fixes 45
  mixed-case instruction names like Chars_TO_Strg, AssignmentAttempt
- Add missing Chars_TO_Strg to FUNCTIONS table

Fix test_harness.lua:
- Update REF_PROJECT path to ~/Documents/siemens/...

All 85 tests pass (38 formatter + 3 plc_json + 36 grammar + 6
diagnostics + 5 builtin). 3 grammar tests marked as expected failures
for known edge cases (GOTO labels, typed DB, combined time values).
2026-07-18 20:22:00 +02:00

143 lines
6.0 KiB
Lua

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 builtin = dofile(script_path .. "/src/builtin_instructions.lua")
test.run_suite("Builtin Instructions Coverage Tests", function()
-- 221 spec instructions from keywords.md
local spec_instructions = {
"ABS", "ACK_FCT_WARN", "ACOS", "ASI_CTRL", "ASIN", "AssignmentAttempt",
"ATAN", "ATH", "ATTACH", "ATTR_DB", "BCDCPL", "BITCMP", "BITSUM",
"BLKMOV", "CAN_TINT", "CEIL", "Chars_TO_Strg", "CONCAT", "CONVERT",
"COS", "COUNTER", "CountOfElements", "CTD", "CTRL_PTO", "CTRL_PWM",
"CTU", "CTUD", "DataLogCreate", "DB_ANY_TO_VARIANT", "DCAT", "DECO",
"DELETE", "DELETE_DB", "DEMUX", "Deserialize", "DeviceStates",
"DIS_AIRT", "DP_TOPOL", "DRUM", "EN_AIRT", "ENCO", "ENDIS_PW",
"EN_IRT", "EXP", "FileReadC", "FileWriteC", "FILL", "FILL_BLK",
"FIND", "FLOOR", "FRAC", "F_TRIG", "GADR_LGC", "GATHER", "GATHER_BLK",
"GEN_DIAG", "Gen_UsrMsg", "GEO2LOG", "GEO_LOG", "Get_AlarmResources",
"Get_AlarmState", "GetBlockName", "GET_DIAG", "GET_ERR_ID",
"GET_ERROR", "GetInstanceName", "GetInstancePath", "GETIO",
"GetSymbolForReference", "GetSymbolName", "GetSymbolPath", "GOTO",
"HTA", "INIT_RD", "INSERT", "IO2MOD", "IS_ARRAY", "JOIN", "LEAD_LAG",
"LEFT", "LEN", "LGC_GADR", "LIMIT", "LN", "LOG", "LOG2GEO", "LOG2MOD",
"LOG_GEO", "LOWER_BOUND", "MAX", "MAX_LEN", "MCTA", "MID", "MIN",
"ModuleStates", "MOVE_BLK", "MOVE_BLK_VARIANT", "MoveFromResolvedSymbol",
"MoveResolvedSymbolsFromBuffer", "MoveResolvedSymbolsToBuffer",
"MoveToResolvedSymbol", "MUX", "NORM_X", "PE_CMD",
"PE_DS3_Write_ET200S", "PEEK", "PEEK_BOOL", "PE_Get_Mode_RSP",
"PE_Identify_RSP", "PE_Measurement_List_RSP",
"PE_Measurement_Value_RSP", "PE_PEM_Status_RSP", "PE_START_END",
"PE_WOL", "POKE", "POKE_BLK", "POKE_BOOL", "PRESET_TIMER",
"Program_Alarm", "QRY_CINT", "Random", "RD_ADDR", "RD_DPAR",
"RD_DPARA", "RD_LGADR", "RD_LOC_T", "RDREC", "RD_SYS_T", "READ_BIG",
"READ_DBL", "ReadFromArrayDB", "ReadFromArrayDBL", "READ_LITTLE",
"RecipeExport", "RecipeImport", "REF", "REPLACE", "RESET_TIMER",
"ResolveSymbols", "RE_TRIGR", "RH_CTRL", "RH_GetPrimaryID", "RIGHT",
"ROL", "ROR", "ROUND", "RTM", "R_TRIG", "RUNTIME", "SCALE", "SCALE_X",
"SCATTER", "SCATTER_BLK", "S_CD", "S_COMP", "S_CONV", "S_CU", "SEG",
"SEL", "Serialize", "SET_CINT", "SET_TIMEZONE", "SET_TINT",
"SET_TINTL", "SHL", "SHR", "SIGN", "SIN", "SMC", "S_MOVE", "S_ODT",
"S_ODTS", "S_OFFDT", "S_PEXT", "SPLIT", "S_PULSE", "SQR", "SQRT",
"Strg_TO_Chars", "STRG_VAL", "SWAP", "SYNC_PI", "SYNC_PO", "T_ADD",
"TAN", "T_COMBINE", "T_COMP", "T_CONV", "T_DIFF", "TIME_TCK", "TOF",
"TON", "TONR", "TP", "TRUNC", "T_SUB", "TypeOf", "TypeOfDB",
"TypeOfElements", "UBLKMOV", "UFILL_BLK", "UMOVE_BLK", "UNSCALE",
"UPDAT_PI", "UPDAT_PO", "UPPER_BOUND", "VAL_STRG", "VariantGet",
"VariantPut", "VARIANT_TO_DB_ANY", "WAIT", "WR_DPARM", "WRIT_DBL",
"WRITE_BIG", "WRITE_LITTLE", "WriteToArrayDB", "WriteToArrayDBL",
"WR_LOC_T", "WRREC", "WR_SYS_T",
}
local function test_all_spec_instructions_known()
local missing = {}
for _, name in ipairs(spec_instructions) do
local known = builtin.is_known_function(name) or builtin.is_known_function_block(name)
if not known then
table.insert(missing, name)
end
end
test.assert(#missing == 0,
string.format("all 221 spec instructions should be known (missing %d: %s)",
#missing, table.concat(missing, ", ")))
end
local function test_data_types_coverage()
local spec_types = {
"BOOL", "BYTE", "WORD", "DWORD", "LWORD",
"SINT", "USINT", "INT", "UINT", "DINT", "UDINT", "LINT", "ULINT",
"REAL", "LREAL", "CHAR", "WCHAR", "STRING", "WSTRING",
"TIME", "LTIME", "S5TIME",
"DATE", "TIME_OF_DAY", "TOD", "DATE_AND_TIME", "DT", "DTL",
"LTOD", "LDT", "VARIANT",
"IEC_TIMER", "IEC_COUNTER",
}
local missing = {}
for _, t in ipairs(spec_types) do
if not builtin.is_known_data_type(t) then
table.insert(missing, t)
end
end
test.assert(#missing == 0,
string.format("all spec data types should be known (missing: %s)",
table.concat(missing, ", ")))
end
local function test_parameter_signatures()
-- Key instructions that should have parameter signatures
local with_params = {
"TON", "TOF", "TP", "TONR",
"CTU", "CTD", "CTUD",
"R_TRIG", "F_TRIG",
"SHL", "SHR", "ROL", "ROR",
"CONCAT", "LEFT", "RIGHT", "MID",
"LIMIT", "MIN", "MAX",
"SCALE_X", "NORM_X",
"WR_SYS_T", "RD_SYS_T", "RD_LOC_T", "WR_LOC_T",
"UBLKMOV", "BLKMOV",
"UNSACLE", "SCALE",
"S_CU", "S_CD", "S_CUD",
"S_ODT", "S_PULSE", "S_OFFDT", "S_PEXT",
"RTM", "DRUM", "DCAT", "MCAT",
"ENDIS_PW", "SET_TIMEZONE",
"LOWER_BOUND", "UPPER_BOUND",
"VAL_STRG", "STRG_VAL",
"VariantGet", "VariantPut",
"WAIT",
}
local missing = {}
for _, name in ipairs(with_params) do
local params = builtin.get_parameters(name)
if not params then
table.insert(missing, name)
end
end
-- Some may not have signatures if not in instructions.md; check the important ones
test.assert(#missing < 5,
string.format("most key instructions should have parameter signatures (missing %d: %s)",
#missing, table.concat(missing, ", ")))
end
local function test_total_count()
local count = 0
for _ in pairs(builtin.FUNCTIONS) do count = count + 1 end
local fb_count = 0
for _ in pairs(builtin.FUNCTION_BLOCKS) do fb_count = fb_count + 1 end
test.assert_gt(count, 300, "should have 300+ functions")
test.assert_gt(fb_count, 35, "should have 35+ function blocks")
end
test_all_spec_instructions_known()
test_data_types_coverage()
test_parameter_signatures()
test_total_count()
end)
test.report()