Testing

Easy3D / Reference / Testing

Easy3D keeps a small test suite registered with CTest (EASY3D_BUILD_TESTS=ON by default). The interesting part is its two-tier design: CNA-free tests always build and run, CNA-dependent tests run when CNA is linked (and are still compile-checked when it is not) — and a third tier for the two renderer adapters, which are always compile-checked only, in every configuration.

The test suite

CTest nameCoversRuns in default build?
basicsVersion info and other CNA-free helpersYes — CNA-free
texture_atlasTextureAtlas: named rects, AddGrid, UV math, throwing/non-throwing lookupsYes — CNA-free
cameraCamera3D, OrbitCamera, FollowCamera — executes real CNA matrix/vector mathNo — needs CNA linked
batchesBillboardBatch, CubeBatch, DebugDraw item storageNo — needs CNA linked
cube_meshAppendCubeMesh/BuildCubeMesh and the DirectionalCube/Plate/TripleCross/PyramidTip builders — vertex counts, index validity, face positions, UV corners, windingNo — needs CNA linked
billboard_meshAppendBillboardMesh/BuildBillboardMesh — corner placement, origin/pivot handling, winding, UV mappingNo — needs CNA linked
(none — compile-check only)CubeMeshRenderer, BillboardMeshRendererNever runs as CTest — see "The third tier" below

Running the tests

Default build — 2 tests

cmake -S . -B build
cmake --build build
ctest --test-dir build          # basics, texture_atlas

CNA-linked build — full runnable suite

cmake -S . -B build -DEASY3D_LINK_CNA=ON
cmake --build build
ctest --test-dir build          # basics, texture_atlas, camera, batches, cube_mesh, billboard_mesh

Why some tests need CNA linked

The camera, batch, cube-mesh, and billboard-mesh tests exercise CNA math at runtime — even just constructing Vector3/Vector2 values calls CNA's compiled constructors. So running them requires linking CNA. Compiling them does not: CNA's math types are declared in headers and only defined in CNA's .cpp files, so translating the test sources to object code needs only the headers.

The compile-check trick

In the default (no-CNA-link) build, the CNA-dependent test sources are still built as OBJECT libraries — compiled but never linked into an executable, so there are no undefined-symbol errors:

add_library(easy3d_test_camera_compilecheck         OBJECT test_camera.cpp)
add_library(easy3d_test_batches_compilecheck        OBJECT test_batches.cpp)
add_library(easy3d_test_cube_mesh_compilecheck      OBJECT test_cube_mesh.cpp)
add_library(easy3d_test_billboard_mesh_compilecheck OBJECT test_billboard_mesh.cpp)

This is a cheap compile-only check that catches CNA header/API drift even in the light default build: if CNA renames a method or changes a signature, the default build breaks immediately instead of waiting for someone to run a CNA-linked build.

The third tier — renderer adapters, always compile-check only

CubeMeshRenderer and BillboardMeshRenderer go one step further than "needs CNA linked": their constructors need a live Microsoft::Xna::Framework::Graphics::GraphicsDevice&, which only exists once a real CNA Game has created a window/graphics context — not something a plain test main() can produce, linked or not. So their tests are always OBJECT libraries, in every configuration:

add_library(easy3d_test_cube_mesh_renderer_compilecheck      OBJECT test_cube_mesh_renderer.cpp)
add_library(easy3d_test_billboard_mesh_renderer_compilecheck OBJECT test_billboard_mesh_renderer.cpp)

Each file's main() does nothing at all — the real content is an unused helper function that calls every public method against real CNA headers, so the compiler still type-checks the whole API surface. The actual runtime verification for these two classes happens outside this repository, against Galaxy Eggbert's real windowed binary (a live GraphicsDevice, plus a pixel readback confirming real draw output).

Test style

The tests are plain C++ programs with a tiny local CHECK macro — no test framework dependency. They print failures with file/line and return a non-zero exit code, which is all CTest needs:

#define CHECK(cond)                                               \
    do {                                                          \
        if (!(cond)) {                                            \
            std::printf("FAIL: %s (line %d)\n", #cond, __LINE__); \
            ++g_failures;                                         \
        }                                                         \
    } while (0)

This matches the project's "small, boring, testable" principle: when you add behavior that can be tested without linking all of CNA, you add or update a small test alongside it.