mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-14 07:54:55 +01:00
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.
150 lines
4.6 KiB
C++
150 lines
4.6 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "simulation/simulationtest.h"
|
|
|
|
#include "application/application.h"
|
|
#include "scene/scene.h"
|
|
#include "simulation/simulation.h"
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/Logs.h"
|
|
#include "vehicle/DynObj.h"
|
|
#include "world/Event.h"
|
|
#include "world/MemCell.h"
|
|
|
|
namespace simulation {
|
|
|
|
test_manager Test;
|
|
|
|
std::string_view to_string(test_result const Result)
|
|
{
|
|
switch (Result)
|
|
{
|
|
case test_result::pass: return "pass";
|
|
case test_result::fail: return "fail";
|
|
case test_result::signal_passed_at_danger: return "signal passed at danger";
|
|
case test_result::timeout: return "timeout";
|
|
case test_result::standstill: return "standstill";
|
|
case test_result::error: return "error";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
void test_manager::init_result_cell()
|
|
{
|
|
auto *cell = new TMemCell({0, -1, std::string(result_cell), "memcell"});
|
|
// neither of the reported values, so that a cell nobody wrote to says nothing
|
|
cell->UpdateValues("", -1.0, 0.0, basic_event::flags::value1 | basic_event::flags::value2);
|
|
simulation::Memory.insert(cell);
|
|
simulation::Region->insert(cell);
|
|
}
|
|
|
|
void test_manager::start()
|
|
{
|
|
if (m_started)
|
|
return;
|
|
m_started = true;
|
|
m_time = 0.0;
|
|
m_resttime = 0.0;
|
|
if (simulation::Vehicles.sequence().empty())
|
|
{
|
|
// most likely the scenery wasn't found, or it isn't a test scenery
|
|
finish(test_result::error, "the scenery has no vehicles");
|
|
return;
|
|
}
|
|
// 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,
|
|
Global.test.pass_on_standstill ? ", standstill passes" : "",
|
|
Global.test.fail_if_stop_overran ? "" : ", signals passed at danger allowed"));
|
|
}
|
|
|
|
void test_manager::update(double const Deltatime)
|
|
{
|
|
if (false == m_started || m_finished)
|
|
return;
|
|
|
|
m_time += Deltatime;
|
|
|
|
if (auto const *cell = simulation::Memory.find(std::string(result_cell)))
|
|
{
|
|
auto const value { cell->Value1() };
|
|
auto const reason { cell->Text().empty() ? std::string() : " (" + cell->Text() + ")" };
|
|
if (value == 0.0)
|
|
{
|
|
finish(test_result::fail, "__test.result set to 0" + reason);
|
|
return;
|
|
}
|
|
if (value == 1.0 && false == m_passrequested)
|
|
{
|
|
m_passrequested = true;
|
|
WriteLog(std::format("test: pass requested at {:.1f} s{}, waiting for every vehicle to stop", m_time, reason));
|
|
}
|
|
}
|
|
|
|
auto const atrest = std::none_of(simulation::Vehicles.sequence().begin(), simulation::Vehicles.sequence().end(),
|
|
[](TDynamicObject const *Vehicle) { return std::abs(Vehicle->GetVelocity()) > 0.01; });
|
|
m_resttime = atrest ? m_resttime + Deltatime : 0.0;
|
|
|
|
if (m_passrequested && m_resttime >= pass_settle_time)
|
|
{
|
|
finish(test_result::pass, "__test.result set to 1 and every vehicle stopped");
|
|
return;
|
|
}
|
|
if (Global.test.standstill > 0.0 && m_resttime >= Global.test.standstill)
|
|
{
|
|
auto const reason { std::format("every vehicle stood still for {:.0f} s", Global.test.standstill) };
|
|
finish(Global.test.pass_on_standstill ? test_result::pass : test_result::standstill, reason);
|
|
return;
|
|
}
|
|
if (Global.test.timeout > 0.0 && m_time >= Global.test.timeout)
|
|
{
|
|
finish(test_result::timeout, std::format("no result after {:.0f} s", Global.test.timeout));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void test_manager::signal_passed_at_danger(std::string const &Vehicle, std::string const &Signal)
|
|
{
|
|
auto const reason { Vehicle + " passed signal " + Signal + " at danger" };
|
|
if (false == Global.test.enabled)
|
|
{
|
|
WriteLog(reason, logtype::generic);
|
|
return;
|
|
}
|
|
if (false == Global.test.fail_if_stop_overran)
|
|
{
|
|
WriteLog(std::format("test: {} at {:.1f} s, allowed by test.fail_if_stop_overran", reason, m_time));
|
|
return;
|
|
}
|
|
finish(test_result::signal_passed_at_danger, reason);
|
|
}
|
|
|
|
void test_manager::finish(test_result const Result, std::string const &Reason)
|
|
{
|
|
if (m_finished || false == Global.test.enabled)
|
|
return;
|
|
m_finished = true;
|
|
m_result = Result;
|
|
|
|
auto const line { std::format("test: {} (exit code {}) after {:.1f} s of simulation time: {}", to_string(Result), static_cast<int>(Result), m_time, Reason) };
|
|
if (Result == test_result::pass)
|
|
WriteLog(line);
|
|
else
|
|
ErrorLog(line);
|
|
// the process has no console of its own; whoever started it with a redirected output gets the verdict there too
|
|
std::fprintf(stdout, "%s\n", line.c_str());
|
|
std::fflush(stdout);
|
|
|
|
Application.queue_quit(true);
|
|
}
|
|
|
|
} // namespace simulation
|