TestVariableSync.cpp
508 lines
| 18.8 KiB
| text/x-c
|
CppLexer
Alexandre Leroux
|
r772 | #include <QObject> | ||
#include <QtTest> | ||||
#include <memory> | ||||
#include <Data/DataProviderParameters.h> | ||||
#include <Data/IDataProvider.h> | ||||
#include <Data/ScalarSeries.h> | ||||
#include <Time/TimeController.h> | ||||
#include <Variable/Variable.h> | ||||
#include <Variable/VariableController.h> | ||||
#include <Variable/VariableModel.h> | ||||
namespace { | ||||
/// Delay after each operation on the variable before validating it (in ms) | ||||
const auto OPERATION_DELAY = 100; | ||||
/** | ||||
* Generates values according to a range. The value generated for a time t is the number of seconds | ||||
* of difference between t and a reference value (which is midnight -> 00:00:00) | ||||
* | ||||
* Example: For a range between 00:00:10 and 00:00:20, the generated values are | ||||
* {10,11,12,13,14,15,16,17,18,19,20} | ||||
*/ | ||||
std::vector<double> values(const SqpRange &range) | ||||
{ | ||||
QTime referenceTime{0, 0}; | ||||
std::vector<double> result{}; | ||||
for (auto i = range.m_TStart; i <= range.m_TEnd; ++i) { | ||||
auto time = DateUtils::dateTime(i).time(); | ||||
result.push_back(referenceTime.secsTo(time)); | ||||
} | ||||
return result; | ||||
} | ||||
r833 | void validateRanges(VariableController &variableController, | |||
const std::map<int, SqpRange> &expectedRanges) | ||||
{ | ||||
for (const auto &expectedRangeEntry : expectedRanges) { | ||||
auto variableIndex = expectedRangeEntry.first; | ||||
auto expectedRange = expectedRangeEntry.second; | ||||
// Gets the variable in the controller | ||||
auto variable = variableController.variableModel()->variable(variableIndex); | ||||
// Compares variable's range to the expected range | ||||
QVERIFY(variable != nullptr); | ||||
auto range = variable->range(); | ||||
qInfo() << "range vs expected range" << range << expectedRange; | ||||
QCOMPARE(range, expectedRange); | ||||
// Compares variable's data with values expected for its range | ||||
auto dataSeries = variable->dataSeries(); | ||||
QVERIFY(dataSeries != nullptr); | ||||
auto it = dataSeries->xAxisRange(range.m_TStart, range.m_TEnd); | ||||
auto expectedValues = values(range); | ||||
qInfo() << std::distance(it.first, it.second) << expectedValues.size(); | ||||
QVERIFY(std::equal(it.first, it.second, expectedValues.cbegin(), expectedValues.cend(), | ||||
[](const auto &dataSeriesIt, const auto &expectedValue) { | ||||
return dataSeriesIt.value() == expectedValue; | ||||
})); | ||||
} | ||||
} | ||||
Alexandre Leroux
|
r772 | /// Provider used for the tests | ||
class TestProvider : public IDataProvider { | ||||
std::shared_ptr<IDataProvider> clone() const { return std::make_shared<TestProvider>(); } | ||||
void requestDataLoading(QUuid acqIdentifier, const DataProviderParameters ¶meters) override | ||||
{ | ||||
const auto &ranges = parameters.m_Times; | ||||
for (const auto &range : ranges) { | ||||
// Generates data series | ||||
auto valuesData = values(range); | ||||
std::vector<double> xAxisData{}; | ||||
for (auto i = range.m_TStart; i <= range.m_TEnd; ++i) { | ||||
xAxisData.push_back(i); | ||||
} | ||||
auto dataSeries = std::make_shared<ScalarSeries>( | ||||
std::move(xAxisData), std::move(valuesData), Unit{"t", true}, Unit{}); | ||||
emit dataProvided(acqIdentifier, dataSeries, range); | ||||
} | ||||
} | ||||
void requestDataAborting(QUuid acqIdentifier) override | ||||
{ | ||||
// Does nothing | ||||
} | ||||
}; | ||||
/** | ||||
* Interface representing an operation performed on a variable controller. | ||||
* This interface is used in tests to apply a set of operations and check the status of the | ||||
* controller after each operation | ||||
*/ | ||||
struct IOperation { | ||||
virtual ~IOperation() = default; | ||||
/// Executes the operation on the variable controller | ||||
virtual void exec(VariableController &variableController) const = 0; | ||||
}; | ||||
/** | ||||
*Variable creation operation in the controller | ||||
*/ | ||||
struct Create : public IOperation { | ||||
r1345 | explicit Create(int index, const SqpRange &range) : m_Index{index},m_range(range) {} | |||
Alexandre Leroux
|
r772 | |||
void exec(VariableController &variableController) const override | ||||
{ | ||||
auto variable = variableController.createVariable(QString::number(m_Index), {}, | ||||
r1345 | std::make_unique<TestProvider>(), m_range); | |||
Alexandre Leroux
|
r772 | } | ||
int m_Index; ///< The index of the variable to create in the controller | ||||
r1345 | SqpRange m_range; | |||
Alexandre Leroux
|
r772 | }; | ||
/** | ||||
* Variable move/shift operation in the controller | ||||
*/ | ||||
struct Move : public IOperation { | ||||
r833 | explicit Move(int index, const SqpRange &newRange, bool shift = false, int delayMS = 10) | |||
: m_Index{index}, m_NewRange{newRange}, m_Shift{shift}, m_DelayMs{delayMS} | ||||
Alexandre Leroux
|
r772 | { | ||
} | ||||
void exec(VariableController &variableController) const override | ||||
{ | ||||
if (auto variable = variableController.variableModel()->variable(m_Index)) { | ||||
r811 | variableController.onRequestDataLoading({variable}, m_NewRange, !m_Shift); | |||
r833 | QTest::qWait(m_DelayMs); | |||
Alexandre Leroux
|
r772 | } | ||
} | ||||
int m_Index; ///< The index of the variable to move | ||||
SqpRange m_NewRange; ///< The new range of the variable | ||||
bool m_Shift; ///< Performs a shift ( | ||||
r833 | int m_DelayMs; ///< wait the delay after running the request ( | |||
Alexandre Leroux
|
r772 | }; | ||
/** | ||||
* Variable synchronization/desynchronization operation in the controller | ||||
*/ | ||||
struct Synchronize : public IOperation { | ||||
explicit Synchronize(int index, QUuid syncId, bool synchronize = true) | ||||
: m_Index{index}, m_SyncId{syncId}, m_Synchronize{synchronize} | ||||
{ | ||||
} | ||||
void exec(VariableController &variableController) const override | ||||
{ | ||||
if (auto variable = variableController.variableModel()->variable(m_Index)) { | ||||
if (m_Synchronize) { | ||||
variableController.onAddSynchronized(variable, m_SyncId); | ||||
} | ||||
else { | ||||
variableController.desynchronize(variable, m_SyncId); | ||||
} | ||||
} | ||||
} | ||||
int m_Index; ///< The index of the variable to sync/desync | ||||
QUuid m_SyncId; ///< The synchronization group of the variable | ||||
bool m_Synchronize; ///< Performs sync or desync operation | ||||
}; | ||||
/** | ||||
* Test Iteration | ||||
* | ||||
* A test iteration includes an operation to be performed, and a set of expected ranges after each | ||||
* operation. Each range is tested after the operation to ensure that: | ||||
* - the range of the variable is the expected range | ||||
* - the data of the variable are those generated for the expected range | ||||
*/ | ||||
struct Iteration { | ||||
std::shared_ptr<IOperation> m_Operation; ///< Operation to perform | ||||
std::map<int, SqpRange> m_ExpectedRanges; ///< Expected ranges (by variable index) | ||||
}; | ||||
using Iterations = std::vector<Iteration>; | ||||
} // namespace | ||||
Q_DECLARE_METATYPE(Iterations) | ||||
class TestVariableSync : public QObject { | ||||
Q_OBJECT | ||||
private slots: | ||||
Alexandre Leroux
|
r892 | void initTestCase() { QSKIP("Temporarily disables TestVariableSync"); } | ||
Alexandre Leroux
|
r772 | /// Input data for @sa testSync() | ||
void testSync_data(); | ||||
r833 | /// Input data for @sa testSyncOneVar() | |||
void testSyncOneVar_data(); | ||||
Alexandre Leroux
|
r772 | /// Tests synchronization between variables through several operations | ||
void testSync(); | ||||
r833 | ||||
/// Tests synchronization between variables through several operations | ||||
void testSyncOneVar(); | ||||
Alexandre Leroux
|
r772 | }; | ||
r809 | namespace { | |||
Alexandre Leroux
|
r772 | |||
r809 | void testSyncCase1() | |||
{ | ||||
Alexandre Leroux
|
r772 | // Id used to synchronize variables in the controller | ||
auto syncId = QUuid::createUuid(); | ||||
/// Generates a range according to a start time and a end time (the date is the same) | ||||
auto range = [](const QTime &startTime, const QTime &endTime) { | ||||
return SqpRange{DateUtils::secondsSinceEpoch(QDateTime{{2017, 1, 1}, startTime, Qt::UTC}), | ||||
DateUtils::secondsSinceEpoch(QDateTime{{2017, 1, 1}, endTime, Qt::UTC})}; | ||||
}; | ||||
auto initialRange = range({12, 0}, {13, 0}); | ||||
Iterations iterations{}; | ||||
// Creates variables var0, var1 and var2 | ||||
r1345 | iterations.push_back({std::make_shared<Create>(0, initialRange), {{0, initialRange}}}); | |||
iterations.push_back({std::make_shared<Create>(1, initialRange), {{0, initialRange}, {1, initialRange}}}); | ||||
Alexandre Leroux
|
r772 | iterations.push_back( | ||
r1345 | {std::make_shared<Create>(2, initialRange), {{0, initialRange}, {1, initialRange}, {2, initialRange}}}); | |||
Alexandre Leroux
|
r772 | |||
// Adds variables into the sync group (ranges don't need to be tested here) | ||||
iterations.push_back({std::make_shared<Synchronize>(0, syncId)}); | ||||
iterations.push_back({std::make_shared<Synchronize>(1, syncId)}); | ||||
iterations.push_back({std::make_shared<Synchronize>(2, syncId)}); | ||||
// Moves var0: ranges of var0, var1 and var2 change | ||||
auto newRange = range({12, 30}, {13, 30}); | ||||
iterations.push_back( | ||||
{std::make_shared<Move>(0, newRange), {{0, newRange}, {1, newRange}, {2, newRange}}}); | ||||
// Moves var1: ranges of var0, var1 and var2 change | ||||
newRange = range({13, 0}, {14, 0}); | ||||
iterations.push_back( | ||||
{std::make_shared<Move>(0, newRange), {{0, newRange}, {1, newRange}, {2, newRange}}}); | ||||
// Moves var2: ranges of var0, var1 and var2 change | ||||
newRange = range({13, 30}, {14, 30}); | ||||
iterations.push_back( | ||||
{std::make_shared<Move>(0, newRange), {{0, newRange}, {1, newRange}, {2, newRange}}}); | ||||
// Desyncs var2 and moves var0: | ||||
// - ranges of var0 and var1 change | ||||
// - range of var2 doesn't change anymore | ||||
auto var2Range = newRange; | ||||
newRange = range({13, 45}, {14, 45}); | ||||
iterations.push_back({std::make_shared<Synchronize>(2, syncId, false)}); | ||||
iterations.push_back( | ||||
{std::make_shared<Move>(0, newRange), {{0, newRange}, {1, newRange}, {2, var2Range}}}); | ||||
// Shifts var0: although var1 is synchronized with var0, its range doesn't change | ||||
auto var1Range = newRange; | ||||
newRange = range({14, 45}, {15, 45}); | ||||
iterations.push_back({std::make_shared<Move>(0, newRange, true), | ||||
{{0, newRange}, {1, var1Range}, {2, var2Range}}}); | ||||
// Moves var0 through several operations: | ||||
// - range of var0 changes | ||||
// - range or var1 changes according to the previous shift (one hour) | ||||
auto moveVar0 = [&iterations](const auto &var0NewRange, const auto &var1ExpectedRange) { | ||||
iterations.push_back( | ||||
{std::make_shared<Move>(0, var0NewRange), {{0, var0NewRange}, {1, var1ExpectedRange}}}); | ||||
}; | ||||
r824 | ||||
Alexandre Leroux
|
r772 | // Pan left | ||
moveVar0(range({14, 30}, {15, 30}), range({13, 30}, {14, 30})); | ||||
// Pan right | ||||
moveVar0(range({16, 0}, {17, 0}), range({15, 0}, {16, 0})); | ||||
// Zoom in | ||||
moveVar0(range({16, 30}, {16, 45}), range({15, 30}, {15, 45})); | ||||
// Zoom out | ||||
r822 | moveVar0(range({16, 15}, {17, 0}), range({15, 15}, {16, 0})); | |||
Alexandre Leroux
|
r772 | |||
r809 | QTest::newRow("sync1") << syncId << initialRange << std::move(iterations) << 200; | |||
} | ||||
void testSyncCase2() | ||||
{ | ||||
// Id used to synchronize variables in the controller | ||||
auto syncId = QUuid::createUuid(); | ||||
/// Generates a range according to a start time and a end time (the date is the same) | ||||
auto dateTime = [](int year, int month, int day, int hours, int minutes, int seconds) { | ||||
return DateUtils::secondsSinceEpoch( | ||||
QDateTime{{year, month, day}, QTime{hours, minutes, seconds}, Qt::UTC}); | ||||
}; | ||||
auto initialRange = SqpRange{dateTime(2017, 1, 1, 12, 0, 0), dateTime(2017, 1, 1, 13, 0, 0)}; | ||||
Iterations iterations{}; | ||||
// Creates variables var0 and var1 | ||||
r1345 | iterations.push_back({std::make_shared<Create>(0, initialRange), {{0, initialRange}}}); | |||
iterations.push_back({std::make_shared<Create>(1, initialRange), {{0, initialRange}, {1, initialRange}}}); | ||||
r809 | ||||
// Adds variables into the sync group (ranges don't need to be tested here) | ||||
iterations.push_back({std::make_shared<Synchronize>(0, syncId)}); | ||||
iterations.push_back({std::make_shared<Synchronize>(1, syncId)}); | ||||
// Moves var0 through several operations: | ||||
// - range of var0 changes | ||||
// - range or var1 changes according to the previous shift (one hour) | ||||
auto moveVar0 = [&iterations](const auto &var0NewRange) { | ||||
iterations.push_back( | ||||
{std::make_shared<Move>(0, var0NewRange), {{0, var0NewRange}, {1, var0NewRange}}}); | ||||
}; | ||||
moveVar0(SqpRange{dateTime(2017, 1, 1, 12, 0, 0), dateTime(2017, 1, 1, 13, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 1, 14, 0, 0), dateTime(2017, 1, 1, 15, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 1, 8, 0, 0), dateTime(2017, 1, 1, 9, 0, 0)}); | ||||
// moveVar0(SqpRange{dateTime(2017, 1, 1, 7, 30, 0), dateTime(2017, 1, 1, 9, 30, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 1, 2, 0, 0), dateTime(2017, 1, 1, 4, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 1, 6, 0, 0), dateTime(2017, 1, 1, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 10, 6, 0, 0), dateTime(2017, 1, 15, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 17, 6, 0, 0), dateTime(2017, 1, 25, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 2, 6, 0, 0), dateTime(2017, 1, 8, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 4, 10, 6, 0, 0), dateTime(2017, 6, 15, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 17, 6, 0, 0), dateTime(2017, 2, 25, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 7, 2, 6, 0, 0), dateTime(2017, 10, 8, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 4, 10, 6, 0, 0), dateTime(2017, 6, 15, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 17, 6, 0, 0), dateTime(2017, 2, 25, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 7, 2, 6, 0, 0), dateTime(2017, 10, 8, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 4, 10, 6, 0, 0), dateTime(2017, 6, 15, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 17, 6, 0, 0), dateTime(2017, 2, 25, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 7, 2, 6, 0, 0), dateTime(2017, 10, 8, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 4, 10, 6, 0, 0), dateTime(2017, 6, 15, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 1, 17, 6, 0, 0), dateTime(2017, 2, 25, 8, 0, 0)}); | ||||
moveVar0(SqpRange{dateTime(2017, 7, 2, 6, 0, 0), dateTime(2017, 10, 8, 8, 0, 0)}); | ||||
QTest::newRow("sync2") << syncId << initialRange << iterations << 4000; | ||||
// QTest::newRow("sync3") << syncId << initialRange << iterations << 5000; | ||||
} | ||||
r833 | ||||
void testSyncOnVarCase1() | ||||
{ | ||||
// Id used to synchronize variables in the controller | ||||
auto syncId = QUuid::createUuid(); | ||||
/// Generates a range according to a start time and a end time (the date is the same) | ||||
auto range = [](const QTime &startTime, const QTime &endTime) { | ||||
return SqpRange{DateUtils::secondsSinceEpoch(QDateTime{{2017, 1, 1}, startTime, Qt::UTC}), | ||||
DateUtils::secondsSinceEpoch(QDateTime{{2017, 1, 1}, endTime, Qt::UTC})}; | ||||
}; | ||||
auto initialRange = range({12, 0}, {13, 0}); | ||||
Iterations creations{}; | ||||
// Creates variables var0, var1 and var2 | ||||
r1345 | creations.push_back({std::make_shared<Create>(0, initialRange), {{0, initialRange}}}); | |||
r833 | ||||
Iterations synchronization{}; | ||||
// Adds variables into the sync group (ranges don't need to be tested here) | ||||
synchronization.push_back({std::make_shared<Synchronize>(0, syncId)}); | ||||
Iterations iterations{}; | ||||
// Moves var0 through several operations | ||||
auto moveOp = [&iterations](const auto &requestedRange, const auto &expectedRange, auto delay) { | ||||
iterations.push_back( | ||||
{std::make_shared<Move>(0, requestedRange, true, delay), {{0, expectedRange}}}); | ||||
}; | ||||
// we assume here 300 ms is enough to finsh a operation | ||||
int delayToFinish = 300; | ||||
// jump to right, let's the operation time to finish | ||||
moveOp(range({14, 30}, {15, 30}), range({14, 30}, {15, 30}), delayToFinish); | ||||
// pan to right, let's the operation time to finish | ||||
moveOp(range({14, 45}, {15, 45}), range({14, 45}, {15, 45}), delayToFinish); | ||||
// jump to left, let's the operation time to finish | ||||
moveOp(range({03, 30}, {04, 30}), range({03, 30}, {04, 30}), delayToFinish); | ||||
// Pan to left, let's the operation time to finish | ||||
moveOp(range({03, 10}, {04, 10}), range({03, 10}, {04, 10}), delayToFinish); | ||||
// Zoom in, let's the operation time to finish | ||||
moveOp(range({03, 30}, {04, 00}), range({03, 30}, {04, 00}), delayToFinish); | ||||
// Zoom out left, let's the operation time to finish | ||||
moveOp(range({01, 10}, {18, 10}), range({01, 10}, {18, 10}), delayToFinish); | ||||
// Go back to initial range | ||||
moveOp(initialRange, initialRange, delayToFinish); | ||||
// jump to right, let's the operation time to finish | ||||
// moveOp(range({14, 30}, {15, 30}), initialRange, delayToFinish); | ||||
// Zoom out left, let's the operation time to finish | ||||
moveOp(range({01, 10}, {18, 10}), initialRange, delayToFinish); | ||||
// Go back to initial range | ||||
moveOp(initialRange, initialRange, 300); | ||||
QTest::newRow("syncOnVarCase1") << syncId << initialRange << std::move(creations) | ||||
<< std::move(iterations); | ||||
} | ||||
r809 | } | |||
void TestVariableSync::testSync_data() | ||||
{ | ||||
// ////////////// // | ||||
// Test structure // | ||||
// ////////////// // | ||||
QTest::addColumn<QUuid>("syncId"); | ||||
QTest::addColumn<SqpRange>("initialRange"); | ||||
QTest::addColumn<Iterations>("iterations"); | ||||
QTest::addColumn<int>("operationDelay"); | ||||
// ////////// // | ||||
// Test cases // | ||||
// ////////// // | ||||
testSyncCase1(); | ||||
r824 | testSyncCase2(); | |||
} | ||||
r833 | void TestVariableSync::testSyncOneVar_data() | |||
Alexandre Leroux
|
r772 | { | ||
r833 | // ////////////// // | |||
// Test structure // | ||||
// ////////////// // | ||||
QTest::addColumn<QUuid>("syncId"); | ||||
QTest::addColumn<SqpRange>("initialRange"); | ||||
QTest::addColumn<Iterations>("creations"); | ||||
QTest::addColumn<Iterations>("iterations"); | ||||
r824 | ||||
r833 | // ////////// // | |||
// Test cases // | ||||
// ////////// // | ||||
testSyncOnVarCase1(); | ||||
} | ||||
void TestVariableSync::testSync() | ||||
{ | ||||
Alexandre Leroux
|
r772 | // Inits controllers | ||
TimeController timeController{}; | ||||
VariableController variableController{}; | ||||
r1345 | //variableController.setTimeController(&timeController); | |||
Alexandre Leroux
|
r772 | |||
QFETCH(QUuid, syncId); | ||||
QFETCH(SqpRange, initialRange); | ||||
r1345 | timeController.setDateTimeRange(initialRange); | |||
Alexandre Leroux
|
r772 | |||
// Synchronization group used | ||||
variableController.onAddSynchronizationGroupId(syncId); | ||||
r809 | // For each iteration: | |||
// - execute operation | ||||
// - compare the variables' state to the expected states | ||||
QFETCH(Iterations, iterations); | ||||
QFETCH(int, operationDelay); | ||||
for (const auto &iteration : iterations) { | ||||
iteration.m_Operation->exec(variableController); | ||||
QTest::qWait(operationDelay); | ||||
r833 | validateRanges(variableController, iteration.m_ExpectedRanges); | |||
r809 | } | |||
r824 | } | |||
r833 | void TestVariableSync::testSyncOneVar() | |||
{ | ||||
// Inits controllers | ||||
TimeController timeController{}; | ||||
VariableController variableController{}; | ||||
r1345 | //variableController.setTimeController(&timeController); | |||
r833 | ||||
QFETCH(QUuid, syncId); | ||||
QFETCH(SqpRange, initialRange); | ||||
r1345 | timeController.setDateTimeRange(initialRange); | |||
r833 | ||||
// Synchronization group used | ||||
variableController.onAddSynchronizationGroupId(syncId); | ||||
// For each iteration: | ||||
// - execute operation | ||||
// - compare the variables' state to the expected states | ||||
QFETCH(Iterations, iterations); | ||||
QFETCH(Iterations, creations); | ||||
for (const auto &creation : creations) { | ||||
creation.m_Operation->exec(variableController); | ||||
QTest::qWait(300); | ||||
} | ||||
for (const auto &iteration : iterations) { | ||||
iteration.m_Operation->exec(variableController); | ||||
} | ||||
if (!iterations.empty()) { | ||||
validateRanges(variableController, iterations.back().m_ExpectedRanges); | ||||
} | ||||
} | ||||
r824 | ||||
Alexandre Leroux
|
r772 | QTEST_MAIN(TestVariableSync) | ||
#include "TestVariableSync.moc" | ||||