mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 03:59:19 +02:00
test: unit tests of the simulation core, run as part of the build
eu07_unit_tests: C++ tests (doctest) of the simulation, which set up the
conditions, run the code and check the outcome. No game data, no window.
- the code under test is the core library (eu07_core), the very objects
the game links, built with the game's settings; engine_stubs.cpp stands
in for the logs, settings and scenery layers the library reaches for.
python gets linked with the settings but isn't needed to run the tests
- test vehicles are JSON definitions in tests/unit/data, with the sections
and keys of the game's vehicle definitions (tables as "rows", _keys are
comments), changed by the tests where needed and loaded from memory by
the game's own loader; an unknown section is an error rather than
silently skipped
- test::vehicle creates the mover the way TDynamicObject::Init() does and
steps it the way the simulation loop does, on a straight track of given
friction and gradient, optionally holding the brake pipe pressure the
way a coupled locomotive would
- the random numbers of the simulation are reseeded before every test
case, so a test gives the same result alone (IDE) or among the others
- building the game runs the tests at the end, once the game is linked,
and prints a one line summary ("Unit tests: 11/11 passed (80 checks)").
A failure prints the full report and fails the build, in CI and locally
alike, and removes the game binary so the next build reruns them. A
change of the tests alone relinks the game so they run (Ninja and
Makefile generators). -DWITH_TESTS=OFF leaves them out
- first tests: definitions reach the vehicle, KE service application and
release, deeper reduction brakes harder, every implemented distributor
valve charges/applies/releases, service braking stops a moving wagon
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
68
tests/unit/README.md
Normal file
68
tests/unit/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Unit tests
|
||||
|
||||
C++ tests of the simulation logic: set up the conditions, run the code, check the outcome. They run
|
||||
as part of every build of the game, in CI and locally alike, and from the IDE. They need no game
|
||||
data and no window: the test vehicles are JSON definitions in `data/`, loaded from memory by the
|
||||
game's own vehicle loader.
|
||||
|
||||
The code under test is `eu07_core`, the library of the simulation parts the game links as well
|
||||
(physics, vehicle definition loader, ...): built once, with the game's settings, and tested as the
|
||||
game uses it. The python library gets linked along with the game's settings, but the tests don't
|
||||
use it and run without it.
|
||||
|
||||
## Running
|
||||
|
||||
- **With the build**: building the game (`WITH_TESTS`, on by default) runs them at the end, once the
|
||||
game is linked, and prints `Unit tests: 11/11 passed (80 checks)`. A failing test prints the full
|
||||
report and fails the build; the game binary gets removed, so the next build links it again and
|
||||
reruns them. They also run when only the tests, a definition in `data/` or `run_tests.cmake`
|
||||
changed (Ninja and Makefile generators). `-DWITH_TESTS=OFF` builds the game without them.
|
||||
- **From the IDE**: run or debug the `eu07_unit_tests` target. CLion recognises doctest and lists the
|
||||
test cases, so a single one can be run or debugged from the gutter.
|
||||
- **By hand**: `ctest --test-dir <build dir> --build-config Release`, or the binary itself, which takes
|
||||
[doctest options](https://github.com/doctest/doctest/blob/master/doc/markdown/commandline.md), e.g.
|
||||
`eu07_unit_tests -tc="brakes*"`. `EU07_UNIT_TESTS_LOG=1` shows the engine log, quiet otherwise.
|
||||
|
||||
## Writing a test
|
||||
|
||||
Add a `test_<topic>.cpp` here, it's picked up on its own.
|
||||
|
||||
```cpp
|
||||
#include "stdafx.h"
|
||||
#include "doctest.h"
|
||||
#include "vehicle.h"
|
||||
|
||||
TEST_CASE("brakes: service braking stops a heavier wagon too") {
|
||||
// conditions: the wagon from data/wagon.json, heavier, running at 40 km/h
|
||||
test::vehicle wagon{test::definition::load("wagon").set("Param", "M", 60000), 40.0 /* km/h */};
|
||||
// action: full service application, as a coupled locomotive would make it
|
||||
wagon.hold_brake_pipe(3.5); // bar
|
||||
wagon.run(40.0 /* s */, test::track{0.15 /* friction */, 0.0 /* gradient */});
|
||||
// outcome
|
||||
CHECK(std::abs(wagon.speed()) < 0.1);
|
||||
}
|
||||
```
|
||||
|
||||
- `definition::load("<name>")` reads `data/<name>.json`, with the sections and keys of the game's
|
||||
vehicle definitions; `set()` and `remove()` change it for the test. Table sections give their
|
||||
rows as `"rows": [[...], ...]`, keys starting with `_` are comments. A section the loader doesn't
|
||||
know is an error (the table of them is in `vehicle.cpp`); a misspelt key isn't caught, so check
|
||||
in the test that the value the test is about made it to the vehicle. Keep the definitions
|
||||
physically sensible: some parts need keys others don't (the ESt brake valves need `Brake.Size`),
|
||||
and a test on a half-defined vehicle tests the definition rather than the simulation.
|
||||
- The random numbers of the simulation are reseeded before every test case, so a test gives the
|
||||
same result alone or among the others.
|
||||
- `vehicle` creates the vehicle the way `TDynamicObject::Init()` does and steps it the way the
|
||||
simulation loop does, on a straight track of the given friction and gradient. Coupled vehicles
|
||||
aren't supported yet.
|
||||
- Check behaviour (speeds, pressures, states the player sees) rather than internal flags, and make
|
||||
the test check its own preconditions (`REQUIRE` that the braking skidded, before checking what a
|
||||
skid leads to).
|
||||
|
||||
## Code to test, and when the tests fail to link
|
||||
|
||||
A source joins the tested code by moving from `SOURCES` to `CORE_SOURCES` in the main
|
||||
`CMakeLists.txt`; the game links it from the library from then on. The library reaches for a little
|
||||
of the rest of the engine, which `engine_stubs.cpp` stands in for in the tests (logs, settings,
|
||||
scenery layers). If a change makes it use something new, either move the real source into the
|
||||
library too, when it stands on its own, or add a stub to `engine_stubs.cpp`.
|
||||
Reference in New Issue
Block a user