From 6ccf4b14bf8a02fb7835dd20837db54ed5a5edcb Mon Sep 17 00:00:00 2001 From: jerrrrycho Date: Sat, 7 Nov 2026 17:38:38 +0100 Subject: [PATCH] feat(simulation): test.no_movement_before_pass, a pass that doesn't wait for a stop The config key from eu07/maszyna#541. By default (yes) a pass reported to __test.result waits until every vehicle has stood still for 1 s; with no it ends the test at once. The key is listed with the other test.* settings in the log and in the test start line. Scenario tests: new case test_mode_pass_while_moving reports the pass from event2 of tor1k as the train runs onto it and checks the run ended with the train still moving (new check ends_moving). The README lists the test.* keys with their defaults and the input the test mode ignores. --- simulation/simulationtest.cpp | 10 +++++-- tests/scenarios/README.md | 18 +++++++++++- .../cases/test_mode_pass_while_moving.json | 28 +++++++++++++++++++ tests/scenarios/run.py | 10 +++++++ utilities/Globals.cpp | 7 +++++ utilities/Globals.h | 1 + 6 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 tests/scenarios/cases/test_mode_pass_while_moving.json diff --git a/simulation/simulationtest.cpp b/simulation/simulationtest.cpp index 439f295f..8d57505f 100644 --- a/simulation/simulationtest.cpp +++ b/simulation/simulationtest.cpp @@ -82,9 +82,10 @@ void test_manager::start() } // a pause set by the scenery or the settings would never be lifted, nobody is there to press the key Global.iPause = 0; - WriteLog(std::format("test: started, timeout {:.0f} s, standstill {:.0f} s{}{}", Global.test.timeout, Global.test.standstill, + WriteLog(std::format("test: started, timeout {:.0f} s, standstill {:.0f} s{}{}{}", Global.test.timeout, Global.test.standstill, Global.test.pass_on_standstill ? ", standstill passes" : "", - Global.test.fail_if_stop_overran ? "" : ", signals passed at danger allowed")); + Global.test.fail_if_stop_overran ? "" : ", signals passed at danger allowed", + Global.test.no_movement_before_pass ? "" : ", pass without waiting for a stop")); } void test_manager::update(double const Deltatime) @@ -103,6 +104,11 @@ void test_manager::update(double const Deltatime) finish(test_result::fail, std::format("__test.result set to 0{}", reason)); return; } + if (holds(value, 1.0) && false == Global.test.no_movement_before_pass) + { + finish(test_result::pass, std::format("__test.result set to 1{}", reason)); + return; + } if (holds(value, 1.0) && false == m_passrequested) { m_passrequested = true; diff --git a/tests/scenarios/README.md b/tests/scenarios/README.md index 1ebd84c7..b57fc4a0 100644 --- a/tests/scenarios/README.md +++ b/tests/scenarios/README.md @@ -23,6 +23,9 @@ With `--test` the simulator: data) but no textures, and the window stays hidden, - has no sound, no network, no python, no pause (not even one set by the scenery), and no crash dialog, +- takes no input from outside: keys and mouse sent to its window, gamepad, UART (a cab + desk), ZeroMQ and commands sent by other programs (`WM_COPYDATA`) are all ignored, and + motion telemetry is off, - runs the simulation as fast as it can in fixed steps of 0.03 s of simulation time per frame (three 10 ms physics steps), whatever the real time, the time speed or the machine, - seeds its random numbers with a fixed seed unless `-seed` or `randomseed` gives one, so two @@ -48,7 +51,7 @@ The test ends with the first of these: | exit code | result | when | | --- | --- | --- | -| 0 | pass | `__test.result` was set to 1 and then every vehicle stood still for 1 s, so a train still moving towards a signal at stop has to stop first. Also the standstill end with `test.pass_on_standstill yes` | +| 0 | pass | `__test.result` was set to 1 and then every vehicle stood still for 1 s, so a train still moving towards a signal at stop has to stop first (with `test.no_movement_before_pass no` the pass ends the test at once). Also the standstill end with `test.pass_on_standstill yes` | | 1 | fail | `__test.result` was set to 0; ends at once | | 2 | signal passed at danger | a train went past a signal showing stop (`SetVelocity 0 0`; `SetVelocity 0 x` on automatic block, which may be passed after a stop, doesn't count); ends at once, unless `test.fail_if_stop_overran no` | | 3 | timeout | `test.timeout` seconds of simulation time went by (default 3600) | @@ -65,9 +68,18 @@ test.timeout 600 test.standstill 120 test.pass_on_standstill no test.fail_if_stop_overran yes +test.no_movement_before_pass yes endconfig ``` +| key | default | meaning | +| --- | --- | --- | +| `test.timeout` | 3600 | seconds of simulation time before the test ends as `timeout`; 0 = never | +| `test.standstill` | 300 | seconds with every vehicle still and no result before the test ends as `standstill`; 0 = never | +| `test.pass_on_standstill` | no | the standstill end passes the test instead | +| `test.fail_if_stop_overran` | yes | a signal passed at danger ends the test; with `no` it's only logged | +| `test.no_movement_before_pass` | yes | a pass waits until every vehicle stands still; with `no` it ends the test at once | + `--test-timeout` on the command line wins over `test.timeout` of the scenery. A signal is passed at danger when the head of a train goes past a signal which its driver @@ -155,6 +167,8 @@ on `tor1k`, the track in front of the signal. The `test_mode_*` cases show the ends of the test mode: `test_mode_pass` (exit 0, the pass event), `test_mode_signal_passed_at_danger` (exit 2), `test_mode_timeout` (exit 3, from `test.timeout`) and `test_mode_standstill` (exit 4, from `test.standstill`). +`test_mode_pass_while_moving` reports the pass from `event2` of `tor1k` (the direction the train runs), as the train runs onto +it, with `test.no_movement_before_pass no`, so the test ends with the train still moving. Checks (speeds in km/h, times in seconds of physicslog time, distances in metres). Most use the physicslog Velocity column, which is the wheel speed: it stays at 0 while the wheels are @@ -178,6 +192,8 @@ the row instead, logged by builds from this branch on. moving off, the wheels stop slipping within `within` seconds. - `holds_still` `moving_speed`, `stop_speed`, `max_distance` (*vehicle speed*): once stopped after moving off, moves no more than `max_distance` metres, e.g. no creeping on locked wheels. +- `ends_moving` `min_speed`: the last row of the physicslog is at `min_speed` or faster, e.g. + the test ended without waiting for the train to stop. New check types go in `CHECKS` in `run.py`; each gets the physicslog rows and the check's parameters and returns `(passed, detail)`. diff --git a/tests/scenarios/cases/test_mode_pass_while_moving.json b/tests/scenarios/cases/test_mode_pass_while_moving.json new file mode 100644 index 00000000..cdf798f1 --- /dev/null +++ b/tests/scenarios/cases/test_mode_pass_while_moving.json @@ -0,0 +1,28 @@ +{ + "description": "Test mode, pass without waiting for a stop: like test_mode_pass, but event2 of the track in front of the exit signal reports the pass as the train runs onto it, and with test.no_movement_before_pass no the test ends at once, the train still moving.", + "base_scenery": "td.scn", + "time": "10:34", + "fragment": "td_red_signal_pass.scn", + "track_events": { + "tor1k": "event2 test_pass" + }, + "test_config": [ + "test.no_movement_before_pass no" + ], + "vars": { + "consist": "node -1 0 {{vehicle}} dynamic pkp\\4e_v1 4e-004 4e 0 headdriver 0 0 enddynamic" + }, + "expect": "pass", + "timeout": 600, + "checks": [ + { + "type": "starts", + "min_speed": 5, + "within": 150 + }, + { + "type": "ends_moving", + "min_speed": 3 + } + ] +} diff --git a/tests/scenarios/run.py b/tests/scenarios/run.py index 770069d1..4c338308 100644 --- a/tests/scenarios/run.py +++ b/tests/scenarios/run.py @@ -416,6 +416,15 @@ def check_holds_still(rows, moving_speed, stop_speed, max_distance): return d <= max_distance, detail +def check_ends_moving(rows, min_speed): + """The run ended with the vehicle still moving, e.g. a pass that didn't wait for the trains to stop.""" + if not rows: + return False, "the physicslog is empty" + last = rows[-1] + ok = last[COL_VELOCITY] >= min_speed + return ok, f"last row t={last[COL_TIME]:.1f}s, v={last[COL_VELOCITY]:.1f} km/h (wanted >= {min_speed})" + + def check_slip_clears(rows, moving_speed, stop_speed, within): """Once the vehicle has stopped after moving off, its wheels stop slipping within `within` seconds (a vehicle left skidding on locked wheels keeps its brakes capped by the sliding adhesion).""" @@ -444,6 +453,7 @@ CHECKS = { "stops": check_stops, "stays_stopped": check_stays_stopped, "distance": check_distance, + "ends_moving": check_ends_moving, } diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 5cdb3ef2..2516da29 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -487,6 +487,12 @@ bool global_settings::ConfigParseTest(cParser& Parser, const std::string& token) return true; } + if (token == "test.no_movement_before_pass") + { + ParseOne(Parser, test.no_movement_before_pass); + return true; + } + return false; } @@ -1783,6 +1789,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "test.standstill", test.standstill ); export_as_text( Output, "test.pass_on_standstill", test.pass_on_standstill ); export_as_text( Output, "test.fail_if_stop_overran", test.fail_if_stop_overran ); + export_as_text( Output, "test.no_movement_before_pass", test.no_movement_before_pass ); export_as_text( Output, "gfx.envmap.enabled", gfx_envmap_enabled ); export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled ); export_as_text( Output, "gfx.postfx.motionblur.shutter", gfx_postfx_motionblur_shutter ); diff --git a/utilities/Globals.h b/utilities/Globals.h index 8fa04e9c..a6a9e122 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -93,6 +93,7 @@ struct global_settings { double standstill{ 300.0 }; // [s] every vehicle standing still this long ends the test; 0 = never bool pass_on_standstill{ false }; // the standstill end passes the test instead of failing it bool fail_if_stop_overran{ true }; // a train passing a signal at stop ends the test as failed; with false it's only logged + bool no_movement_before_pass{ true }; // a pass waits until every vehicle stands still; with false it ends the test at once bool timeout_from_command_line{ false }; // --test-timeout given, the scenery doesn't override it } test; bool editor_tracks{ false }; // F3 track workspace: draw and pick tracks without the scenery draw-range cap