17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-08 11:34:19 +01:00
Files
maszyna/tests/unit/COVERAGE.md
jerrrrycho 466cbbbfa1 Test/core unit coverage (#235)
* test: real vehicles and golden traces in the unit tests

The game's own vehicles (dynamic/ of the release used for the tests) as
JSON of what the game's vehicle loader reads of their .fiz files, includes
expanded: test_fiz.cpp converts them on demand and checks the loader reads
the same sections, values and rows as of the game's file (fiz.h). Chosen
to cover every engine type, brake valve, brake handle, local brake,
coupler type, door type and brake delay set the game data uses.

Golden traces (golden.h) characterise the simulation, compared within a
relative tolerance or exactly, re-recorded with
EU07_UNIT_TESTS_UPDATE_GOLDEN=1:
- the parameters of every real vehicle as loaded,
- the brakes of every real vehicle through charge, service steps,
  release, overcharge and emergency, on every brake delay setting,
- every position of every driver's and local brake handle,
- every powered vehicle started up the way the AI does it, driven to
  full power and braked to a stop (drive.h),
- the real electric units and a locomotive with wagons driven whole, as
  trains of coupled vehicles stepped the way the game steps them
  (consist.h),
- scenarios of the controls (scenario.h): doors, reversing and cab
  change, slip and sand, emergency stops, releasers, the ep and the
  electrodynamic brake, heating, loading, a collision, and frames of
  several physics iterations.

* test: commands passed between vehicles

Every command RunCommand knows run on every real vehicle and recorded,
commands travelling through a train with a vehicle facing backwards, a
coupling without the control cable, and loading and unloading by commands.

* test: every switch of the cab, sent to the next vehicle

Each switch sending its state to the train, each way and to each range,
with the controllers and direction among them, on every real vehicle
driving another of its kind, recorded for both.

* test: the switches of the cab worked on a pair under the catenary

The same switches with two seconds for the vehicles to act on each, so
the pantographs rise, the main switch closes and the auxiliaries start.

* test: gauges of trains in multiple and the damage descriptions

The ammeters and the brake force the AI expects, recorded on the electric
units and locomotives in multiple driven, the damage described on the
driver's panel and ammeters showing the current of controlled vehicles.

* test: give load commands their place, as the game does

The loading test set its commands with SetInternalCommand, which leaves
TCommand::Location as it is: uninitialised in a fresh vehicle, so the
distance check of Load= and UnLoad= read garbage and the test failed
now and then. The game gives them through PutCommand with the place of
the platform, and so do the tests now.

* test: brake valves and handles no vehicle of the game data has

Fitted to a wagon and to an EU07: the valve variants and handles the
loader knows that no real vehicle uses, the CV1 ones and the test handle
among them, and a name the loader doesn't know.

* test: braking from speed with the automatic ED brake, and a draisine

Trains with the EP09, the E6ACT and the EW58 braked by ep, light and full
service applications from speed, and the draisine pumped from rest and
rolling. Only the E6ACT brings its electrodynamic brake in here.

* test: the EP09's electrodynamic brake in service braking held to a stop

start_up() took a vehicle for ready once its line breaker was closed and
its reservoir full, which a train made already moving is at once: the
EP09's manually started converter was never switched on, so there was
no 110 V and no electrodynamic brake. It now waits for the converter.

The EP09 with wagons is braked from 120 km/h by light, medium, high and
full service applications held to a stop: the motors brake, the air of
its cylinders is let out and given back as the train slows down. The
flag of the electrodynamic brake stays on at a standstill below 2.1 bar.

C strings given to CAPTURE are shown as text.

* test: the converter switched on before a train is ready, the EW58's ep-staged electrodynamic brake

start_up() called a train ready as soon as its line breaker closed and the air was there, so the converter
of a unit having it in another car than the cab (the EW58's middle car) was never switched on, and its
electrodynamic brake, gated by the 110 V, never came in. The converter and the compressor now always get
switched on first. The EW58 brakes electrically by the stages of the ep brake, not by the pipe alone.

* test: the ET42 and the 214M railbus among the real vehicles

The ET42's halves (its own series motor branch: the motors in series at the high current setting) and the
214MA, a diesel railbus with the hydraulic torque converter, the retarder and the controller of an induction
motor drive, converted from the game data and recorded in every sweep of the real vehicles.

* test: the wagons' hand brakes released with the train's brakes; the 754, SM31 and SU46

release_brakes() released the hand brake of the driver's vehicle only, so every wagon set up braked kept its
hand brake on and got dragged along. It now releases the hand and spring brakes of the whole train, as
TController::AutoRewident() does when the AI takes it over, and the traces of the trains with wagons are
recorded anew.

The 754, the SM31 and the SU46 join the real vehicles for the kinds of the diesel-electrics' field shunting
relays the others lack (RelayType 0, 1 and 46).

* test: traction on a lost and too high catenary voltage, the ET42, the diesel-electrics' shunting relays

The line breaker opened by the voltage relays of a series motor locomotive and by the limits of an induction
motor one. The ET42 at the high current setting, which keeps its halves' motors in series, and its switched
electrodynamic brake, whose current trips the overload relay from the top speed of the low setting. The field
shunting of the diesel-electrics through each kind of their relays, at full power and notched back.

* test: the EW58's electrodynamic brake stages, the EU47's speed control and braking at the voltage's limit

The stages of the EW58's motor brake set by the positions of its ep handle, the first one alone over
100 km/h. The EU47's second controller holding, raising and lowering the speed set, its local brake setting
the flag of the electrodynamic brake without the motors braking, and its brake feeding back less current into
a catenary over the drive's MaxV.

* test: diesel engines too cold and too hot to start, the 214M railbus's controller and retarder

The heat of the diesel engines: at -10 degrees the ones with the lowest water temperature set don't start
until the heater warms them, at 88 degrees the ones with a second water circuit don't start while it cools,
and the SM42 run hot at full power is kept under its highest temperature by its fans and shutters. The 214M
railbus's controller from the air brake through the retarder and neutral to power, its torque converter's
lock-up and its gearbox.

The controller helpers of the traction tests (full power, waiting for the line contactors, and notching back)
move to test::scenario.

* test: couplers coupled, uncoupled, broken and colliding; loads assigned, loaded and unloaded

Coupling by hand touching or apart and of the same kind or not, with the sounds of what changes; uncoupling a
screw coupler pressed together, stretched, with the hoses alone or broken; the automatic couplers of electric
units coupling on contact, without the control cable between two kinds; a coupler broken with its brake
hose; collisions over 15 km/h derailing. The pantographs set up by the pantstate load, a load of one kind at
a time up to the capacity, and the steps of loading and unloading.

* test: the battery drained and charged, the pantographs' tank and pressure switch, the 214M's speed control

The EU07's battery drained without the converter by the lights and the small compressor until it opens the
line breaker and nothing starts, charged by the converter, and switched off. The pantographs on their own
leaking tank until the pressure switch opens the line breaker, and the small compressor pumping it up and
priming the switch. The 214M railbus's speed control set by its buttons, steps and power limit, braking down
with the retarder, and standing by.

* test: the MHZ handles' buttons, the valves at speed, every brake material, the NESt3's relays

The overload and the high pressure buttons of the MHZ handles. The KE and the LSt at speed in the R and P
settings, under the anti-slip request and the releaser button. Every material of the brake shoes and disks
the loader takes, braking a wagon from speed, the ones no game vehicle has among them. The CV1_R and the
Oerlikon valve's relays set by its name (-ED, AL2 without ESt3) fitted to a wagon.

* test: the loader's lists and lines: the 218M, SA134 and 22WEc, a compressor list, Pmax by speed, skipped lines

The 218M (V2NList), the SA134 (HTCList) and the Elf 22WEc (ffBrakeList) join the real vehicles for the lists
the others lack. Definitions made by the tests for what no game file has: a compressor list, whose table of
whole numbers reads a factor of 0.9 as 0; the compressor's limits apart for the rear cab; the highest power
of an induction motor drive by the speed, extrapolated past the table; and the lines the loader skips or
trims (comments, a hash anywhere, leading spaces, carriage returns).

* test: what the cab and the AI call on a vehicle: gauges of the train, the split ED brake, spring brake, cut-off

The engine rotation of up to three vehicles in multiple and the lamps of the overload relay and the starting
resistors read through the control cable; the engine's idle speed and the ET42's second controller shown in
its dynamic brake; the controller of the electrodynamic brake apart from the local brake, which no game
vehicle has yet; the spring brake shut off and released, the ep brake's fuse and the handle stepped by the AI;
damaged motors cut off; the cab activated by itself; a road vehicle moved across a road, for which the tests'
track gets a width; and the lamps and the buzzer of the security system left unattended.

* test: the EP05 and its camshaft among the real vehicles, the headlights' colour and brightness

The EP05, whose camshaft closes the line breaker and the line contactors by the camshaft's own position,
converted from the game data and recorded in every sweep of the real vehicles. The Headlights section, which
only the 214M's own file has, in a definition made by the test.

* test: what the unit tests reach of the simulation core, what's dead or left, and what they found

COVERAGE.md: the blocks of Mover.cpp, hamulce.cpp, Oerlikon_ESt.cpp and friction.cpp the tests run, the code
nothing in the game runs, the code only data no game vehicle has would run, what's reachable and left, and
the behaviour the tests found that may deserve a look.

* test: check invariants of the physics on every step of every golden trace

Rules any vehicle keeps whatever a test does (no NaN, cylinders and pipes
within their limits, no current with the line breaker or contactors open,
no force without power, distance as the speed runs, no acceleration over
5 g), checked on every step, with no comparison to a recorded file. The
traces where a test or a game vehicle breaks one are excused with the
reason; the vehicles' cases are in the findings of COVERAGE.md.

* test: key figures of every golden trace, per part of the run

Duration, speeds, distance, time to stop, the brake pipe's lowest
pressure and time to recover, the peaks of the cylinders, the motor
current and the tractive force, for each part between two events, in
data/golden/<name>.figures. Compared within 0.5% or the last decimal, so
they hold on any compiler, and an intended change of behaviour shows in
a few readable lines. Traces sampled by hand keep none.

* test: keep the golden samples out of the repository, recorded on the code before a change

The samples of the golden traces are 29 MB and only mean something against
the code before the change they check, so they're recorded locally before it
(EU07_UNIT_TESTS_UPDATE_GOLDEN=1) and compared after it, and ignored by git.
The key figures and the invariants stay, and are what the CI checks.

Without samples recorded a trace compares only its key figures, but with
EU07_UNIT_TESTS_GOLDEN_EXACT=1, the check of a change which shouldn't alter
anything, it fails, so the check can't pass by having nothing to compare.

* fix: draw the noise of the control pipe pressure in one order on every compiler

The order of the two Random(10) calls in 'Random(10) - Random(10)' is
unspecified: GCC draws them the other way round than MSVC Release, which
mirrored the control pipe pressure around 5 bar and failed 32 unit tests
on the Linux CI. The draws are sequenced in the order of MSVC Release,
which the golden traces were recorded with.

* test: count the start-up rounds with an integer, not a double (SonarCloud S2193)

* test: behaviour in CHECKs, the key figures out of the repository

The key figures of the golden traces are recorded with their samples on the code
before a change, and compared after it, instead of being kept: a trace without
them checks the invariants and its test's CHECKs, which is what the CI runs.

The scenarios of the controls state what they show in CHECKs, and the runs of
every real vehicle what holds for each of them. Writing them showed scenarios
which didn't do what they were named for: the cab change stopped in the engine
room, the slip runs put the controller to full power at once and never got the
line contactors closed, and the train stopped by the alarm chain never got its
pipe charged again. They do it now; the rest went to the findings.

* test: the SonarCloud findings in the unit tests

std::format for the texts put together, emplace_back, const references, string_view parameters, std::array and std::vector for the C arrays, the ranges algorithms, names no longer shadowing the coupler ends, comments in the empty loops; start_up(), trace::check() and the converter of the definitions split up. BrakeForceR() is const, which it always was in effect, so the gauges test needs no const_cast. The golden traces compare bit for bit.
2026-11-07 12:37:12 +01:00

11 KiB

Coverage of the simulation core by the unit tests

These tests characterise the physics of the vehicles (McZapkie/Mover.cpp, hamulce.cpp, Oerlikon_ESt.cpp, friction.cpp). They exist to show that refactors of those files, such as splitting TractionForce or RunCommand, leave the behaviour as it was. This page lists:

  • what the tests reach;
  • what nothing in the game ever runs;
  • what only data no game vehicle has would run;
  • what is left to test;
  • the behaviour the tests found that may deserve a look.

How it was measured

Coverage was measured on Windows with MSVC's SanitizerCoverage (/fsanitize-coverage=trace-pc-guard) on the unit test binary. The counts are basic blocks, which comes close to branch coverage: both sides of a condition are blocks of their own. The tool is local and not part of the build, by decision (2026-10). Any block-coverage tool run over eu07_unit_tests gives comparable numbers.

File Blocks run, at the start Now
Mover.cpp 8.9% 80.7% (3323 of 4118)
hamulce.cpp 30% 76.3% (649 of 851)
Oerlikon_ESt.cpp 58% 87.4% (118 of 135)
friction.cpp 9% 90.9% (10 of 11)

The aim is branch coverage with the rest explained below, not 100%.

Code nothing in the game runs

Nothing creates these objects or calls these functions anywhere in the game, which was checked against the whole source tree. A refactor can delete them, or leave them untested.

  • Brake valves that never get created:
    • TESt, TESt3 and TESt3AL2, including Init, PLC and SetLP. The valve names ESt3, ESt3AL2 and ESt4 all create TNESt3.
    • TESt4R::GetPF, overridden by TLSt and never called by it.
    • TESt::CheckState, CVs, BVs, GetPF and EStParams, and TNESt3::EStParams.
  • TFV4a (GetPF, Init): the FV4a handle of the vehicle definitions creates TFV4aM.
  • Base implementations always overridden: TFricMat::GetFC (every material, the default P10 too, has its own) and TPrzekladnik::Update (every relay has its own).
  • Functions without callers:
    • In hamulce.cpp: PF_old, TBrake::GetBCF, TDriverHandle::OvrldButton, TReservoir::pa and TBrakeCyl::pa.
    • In Mover.cpp: BrakeVP, PipeRatio, RealPipeRatio, IncBrakeLevel, DecBrakeLevel, IncBrakePress, DecBrakePress, MainCtrlActualPowerPos and GetExternalCommand.
  • Inverter failures of the induction motor drives (inverter::Failure_Drive, Failure_Const, Activate): nothing ever sets them. The cab only shows them. The branches reading them in TractionForce can't be reached.

Code only data no game vehicle has would run

Where it was worth it, a test fits the missing parameter to a real vehicle or builds a definition with it (definition::set()). Those cases are marked "tested" below. The rest is reachable only by a vehicle definition the game data doesn't have:

  • Tested, fitted or handmade:
    • The valves CV1, CV1_R, CV1_L_TR, the W, K and Hik variants, SW, NESt3 and M483 (brake cycle: the valves the game knows…).
    • An Oerlikon valve named with -ED or AL2 without ESt3 (its relays TPrzekED and TPrzekCiagly).
    • The brake materials FR513, FR510, P10yBg and P10yBgu (brake cycle: every material…).
    • The handles test, D2, Westinghouse, MHZ_EN57 and St113 fitted to an EU07.
    • A CompressorList, a PmaxList and a Headlights section.
    • The rear cab's compressor limits (MinCP_B, MaxCP_B).
    • The controller of the electrodynamic brake apart from the local brake (SplitEDPneumaticBrake).
  • Not tested:
    • The retarder's clutch (R_IsClutch, R_ClutchSpeed) and the retarder worked by the local brake (R_WithIndividual).
    • The automatic relays of type 2 (RelayType=2).
    • The variant of CheckLocomotiveParameters for a definition whose RList doesn't match its powered axles.

Reachable, not tested yet

These are run by the game, by vehicles it has or by the cab, the AI or the scenery, but no test reaches them yet. Most of them are the far sides of conditions inside functions the tests do run.

  • Entry points from outside the core:
    • TBrake::ForceEmptiness and its overrides: a scenario's vehicle set up with empty brakes (DynObj.cpp).
    • TBrake::GetSoundFlag: the sounds of the brakes.
    • LoadFIZ and reload_FIZ: the file loading around LoadFIZ_Text, which the tests call directly.
    • TSecuritySystem::set_cabsignal_lock.
    • TMHZ_EN57::GetEP: an MHZ_EN57 handle in the EP mode of the brake.
  • Inside the functions the tests run (blocks run of the function's total):
    • TractionForce (305 of 373): the inverter failures above. The rest are mostly halves of conditions:
      • the SST table of the series-excited diesel-electrics;
      • the speed control's brake intervention with the air under BrakeInterventionVel;
      • a diesel-electric's generator tripping on overcurrent;
      • the ffBrakeList of the 22WEc, whose electrodynamic brake no test applies.
    • AutoRelayCheck (87 of 119): the ET22's group switch under some sequences, the relays at reverse with BackwardsBranchesAllowed, and the shunting of the EZTs.
    • update_doors (134 of 160), PantographsCheck (44 of 64) and PowerCouplersCheck (70 of 88): door modes and pantograph valve kinds only some game vehicles have, and the 24 V circuit across couplers.
    • RelayReset (29 of 43) and MainSwitchCheck (16 of 27): relays tripping in states the drive cycles don't reach.
    • UpdatePipePressure (108 of 130): the pressure switch lock of an electric unit's pantographs, which the EW58's automatic valve keeps away. Also the EP mode of the EN57's handle and the LockPipe thresholds (LPOn, LPOff), which no vehicle sets.
    • TCV1L_TR::GetPF (9 of 20), TKE::GetPF (28 of 37) and the MHZ handles: settings (G with the CV1L_TR, the KE's anti-slip pressure ASBP) that no real vehicle has together.

What the tests found

The tests record the behaviour as it is. The following points may deserve a look by someone who knows how the real vehicles behave.

  • Random(10) - Random(10) (Mover.cpp, the noise of the control pipe pressure CntrlPipePress): the order the two calls were evaluated in isn't specified. MSVC Release, MSVC Debug and GCC gave different traces, a control pipe pressure mirrored around 5 bar, which failed 32 tests on the Linux CI. Fixed: the draws are sequenced in the order of MSVC Release, which the golden traces were recorded with.
  • TCommand::Location isn't initialised in a new vehicle. The game always sets it through PutCommand(), but a test calling RunCommand() directly got different results run to run.
  • CompressorList is int[4][9] (MOVER.h), though its factors are fractions: 0.9 is read as 0. No game vehicle has the list yet.
  • A train whose pipe leaks under 2.75 bar before its start-up can't be released:
    • Where this happens: an FV4a handle and an LSt valve, for example 4 minutes standing with wagons, waiting for a hot engine.
    • The cause: the pipe gets cut off the main reservoir (lock_old in UpdatePipePressure). The AI presses its unlock button only for the lock set by LPOn and LPOff (control_main_pipe).
    • The same after every emergency stop of such a train, by the alarm chain or the radio-stop: reset, it stays with its pipe empty until the releaser is held (b_rls), which the test of the emergency stops does.
  • EW58:
    • Its electrodynamic brake comes in only by the stages of the ep brake (TUHEX), never by the brake pipe alone.
    • It needs 110 V, from the converter of the middle car only.
  • ET42:
    • At the high current setting the controller stops at the last series position, and the train crawls.
    • From the low setting's top speed (over 100 km/h) the dynamic brake's current trips the overload relay at once.
  • EU47: its local brake sets the flag of the electrodynamic brake, which keeps the train brake's air out, but the motors don't brake. The local brake's air does it.
  • EP09: the flag of the automatic electrodynamic brake stays on at a standstill while the cylinders hold less than 2.1 bar, and it flicks on while a high application is released.
  • Diesel engines with a second water circuit (SU45, ST44) started at 88 degrees: they don't start until that circuit, over its limit (74 or 75 degrees), cools. Off, it cools slowly.
  • Brake cylinders over the vehicle's highest pressure (found by the invariants, see the README):
    • The K valve of the SM03 and the EU04 fills the cylinders to the pressure of the auxiliary reservoir in a full service or emergency application: 4.27 bar of the SM03's MaxBP 3.5, 3.19 bar of the EU04's 2.8.
    • The ESt3d_PZZ valve of the SN61 does so in an emergency: 4.6 bar of its MaxBP 3.8.
    • The EU07's LSt overshoots a little, to 4.0 bar of its 3.8.
  • Diesel engines driven over their top speed: a lower gear taken at speed drives the engine through the wheels over its nmax, the SM03's by 10%, the WMB10's by 25%. The cutoff (nmax_cutoff) only takes the fuel off.
  • The ELF's C car's main reservoir over its governor: the cars share the supply pipe, the A car's governor stops at 9 bar, the C car's at 8.5, so the C car's reservoir reaches 8.64 bar.
  • Wagons thrown about as a train starts with full physics of the couplers: the slack taken up throws them at up to 5.7 g, and back within a step.
  • Nothing limits the speed: a light train at full power runs well past its Vmax (the 754 past 160 km/h, against a Vmax of 100).
  • Vehicles that don't do what they should, as driven by the tests:
    • An EN57 doesn't get ready (main reservoir).
    • An EN57AKL alone doesn't charge its pipe.
    • The TEM2 barely moves, and the SM31 crawls at 0.3 km/h at full power, its motors taking 444 A.
    • The draisine can't start from rest (the pump's force is under the resistance of a standing draisine).
    • The Elf and the Impuls, their doors opened with the steps permitted, close the doors as they start off but keep the steps out, which keeps them from moving.
    • The ED72 holds an ep application with its handle at running, and with the ep brake switched off too; only the ep release position lets it off. The EW58 releases at running.
    • The road vehicle (MAN) is only slowed down by a minute of full service.
  • A train run with five physics steps in a frame: an EU07 with wagons takes no current, its controller at full power, and rolls at a few km/h. The test steps the vehicles the way TDynamicObject does (FastComputeMovement for the steps before the last one), so the game may do the same.
  • Game data:
    • The 214M's file has TurboPos and MotorParamTable twice, and the EN57AL's Engine twice. The loader reads each in turn, so the later one overrides what both set.
    • A wagon set up braked keeps its hand brake on until someone releases it. The AI does that for the whole train (AutoRewident), so a test has to as well.