From 6872ba2bcb3ce049680bc98e7b6f1f1578dc3442 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 10:40:33 -0700 Subject: [PATCH] =?UTF-8?q?test(extract):=20lock=20in=20DBC=E2=86=92JSON?= =?UTF-8?q?=20emission=20round-trip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4 tests covering the open_format_emitter: - emitJsonFromDbc round-trips a hand-built 2-record DBC through DBCFile::load (which auto-detects JSON via the '{' prefix) and recovers identical record/field/value data. - Missing input file → graceful failure (no JSON written). - Bad DBC magic → graceful failure. - emitOpenFormats walks a subdirectory and writes the side-file in the right place (matches the extractor's recursive walk). Brings ctest target count to 31. --- tests/CMakeLists.txt | 27 ++++++++ tests/test_open_format_emitter.cpp | 108 +++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 tests/test_open_format_emitter.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c63c0946..862c7b4c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -414,6 +414,33 @@ target_link_libraries(test_open_formats PRIVATE catch2_main) add_test(NAME open_formats COMMAND test_open_formats) register_test_target(test_open_formats) +# ── test_open_format_emitter ───────────────────────────────── +# Locks in the asset_extract → wowee open-format conversion path. +add_executable(test_open_format_emitter + test_open_format_emitter.cpp + ${CMAKE_SOURCE_DIR}/tools/asset_extract/open_format_emitter.cpp + ${CMAKE_SOURCE_DIR}/src/pipeline/dbc_loader.cpp + ${CMAKE_SOURCE_DIR}/src/pipeline/blp_loader.cpp + ${CMAKE_SOURCE_DIR}/src/pipeline/m2_loader.cpp + ${CMAKE_SOURCE_DIR}/src/pipeline/wmo_loader.cpp + ${CMAKE_SOURCE_DIR}/src/pipeline/adt_loader.cpp + ${CMAKE_SOURCE_DIR}/src/pipeline/wowee_model.cpp + ${CMAKE_SOURCE_DIR}/src/pipeline/wowee_building.cpp + ${CMAKE_SOURCE_DIR}/src/pipeline/wowee_collision.cpp + ${CMAKE_SOURCE_DIR}/src/core/logger.cpp +) +target_include_directories(test_open_format_emitter PRIVATE + ${TEST_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR} +) +target_include_directories(test_open_format_emitter SYSTEM PRIVATE + ${TEST_SYSTEM_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/extern/nlohmann +) +target_link_libraries(test_open_format_emitter PRIVATE catch2_main) +add_test(NAME open_format_emitter COMMAND test_open_format_emitter) +register_test_target(test_open_format_emitter) + # ── test_camera ────────────────────────────────────────────── add_executable(test_camera test_camera.cpp diff --git a/tests/test_open_format_emitter.cpp b/tests/test_open_format_emitter.cpp new file mode 100644 index 00000000..5c44b1d9 --- /dev/null +++ b/tests/test_open_format_emitter.cpp @@ -0,0 +1,108 @@ +// Tests for the asset_extract open-format emitter — verifies that the +// MPQ→loose-files pipeline produces wowee-readable side-files for the +// most common file types without touching the originals. +#include +#include "tools/asset_extract/open_format_emitter.hpp" +#include "pipeline/dbc_loader.hpp" + +#include +#include +#include +#include + +using namespace wowee::tools; +namespace fs = std::filesystem; + +static const std::string TEST_DIR = "test_emitter_out"; + +static void cleanup() { fs::remove_all(TEST_DIR); } + +TEST_CASE("emitJsonFromDbc produces wowee-DBC-loadable JSON", "[emitter]") { + fs::create_directories(TEST_DIR); + std::string dbcPath = TEST_DIR + "/sample.dbc"; + std::string jsonPath = TEST_DIR + "/sample.json"; + + // Hand-build a 2-record DBC (3 fields each, no string block) so the + // round trip is small and deterministic. + { + std::ofstream f(dbcPath, std::ios::binary); + const char magic[4] = {'W','D','B','C'}; + f.write(magic, 4); + uint32_t recordCount = 2, fieldCount = 3, recordSize = 12, stringBlockSize = 1; + f.write(reinterpret_cast(&recordCount), 4); + f.write(reinterpret_cast(&fieldCount), 4); + f.write(reinterpret_cast(&recordSize), 4); + f.write(reinterpret_cast(&stringBlockSize), 4); + // 2 records of 3 uint32s + uint32_t rec[6] = {1, 100, 200, + 2, 300, 400}; + f.write(reinterpret_cast(rec), sizeof(rec)); + char nul = 0; + f.write(&nul, 1); + } + + REQUIRE(emitJsonFromDbc(dbcPath, jsonPath)); + REQUIRE(fs::exists(jsonPath)); + + // The JSON should round-trip back through DBCFile::loadJSON. + std::ifstream in(jsonPath, std::ios::binary | std::ios::ate); + auto sz = in.tellg(); + std::vector bytes(static_cast(sz)); + in.seekg(0); + in.read(reinterpret_cast(bytes.data()), sz); + + wowee::pipeline::DBCFile dbc; + // Public DBCFile::load detects '{' prefix and dispatches to loadJSON. + REQUIRE(dbc.load(bytes)); + REQUIRE(dbc.getRecordCount() == 2); + REQUIRE(dbc.getFieldCount() == 3); + REQUIRE(dbc.getUInt32(0, 0) == 1); + REQUIRE(dbc.getUInt32(0, 1) == 100); + REQUIRE(dbc.getUInt32(1, 2) == 400); + + cleanup(); +} + +TEST_CASE("emitJsonFromDbc fails gracefully on missing input", "[emitter]") { + REQUIRE_FALSE(emitJsonFromDbc("does_not_exist.dbc", "should_not_be_written.json")); + REQUIRE_FALSE(fs::exists("should_not_be_written.json")); +} + +TEST_CASE("emitJsonFromDbc fails gracefully on bad magic", "[emitter]") { + fs::create_directories(TEST_DIR); + std::string dbcPath = TEST_DIR + "/bad.dbc"; + std::string jsonPath = TEST_DIR + "/bad.json"; + { + std::ofstream f(dbcPath, std::ios::binary); + const char junk[20] = {'F','A','I','L', 0}; + f.write(junk, 20); + } + REQUIRE_FALSE(emitJsonFromDbc(dbcPath, jsonPath)); + cleanup(); +} + +TEST_CASE("emitOpenFormats walks a directory and writes side-files", "[emitter]") { + fs::create_directories(TEST_DIR + "/sub"); + // One DBC in a subdir + { + std::ofstream f(TEST_DIR + "/sub/test.dbc", std::ios::binary); + const char magic[4] = {'W','D','B','C'}; + f.write(magic, 4); + uint32_t r = 1, fc = 1, rs = 4, sb = 1; + f.write(reinterpret_cast(&r), 4); + f.write(reinterpret_cast(&fc), 4); + f.write(reinterpret_cast(&rs), 4); + f.write(reinterpret_cast(&sb), 4); + uint32_t v = 42; + f.write(reinterpret_cast(&v), 4); + char nul = 0; + f.write(&nul, 1); + } + OpenFormatStats stats; + emitOpenFormats(TEST_DIR, /*png*/ false, /*json*/ true, + /*wom*/ false, /*wob*/ false, /*terrain*/ false, stats); + REQUIRE(stats.jsonDbcOk == 1); + REQUIRE(stats.jsonDbcFail == 0); + REQUIRE(fs::exists(TEST_DIR + "/sub/test.json")); + cleanup(); +}