The log was printed in colour to stdout too (debuglog bit 2), so a script reading
the verdict of a test got the whole log with it. In test mode the log goes to the
log file only and stdout has the `test: ...` line alone; the usage text of a bad
command line goes to stderr. Whether the run is a test is now known before the
log service starts.
The config block of a scenery is read like the settings files, after the test
mode settings were applied, so a scenery with soundenabled, multiplayer,
execonexit, feedbackmode or the like in it got them back in a test run. The test
mode settings now are applied again after each config block of the scenery;
apply_test_mode_settings() moved to simulationtest.cpp for that.
The simulator reads eu07.ini from %APPDATA%/MaSzyna (~/.config/MaSzyna on Linux)
when there is one, in place of the game folder's. A test run then depended on
whose machine it ran on. With --test only the game folder's eu07.ini is read,
then the files given with -ini. The test mode options are taken from the command
line before the settings for that; --test-timeout still wins over test.timeout
of any settings file.
The command line options of the test mode and the end conditions checked after
each frame are now plain functions in simulation/simulationtestrules.cpp, which
needs nothing of the running simulation: parse_test_command_line() and
evaluate_frame(). The file is part of the core library, so the unit tests check
them (tests/unit/test_test_mode.cpp): the --test-timeout value, values of other
options not taken for options, a pass that waits for the stop, the standstill,
the timeout. test_manager only gathers what a frame brought and acts on the
verdict. No change in behaviour.
The test mode turned off what comes in from outside, but a few settings
of the user's eu07.ini still reached out of a test run:
- feedbackmode: 1 and 2 toggle Caps Lock / Scroll Lock by injecting key
events into the whole system, 3 and 4 drive an LPT port or a PoKeys
desk (and 4 reads the desk's switches);
- multiplayer: events are reported to a multiplayer server, and some
events are held back for it;
- execonexit: a program is run with system() at the end of each run,
which also waits for it;
- showsystemconsole: a console of the run's own pops up and stdout is
reopened on it, so whoever started the test with a redirected output
doesn't get the `test:` line (and a console the run was started from
would be hidden without it);
- imgui.ini in the game folder was written by every run, and by several
at once with run.py -j.
All of these are off in test mode. Nothing changes outside it.
- `--test` is looked for before the rest of the command line is read. An
unknown option before it (e.g. `-s x.scn --test-timout 90 --test`) used
to end the run with exit code 0, which a caller reads as a pass.
- `--test-timeout` without a value, or with one that isn't a number of
seconds, fails the run instead of leaving the default or, through atof,
turning the timeout off.
- These, a missing -s, a failed start and an exception caught in main()
end the test through test_manager::conclude(), which writes the usual
`test: error (exit code 5) ... : <reason>` line to stdout and the log.
Before, `eu07 --test` alone exited with 5 and nothing anywhere: the
error went to the log queue and the process ended before the log
thread wrote it. In test mode main() now stops the log thread and
waits for it before _Exit().
Outside the test mode nothing changes: conclude() does nothing there.
With --test the gamepad, mouse picking, UART, ZeroMQ and motion telemetry are
off, the hidden window gets no key, mouse, scroll or char callbacks, and the
window procedure that takes WM_COPYDATA commands from other programs isn't
installed, so nothing but the scenery drives a test run.
The test manager is reached through simulation::test() instead of a global,
the result cell is owned by a unique_ptr until the memory table takes it, the
cell value is compared with a margin, strings are built with std::format and
the --test settings moved to apply_test_mode_settings(). The driver's signal
check is folded into UpdateSpeedPosition(), which updates a speed table entry
and reports a signal at stop the head of the train just passed. The test.*
keys are listed with the other settings at the start of the log.
eu07 --test -s <scenery> [--test-timeout <s>] runs the scenery in ghostview with
the null renderer (models loaded, no textures), a hidden window, no sound,
network or python, and in fixed steps of 0.03 s of simulation time per frame
(three 10 ms physics steps) as fast as it can. Without -seed it uses a fixed
seed, so two runs give byte-identical physicslogs.
The scenery reports its result to the __test.result memory cell, which the
simulator always creates: value 1 passes once every vehicle has stood still for
1 s, value 0 fails at once. The run ends with exit code
0 pass, 1 fail,
2 a train passed a signal at stop (SetVelocity 0 0, as seen in its driver's
speed table; SetVelocity 0 x on automatic block doesn't count),
3 timeout (test.timeout, default 3600 s of simulation time),
4 standstill (every vehicle still for test.standstill s, default 300, with no
result; a pass with test.pass_on_standstill yes),
5 error (no -s, the scenery didn't load or has no vehicles, an exception).
The test.* keys go in the scenery's config block; test.fail_if_stop_overran no
only logs a signal passed at danger. Outside the test mode a signal passed at
danger is logged.
The null renderer gets a ui backend which draws nothing, hands out the null
texture and material instead of throwing, and ends the ui frame. A GL context
is still made for the hidden window, without asking for a version, as parts
of the ui create textures with gl calls of their own. No crash dialog or
message box in test mode; the window code no longer needs a monitor.
tests/scenarios/run.py builds a test scenery from a game install's scenery
(own trainsets removed, case fragment added), runs eu07 in ghostview with
physicslog on, and checks what the vehicle did: starts, max speed, brakes,
stops. First case: EU07 on td.scn driven by AI on the td/rozklad timetable,
with the Stacja1 exit route set by an event launcher instead of shift+4.
eu07 gets a -ini <file> option: an extra settings file read after eu07.ini,
so a test run can switch to a window and turn on physicslog without touching
the user's config.
Local only: scenery and vehicle data are not in the repo.
- the log stops through a flag kept in a function, not a global one
- the messages are put together with std::format
- a vehicle dropped on a trainset in the launcher goes in once the
trainset is drawn, so the vehicles are gone through with a plain loop
again; the drop split into a new vehicle and a moved one
- the scenario that can't be opened is caught as the runtime error it
is, a file name outside of the code page as the system error it is
- a track of an unknown type is left out through the pointer owning it
- the keyword of a track type written out by a function of its own
- the picked node forgotten with ranges::replace_if, the empty
Forget_Node of the base renderer told why it is empty
- FileCased takes the path it works on
A scenario failing past its opening popped the loader mode and went on to pop it again and push the driver mode with the scenery half loaded; it goes back to the launcher as one that can't be opened. FileCased() goes through the folder only where the file system tells the case apart: on Windows a missing file was looked for in its whole folder for nothing, and a name outside the code page threw from the parser. The Discord thread sleeps a quarter of a second outside the driver mode, not to hold the closing up.
The log service stopped with applicationQuitOrder, so whatever was logged while the simulator closed was lost; it now runs until StopLogService() and writes out what came before. The closing logs each of its steps with the time since it began, to tell where a closing that hangs stops. Outside the driver mode the Discord thread spun a core in its loop, it now sleeps a second between the checks.
Consolidates platform-specific config-directory logic (APPDATA on Windows,
~/Library/Application Support on macOS, ~/.config on Linux) into a single
user_config_path(filename) function in utilities/utilities.{cpp,h}, removing
copy-pasted #ifdef blocks from application.cpp, editor/editorSettings.cpp,
input/{keyboard,gamepad,drivermouse,uart}input.cpp.