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

390 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WoWee Testing Guide
This document covers everything needed to build, run, lint, and extend the WoWee test suite.
---
## Table of Contents
1. [Overview](#overview)
2. [Prerequisites](#prerequisites)
3. [Test Suite Layout](#test-suite-layout)
4. [Building the Tests](#building-the-tests)
- [Release Build (normal)](#release-build-normal)
- [Debug + ASAN/UBSan Build](#debug--asanubsan-build)
5. [Running Tests](#running-tests)
- [test.sh — the unified entry point](#testsh--the-unified-entry-point)
- [Running directly with ctest](#running-directly-with-ctest)
6. [Lint (clang-tidy)](#lint-clang-tidy)
- [Running lint](#running-lint)
- [Applying auto-fixes](#applying-auto-fixes)
- [Configuration (.clang-tidy)](#configuration-clang-tidy)
7. [ASAN / UBSan](#asan--ubsan)
8. [Adding New Tests](#adding-new-tests)
9. [CI Reference](#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](BUILD_INSTRUCTIONS.md) for platform-specific dependency installation.
### Linux (Ubuntu / Debian)
```bash
sudo apt update
sudo apt install -y \
build-essential cmake pkg-config git \
libssl-dev \
clang-tidy
```
### Linux (Arch)
```bash
sudo pacman -S --needed base-devel cmake pkgconf git openssl clang
```
### macOS
```bash
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:
```bash
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.
```bash
# 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):
```bash
./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.
```bash
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.
```bash
# 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
```bash
# 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:
```bash
./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
```bash
./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:
```bash
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
```
### Applying auto-fixes
Some clang-tidy checks can apply fixes automatically (e.g. `modernize-*`, `readability-*`):
```bash
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](.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:
```cpp
// 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
```bash
# 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:
```cpp
#include "catch_amalgamated.hpp"
TEST_CASE("SomeFeature does X", "[tag]") {
REQUIRE(1 + 1 == 2);
}
```
2. **Register** the test in `tests/CMakeLists.txt` following the existing pattern:
```cmake
# ── 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
```
3. **Build** and verify:
```bash
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:**
```yaml
- 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](BUILD_INSTRUCTIONS.md) for full platform dependency installation steps required before any CI job.