test(extract): lock in DBC→JSON emission round-trip

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.
This commit is contained in:
Kelsi 2026-05-06 10:40:33 -07:00
parent d4c69a2b46
commit 6872ba2bcb
2 changed files with 135 additions and 0 deletions

View file

@ -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

View file

@ -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 <catch_amalgamated.hpp>
#include "tools/asset_extract/open_format_emitter.hpp"
#include "pipeline/dbc_loader.hpp"
#include <filesystem>
#include <fstream>
#include <cstring>
#include <vector>
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<const char*>(&recordCount), 4);
f.write(reinterpret_cast<const char*>(&fieldCount), 4);
f.write(reinterpret_cast<const char*>(&recordSize), 4);
f.write(reinterpret_cast<const char*>(&stringBlockSize), 4);
// 2 records of 3 uint32s
uint32_t rec[6] = {1, 100, 200,
2, 300, 400};
f.write(reinterpret_cast<const char*>(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<uint8_t> bytes(static_cast<size_t>(sz));
in.seekg(0);
in.read(reinterpret_cast<char*>(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<const char*>(&r), 4);
f.write(reinterpret_cast<const char*>(&fc), 4);
f.write(reinterpret_cast<const char*>(&rs), 4);
f.write(reinterpret_cast<const char*>(&sb), 4);
uint32_t v = 42;
f.write(reinterpret_cast<const char*>(&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();
}