18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-14 20:20:51 +01:00
Files
maszyna/simulation/simulationtest.cpp
jerrrrycho 4356e7c3fd fix(simulation): a test run that can't start ends with the error code and says why
- `--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.
2026-11-08 09:36:28 +01:00

183 lines
5.5 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"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <format>
#include <memory>
namespace {
// the cell holds what the scenery wrote, so the values are whole numbers; the margin only keeps a stray fraction out
bool holds(double const Value, double const Expected)
{
return std::abs(Value - Expected) < 1e-6;
}
} // namespace
namespace simulation {
test_manager &test()
{
static test_manager instance;
return instance;
}
std::string_view to_string(test_result const Result)
{
using enum test_result;
switch (Result)
{
case pass: return "pass";
case fail: return "fail";
case signal_passed_at_danger: return "signal passed at danger";
case timeout: return "timeout";
case standstill: return "standstill";
case error: return "error";
}
return "unknown";
}
void test_manager::init_result_cell() const
{
auto cell { std::make_unique<TMemCell>(scene::node_data{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::Region->insert(cell.get());
// the table owns its cells
simulation::Memory.insert(cell.release());
}
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",
Global.test.no_movement_before_pass ? "" : ", pass without waiting for a stop"));
}
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() : std::format(" ({})", cell->Text()) };
if (holds(value, 0.0))
{
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;
WriteLog(std::format("test: pass requested at {:.1f} s{}, waiting for every vehicle to stop", m_time, reason));
}
}
auto const atrest { std::ranges::none_of(simulation::Vehicles.sequence(),
[](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_view const Vehicle, std::string_view const Signal)
{
auto const reason { std::format("{} passed signal {} at danger", Vehicle, Signal) };
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_view const Reason)
{
if (conclude(Result, Reason))
Application.queue_quit(true);
}
bool test_manager::conclude(test_result const Result, std::string_view const Reason)
{
if (m_finished || false == Global.test.enabled)
return false;
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);
return true;
}
} // namespace simulation