实现 12.3 toml 读取:schema 校验、文件集合、工程哈希。
- Project.h/cpp:toml++ 解析 + 字段白名单(未知键带行号报错)、name/entry/cycle_limit/dt_ms/files.st 必填、io 条目完整性、entry 必须 program MAIN - compile_files:files.st ∪ gvl.file 去重;compute_project_hash:路径排序后 FNV-1a 64 增量,缺文件报 file missing - STCompiler <project.toml>:打印文件列表与哈希,失败退出码 1,--help - compiler 私有链接 toml++ v3.4.0;测试固化 compiler_toml(35 断言,line1 哈希冻结 0xc3fe4ae74ad45900)
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* @file compiler_test.cpp
|
||||
* @brief compiler 库测试:toml schema、文件集合、工程哈希
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<std::string> 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<std::string> 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;
|
||||
}
|
||||
Reference in New Issue
Block a user