/** * @file machine_test.cpp * @brief machine.toml V2 加载与强校验测试(阶段 2) * @author * @date 2026-08-21 */ #include #include #include #include #include "compiler/MachineConfig.h" #include "compiler/Stb.h" #include "compiler/TypeInfo.h" #ifndef REPO_ROOT #define REPO_ROOT "." #endif static int g_checks = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ return false; \ } \ ++g_checks; \ } while (0) // ---- 写临时 toml ---- static std::string write_tmp(const char* name, const std::string& content) { const std::filesystem::path dir = std::filesystem::temp_directory_path(); const std::filesystem::path path = dir / name; std::FILE* f = std::fopen(path.string().c_str(), "w"); std::fputs(content.c_str(), f); std::fclose(f); return path.string(); } static bool expect_load_err(const char* name, const std::string& content, const char* keyword) { const std::string path = write_tmp(name, content); compiler::MachineConfig cfg; std::string err; if (cfg.load(path, &err)) { std::printf("FAIL %s: loaded ok\n", name); std::remove(path.c_str()); return false; } if (err.find("machine error") != 0) { std::printf("FAIL %s: want 'machine error', got '%s'\n", name, err.c_str()); std::remove(path.c_str()); return false; } if (err.find(keyword) == std::string::npos) { std::printf("FAIL %s: want '%s', got '%s'\n", name, keyword, err.c_str()); std::remove(path.c_str()); return false; } ++g_checks; std::remove(path.c_str()); return true; } // 合法 type 模板(19 条全集,可注入损坏;缺 DT 时用于"缺全集"负例) static const char* kTypesAll = "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"bool\"\nfunc = 0\n" "[[type]]\nname = \"BYTE\"\nbase = \"uint8\"\nkind = \"bit\"\nfunc = 0\n" "[[type]]\nname = \"WORD\"\nbase = \"uint16\"\nkind = \"bit\"\nfunc = 2\n" "[[type]]\nname = \"DWORD\"\nbase = \"uint32\"\nkind = \"bit\"\nfunc = 4\n" "[[type]]\nname = \"LWORD\"\nbase = \"uint64\"\nkind = \"bit\"\nfunc = 6\n" "[[type]]\nname = \"SINT\"\nbase = \"int8\"\nkind = \"int\"\nfunc = 1\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 3\n" "[[type]]\nname = \"DINT\"\nbase = \"int32\"\nkind = \"int\"\nfunc = 5\n" "[[type]]\nname = \"LINT\"\nbase = \"int64\"\nkind = \"int\"\nfunc = 7\n" "[[type]]\nname = \"USINT\"\nbase = \"uint8\"\nkind = \"uint\"\nfunc = 0\n" "[[type]]\nname = \"UINT\"\nbase = \"uint16\"\nkind = \"uint\"\nfunc = 2\n" "[[type]]\nname = \"UDINT\"\nbase = \"uint32\"\nkind = \"uint\"\nfunc = 4\n" "[[type]]\nname = \"ULINT\"\nbase = \"uint64\"\nkind = \"uint\"\nfunc = 6\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\nkind = \"time\"\nfunc = 7\n" "[[type]]\nname = \"REAL\"\nbase = \"float32\"\nkind = \"float\"\nfunc = 8\n" "[[type]]\nname = \"LREAL\"\nbase = \"float64\"\nkind = \"float\"\nfunc = 9\n" "[[type]]\nname = \"DATE\"\nbase = \"uint32\"\nkind = \"date\"\nfunc = 4\n" "[[type]]\nname = \"TOD\"\nbase = \"uint32\"\nkind = \"date\"\nfunc = 4\n" "[[type]]\nname = \"DT\"\nbase = \"uint64\"\nkind = \"date\"\nfunc = 6\n"; // 合法 op 模板(含 CAL 与 RET,可注入损坏) static const char* kOpsBasic = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n" "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n" "[[op]]\nname = \"RET\"\nprefix = \"10\"\nop = 1\nfmt = \"NONE\"\nfunc = \"none\"\nparams = []\nenabled = true\n"; // 合法 fb 模板(15 条全集,可注入损坏) static const char* kFbsAll = "[[fb]]\nname = \"TON\"\nfb_id = 0\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n" "[[fb]]\nname = \"TOF\"\nfb_id = 1\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n" "[[fb]]\nname = \"TP\"\nfb_id = 2\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n" "[[fb]]\nname = \"CTU\"\nfb_id = 3\nfields = [[\"cu\", \"BOOL\"], [\"r\", \"BOOL\"], [\"pv\", \"INT\"], [\"q\", \"BOOL\"], [\"cv\", \"INT\"]]\n" "[[fb]]\nname = \"DCTU\"\nfb_id = 4\nfields = [[\"cu\", \"BOOL\"], [\"r\", \"BOOL\"], [\"pv\", \"DINT\"], [\"q\", \"BOOL\"], [\"cv\", \"DINT\"]]\n" "[[fb]]\nname = \"CTD\"\nfb_id = 5\nfields = [[\"cd\", \"BOOL\"], [\"ld\", \"BOOL\"], [\"pv\", \"INT\"], [\"q\", \"BOOL\"], [\"cv\", \"INT\"]]\n" "[[fb]]\nname = \"DCTD\"\nfb_id = 6\nfields = [[\"cd\", \"BOOL\"], [\"ld\", \"BOOL\"], [\"pv\", \"DINT\"], [\"q\", \"BOOL\"], [\"cv\", \"DINT\"]]\n" "[[fb]]\nname = \"CTUD\"\nfb_id = 7\nfields = [[\"cu\", \"BOOL\"], [\"cd\", \"BOOL\"], [\"r\", \"BOOL\"], [\"lu\", \"BOOL\"], [\"pv\", \"INT\"], [\"qu\", \"BOOL\"], [\"qd\", \"BOOL\"], [\"cv\", \"INT\"]]\n" "[[fb]]\nname = \"DCTUD\"\nfb_id = 8\nfields = [[\"cu\", \"BOOL\"], [\"cd\", \"BOOL\"], [\"r\", \"BOOL\"], [\"lu\", \"BOOL\"], [\"pv\", \"DINT\"], [\"qu\", \"BOOL\"], [\"qd\", \"BOOL\"], [\"cv\", \"DINT\"]]\n" "[[fb]]\nname = \"R_TRIG\"\nfb_id = 9\nfields = [[\"clk\", \"BOOL\"], [\"q\", \"BOOL\"]]\n" "[[fb]]\nname = \"F_TRIG\"\nfb_id = 10\nfields = [[\"clk\", \"BOOL\"], [\"q\", \"BOOL\"]]\n" "[[fb]]\nname = \"SR\"\nfb_id = 11\nfields = [[\"S1\", \"BOOL\"], [\"R\", \"BOOL\"], [\"Q1\", \"BOOL\"]]\n" "[[fb]]\nname = \"RS\"\nfb_id = 12\nfields = [[\"SET\", \"BOOL\"], [\"RESET1\", \"BOOL\"], [\"Q1\", \"BOOL\"]]\n" "[[fb]]\nname = \"PWM\"\nfb_id = 13\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"duty\", \"REAL\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\n" "[[fb]]\nname = \"RTC\"\nfb_id = 14\nfields = [[\"enable\", \"BOOL\"], [\"date\", \"DATE\"], [\"tod\", \"TOD\"]]\n"; // ---- 1. 正例:仓库 machine.toml ---- static bool test_positive() { compiler::MachineConfig cfg; std::string err; CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); CHECK(cfg.ok()); CHECK(cfg.model_name() == "STATOR"); CHECK(cfg.version() == 2); CHECK(cfg.types().size() == 19); const compiler::ConfigType* bt = cfg.find_type("BOOL"); CHECK(bt != nullptr && bt->base == "uint8" && bt->kind == "bool" && bt->func == 0); CHECK(cfg.find_type("INT") != nullptr && cfg.find_type("INT")->base == "int16" && cfg.find_type("INT")->kind == "int" && cfg.find_type("INT")->func == 3); CHECK(cfg.find_type("REAL") != nullptr && cfg.find_type("REAL")->func == 8); CHECK(cfg.find_type("LREAL") != nullptr && cfg.find_type("LREAL")->func == 9); CHECK(cfg.find_type("DATE") != nullptr && cfg.find_type("DATE")->func == 4); CHECK(cfg.find_type("TOD") != nullptr && cfg.find_type("TOD")->func == 4); CHECK(cfg.find_type("DT") != nullptr && cfg.find_type("DT")->func == 6); CHECK(cfg.find_type("NOPE") == nullptr); CHECK(cfg.ops().size() == 72); const compiler::ConfigOp* move = cfg.find_op("MOVE"); CHECK(move != nullptr && move->prefix == "11" && move->op == 0 && move->fmt == "RR" && move->func_mode == "width" && move->params.size() == 2); const compiler::ConfigOp* load = cfg.find_op("LOAD"); CHECK(load != nullptr && load->prefix == "010001" && load->fmt == "SLOT" && load->func_mode == "full"); const compiler::ConfigOp* cal = cfg.find_op("CAL"); CHECK(cal != nullptr && cal->prefix == "010101" && cal->fmt == "CAL" && cal->func_mode == "none" && cal->params.size() == 2); CHECK(cfg.find_op_by_key("11", 0) != nullptr && cfg.find_op_by_key("11", 0)->name == "MOVE"); CHECK(cfg.find_op_by_key("010001", 1) != nullptr && cfg.find_op_by_key("010001", 1)->name == "STORE"); CHECK(cfg.op_enabled("11", 0) && cfg.op_enabled("010101", 0)); CHECK(!cfg.op_enabled("11", 63)); CHECK(cfg.find_op("NOPE") == nullptr); CHECK(cfg.fbs().size() == 15); const compiler::ConfigFb* ton = cfg.find_fb("TON"); CHECK(ton != nullptr && ton->fb_id == 0 && ton->fields.size() == 4); CHECK(cfg.find_fb("DCTU") != nullptr && cfg.find_fb("DCTU")->fb_id == 4 && cfg.find_fb("DCTU")->fields[2].type == "DINT"); CHECK(cfg.find_fb_by_id(14) != nullptr && cfg.find_fb_by_id(14)->name == "RTC"); CHECK(cfg.find_fb_by_id(15) == nullptr); // 基元表 CHECK(compiler::MachineConfig::find_prim("int16") != nullptr); const compiler::PrimType* p = compiler::MachineConfig::find_prim("uint8"); CHECK(p != nullptr && p->width == 1 && !p->is_signed && !p->is_float); CHECK(compiler::MachineConfig::find_prim("float64") != nullptr); CHECK(compiler::MachineConfig::find_prim("bfloat16") == nullptr); return true; } // ---- 2. 负例:V2 校验项 ---- static bool test_negative() { const std::string m = "[meta]\nname = \"STATOR\"\nversion = 2\n"; // version != 2 if (!expect_load_err("mc_v2_bad1.toml", "[meta]\nname = \"STATOR\"\nversion = 1\n" + std::string(kTypesAll) + kOpsBasic + kFbsAll, "version must be 2")) { return false; } // type 缺全集(去掉 DT) { std::string types = kTypesAll; const std::string dt = "[[type]]\nname = \"DT\"\nbase = \"uint64\"\nkind = \"date\"\nfunc = 6\n"; const size_t pos = types.find(dt); types.erase(pos, dt.size()); if (!expect_load_err("mc_v2_bad2.toml", m + types + kOpsBasic + kFbsAll, "missing 'DT'")) { return false; } } // base 未命中基元 { std::string types = kTypesAll; const std::string old = "[[type]]\nname = \"BYTE\"\nbase = \"uint8\"\nkind = \"bit\"\nfunc = 0\n"; const std::string bad = "[[type]]\nname = \"BYTE\"\nbase = \"bigint\"\nkind = \"bit\"\nfunc = 0\n"; types.replace(types.find(old), old.size(), bad); if (!expect_load_err("mc_v2_bad3.toml", m + types + kOpsBasic + kFbsAll, "unknown base")) { return false; } } // func 与 base 互锁破坏(INT func=0) { std::string types = kTypesAll; const std::string old = "[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 3\n"; const std::string bad = "[[type]]\nname = \"INT\"\nbase = \"int16\"\nkind = \"int\"\nfunc = 0\n"; types.replace(types.find(old), old.size(), bad); if (!expect_load_err("mc_v2_bad4.toml", m + types + kOpsBasic + kFbsAll, "func 0 mismatches base")) { return false; } } // kind 非法 { std::string types = kTypesAll; const std::string old = "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"bool\"\nfunc = 0\n"; const std::string bad = "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nkind = \"weird\"\nfunc = 0\n"; types.replace(types.find(old), old.size(), bad); if (!expect_load_err("mc_v2_bad5.toml", m + types + kOpsBasic + kFbsAll, "bad kind")) { return false; } } // prefix 非法 { std::string ops = kOpsBasic; const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"; const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"99\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"; ops.replace(ops.find(old), old.size(), bad); if (!expect_load_err("mc_v2_bad6.toml", m + kTypesAll + ops + kFbsAll, "bad prefix")) { return false; } } // prefix-fmt 互锁破坏(11 + RRR) { std::string ops = kOpsBasic; const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"; const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RRR\"\nfunc = \"width\"\nparams = [\"rd\", \"ra\", \"rb\"]\nenabled = true\n"; ops.replace(ops.find(old), old.size(), bad); if (!expect_load_err("mc_v2_bad7.toml", m + kTypesAll + ops + kFbsAll, "must pair with fmt")) { return false; } } // (prefix, op) 分区内重复 { std::string ops = kOpsBasic; ops += "[[op]]\nname = \"DUP\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"a\", \"b\"]\nenabled = true\n"; if (!expect_load_err("mc_v2_bad8.toml", m + kTypesAll + ops + kFbsAll, "duplicate (prefix, op)")) { return false; } } // func 类别非法 { std::string ops = kOpsBasic; const std::string old = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"width\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"; const std::string bad = "[[op]]\nname = \"MOVE\"\nprefix = \"11\"\nop = 0\nfmt = \"RR\"\nfunc = \"magic\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n"; ops.replace(ops.find(old), old.size(), bad); if (!expect_load_err("mc_v2_bad9.toml", m + kTypesAll + ops + kFbsAll, "bad func mode")) { return false; } } // params 数量与 fmt 不符(CAL 应为 2) { std::string ops = kOpsBasic; const std::string old = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n"; const std::string bad = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\"]\nenabled = true\n"; ops.replace(ops.find(old), old.size(), bad); if (!expect_load_err("mc_v2_bad10.toml", m + kTypesAll + ops + kFbsAll, "params count")) { return false; } } // fb 缺全集(去掉 RTC) { std::string fbs = kFbsAll; const std::string rtc = "[[fb]]\nname = \"RTC\"\nfb_id = 14\nfields = [[\"enable\", \"BOOL\"], [\"date\", \"DATE\"], [\"tod\", \"TOD\"]]\n"; fbs.erase(fbs.find(rtc), rtc.size()); if (!expect_load_err("mc_v2_bad11.toml", m + kTypesAll + kOpsBasic + fbs, "missing fb_id 14")) { return false; } } // fb 字段类型未知 { std::string fbs = kFbsAll; const std::string old = "[[\"in\", \"BOOL\"], [\"pt\", \"TIME\"]"; const std::string bad = "[[\"in\", \"NOPE\"], [\"pt\", \"TIME\"]"; fbs.replace(fbs.find(old), old.size(), bad); if (!expect_load_err("mc_v2_bad12.toml", m + kTypesAll + kOpsBasic + fbs, "unknown field type")) { return false; } } // 缺 CAL(op-fb 互锁) { std::string ops = kOpsBasic; const std::string cal = "[[op]]\nname = \"CAL\"\nprefix = \"010101\"\nop = 0\nfmt = \"CAL\"\nfunc = \"none\"\nparams = [\"fb_id\", \"slot\"]\nenabled = true\n"; ops.erase(ops.find(cal), cal.size()); if (!expect_load_err("mc_v2_bad13.toml", m + kTypesAll + ops + kFbsAll, "op-fb interlock")) { return false; } } // 语法错误 if (!expect_load_err("mc_v2_bad14.toml", "[meta\nname = \"S\"\n", "parse error")) { return false; } return true; } // ---- 3. 类型元数据(TypeInfo,V2)---- static bool test_type_meta() { compiler::MachineConfig cfg; std::string err; CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); // BOOL:1 字节、无符号、kind bool、func 0 const compiler::TypeMeta b = compiler::type_meta(cfg, "BOOL"); CHECK(b && b.width() == 1 && !b.is_signed() && !b.is_float() && b.func() == 0); CHECK(b.kind() == "bool"); // 大小写不敏感(Linker type_name 是小写) const compiler::TypeMeta b2 = compiler::type_meta(cfg, "bool"); CHECK(b2 && b2.func() == 0 && b2.width() == 1); // INT:2 字节、有符号、kind int、func 3 const compiler::TypeMeta i = compiler::type_meta(cfg, "int"); CHECK(i && i.width() == 2 && i.is_signed() && !i.is_float() && i.func() == 3); CHECK(i.kind() == "int"); // REAL:4 字节、浮点、func 8;TOD:4 字节、func 4 const compiler::TypeMeta r = compiler::type_meta(cfg, "REAL"); CHECK(r && r.width() == 4 && r.is_float() && r.func() == 8); const compiler::TypeMeta tod = compiler::type_meta(cfg, "TOD"); CHECK(tod && tod.width() == 4 && tod.func() == 4); // 未定义类型 → 空 CHECK(!compiler::type_meta(cfg, "NOPE")); CHECK(!compiler::type_meta(cfg, "")); return true; } // ---- 4. SHA-256 已知向量 + 型号标识 ---- static bool test_sha256() { { uint8_t out[compiler::kSha256Size]; compiler::sha256(nullptr, 0, out); const uint8_t want[32] = {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}; CHECK(std::memcmp(out, want, 32) == 0); } { const uint8_t abc[] = {'a', 'b', 'c'}; uint8_t out[compiler::kSha256Size]; compiler::sha256(abc, 3, out); const uint8_t want[32] = {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}; CHECK(std::memcmp(out, want, 32) == 0); } // 型号标识:STATOR + 2 → "STATOR2" 补 '\0' char mid[compiler::kModelIdSize]; compiler::fill_model_id("STATOR", 2, mid); CHECK(std::strncmp(mid, "STATOR2", 7) == 0); CHECK(mid[7] == '\0'); CHECK(mid[31] == '\0'); return true; } int main() { if (!test_positive()) return 1; if (!test_negative()) return 1; if (!test_type_meta()) return 1; if (!test_sha256()) return 1; std::printf("machine_test: %d checks passed\n", g_checks); return 0; }