17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 10:19:20 +02:00
Files
maszyna/tests/unit
jerrrrycho db220a1a98 fix: automatic coupler crashes, horns, compressor start delay, radio and cab lever sounds
fix(cab): the line contactors lamp goes out with the pressure switch (eu07/maszyna#470)

The lamp of the open line contactors, which tells the start can be made, went out at 1 bar in the brake
cylinders, while the pressure switch cuts the start off only at 2 bar (or below 3.6 bar in the main pipe), so
with the brakes partly applied the start could be made with the lamp out. With a pressure switch fitted the lamp
now follows it; without one it goes out at 1 bar as before. The same holds for the lamp of the second unit, which
used to keep its last state between 1 bar and the cut-off. The condition is
TMoverParameters::LineContactorsLampLit().

Tests: tests/unit/test_line_contactors.cpp.

refactor: Sonar findings of PR 217

- horn_supply::pneumatic shadowed the pneumatic enumerator of the sound flags (cpp:S1117), it's horn_supply::air;
  the supplies read with using enum (S6177),
- CouplerForce(): the automatic coupling refused or caught without another level of nested ifs (S134), the
  same outcome: too fast, the coupling is locked; caught, coupled and locked; otherwise left to try again,
- CompressorCheck(): the power the compressor doesn't draw yet noted without a TODO (S1135),
- tests: the previous crash damage setting in an in-class initializer (S3230), the loops which stop early with
  break rather than in their conditions (S886), the stand-ins of test::pair in a std::vector rather than an array
  (S5945), units in the comments of the example without /* */ (S1103).

test: the cases the changes of the branch left without a test

- Cntrl.: BDelay1 to BDelay4 reach the brake delays (now through std::format), the default start types of the
  ground and converter overload relays of a multiple unit and of other vehicles (out of the nested conditionals),
- the compressor: fed from the low voltage it waits from the low voltage coming on; the pressure switch stops it
  above the cut-out pressure, without one the excess air is vented (the vented fraction rewritten),
- the horns: a vehicle without any air sounds as before, the threshold pressure, an electric horn on 110 V,
- the positions of a cab control below zero, and a control without position sounds.

refactor(cab): the kind of the train heating switch as a flag

Read once with the vehicle definition, rather than compared as text in each of the switch commands.

feat(cab): train heating switch springing back (MaSzyna-EU07/maszyna#85)

TrainHeating=[toggle]|impulse in the Switches: section of the vehicle definition makes the train heating switch
an unstable one: it goes up to switch the heating on, down to switch it off, and back to the middle once let go,
instead of staying where it was put. The position of such a switch isn't set from the heating state when the cab
is entered.

Tests: tests/unit/test_switches.cpp.

feat(sound): mirror sounds (MaSzyna-EU07/maszyna#84, eu07/maszyna#322)

The sound definitions of a vehicle (.mmd) take mirroropen: and mirrorclose:, played as a mirror sets off
unfolding or folding, and the locations section mirrors: <offset>... end, the offsets along the vehicle of the
mirror pairs, as for the doors (negative ahead of the centre), each pair at the sides of the vehicle. As the
animation, only the mirrors at the occupied cab sound. The start of a move is mirror_moves::motion in
vehicle/mirrors.h.

Tests: tests/unit/test_mirrors.cpp.

feat(cab): joint oil and fuel pump switch (MaSzyna-EU07/maszyna#81)

A cab control oilfuelpump_sw: switches both the oil and the fuel pump at once, with the new command
oilfuelpumptoggle (no key by default). As a two-state switch it turns both pumps off when both are on and both on
otherwise; as an impulse switch it's held to start them, like the separate pump switches. Its position follows
the pumps when the cab is entered.

refactor(editor): text fields filled without strncpy (MaSzyna-EU07/maszyna#112)

The last three strncpy calls Sonar flags (cpp:S5816), in the road and track editors, copy the text into the
ImGui field buffers with std::string::copy and terminate it right after; the text gets cut to the buffer as
before.

fix: automatic coupler crashes, horns, compressor start delay, radio and cab lever sounds

- automatic couplers don't catch above the safe collision speed (#86): with crash damage on they couple at a
  closing speed of 15 km/h at most, a faster hit locks the coupling until the vehicles part and the collision
  runs its course. On the way: CollisionVelocity() read a head-on 10 + 10 km/h as 14.1 km/h (quaternions turn
  by half the angle), it's now taken from the touching ends; CollisionDetect() took the other vehicle's speed
  the wrong way round for vehicles facing each other. Head-on hits now derail sooner, at the intended limit,
- a single horn lever can't sound both tones at once (eu07/maszyna#392),
- a skid ends once the vehicle stops on locked wheels (eu07/maszyna#479),
- electronic destination signs are blanked at the end of the timetable (eu07/maszyna#446),
- horns need the air or the voltage to sound (eu07/maszyna#290, #76): HornType=[Pneumatic]|Electric and
  Horn3Type in Cntrl.; a pneumatic horn needs 0.2 MPa in the main reservoir (or the main pipe without one),
  an electric one the low voltage; the lever stays where it was put,
- compressor start delay (#67): CompressorStartDelay [s] in Brake., counted from the compressor getting its
  power, after the engine starts say; none by default,
- the channel and volume buttons of a radio without power do nothing (eu07/maszyna#429); the power check of
  the radio is TMoverParameters::RadioPowered(),
- a lever moved smoothly sounds the positions it passes (#83).

Sonar findings in the functions changed: nesting, nested conditionals, commented-out code, std::format,
references to const. Unit tests: test::pair for two vehicles running into each other, test_couplers,
test_horn, test_wheelslip, test_compressor, test_radio, test_gauge_positions.
2026-10-17 16:15:56 +02:00
..
2026-10-15 17:56:41 +02:00
2026-10-15 17:56:41 +02:00
2026-10-15 17:56:41 +02:00
2026-10-15 17:56:41 +02:00

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, 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.

#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.
  • Logic which lives in classes too big to set up in a test (the AI driver, the cab) goes into free functions on the vehicle state the class calls, and the test calls them the same way: see vehicle/handover.h, which TController::TakeControl() uses, and test_handover.cpp.
  • 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. pair puts two of them on that track facing each other with the given ends and runs them together, as the simulation runs the vehicles of a scene: the coupler and buffer forces, collisions and automatic coupling come from the physics (test_couplers.cpp). Longer consists 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).

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.