rexglue-skate3/tests/unit/codegen/template_registry_test.cpp
2026-05-18 22:41:13 +01:00

207 lines
7.3 KiB
C++

/**
* @file tests/unit/codegen/template_registry_test.cpp
* @brief Unit tests for TemplateRegistry
*
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
* @license BSD 3-Clause License
*/
#include <catch2/catch_test_macros.hpp>
#include <rex/codegen/template_registry.h>
#include <filesystem>
#include <fstream>
#include <algorithm>
namespace fs = std::filesystem;
// Helper to create a temp directory for test overrides
static fs::path CreateTempDir() {
auto tmp = fs::temp_directory_path() / "rex_template_test";
fs::create_directories(tmp);
return tmp;
}
static void CleanupTempDir(const fs::path& dir) {
fs::remove_all(dir);
}
static void WriteTempFile(const fs::path& path, const std::string& content) {
fs::create_directories(path.parent_path());
std::ofstream f(path);
f << content;
}
TEST_CASE("TemplateRegistry: registeredIds returns all template IDs", "[TemplateRegistry]") {
rex::codegen::TemplateRegistry registry;
auto ids = registry.registeredIds();
REQUIRE(ids.size() == 17);
auto has = [&](const std::string& id) {
return std::find(ids.begin(), ids.end(), id) != ids.end();
};
CHECK(has("init/cmakelists"));
CHECK(has("init/cmake_presets"));
CHECK(has("init/main_cpp"));
CHECK(has("init/app_header"));
CHECK(has("init/manifest_toml"));
CHECK(has("init/rexglue_cmake"));
CHECK(has("codegen/init_h"));
CHECK(has("codegen/init_cpp"));
CHECK(has("codegen/sources_cmake"));
CHECK(has("codegen/_indirect_call"));
CHECK(has("codegen/dll_targets_cmake"));
CHECK(has("codegen/module_registry_cpp"));
CHECK(has("codegen/register_cpp"));
CHECK(has("test/ppc_config_h"));
CHECK(has("test/ppc_test_cases_cpp"));
CHECK(has("test/ppc_test_decls_h"));
CHECK(has("test/ppc_test_functions_cpp"));
}
TEST_CASE("TemplateRegistry: render with simple CLI data", "[TemplateRegistry]") {
rex::codegen::TemplateRegistry registry;
std::string json = R"({
"names": {"snake_case": "test_app"},
"sdk_version": "1.0.0",
"include_stamp": false,
"xex_path": "assets/default.xex",
"out_directory_path": "generated/default"
})";
std::string result = registry.render("init/manifest_toml", json);
CHECK(result.find("name = \"test_app\"") != std::string::npos);
CHECK(result.find("[entrypoint]") != std::string::npos);
CHECK(result.find("file_path = \"assets/default.xex\"") != std::string::npos);
}
TEST_CASE("TemplateRegistry: render with codegen data", "[TemplateRegistry]") {
rex::codegen::TemplateRegistry registry;
std::string json = R"({
"project": "test_proj",
"image_base": "0x82000000",
"image_size": "0x1000000",
"code_base": "0x82010000",
"code_size": "0x100000",
"rexcrt_heap": 1,
"config_flags": {},
"functions": [],
"imports": []
})";
std::string result = registry.render("codegen/init_cpp", json);
CHECK(result.find("PPCImageConfig") != std::string::npos);
CHECK(result.find("test_proj") != std::string::npos);
}
TEST_CASE("TemplateRegistry: render unknown ID throws TemplateError", "[TemplateRegistry]") {
rex::codegen::TemplateRegistry registry;
REQUIRE_THROWS_AS(registry.render("nonexistent/template_id", "{}"), rex::codegen::TemplateError);
}
TEST_CASE("TemplateRegistry: init_h includes shared indirect-call partial", "[TemplateRegistry]") {
rex::codegen::TemplateRegistry registry;
std::string json = R"({
"config_flags": {
"skip_lr": false,
"ctr_as_local": false,
"xer_as_local": false,
"reserved_as_local": false,
"skip_msr": false,
"cr_as_local": false,
"non_argument_as_local": false,
"non_volatile_as_local": false
},
"image_base": "0x82000000",
"image_size": "0x1000000",
"code_base": "0x82010000",
"code_size": "0x100000",
"thunk_reserve_size": "0x1000",
"rexcrt_heap": false,
"functions": [],
"imports": []
})";
std::string result = registry.render("codegen/init_h", json);
CHECK(result.find("REX_LOOKUP_FUNC") != std::string::npos);
CHECK(result.find("ResolveIndirectFunction") != std::string::npos);
CHECK(result.find("last_indirect_target") != std::string::npos);
CHECK(result.find("REX_THUNK_RESERVE_SIZE") != std::string::npos);
CHECK(result.find("[[likely]]") != std::string::npos);
CHECK(result.find("[[unlikely]]") != std::string::npos);
}
TEST_CASE("TemplateRegistry: ppc_config_h includes shared indirect-call partial",
"[TemplateRegistry]") {
rex::codegen::TemplateRegistry registry;
std::string json = R"({
"image_base": "0x82000000",
"image_size": "0x1000000",
"code_base": "0x82010000",
"code_size": "0x100000",
"thunk_reserve_size": "0x1000"
})";
std::string result = registry.render("test/ppc_config_h", json);
CHECK(result.find("ResolveIndirectFunction") != std::string::npos);
CHECK(result.find("last_indirect_target") != std::string::npos);
CHECK(result.find("[[likely]]") != std::string::npos);
}
TEST_CASE("TemplateRegistry: cmake_var callback works", "[TemplateRegistry]") {
rex::codegen::TemplateRegistry registry;
std::string result = registry.renderString("{{ cmake_var(\"FOO\") }}", "{}");
CHECK(result == "${FOO}");
}
TEST_CASE("TemplateRegistry: loadOverrides with valid override", "[TemplateRegistry]") {
auto tmpDir = CreateTempDir();
// Write a custom init/manifest_toml.inja override
WriteTempFile(tmpDir / "init" / "manifest_toml.inja", "custom_override = true\n");
rex::codegen::TemplateRegistry registry;
registry.loadOverrides(tmpDir);
std::string result = registry.render("init/manifest_toml", "{}");
CHECK(result.find("custom_override = true") != std::string::npos);
// Should NOT contain the default template content
CHECK(result.find("[entrypoint]") == std::string::npos);
CleanupTempDir(tmpDir);
}
TEST_CASE("TemplateRegistry: loadOverrides ignores unknown IDs", "[TemplateRegistry]") {
auto tmpDir = CreateTempDir();
// Write a file with unrecognized canonical ID
WriteTempFile(tmpDir / "unknown" / "template.inja", "should be ignored\n");
rex::codegen::TemplateRegistry registry;
// Should not throw
REQUIRE_NOTHROW(registry.loadOverrides(tmpDir));
// Unknown template should still not be renderable
REQUIRE_THROWS_AS(registry.render("unknown/template", "{}"), rex::codegen::TemplateError);
CleanupTempDir(tmpDir);
}
TEST_CASE("Template: manifest_toml emits sdk_version when include_stamp is true",
"[TemplateRegistry][manifest]") {
rex::codegen::TemplateRegistry registry;
std::string json =
R"({"names": {"snake_case": "mygame", "pascal_case": "Mygame", "upper_case": "MYGAME"}, "sdk_version": "0.8.0", "include_stamp": true})";
std::string out = registry.render("init/manifest_toml", json);
CHECK(out.find("sdk_version = \"0.8.0\"") != std::string::npos);
CHECK(out.find("name = \"mygame\"") != std::string::npos);
}
TEST_CASE("Template: manifest_toml omits sdk_version when include_stamp is false",
"[TemplateRegistry][manifest]") {
rex::codegen::TemplateRegistry registry;
std::string json =
R"({"names": {"snake_case": "mygame", "pascal_case": "Mygame", "upper_case": "MYGAME"}, "sdk_version": "0.8.0", "include_stamp": false})";
std::string out = registry.render("init/manifest_toml", json);
CHECK(out.find("sdk_version") == std::string::npos);
CHECK(out.find("name = \"mygame\"") != std::string::npos);
}