Files
qt5-legatus-legacy/Lts/Services/AreaEffectSimulation/UnitTest_FireAutomaton/aes_test_fire_automaton.cpp
T
2026-08-13 18:33:10 +02:00

414 lines
13 KiB
C++
Executable File

/**
* AES module test fire automaton
* $Id$
* $HeadURL$
* $Author$
*/
#include <QCoreApplication>
#include <QTimer>
#include <QDateTime>
#include <QTest>
#include <QVector>
#include <QSignalSpy>
#include <nglapplication/nglcoreapplication.h>
#include <AreaEffectSimulation/Utils/fire_automaton.h>
#include <AreaEffectSimulation/logger.h>
#include "aes_test_fire_automaton.h"
using namespace AreaEffectSimulation;
void TestFireAutomaton::initTestCase()
{ }
void TestFireAutomaton::init()
{
m_receivedFire.clear();
m_finishedTime = 0;
}
void TestFireAutomaton::fireReceived(int32_t id)
{
m_receivedFire.append({QDateTime::currentMSecsSinceEpoch(), id});
}
/*
Test basic pattern with fire slots = rounds
*/
void TestFireAutomaton::testSchedule_0()
{
int32_t effectDurationMSecs = 500;
int32_t cadence = 100;
int32_t numberOfRounds = 6;
QVector<int32_t> effectorIds = {1};
QString pattern = QStringLiteral("all, next, all, next, all, next");
QVector<qint64> expectedDelays = {0, 100, 200, 300, 400, 500};
QVector<QVector<int32_t>> expectedSchedule = {{1}, {1}, {1}, {1}, {1}, {1}};
testScheduling(effectDurationMSecs, FireAutomaton::TimeType::Duration, numberOfRounds, effectorIds, pattern, expectedDelays, expectedSchedule);
testScheduling(cadence, FireAutomaton::TimeType::Cadence, numberOfRounds, effectorIds, pattern, expectedDelays, expectedSchedule);
testStepping(numberOfRounds, effectorIds, pattern, expectedSchedule);
}
/*
Test pattern with fill element
*/
void TestFireAutomaton::testSchedule_1()
{
int32_t effectDurationMSecs = 800;
int32_t cadence = 100;
int32_t numberOfRounds = 13;
QVector<int32_t> effectorIds = {1, 2, 3};
QString pattern = QStringLiteral("all,next*,all");
QVector<qint64> expectedDelays = {0, 0, 0, 100, 200, 300, 400, 500, 600, 700, 800, 800, 800};
QVector<QVector<int32_t>> expectedSchedule = {{1, 2, 3}, {1}, {2}, {3}, {1}, {2}, {3}, {1}, {1, 2, 3}};
testScheduling(effectDurationMSecs, FireAutomaton::TimeType::Duration, numberOfRounds, effectorIds, pattern, expectedDelays, expectedSchedule);
testScheduling(cadence, FireAutomaton::TimeType::Cadence, numberOfRounds, effectorIds, pattern, expectedDelays, expectedSchedule);
testStepping(numberOfRounds, effectorIds, pattern, expectedSchedule);
}
/*
Test fill pattern element with 0 fill
*/
void TestFireAutomaton::testSchedule_2()
{
int32_t effectDurationMSecs = 200;
int32_t cadence = 200;
int32_t numberOfRounds = 6;
QVector<int32_t> effectorIds = {1, 2, 3};
QString pattern = QStringLiteral("all,all*,all");
QVector<qint64> expectedDelays = {0, 0, 0, 200, 200, 200};
QVector<QVector<int32_t>> expectedSchedule = {{1,2,3}, {1,2,3}};
testScheduling(effectDurationMSecs, FireAutomaton::TimeType::Duration, numberOfRounds, effectorIds, pattern, expectedDelays, expectedSchedule);
testScheduling(cadence, FireAutomaton::TimeType::Cadence, numberOfRounds, effectorIds, pattern, expectedDelays, expectedSchedule);
testStepping(numberOfRounds, effectorIds, pattern, expectedSchedule);
}
/*
Test pattern with fire slots > rounds
*/
void TestFireAutomaton::testSchedule_3()
{
int32_t effectDurationMSecs = 200;
int32_t cadence = 200;
int32_t numberOfRounds = 5;
QVector<int32_t> effectorIds = {1, 2, 3, 4};
QString pattern = QStringLiteral("all,all");
QVector<qint64> expectedDelays = {0, 0, 0, 0, 200};
QVector<QVector<int32_t>> expectedSchedule = {{1, 2, 3, 4}, {1}};
testScheduling(effectDurationMSecs, FireAutomaton::TimeType::Duration, numberOfRounds, effectorIds, pattern, expectedDelays, expectedSchedule);
testScheduling(cadence, FireAutomaton::TimeType::Cadence, numberOfRounds, effectorIds, pattern, expectedDelays, expectedSchedule);
testStepping(numberOfRounds, effectorIds, pattern, expectedSchedule);
}
/*
Test pattern with rounds > fire slots
*/
void TestFireAutomaton::testSchedule_4()
{
int32_t effectDurationMSecs = 100;
int32_t numberOfRounds = 5;
QVector<int32_t> effectorIds = {1};
QString pattern = QStringLiteral("next, next");
QVector<qint64> expectedDelays = {0, 100};
QVector<QVector<int32_t>> expectedSchedule = {{1}, {1}};
FireAutomaton automaton;
QVERIFY(!automaton.init(effectDurationMSecs, FireAutomaton::TimeType::Duration, numberOfRounds, effectorIds, pattern));
}
void TestFireAutomaton::testScheduling(int32_t time, FireAutomaton::TimeType type, int32_t numberOfRounds, const QVector<int32_t>& effectorIds,
const QString& pattern, QVector<qint64> expectedDelays, QVector<QVector<int32_t>> expectedSchedule)
{
FireAutomaton automaton;
QVERIFY(automaton.init(time, type, numberOfRounds, effectorIds, pattern));
QSignalSpy spyFire(&automaton, SIGNAL(fire(int32_t)));
QVERIFY(spyFire.isValid());
QSignalSpy spyFinished(&automaton, SIGNAL(finished()));
QVERIFY(spyFinished.isValid());
connect(&automaton, &FireAutomaton::fire, this, &TestFireAutomaton::fireReceived);
qint64 startTimeMSecs = QDateTime::currentMSecsSinceEpoch();
automaton.start();
QVERIFY(spyFinished.wait(1000));
QVERIFY(withinTolerance(QDateTime::currentMSecsSinceEpoch() - startTimeMSecs, expectedDelays.last(), 50));
int32_t expectedShots = 0;
for(const auto& tick : expectedSchedule)
expectedShots += tick.size();
QCOMPARE(spyFire.count(), expectedShots);
for(int32_t i=0; i < numberOfRounds; i++) {
withinTolerance(QDateTime::currentMSecsSinceEpoch() - startTimeMSecs, expectedDelays.last(), 50);
}
QVector<QVector<int32_t>> schedule;
QVector<int32_t> tickIds;
qint64 firstFireTime = m_receivedFire[0].time;
if(expectedDelays.size() != m_receivedFire.size())
{
QVERIFY(false);
return;
}
// first fire time is basis for delays
for(int i=0; i < m_receivedFire.size(); i++)
{
m_receivedFire[i].time = m_receivedFire[i].time - firstFireTime;
QVERIFY(withinTolerance(m_receivedFire[i].time, expectedDelays[i], 50));
}
tickIds.clear();
tickIds.append(m_receivedFire[0].id);
for(int i=1; i < m_receivedFire.size(); i++)
{
if(withinTolerance(m_receivedFire[i - 1].time, m_receivedFire[i].time, 50))
{
tickIds.append(m_receivedFire[i].id);
} else {
schedule.append(tickIds);
tickIds.clear();
tickIds.append(m_receivedFire[i].id);
}
}
schedule.append(tickIds);
QVERIFY(schedule.size() == expectedSchedule.size());
for(int i=0; i < schedule.size(); i++)
{
QVERIFY(schedule[i] == expectedSchedule[i]);
}
m_receivedFire.clear();
}
void TestFireAutomaton::testStepping(int32_t numberOfRounds, const QVector<int32_t>& effectorIds,
const QString& pattern, QVector<QVector<int32_t>> expectedSchedule)
{
FireAutomaton automaton;
QVERIFY(automaton.init(0, FireAutomaton::TimeType::Cadence, numberOfRounds, effectorIds, pattern));
QSignalSpy spyFire(&automaton, SIGNAL(fire(int32_t)));
QVERIFY(spyFire.isValid());
QSignalSpy spyFinished(&automaton, SIGNAL(finished()));
QVERIFY(spyFinished.isValid());
connect(&automaton, &FireAutomaton::fire, this, &TestFireAutomaton::fireReceived);
for(int i=0; i < expectedSchedule.size(); i++)
{
QVERIFY(automaton.performStep());
QTest::qWait(100);
}
QVERIFY(spyFinished.count() == 1);
QVector<QVector<int32_t>> schedule;
QVector<int32_t> tickIds;
qint64 firstFireTime = m_receivedFire[0].time;
// first fire time is basis for delays
for(int i=0; i < m_receivedFire.size(); i++)
{
m_receivedFire[i].time = m_receivedFire[i].time - firstFireTime;
}
tickIds.clear();
tickIds.append(m_receivedFire[0].id);
for(int i=1; i < m_receivedFire.size(); i++)
{
if(withinTolerance(m_receivedFire[i - 1].time, m_receivedFire[i].time, 50))
{
tickIds.append(m_receivedFire[i].id);
} else {
schedule.append(tickIds);
tickIds.clear();
tickIds.append(m_receivedFire[i].id);
}
}
schedule.append(tickIds);
QVERIFY(schedule.size() == expectedSchedule.size());
for(int i=0; i < schedule.size(); i++)
{
QVERIFY(schedule[i] == expectedSchedule[i]);
}
m_receivedFire.clear();
}
void TestFireAutomaton::testResumePaused()
{
QVector<int32_t> effectorIds = {1};
int32_t effectDurationMSecs = 1000;
int32_t numberOfRounds = 2;
FireAutomaton automaton;
QVERIFY(automaton.init(effectDurationMSecs, FireAutomaton::TimeType::Duration, numberOfRounds, effectorIds, QStringLiteral("all*")));
QSignalSpy spyFire(&automaton, SIGNAL(fire(int32_t)));
QVERIFY(spyFire.isValid());
QSignalSpy spyFinished(&automaton, SIGNAL(finished()));
QVERIFY(spyFinished.isValid());
qint64 startTimeMSecs = QDateTime::currentMSecsSinceEpoch();
QVERIFY(automaton.start());
QTest::qWait(100);
automaton.pause();
QCOMPARE(automaton.getState(), FireAutomaton::State::Paused);
qint64 pauseTimeMSecs = QDateTime::currentMSecsSinceEpoch();
QVERIFY(spyFire.count() == 1);
QTest::qWait(100);
qint64 resumeTimeMSecs = QDateTime::currentMSecsSinceEpoch();
automaton.resume();
QCOMPARE(automaton.getState(), FireAutomaton::State::Running);
QVERIFY(spyFinished.wait(2000));
qint64 measuredDuration = (QDateTime::currentMSecsSinceEpoch() - startTimeMSecs) - resumeTimeMSecs + pauseTimeMSecs;
QVERIFY(spyFinished.count() == 1);
QVERIFY(withinTolerance(measuredDuration, effectDurationMSecs, 100));
QVERIFY(spyFire.count() == numberOfRounds);
}
void TestFireAutomaton::testStop()
{
QVector<int32_t> effectorIds = {1, 2, 3, 4, 5, 6};
int32_t effectDurationMSecs = 500;
int32_t numberOfRounds = 10;
FireAutomaton automaton;
QVERIFY(automaton.init(effectDurationMSecs, FireAutomaton::TimeType::Duration, numberOfRounds, effectorIds, QStringLiteral("next*")));
QTest::qWait(10);
QSignalSpy spyFire(&automaton, SIGNAL(fire(int32_t)));
QVERIFY(spyFire.isValid());
QSignalSpy spyFinished(&automaton, SIGNAL(finished()));
QVERIFY(spyFinished.isValid());
QVERIFY(automaton.start());
QTest::qWait(100);
automaton.stop();
int32_t fireCount = spyFire.count();
QVERIFY(!spyFinished.wait(1000)); // expected to timeout
QCOMPARE(spyFire.count(), fireCount); // expected to not have changed
QVERIFY(spyFire.count() < numberOfRounds);
// start to finish
qint64 startTimeMSecs = QDateTime::currentMSecsSinceEpoch();
QVERIFY(automaton.start());
QVERIFY(spyFinished.wait(1000));
QVERIFY(spyFire.count() - fireCount == numberOfRounds);
QVERIFY(withinTolerance(QDateTime::currentMSecsSinceEpoch() - startTimeMSecs, effectDurationMSecs, 50));
}
void TestFireAutomaton::testStates()
{
QVector<int32_t> effectorIds = {11, 10};
int32_t effectDurationMSecs = 500;
int32_t numberOfRounds = 10;
FireAutomaton automaton;
QSignalSpy spyFinished(&automaton, SIGNAL(finished()));
QVERIFY(spyFinished.isValid());
// init
QCOMPARE(automaton.getState(), FireAutomaton::State::Uninitialized);
QVERIFY(!automaton.start());
QVERIFY(!automaton.pause());
QVERIFY(!automaton.resume());
QVERIFY(automaton.init(effectDurationMSecs, FireAutomaton::TimeType::Duration, numberOfRounds, effectorIds, QStringLiteral("all*")));
QCOMPARE(automaton.getState(), FireAutomaton::State::Stopped);
QVERIFY(!automaton.pause());
QVERIFY(!automaton.resume());
// start -> pause -> resume -> stop
QVERIFY(automaton.start());
QCOMPARE(automaton.getState(), FireAutomaton::State::Running);
QVERIFY(!automaton.start());
QVERIFY(!automaton.resume());
QVERIFY(automaton.pause());
QCOMPARE(automaton.getState(), FireAutomaton::State::Paused);
QVERIFY(!automaton.start());
QVERIFY(!automaton.pause());
QVERIFY(automaton.resume());
QCOMPARE(automaton.getState(), FireAutomaton::State::Running);
QVERIFY(spyFinished.wait(1000));
QCOMPARE(automaton.getState(), FireAutomaton::State::Stopped);
QVERIFY(!automaton.pause());
QCOMPARE(automaton.getState(), FireAutomaton::State::Stopped);
QVERIFY(!automaton.resume());
QCOMPARE(automaton.getState(), FireAutomaton::State::Stopped);
// start -> stop
QVERIFY(automaton.start());
QCOMPARE(automaton.getState(), FireAutomaton::State::Running);
QVERIFY(!automaton.start());
QVERIFY(!automaton.resume());
QVERIFY(spyFinished.wait(1000));
QCOMPARE(automaton.getState(), FireAutomaton::State::Stopped);
// start -> finish
QVERIFY(automaton.start());
QCOMPARE(automaton.getState(), FireAutomaton::State::Running);
QVERIFY(!automaton.start());
QVERIFY(!automaton.resume());
QVERIFY(spyFinished.wait(1000));
QCOMPARE(automaton.getState(), FireAutomaton::State::Stopped);
}
int main(int argc, char *argv[])
{
qRegisterMetaType<int32_t>("int32_t");
NGLCoreApplication app(argc, argv);
app.createLogger("UnitTest_FireAutomaton", "log4cxx_aes.props");
TestFireAutomaton testCase;
return QTest::qExec(&testCase, argc, argv);
}