/** * @file compiler_test.cpp * @brief compiler 库测试:toml schema、文件集合、工程哈希 * @author * @date 2026-08-21 */ #include #include #include #include #include "compiler/Project.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 void rm_tmp(const std::string& path) { std::remove(path.c_str()); } // ---- 1. line1 正例 ---- static bool test_line1() { using namespace compiler; Project p; std::string err; CHECK(parse_project(std::string(REPO_ROOT) + "/examples/line1/project.toml", &p, &err)); CHECK(p.name == "line1"); CHECK(p.entry == "program MAIN"); CHECK(p.cycle_limit == 100000); CHECK(p.dt_ms == 10); CHECK(p.files_st.size() == 3); CHECK(p.files_st[0] == "globals.st"); CHECK(p.gvl_file == "globals.st"); CHECK(p.io.size() == 3); CHECK(p.io[0].var == "EmergencyStop" && p.io[0].is_input && p.io[0].bit == 2); CHECK(p.io[2].var == "Q0_0" && !p.io[2].is_input); CHECK(p.base_dir == std::string(REPO_ROOT) + "/examples/line1"); const std::vector files = compile_files(p); CHECK(files.size() == 3); // gvl 已在 files.st,不重复 uint64_t h = 0; CHECK(compute_project_hash(p, &h, &err)); CHECK(h == 0xc3fe4ae74ad45900ull); // 冻结值:确定性回放 return true; } // ---- 2. 无 io / 无 gvl 的用例 ---- static bool test_minimal() { using namespace compiler; Project p; std::string err; CHECK(parse_project(std::string(REPO_ROOT) + "/tests/cases/01_empty_main/project.toml", &p, &err)); CHECK(p.gvl_file.empty()); CHECK(p.io.empty()); CHECK(compile_files(p).size() == 1); CHECK(parse_project(std::string(REPO_ROOT) + "/tests/cases/15_fb_instance/project.toml", &p, &err)); CHECK(p.gvl_file.empty()); return true; } // ---- 3. gvl.file 不在 files.st 时追加 ---- static bool test_gvl_append() { using namespace compiler; const std::string path = write_tmp( "stator_gvl_append.toml", "[project]\nname = \"x\"\nentry = \"program MAIN\"\ncycle_limit = 1\ndt_ms = 1\n" "[files]\nst = [\"main.st\"]\n[gvl]\nfile = \"globals.st\"\n"); Project p; std::string err; CHECK(parse_project(path, &p, &err)); const std::vector files = compile_files(p); CHECK(files.size() == 2); CHECK(files[0] == "main.st"); CHECK(files[1] == "globals.st"); rm_tmp(path); return true; } // ---- 4. 负例:schema 校验类别 ---- static bool expect_err(const std::string& path, const char* prefix) { compiler::Project p; std::string err; if (compiler::parse_project(path, &p, &err)) { std::printf("FAIL %s: parsed ok\n", path.c_str()); return false; } if (err.find(prefix) != 0) { std::printf("FAIL %s: want '%s', got '%s'\n", path.c_str(), prefix, err.c_str()); return false; } ++g_checks; return true; } static bool test_negative() { using namespace compiler; const std::string b1 = write_tmp( "stator_bad1.toml", "[project]\nname = \"x\"\nentry = \"program MAIN\"\ncycle_limit = 1\ndt_ms = 1\n" "[files]\nst = [\"a.st\"]\n[bogus]\nx = 1\n"); if (!expect_err(b1, "unknown key")) return false; const std::string b2 = write_tmp( "stator_bad2.toml", "[project]\nentry = \"program MAIN\"\ncycle_limit = 1\ndt_ms = 1\n" "[files]\nst = [\"a.st\"]\n"); if (!expect_err(b2, "missing field")) return false; const std::string b3 = write_tmp( "stator_bad3.toml", "[project]\nname = \"x\"\nentry = \"program foo\"\ncycle_limit = 1\ndt_ms = 1\n" "[files]\nst = [\"a.st\"]\n"); if (!expect_err(b3, "invalid value")) return false; const std::string b4 = write_tmp( "stator_bad4.toml", "[project]\nname = \"x\"\nentry = \"program MAIN\"\ncycle_limit = 1\ndt_ms = 1\n" "[files]\nst = [\"a.st\"]\n[[io.input]]\nvar = \"I0_0\"\nchannel = 0\n"); if (!expect_err(b4, "missing field")) return false; const std::string b5 = write_tmp("stator_bad5.toml", "[project\nname = \"x\"\n"); if (!expect_err(b5, "parse error")) return false; // 缺文件(compute_project_hash 层) const std::string b6 = write_tmp( "stator_bad6.toml", "[project]\nname = \"x\"\nentry = \"program MAIN\"\ncycle_limit = 1\ndt_ms = 1\n" "[files]\nst = [\"nope.st\"]\n"); Project p; std::string err; CHECK(parse_project(b6, &p, &err)); uint64_t h = 0; CHECK(!compute_project_hash(p, &h, &err)); CHECK(err.find("file missing") == 0); // toml 文件本身不存在 if (!expect_err(std::filesystem::temp_directory_path().string() + "/stator_nope.toml", "parse error")) { return false; } rm_tmp(b1); rm_tmp(b2); rm_tmp(b3); rm_tmp(b4); rm_tmp(b5); rm_tmp(b6); return true; } // ---- 5. 20 个用例全部可解析 + 可哈希 ---- static bool test_all_cases() { using namespace compiler; int n = 0; for (const auto& dir : std::filesystem::directory_iterator( std::string(REPO_ROOT) + "/tests/cases")) { if (!dir.is_directory()) { continue; } const std::string toml = dir.path().string() + "/project.toml"; Project p; std::string err; if (!parse_project(toml, &p, &err)) { std::printf("FAIL parse %s: %s\n", toml.c_str(), err.c_str()); return false; } uint64_t h = 0; if (!compute_project_hash(p, &h, &err)) { std::printf("FAIL hash %s: %s\n", toml.c_str(), err.c_str()); return false; } ++n; } CHECK(n == 20); return true; } int main() { if (!test_line1()) return 1; if (!test_minimal()) return 1; if (!test_gvl_append()) return 1; if (!test_negative()) return 1; if (!test_all_cases()) return 1; std::printf("compiler_test: %d checks passed\n", g_checks); return 0; }