Kelsidavis-WoWee/TESTING.md
Paul 2cb47bf126 chore(testing): add unit tests and update core render/network pipelines
- add new tests:
  - test_blp_loader.cpp
  - test_dbc_loader.cpp
  - test_entity.cpp
  - test_frustum.cpp
  - test_m2_structs.cpp
  - test_opcode_table.cpp
  - test_packet.cpp
  - test_srp.cpp
  - CMakeLists.txt
- add docs and progress tracking:
  - TESTING.md
  - perf_baseline.md
- update project config/build:
  - .gitignore
  - CMakeLists.txt
  - test.sh
- core engine updates:
  - application.cpp
  - game_handler.cpp
  - world_socket.cpp
  - adt_loader.cpp
  - asset_manager.cpp
  - m2_renderer.cpp
  - post_process_pipeline.cpp
  - renderer.cpp
  - terrain_manager.cpp
  - game_screen.cpp
- add profiler header:
  - profiler.hpp
2026-04-03 09:41:34 +03:00

11 KiB
Raw Blame History

WoWee Testing Guide

This document covers everything needed to build, run, lint, and extend the WoWee test suite.


Table of Contents

  1. Overview
  2. Prerequisites
  3. Test Suite Layout
  4. Building the Tests
  5. Running Tests
  6. Lint (clang-tidy)
  7. ASAN / UBSan
  8. Adding New Tests
  9. CI Reference

Overview

WoWee uses Catch2 v3 (amalgamated) for unit testing and clang-tidy for static analysis. The test.sh script is the single entry point for both.

Command What it does
./test.sh Runs both unit tests (Release) and lint
./test.sh --test Runs unit tests only (Release build)
./test.sh --lint Runs clang-tidy only
./test.sh --asan Runs unit tests under ASAN + UBSan (Debug build)
FIX=1 ./test.sh --lint Applies clang-tidy auto-fixes in-place

All commands exit non-zero on any failure.


Prerequisites

The test suite requires the same base toolchain used to build the project. See BUILD_INSTRUCTIONS.md for platform-specific dependency installation.

Linux (Ubuntu / Debian)

sudo apt update
sudo apt install -y \
  build-essential cmake pkg-config git \
  libssl-dev \
  clang-tidy

Linux (Arch)

sudo pacman -S --needed base-devel cmake pkgconf git openssl clang

macOS

brew install cmake openssl@3 llvm
# Add LLVM tools to PATH so clang-tidy is found:
export PATH="$(brew --prefix llvm)/bin:$PATH"

Windows (MSYS2)

Install the full toolchain as described in BUILD_INSTRUCTIONS.md, then add:

pacman -S --needed mingw-w64-x86_64-clang-tools-extra

Test Suite Layout

tests/
  CMakeLists.txt         — CMake test configuration
  test_packet.cpp        — Network packet encode/decode
  test_srp.cpp           — SRP-6a authentication math (requires OpenSSL)
  test_opcode_table.cpp  — Opcode registry lookup
  test_entity.cpp        — ECS entity basics
  test_dbc_loader.cpp    — DBC binary file parsing
  test_m2_structs.cpp    — M2 model struct layout / alignment
  test_blp_loader.cpp    — BLP texture file parsing
  test_frustum.cpp       — View-frustum culling math

The Catch2 v3 amalgamated source lives at:

extern/catch2/
  catch_amalgamated.hpp
  catch_amalgamated.cpp

Building the Tests

Tests are not built by default. Enable them with -DWOWEE_BUILD_TESTS=ON.

Release Build (normal)

Note: Per project rules, always use rebuild.sh for a full clean build. Direct cmake --build is fine for test-only incremental builds.

# Configure (only needed once)
cmake -B build -DCMAKE_BUILD_TYPE=Release -DWOWEE_BUILD_TESTS=ON

# Build test targets (fast — only compiles tests and Catch2)
cmake --build build --target \
  test_packet test_srp test_opcode_table test_entity \
  test_dbc_loader test_m2_structs test_blp_loader test_frustum

Or simply run a full rebuild (builds everything including the main binary):

./rebuild.sh      # ~10 minutes — see BUILD_INSTRUCTIONS.md

Debug + ASAN/UBSan Build

A separate CMake build directory is used so ASAN flags do not pollute the Release binary.

cmake -B build_asan \
  -DCMAKE_BUILD_TYPE=Debug \
  -DWOWEE_ENABLE_ASAN=ON \
  -DWOWEE_BUILD_TESTS=ON

cmake --build build_asan --target \
  test_packet test_srp test_opcode_table test_entity \
  test_dbc_loader test_m2_structs test_blp_loader test_frustum

CMake will print: Test targets: ASAN + UBSan ENABLED when configured correctly.


Running Tests

test.sh — the unified entry point

test.sh is the recommended way to run tests and/or lint. It handles build-directory discovery, dependency checking, and exit-code aggregation across both steps.

# Run everything (tests + lint) — default when no flags are given
./test.sh

# Tests only (Release build)
./test.sh --test

# Tests only under ASAN+UBSan (Debug build — requires build_asan/)
./test.sh --asan

# Lint only
./test.sh --lint

# Both tests and lint explicitly
./test.sh --test --lint

# Usage summary
./test.sh --help

Exit codes:

Outcome Exit code
All tests passed, lint clean 0
Any test failed 1
Any lint diagnostic 1
Both test failure and lint issues 1

Running directly with ctest

# Release build
cd build
ctest --output-on-failure

# ASAN build
cd build_asan
ctest --output-on-failure

# Run one specific test suite by name
ctest --output-on-failure -R srp

# Verbose output (shows every SECTION and REQUIRE)
ctest --output-on-failure -V

You can also run a test binary directly for detailed Catch2 output:

./build/bin/test_srp
./build/bin/test_srp --reporter console
./build/bin/test_srp "[authentication]"    # run only tests tagged [authentication]

Lint (clang-tidy)

The project uses clang-tidy to enforce C++20 best practices on all first-party sources under src/. Third-party code (anything in extern/) and generated files are excluded.

Running lint

./test.sh --lint

Under the hood the script:

  1. Locates clang-tidy (tries versions 1418, then clang-tidy).
  2. Uses run-clang-tidy for parallel execution when available; falls back to sequential.
  3. Reads build/compile_commands.json (generated by CMake) for compiler flags.
  4. Feeds GCC stdlib include paths as -isystem extras so clang-tidy can resolve <vector>, <string>, etc. when the compile-commands were generated with GCC.

compile_commands.json is regenerated automatically by any CMake configure step. If you only want to update it without rebuilding:

cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

Applying auto-fixes

Some clang-tidy checks can apply fixes automatically (e.g. modernize-*, readability-*):

FIX=1 ./test.sh --lint

Caution: Review the diff before committing — automatic fixes occasionally produce non-idiomatic results in complex template code.

Configuration (.clang-tidy)

The active check set is defined in .clang-tidy at the repository root.

Enabled check categories:

Category What it catches
bugprone-* Common bug patterns (signed overflow, misplaced =, etc.)
clang-analyzer-* Deep flow-analysis: null dereferences, memory leaks, dead stores
performance-* Unnecessary copies, inefficient STL usage
modernize-* (subset) Pre-C++11 patterns that should use modern equivalents
readability-* (subset) Control-flow simplification, redundant code

Notable suppressions (see .clang-tidy for details):

Suppressed check Reason
bugprone-easily-swappable-parameters High false-positive rate in graphics/math APIs
clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling Intentional low-level buffer code in rendering
performance-avoid-endl std::endl is used intentionally for logger flushing

To suppress a specific warning inline, use:

// NOLINT(bugprone-narrowing-conversions)
uint8_t byte = static_cast<uint8_t>(value); // NOLINT

ASAN / UBSan

AddressSanitizer (ASAN) and Undefined Behaviour Sanitizer (UBSan) are applied to all test targets when WOWEE_ENABLE_ASAN=ON.

Both the test executables and the catch2_main static library are recompiled with:

-fsanitize=address,undefined -fno-omit-frame-pointer

This means any heap overflow, stack buffer overflow, use-after-free, null dereference, signed integer overflow, or misaligned access detected during a test will abort the process and print a human-readable report to stderr.

Workflow

# 1. Configure once (only needs to be re-run when CMakeLists.txt changes)
cmake -B build_asan \
  -DCMAKE_BUILD_TYPE=Debug \
  -DWOWEE_ENABLE_ASAN=ON \
  -DWOWEE_BUILD_TESTS=ON

# 2. Build test binaries (fast incremental after the first build)
cmake --build build_asan --target test_packet test_srp   # etc.

# 3. Run
./test.sh --asan

Interpreting ASAN output

A failing ASAN report looks like:

==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000010
READ of size 4 at 0x602000000010 thread T0
    #0 0x... in PacketBuffer::read_uint32 src/network/packet.cpp:42
    #1 0x... in test_packet tests/test_packet.cpp:88

Address the issue in the source file and re-run. Do not suppress ASAN reports without a code fix.


Adding New Tests

  1. Create tests/test_<name>.cpp with a standard Catch2 v3 structure:
#include "catch_amalgamated.hpp"

TEST_CASE("SomeFeature does X", "[tag]") {
    REQUIRE(1 + 1 == 2);
}
  1. Register the test in tests/CMakeLists.txt following the existing pattern:
# ── test_<name> ──────────────────────────────────────────────
add_executable(test_<name>
    test_<name>.cpp
    ${TEST_COMMON_SOURCES}
    ${CMAKE_SOURCE_DIR}/src/<module>/<file>.cpp   # source under test
)
target_include_directories(test_<name> PRIVATE ${TEST_INCLUDE_DIRS})
target_include_directories(test_<name> SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS})
target_link_libraries(test_<name> PRIVATE catch2_main)
add_test(NAME <name> COMMAND test_<name>)
register_test_target(test_<name>)   # required — enables ASAN propagation
  1. Build and verify:
cmake --build build --target test_<name>
./test.sh --test

The register_test_target() macro call is mandatory — without it the new test will not receive ASAN/UBSan flags when WOWEE_ENABLE_ASAN=ON.


CI Reference

The following commands map to typical CI jobs:

Job Command
Unit tests (Release) ./test.sh --test
Unit tests (ASAN+UBSan) ./test.sh --asan
Lint ./test.sh --lint
Full check (tests + lint) ./test.sh

Configuring the ASAN job in CI:

- name: Configure ASAN build
  run: |
    cmake -B build_asan \
      -DCMAKE_BUILD_TYPE=Debug \
      -DWOWEE_ENABLE_ASAN=ON \
      -DWOWEE_BUILD_TESTS=ON

- name: Build test targets
  run: |
    cmake --build build_asan --target \
      test_packet test_srp test_opcode_table test_entity \
      test_dbc_loader test_m2_structs test_blp_loader test_frustum

- name: Run ASAN tests
  run: ./test.sh --asan

See BUILD_INSTRUCTIONS.md for full platform dependency installation steps required before any CI job.