/** * @file machine_test.cpp * @brief machine.toml 加载与强校验测试(阶段 A 步骤 3) * @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 char* 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, f); std::fclose(f); return path.string(); } static bool expect_load_err(const char* name, const char* 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; } // 合法内容模板(可注入损坏) static const char* kGood = "[meta]\nname = \"STATOR\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nrange = [0, 1]\ntag = 0\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n" "[[op]]\nname = \"CAL_TON\"\nopcode = 24\nclass = \"instance\"\nformat = \"CAL\"\nparams = [\"instance\"]\nenabled = true\n" "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n" "[[fb]]\nname = \"ton\"\nopcode = 24\nfields = [[\"in\", \"BOOL\"], [\"pt\", \"TIME\"], [\"q\", \"BOOL\"], [\"et\", \"TIME\"]]\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() == 1); CHECK(cfg.types().size() == 3); const compiler::ConfigType* bt = cfg.find_type("BOOL"); CHECK(bt != nullptr && bt->base == "uint8" && bt->has_range && bt->range_min == 0 && bt->range_max == 1 && bt->tag == 0); CHECK(cfg.find_type("INT") != nullptr && cfg.find_type("INT")->tag == 1); CHECK(cfg.find_type("TIME") != nullptr && cfg.find_type("TIME")->tag == 2); CHECK(cfg.find_type("REAL") == nullptr); CHECK(cfg.ops().size() == 34); const compiler::ConfigOp* add = cfg.find_op("ADD"); CHECK(add != nullptr && add->opcode == 5 && add->format == "RRR" && !add->is_instance && add->params.size() == 3); const compiler::ConfigOp* ton = cfg.find_op("CAL_TON"); CHECK(ton != nullptr && ton->opcode == 24 && ton->is_instance && ton->format == "CAL"); CHECK(cfg.find_op("CAL_F_TRIG") != nullptr && cfg.find_op("CAL_F_TRIG")->opcode == 31); CHECK(cfg.find_op_by_code(33) != nullptr && cfg.find_op_by_code(33)->name == "RET"); CHECK(cfg.op_enabled(0) && cfg.op_enabled(24)); CHECK(cfg.find_op("NOPE") == nullptr); CHECK(cfg.fbs().size() == 8); const compiler::ConfigFb* ctud = cfg.find_fb("ctud"); CHECK(ctud != nullptr && ctud->opcode == 29 && ctud->fields.size() == 8); CHECK(cfg.find_fb("r_trig") != nullptr && cfg.find_fb("r_trig")->fields.size() == 2); // 基元表 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. 负例:9 类 ---- static bool test_negative() { // 缺属性(enabled 缺失) if (!expect_load_err( "mc_bad1.toml", "[meta]\nname = \"S\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\n", "missing field 'enabled'")) { return false; } // base 未命中基元 if (!expect_load_err( "mc_bad2.toml", "[meta]\nname = \"S\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"bigint\"\ntag = 0\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", "unknown base")) { return false; } // range min > max if (!expect_load_err( "mc_bad3.toml", "[meta]\nname = \"S\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\nrange = [1, 0]\ntag = 0\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", "range min > max")) { return false; } // tag 契约破坏(BOOL tag=5) if (!expect_load_err( "mc_bad4.toml", "[meta]\nname = \"S\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 5\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", "tag out of contract")) { return false; } // tag 契约破坏(INT 占 tag 0) if (!expect_load_err( "mc_bad5.toml", "[meta]\nname = \"S\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 1\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 0\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n", "type contract broken")) { return false; } // opcode 重号 if (!expect_load_err( "mc_bad6.toml", "[meta]\nname = \"S\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\", \"rs\"]\nenabled = true\n" "[[op]]\nname = \"NOPE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"a\", \"b\"]\nenabled = true\n", "duplicate opcode")) { return false; } // 类别-格式互锁破坏(instance + RRR) if (!expect_load_err( "mc_bad7.toml", "[meta]\nname = \"S\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"BAD\"\nopcode = 24\nclass = \"instance\"\nformat = \"RRR\"\nparams = [\"a\", \"b\", \"c\"]\nenabled = true\n", "class-format mismatch")) { return false; } // params 数量与 format 不符 if (!expect_load_err( "mc_bad8.toml", "[meta]\nname = \"S\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"MOVE\"\nopcode = 0\nclass = \"plain\"\nformat = \"RR\"\nparams = [\"rd\"]\nenabled = true\n", "params count")) { return false; } // fb.opcode 不匹配 instance op if (!expect_load_err( "mc_bad9.toml", "[meta]\nname = \"S\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"RET\"\nopcode = 33\nclass = \"plain\"\nformat = \"NONE\"\nparams = []\nenabled = true\n" "[[fb]]\nname = \"ton\"\nopcode = 33\nfields = [[\"in\", \"BOOL\"]]\n", "must match an instance op")) { return false; } // fb 字段类型未命中 type 表 if (!expect_load_err( "mc_bad10.toml", "[meta]\nname = \"S\"\nversion = 1\n" "[[type]]\nname = \"BOOL\"\nbase = \"uint8\"\ntag = 0\n" "[[type]]\nname = \"INT\"\nbase = \"int16\"\ntag = 1\n" "[[type]]\nname = \"TIME\"\nbase = \"int64\"\ntag = 2\n" "[[op]]\nname = \"CAL_TON\"\nopcode = 24\nclass = \"instance\"\nformat = \"CAL\"\nparams = [\"instance\"]\nenabled = true\n" "[[fb]]\nname = \"ton\"\nopcode = 24\nfields = [[\"in\", \"REAL\"]]\n", "unknown field type")) { return false; } // 语法错误 if (!expect_load_err("mc_bad11.toml", "[meta\nname = \"S\"\n", "parse error")) { return false; } return true; } // ---- 3. 合法最小配置正例 ---- static bool test_positive_min() { const std::string path = write_tmp("mc_good.toml", kGood); compiler::MachineConfig cfg; std::string err; CHECK(cfg.load(path, &err)); CHECK(cfg.ops().size() == 3); CHECK(cfg.fbs().size() == 1); CHECK(cfg.find_fb("ton")->fields.size() == 4); std::remove(path.c_str()); return true; } // ---- 4. 类型元数据(TypeInfo)---- static bool test_type_meta() { compiler::MachineConfig cfg; std::string err; CHECK(cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)); // BOOL:base=uint8 → 1 字节、无符号、非浮点、tag 0、range [0,1] const compiler::TypeMeta b = compiler::type_meta(cfg, "BOOL"); CHECK(b && b.width() == 1 && !b.is_signed() && !b.is_float() && b.tag() == 0); CHECK(b.has_range() && b.value_in_range(0) && b.value_in_range(1)); CHECK(!b.value_in_range(2) && !b.value_in_range(-1)); // 大小写不敏感(Linker type_name 是小写) const compiler::TypeMeta b2 = compiler::type_meta(cfg, "bool"); CHECK(b2 && b2.tag() == 0 && b2.width() == 1); // INT:int16 → 2 字节、有符号、tag 1、无 range const compiler::TypeMeta i = compiler::type_meta(cfg, "int"); CHECK(i && i.width() == 2 && i.is_signed() && !i.is_float() && i.tag() == 1); CHECK(!i.has_range() || i.value_in_range(30000)); // TIME:int64 → 8 字节、有符号、tag 2 const compiler::TypeMeta t = compiler::type_meta(cfg, "TIME"); CHECK(t && t.width() == 8 && t.is_signed() && !t.is_float() && t.tag() == 2); // 未定义类型 → 空 CHECK(!compiler::type_meta(cfg, "REAL")); CHECK(!compiler::type_meta(cfg, "")); return true; } // ---- 5. 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 + 1 → "STATOR1" 补 '\0' char mid[compiler::kModelIdSize]; compiler::fill_model_id("STATOR", 1, mid); CHECK(std::strncmp(mid, "STATOR1", 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_positive_min()) 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; }