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()