diff --git a/core/include/Common/SignalWaiter.h b/core/include/Common/SignalWaiter.h new file mode 100644 index 0000000..3dcc8f2 --- /dev/null +++ b/core/include/Common/SignalWaiter.h @@ -0,0 +1,38 @@ +#ifndef SCIQLOP_SIGNALWAITER_H +#define SCIQLOP_SIGNALWAITER_H + +#include "CoreGlobal.h" + +#include + +/** + * Class for synchronously waiting for the reception of a signal. The signal to wait is passed to + * the construction of the object. When starting the wait, a timeout can be set to exit if the + * signal has not been sent + */ +class SCIQLOP_CORE_EXPORT SignalWaiter : public QObject { + Q_OBJECT +public: + /** + * Ctor + * @param object the sender of the signal + * @param signal the signal to listen + */ + explicit SignalWaiter(QObject &sender, const char *signal); + + /** + * Starts the signal and leaves after the signal has been received, or after the timeout + * @param timeout the timeout set (if 0, uses a default timeout) + * @return true if the signal was sent, false if the timeout occured + */ + bool wait(int timeout); + +private: + bool m_Timeout; + QEventLoop m_EventLoop; + +private slots: + void timeout(); +}; + +#endif // SCIQLOP_SIGNALWAITER_H diff --git a/core/include/Data/SqpRange.h b/core/include/Data/SqpRange.h index 9fb5cc3..d51fc0f 100644 --- a/core/include/Data/SqpRange.h +++ b/core/include/Data/SqpRange.h @@ -18,8 +18,8 @@ struct SqpRange { static SqpRange fromDateTime(const QDate &startDate, const QTime &startTime, const QDate &endDate, const QTime &endTime) { - return {DateUtils::secondsSinceEpoch(QDateTime{startDate, startTime}), - DateUtils::secondsSinceEpoch(QDateTime{endDate, endTime})}; + return {DateUtils::secondsSinceEpoch(QDateTime{startDate, startTime, Qt::UTC}), + DateUtils::secondsSinceEpoch(QDateTime{endDate, endTime, Qt::UTC})}; } /// Start time (UTC) diff --git a/core/include/Variable/VariableController.h b/core/include/Variable/VariableController.h index 66aab49..9c02286 100644 --- a/core/include/Variable/VariableController.h +++ b/core/include/Variable/VariableController.h @@ -85,6 +85,10 @@ signals: /// Signal emitted when a sub range of the cacheRange of the variable can be displayed void updateVarDisplaying(std::shared_ptr variable, const SqpRange &range); + /// Signal emitted when all acquisitions related to the variables have been completed (whether + /// validated, canceled, or failed) + void acquisitionFinished(); + public slots: /// Request the data loading of the variable whithin range void onRequestDataLoading(QVector > variables, const SqpRange &range, diff --git a/core/meson.build b/core/meson.build index cc49a89..8ce78de 100644 --- a/core/meson.build +++ b/core/meson.build @@ -5,6 +5,7 @@ catalogueapi_dep = dependency('CatalogueAPI', required : true, fallback:['Catalo core_moc_headers = [ 'include/Catalogue/CatalogueController.h', + 'include/Common/SignalWaiter.h', 'include/Data/IDataProvider.h', 'include/DataSource/DataSourceController.h', 'include/DataSource/DataSourceItemAction.h', @@ -24,6 +25,7 @@ core_moc_files = qt5.preprocess(moc_headers : core_moc_headers) core_sources = [ 'src/Common/DateUtils.cpp', + 'src/Common/SignalWaiter.cpp', 'src/Common/StringUtils.cpp', 'src/Common/MimeTypesDef.cpp', 'src/Catalogue/CatalogueController.cpp', diff --git a/core/src/Common/SignalWaiter.cpp b/core/src/Common/SignalWaiter.cpp new file mode 100644 index 0000000..73c1f59 --- /dev/null +++ b/core/src/Common/SignalWaiter.cpp @@ -0,0 +1,36 @@ +#include "Common/SignalWaiter.h" + +#include + +namespace { + +const auto DEFAULT_TIMEOUT = 30000; + +} // namespace + +SignalWaiter::SignalWaiter(QObject &sender, const char *signal) : m_Timeout{false} +{ + connect(&sender, signal, &m_EventLoop, SLOT(quit())); +} + +bool SignalWaiter::wait(int timeout) +{ + if (timeout == 0) { + timeout = DEFAULT_TIMEOUT; + } + + QTimer timer{}; + timer.setInterval(timeout); + timer.start(); + connect(&timer, &QTimer::timeout, this, &SignalWaiter::timeout); + + m_EventLoop.exec(); + + return !m_Timeout; +} + +void SignalWaiter::timeout() +{ + m_Timeout = true; + m_EventLoop.quit(); +} diff --git a/core/src/Variable/VariableController.cpp b/core/src/Variable/VariableController.cpp index e76989b..7e1e4b0 100644 --- a/core/src/Variable/VariableController.cpp +++ b/core/src/Variable/VariableController.cpp @@ -890,6 +890,9 @@ void VariableController::VariableControllerPrivate::updateVariables(QUuid varReq // cleaning varRequestId qCDebug(LOG_VariableController()) << tr("m_VarGroupIdToVarIds erase") << varRequestId; m_VarGroupIdToVarIds.erase(varRequestId); + if (m_VarGroupIdToVarIds.empty()) { + emit q->acquisitionFinished(); + } } } @@ -1019,6 +1022,9 @@ void VariableController::VariableControllerPrivate::cancelVariableRequest(QUuid } qCDebug(LOG_VariableController()) << tr("cancelVariableRequest: erase") << varRequestId; m_VarGroupIdToVarIds.erase(varRequestId); + if (m_VarGroupIdToVarIds.empty()) { + emit q->acquisitionFinished(); + } } void VariableController::VariableControllerPrivate::executeVarRequest(std::shared_ptr var, diff --git a/plugins/amda/meson.build b/plugins/amda/meson.build index 52109e4..6c4e483 100644 --- a/plugins/amda/meson.build +++ b/plugins/amda/meson.build @@ -72,7 +72,9 @@ tests_sources = [ 'tests/FuzzingOperations.h', 'tests/FuzzingOperations.cpp', 'tests/FuzzingUtils.h', - 'tests/FuzzingUtils.cpp' + 'tests/FuzzingUtils.cpp', + 'tests/FuzzingValidators.h', + 'tests/FuzzingValidators.cpp' ] foreach unit_test : tests diff --git a/plugins/amda/tests/FuzzingDefs.cpp b/plugins/amda/tests/FuzzingDefs.cpp index 71e863f..d47bbd8 100644 --- a/plugins/amda/tests/FuzzingDefs.cpp +++ b/plugins/amda/tests/FuzzingDefs.cpp @@ -1,8 +1,109 @@ #include "FuzzingDefs.h" +const QString ACQUISITION_TIMEOUT_PROPERTY = QStringLiteral("acquisitionTimeout"); const QString NB_MAX_OPERATIONS_PROPERTY = QStringLiteral("component"); +const QString NB_MAX_SYNC_GROUPS_PROPERTY = QStringLiteral("nbSyncGroups"); const QString NB_MAX_VARIABLES_PROPERTY = QStringLiteral("nbMaxVariables"); const QString AVAILABLE_OPERATIONS_PROPERTY = QStringLiteral("availableOperations"); +const QString CACHE_TOLERANCE_PROPERTY = QStringLiteral("cacheTolerance"); +const QString INITIAL_RANGE_PROPERTY = QStringLiteral("initialRange"); const QString MAX_RANGE_PROPERTY = QStringLiteral("maxRange"); const QString METADATA_POOL_PROPERTY = QStringLiteral("metadataPool"); const QString PROVIDER_PROPERTY = QStringLiteral("provider"); +const QString OPERATION_DELAY_BOUNDS_PROPERTY = QStringLiteral("operationDelays"); +const QString VALIDATORS_PROPERTY = QStringLiteral("validators"); +const QString VALIDATION_FREQUENCY_BOUNDS_PROPERTY = QStringLiteral("validationFrequencyBounds"); + +// //////////// // +// FuzzingState // +// //////////// // + +const SyncGroup &FuzzingState::syncGroup(SyncGroupId id) const +{ + return m_SyncGroupsPool.at(id); +} + +SyncGroup &FuzzingState::syncGroup(SyncGroupId id) +{ + return m_SyncGroupsPool.at(id); +} + +const VariableState &FuzzingState::variableState(VariableId id) const +{ + return m_VariablesPool.at(id); +} + +VariableState &FuzzingState::variableState(VariableId id) +{ + return m_VariablesPool.at(id); +} + +SyncGroupId FuzzingState::syncGroupId(VariableId variableId) const +{ + auto end = m_SyncGroupsPool.cend(); + auto it + = std::find_if(m_SyncGroupsPool.cbegin(), end, [&variableId](const auto &syncGroupEntry) { + const auto &syncGroup = syncGroupEntry.second; + return syncGroup.m_Variables.find(variableId) != syncGroup.m_Variables.end(); + }); + + return it != end ? it->first : SyncGroupId{}; +} + +std::vector FuzzingState::syncGroupsIds() const +{ + std::vector result{}; + + for (const auto &entry : m_SyncGroupsPool) { + result.push_back(entry.first); + } + + return result; +} + +void FuzzingState::synchronizeVariable(VariableId variableId, SyncGroupId syncGroupId) +{ + if (syncGroupId.isNull()) { + return; + } + + // Registers variable into sync group: if it's the first variable, sets the variable range as + // the sync group range + auto &syncGroup = m_SyncGroupsPool.at(syncGroupId); + syncGroup.m_Variables.insert(variableId); + if (syncGroup.m_Variables.size() == 1) { + auto &variableState = m_VariablesPool.at(variableId); + syncGroup.m_Range = variableState.m_Range; + } +} + +void FuzzingState::desynchronizeVariable(VariableId variableId, SyncGroupId syncGroupId) +{ + if (syncGroupId.isNull()) { + return; + } + + // Unregisters variable from sync group: if there is no more variable in the group, resets the + // range + auto &syncGroup = m_SyncGroupsPool.at(syncGroupId); + syncGroup.m_Variables.erase(variableId); + if (syncGroup.m_Variables.empty()) { + syncGroup.m_Range = INVALID_RANGE; + } +} + +void FuzzingState::updateRanges(VariableId variableId, const SqpRange &newRange) +{ + auto syncGroupId = this->syncGroupId(variableId); + + // Retrieves the variables to update: + // - if the variable is synchronized to others, updates all synchronized variables + // - otherwise, updates only the variable + auto variablesToUpdate = syncGroupId.isNull() ? std::set{variableId} + : m_SyncGroupsPool.at(syncGroupId).m_Variables; + + // Sets new range + for (const auto &variableId : variablesToUpdate) { + m_VariablesPool.at(variableId).m_Range = newRange; + } +} diff --git a/plugins/amda/tests/FuzzingDefs.h b/plugins/amda/tests/FuzzingDefs.h index 8d6f092..6b66508 100644 --- a/plugins/amda/tests/FuzzingDefs.h +++ b/plugins/amda/tests/FuzzingDefs.h @@ -1,9 +1,15 @@ #ifndef SCIQLOP_FUZZINGDEFS_H #define SCIQLOP_FUZZINGDEFS_H +#include + #include +#include #include +#include +#include + // /////// // // Aliases // // /////// // @@ -17,15 +23,27 @@ using Properties = QVariantHash; // Constants // // ///////// // +/// Timeout set for data acquisition for an operation (in ms) +extern const QString ACQUISITION_TIMEOUT_PROPERTY; + /// Max number of operations to generate extern const QString NB_MAX_OPERATIONS_PROPERTY; +/// Max number of sync groups to create through operations +extern const QString NB_MAX_SYNC_GROUPS_PROPERTY; + /// Max number of variables to manipulate through operations extern const QString NB_MAX_VARIABLES_PROPERTY; /// Set of operations available for the test extern const QString AVAILABLE_OPERATIONS_PROPERTY; +/// Tolerance used for variable's cache (in ratio) +extern const QString CACHE_TOLERANCE_PROPERTY; + +/// Range with which the timecontroller is initialized +extern const QString INITIAL_RANGE_PROPERTY; + /// Max range that an operation can reach extern const QString MAX_RANGE_PROPERTY; @@ -35,4 +53,76 @@ extern const QString METADATA_POOL_PROPERTY; /// Provider used to retrieve data extern const QString PROVIDER_PROPERTY; +/// Min/max times left for an operation to execute +extern const QString OPERATION_DELAY_BOUNDS_PROPERTY; + +/// Validators used to validate an operation +extern const QString VALIDATORS_PROPERTY; + +/// Min/max number of operations to execute before calling validation of the current test's state +extern const QString VALIDATION_FREQUENCY_BOUNDS_PROPERTY; + +// /////// // +// Structs // +// /////// // + +class Variable; +struct VariableState { + std::shared_ptr m_Variable{nullptr}; + SqpRange m_Range{INVALID_RANGE}; +}; + +using VariableId = int; +using VariablesPool = std::map; + +/** + * Defines a synchronization group for a fuzzing state. A group reports the variables synchronized + * with each other, and the current range of the group (i.e. range of the last synchronized variable + * that has been moved) + */ +struct SyncGroup { + std::set m_Variables{}; + SqpRange m_Range{INVALID_RANGE}; +}; + +using SyncGroupId = QUuid; +using SyncGroupsPool = std::map; + +/** + * Defines a current state during a fuzzing state. It contains all the variables manipulated during + * the test, as well as the synchronization status of these variables. + */ +struct FuzzingState { + const SyncGroup &syncGroup(SyncGroupId id) const; + SyncGroup &syncGroup(SyncGroupId id); + + const VariableState &variableState(VariableId id) const; + VariableState &variableState(VariableId id); + + /// @return the identifier of the synchronization group in which the variable passed in + /// parameter is located. If the variable is not in any group, returns an invalid identifier + SyncGroupId syncGroupId(VariableId variableId) const; + + /// @return the set of synchronization group identifiers + std::vector syncGroupsIds() const; + + /// Updates fuzzing state according to a variable synchronization + /// @param variableId the variable that is synchronized + /// @param syncGroupId the synchronization group + void synchronizeVariable(VariableId variableId, SyncGroupId syncGroupId); + + /// Updates fuzzing state according to a variable desynchronization + /// @param variableId the variable that is desynchronized + /// @param syncGroupId the synchronization group from which to remove the variable + void desynchronizeVariable(VariableId variableId, SyncGroupId syncGroupId); + + /// Updates the range of a variable and all variables to which it is synchronized + /// @param the variable for which to affect the range + /// @param the range to affect + void updateRanges(VariableId variableId, const SqpRange &newRange); + + VariablesPool m_VariablesPool; + SyncGroupsPool m_SyncGroupsPool; +}; + #endif // SCIQLOP_FUZZINGDEFS_H diff --git a/plugins/amda/tests/FuzzingOperations.cpp b/plugins/amda/tests/FuzzingOperations.cpp index 124ae35..569cc6b 100644 --- a/plugins/amda/tests/FuzzingOperations.cpp +++ b/plugins/amda/tests/FuzzingOperations.cpp @@ -8,18 +8,21 @@ #include +#include + Q_LOGGING_CATEGORY(LOG_FuzzingOperations, "FuzzingOperations") namespace { struct CreateOperation : public IFuzzingOperation { - bool canExecute(std::shared_ptr variable) const override + bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const override { // A variable can be created only if it doesn't exist yet - return variable == nullptr; + return fuzzingState.variableState(variableId).m_Variable == nullptr; } - void execute(std::shared_ptr &variable, VariableController &variableController, + void execute(VariableId variableId, FuzzingState &fuzzingState, + VariableController &variableController, const Properties &properties) const override { // Retrieves metadata pool from properties, and choose one of the metadata entries to @@ -32,29 +35,181 @@ struct CreateOperation : public IFuzzingOperation { = properties.value(PROVIDER_PROPERTY).value >(); auto variableName = QString{"Var_%1"}.arg(QUuid::createUuid().toString()); - qCInfo(LOG_FuzzingOperations()) - << "Creating variable" << variableName << "(metadata:" << variableMetadata << ")"; + qCInfo(LOG_FuzzingOperations()).noquote() + << "Creating variable" << variableName << "(metadata:" << variableMetadata << ")..."; auto newVariable = variableController.createVariable(variableName, variableMetadata, variableProvider); - std::swap(variable, newVariable); + + // Updates variable's state + auto &variableState = fuzzingState.variableState(variableId); + variableState.m_Range = properties.value(INITIAL_RANGE_PROPERTY).value(); + std::swap(variableState.m_Variable, newVariable); } }; -struct UnknownOperation : public IFuzzingOperation { - bool canExecute(std::shared_ptr variable) const override +struct DeleteOperation : public IFuzzingOperation { + bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const override { - Q_UNUSED(variable); - return false; + // A variable can be delete only if it exists + return fuzzingState.variableState(variableId).m_Variable != nullptr; } - void execute(std::shared_ptr &variable, VariableController &variableController, + void execute(VariableId variableId, FuzzingState &fuzzingState, + VariableController &variableController, const Properties &) const override + { + auto &variableState = fuzzingState.variableState(variableId); + + qCInfo(LOG_FuzzingOperations()).noquote() + << "Deleting variable" << variableState.m_Variable->name() << "..."; + variableController.deleteVariable(variableState.m_Variable); + + // Updates variable's state + variableState.m_Range = INVALID_RANGE; + variableState.m_Variable = nullptr; + + // Desynchronizes the variable if it was in a sync group + auto syncGroupId = fuzzingState.syncGroupId(variableId); + fuzzingState.desynchronizeVariable(variableId, syncGroupId); + } +}; + +/** + * Defines a move operation through a range. + * + * A move operation is determined by three functions: + * - Two 'move' functions, used to indicate in which direction the beginning and the end of a range + * are going during the operation. These functions will be: + * -- {<- / <-} for pan left + * -- {-> / ->} for pan right + * -- {-> / <-} for zoom in + * -- {<- / ->} for zoom out + * - One 'max move' functions, used to compute the max delta at which the operation can move a + * range, according to a max range. For exemple, for a range of {1, 5} and a max range of {0, 10}, + * max deltas will be: + * -- {0, 4} for pan left + * -- {6, 10} for pan right + * -- {3, 3} for zoom in + * -- {0, 6} for zoom out (same spacing left and right) + */ +struct MoveOperation : public IFuzzingOperation { + using MoveFunction = std::function; + using MaxMoveFunction = std::function; + + explicit MoveOperation(MoveFunction rangeStartMoveFun, MoveFunction rangeEndMoveFun, + MaxMoveFunction maxMoveFun, + const QString &label = QStringLiteral("Move operation")) + : m_RangeStartMoveFun{std::move(rangeStartMoveFun)}, + m_RangeEndMoveFun{std::move(rangeEndMoveFun)}, + m_MaxMoveFun{std::move(maxMoveFun)}, + m_Label{label} + { + } + + bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const override + { + return fuzzingState.variableState(variableId).m_Variable != nullptr; + } + + void execute(VariableId variableId, FuzzingState &fuzzingState, + VariableController &variableController, const Properties &properties) const override { - Q_UNUSED(variable); - Q_UNUSED(variableController); - Q_UNUSED(properties); - // Does nothing + auto &variableState = fuzzingState.variableState(variableId); + auto variable = variableState.m_Variable; + + // Gets the max range defined + auto maxRange = properties.value(MAX_RANGE_PROPERTY, QVariant::fromValue(INVALID_RANGE)) + .value(); + auto variableRange = variableState.m_Range; + + if (maxRange == INVALID_RANGE || variableRange.m_TStart < maxRange.m_TStart + || variableRange.m_TEnd > maxRange.m_TEnd) { + qCWarning(LOG_FuzzingOperations()) << "Can't execute operation: invalid max range"; + return; + } + + // Computes the max delta at which the variable can move, up to the limits of the max range + auto deltaMax = m_MaxMoveFun(variableRange, maxRange); + + // Generates random delta that will be used to move variable + auto delta = RandomGenerator::instance().generateDouble(0, deltaMax); + + // Moves variable to its new range + auto isSynchronized = !fuzzingState.syncGroupId(variableId).isNull(); + auto newVariableRange = SqpRange{m_RangeStartMoveFun(variableRange.m_TStart, delta), + m_RangeEndMoveFun(variableRange.m_TEnd, delta)}; + qCInfo(LOG_FuzzingOperations()).noquote() + << "Performing" << m_Label << "on" << variable->name() << "(from" << variableRange + << "to" << newVariableRange << ")..."; + variableController.onRequestDataLoading({variable}, newVariableRange, isSynchronized); + + // Updates state + fuzzingState.updateRanges(variableId, newVariableRange); + } + + MoveFunction m_RangeStartMoveFun; + MoveFunction m_RangeEndMoveFun; + MaxMoveFunction m_MaxMoveFun; + QString m_Label; +}; + +struct SynchronizeOperation : public IFuzzingOperation { + bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const override + { + auto variable = fuzzingState.variableState(variableId).m_Variable; + return variable != nullptr && !fuzzingState.m_SyncGroupsPool.empty() + && fuzzingState.syncGroupId(variableId).isNull(); + } + + void execute(VariableId variableId, FuzzingState &fuzzingState, + VariableController &variableController, const Properties &) const override + { + auto &variableState = fuzzingState.variableState(variableId); + + // Chooses a random synchronization group and adds the variable into sync group + auto syncGroupId = RandomGenerator::instance().randomChoice(fuzzingState.syncGroupsIds()); + qCInfo(LOG_FuzzingOperations()).noquote() + << "Adding" << variableState.m_Variable->name() << "into synchronization group" + << syncGroupId << "..."; + variableController.onAddSynchronized(variableState.m_Variable, syncGroupId); + + // Updates state + fuzzingState.synchronizeVariable(variableId, syncGroupId); + } +}; + +struct DesynchronizeOperation : public IFuzzingOperation { + bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const override + { + auto variable = fuzzingState.variableState(variableId).m_Variable; + return variable != nullptr && !fuzzingState.syncGroupId(variableId).isNull(); + } + + void execute(VariableId variableId, FuzzingState &fuzzingState, + VariableController &variableController, const Properties &) const override + { + auto &variableState = fuzzingState.variableState(variableId); + + // Gets the sync group of the variable + auto syncGroupId = fuzzingState.syncGroupId(variableId); + + qCInfo(LOG_FuzzingOperations()).noquote() + << "Removing" << variableState.m_Variable->name() << "from synchronization group" + << syncGroupId << "..."; + variableController.onAddSynchronized(variableState.m_Variable, syncGroupId); + + // Updates state + fuzzingState.desynchronizeVariable(variableId, syncGroupId); + } +}; + +struct UnknownOperation : public IFuzzingOperation { + bool canExecute(VariableId, const FuzzingState &) const override { return false; } + + void execute(VariableId, FuzzingState &, VariableController &, + const Properties &) const override + { } }; @@ -65,6 +220,42 @@ std::unique_ptr FuzzingOperationFactory::create(FuzzingOperat switch (type) { case FuzzingOperationType::CREATE: return std::make_unique(); + case FuzzingOperationType::DELETE: + return std::make_unique(); + case FuzzingOperationType::PAN_LEFT: + return std::make_unique( + std::minus(), std::minus(), + [](const SqpRange &range, const SqpRange &maxRange) { + return range.m_TStart - maxRange.m_TStart; + }, + QStringLiteral("Pan left operation")); + case FuzzingOperationType::PAN_RIGHT: + return std::make_unique( + std::plus(), std::plus(), + [](const SqpRange &range, const SqpRange &maxRange) { + return maxRange.m_TEnd - range.m_TEnd; + }, + QStringLiteral("Pan right operation")); + case FuzzingOperationType::ZOOM_IN: + return std::make_unique( + std::plus(), std::minus(), + [](const SqpRange &range, const SqpRange &maxRange) { + Q_UNUSED(maxRange) + return range.m_TEnd - (range.m_TStart + range.m_TEnd) / 2.; + }, + QStringLiteral("Zoom in operation")); + case FuzzingOperationType::ZOOM_OUT: + return std::make_unique( + std::minus(), std::plus(), + [](const SqpRange &range, const SqpRange &maxRange) { + return std::min(range.m_TStart - maxRange.m_TStart, + maxRange.m_TEnd - range.m_TEnd); + }, + QStringLiteral("Zoom out operation")); + case FuzzingOperationType::SYNCHRONIZE: + return std::make_unique(); + case FuzzingOperationType::DESYNCHRONIZE: + return std::make_unique(); default: // Default case returns unknown operation break; diff --git a/plugins/amda/tests/FuzzingOperations.h b/plugins/amda/tests/FuzzingOperations.h index cbc3dc0..98a0a63 100644 --- a/plugins/amda/tests/FuzzingOperations.h +++ b/plugins/amda/tests/FuzzingOperations.h @@ -11,28 +11,38 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_FuzzingOperations) -class Variable; class VariableController; /** * Enumeration of types of existing fuzzing operations */ -enum class FuzzingOperationType { CREATE }; +enum class FuzzingOperationType { + CREATE, + DELETE, + PAN_LEFT, + PAN_RIGHT, + ZOOM_IN, + ZOOM_OUT, + SYNCHRONIZE, + DESYNCHRONIZE +}; /// Interface that represents an operation that can be executed during a fuzzing test struct IFuzzingOperation { virtual ~IFuzzingOperation() noexcept = default; - /// Checks if the operation can be executed according to the current state of the variable - /// passed in parameter - virtual bool canExecute(std::shared_ptr variable) const = 0; - /// Executes the operation on the variable passed in parameter - /// @param variable the variable on which to execute the operation + /// Checks if the operation can be executed according to the current test's state for the + /// variable passed in parameter + virtual bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const = 0; + /// Executes the operation on the variable for which its identifier is passed in parameter + /// @param variableId the variable identifier + /// @param fuzzingState the current test's state on which to find the variable and execute the + /// operation /// @param variableController the controller associated to the operation /// @param properties properties that can be used to configure the operation - /// @remarks variable is passed as a reference because, according to the operation, it can be - /// modified (in/out parameter) - virtual void execute(std::shared_ptr &variable, + /// @remarks fuzzingState is passed as a reference because, according to the operation, it can + /// be modified (in/out parameter) + virtual void execute(VariableId variableId, FuzzingState &fuzzingState, VariableController &variableController, const Properties &properties = {}) const = 0; }; @@ -43,7 +53,4 @@ struct FuzzingOperationFactory { static std::unique_ptr create(FuzzingOperationType type); }; -using OperationsTypes = std::set; -Q_DECLARE_METATYPE(OperationsTypes) - #endif // SCIQLOP_FUZZINGOPERATIONS_H diff --git a/plugins/amda/tests/FuzzingUtils.h b/plugins/amda/tests/FuzzingUtils.h index adfe293..6ae89ea 100644 --- a/plugins/amda/tests/FuzzingUtils.h +++ b/plugins/amda/tests/FuzzingUtils.h @@ -1,6 +1,7 @@ #ifndef SCIQLOP_FUZZINGUTILS_H #define SCIQLOP_FUZZINGUTILS_H +#include #include /** @@ -16,10 +17,17 @@ public: /// Generates a random int between [min, max] int generateInt(int min, int max); - /// Returns a random element among the elements of a container. If the container is empty, - /// returns an element built by default + /** + * Returns a random element among the elements of a container. An item may be more likely to be + * selected if it has an associated weight greater than other items + * @param container the container from which to retrieve an element + * @param weights the weight associated to each element of the container. The vector must have + * the same size of the container for the weights to be effective + * @param nbDraws the number of random draws to perform + * @return the random element retrieved, an element built by default if the container is empty + */ template - ValueType randomChoice(const T &container); + ValueType randomChoice(const T &container, const std::vector &weights = {}); private: std::mt19937 m_Mt; @@ -28,14 +36,28 @@ private: }; template -ValueType RandomGenerator::randomChoice(const T &container) +ValueType RandomGenerator::randomChoice(const T &container, const std::vector &weights) { if (container.empty()) { return ValueType{}; } - auto randomIndex = generateInt(0, container.size() - 1); - return container.at(randomIndex); + // Generates weights for each element: if the weights passed in parameter are malformed (the + // number of weights defined is inconsistent with the number of elements in the container, or + // all weights are zero), default weights are used + auto nbIndexes = container.size(); + std::vector indexWeights(nbIndexes); + if (weights.size() != nbIndexes || std::all_of(weights.cbegin(), weights.cend(), + [](const auto &val) { return val == 0.; })) { + std::fill(indexWeights.begin(), indexWeights.end(), 1.); + } + else { + std::copy(weights.begin(), weights.end(), indexWeights.begin()); + } + + // Performs a draw to determine the index to return + std::discrete_distribution<> d{indexWeights.cbegin(), indexWeights.cend()}; + return container.at(d(m_Mt)); } #endif // SCIQLOP_FUZZINGUTILS diff --git a/plugins/amda/tests/FuzzingValidators.cpp b/plugins/amda/tests/FuzzingValidators.cpp new file mode 100644 index 0000000..45f1100 --- /dev/null +++ b/plugins/amda/tests/FuzzingValidators.cpp @@ -0,0 +1,228 @@ +#include "FuzzingValidators.h" +#include "FuzzingDefs.h" + +#include +#include + +#include + +#include + +Q_LOGGING_CATEGORY(LOG_FuzzingValidators, "FuzzingValidators") + +namespace { + +// ////////////// // +// DATA VALIDATOR // +// ////////////// // + +/// Singleton used to validate data of a variable +class DataValidatorHelper { +public: + /// @return the single instance of the helper + static DataValidatorHelper &instance(); + virtual ~DataValidatorHelper() noexcept = default; + + virtual void validate(const VariableState &variableState) const = 0; +}; + +/** + * Default implementation of @sa DataValidatorHelper + */ +class DefaultDataValidatorHelper : public DataValidatorHelper { +public: + void validate(const VariableState &variableState) const override + { + Q_UNUSED(variableState); + qCWarning(LOG_FuzzingValidators()).noquote() << "Checking variable's data... WARN: no data " + "verification is available for this server"; + } +}; + +/// Data resolution in local server's files +const auto LOCALHOST_SERVER_RESOLUTION = 4; +/// Reference value used to generate the data on the local server (a value is the number of seconds +/// between the data date and this reference date) +const auto LOCALHOST_REFERENCE_VALUE + = DateUtils::secondsSinceEpoch(QDateTime{QDate{2000, 1, 1}, QTime{}, Qt::UTC}); + +/** + * Implementation of @sa DataValidatorHelper for the local AMDA server + */ +class LocalhostServerDataValidatorHelper : public DataValidatorHelper { +public: + void validate(const VariableState &variableState) const override + { + // Don't check data for null variable + if (!variableState.m_Variable || variableState.m_Range == INVALID_RANGE) { + return; + } + + auto message = "Checking variable's data..."; + auto toDateString = [](double value) { return DateUtils::dateTime(value).toString(); }; + + // Checks that data are defined + auto variableDataSeries = variableState.m_Variable->dataSeries(); + if (variableDataSeries == nullptr && variableState.m_Range != INVALID_RANGE) { + qCInfo(LOG_FuzzingValidators()).noquote() + << message << "FAIL: the variable has no data while a range is defined"; + QFAIL(""); + } + + auto dataIts = variableDataSeries->xAxisRange(variableState.m_Range.m_TStart, + variableState.m_Range.m_TEnd); + + // Checks that the data are well defined in the range: + // - there is at least one data + // - the data are consistent (no data holes) + if (std::distance(dataIts.first, dataIts.second) == 0) { + qCInfo(LOG_FuzzingValidators()).noquote() + << message << "FAIL: the variable has no data"; + QFAIL(""); + } + + auto firstXAxisData = dataIts.first->x(); + auto lastXAxisData = (dataIts.second - 1)->x(); + + if (std::abs(firstXAxisData - variableState.m_Range.m_TStart) > LOCALHOST_SERVER_RESOLUTION + || std::abs(lastXAxisData - variableState.m_Range.m_TEnd) + > LOCALHOST_SERVER_RESOLUTION) { + qCInfo(LOG_FuzzingValidators()).noquote() + << message << "FAIL: the data in the defined range are inconsistent (data hole " + "found at the beginning or the end)"; + QFAIL(""); + } + + auto dataHoleIt = std::adjacent_find( + dataIts.first, dataIts.second, [](const auto &it1, const auto &it2) { + /// @todo: validate resolution + return std::abs(it1.x() - it2.x()) > 2 * (LOCALHOST_SERVER_RESOLUTION - 1); + }); + + if (dataHoleIt != dataIts.second) { + qCInfo(LOG_FuzzingValidators()).noquote() + << message << "FAIL: the data in the defined range are inconsistent (data hole " + "found between times " + << toDateString(dataHoleIt->x()) << "and " << toDateString((dataHoleIt + 1)->x()) + << ")"; + QFAIL(""); + } + + // Checks values + auto dataIndex = 0; + for (auto dataIt = dataIts.first; dataIt != dataIts.second; ++dataIt, ++dataIndex) { + auto xAxisData = dataIt->x(); + auto valuesData = dataIt->values(); + for (auto valueIndex = 0, valueEnd = valuesData.size(); valueIndex < valueEnd; + ++valueIndex) { + auto value = valuesData.at(valueIndex); + auto expectedValue = xAxisData + valueIndex * LOCALHOST_SERVER_RESOLUTION + - LOCALHOST_REFERENCE_VALUE; + + if (value != expectedValue) { + qCInfo(LOG_FuzzingValidators()).noquote() + << message << "FAIL: incorrect value data at time" + << toDateString(xAxisData) << ", index" << valueIndex << "(found:" << value + << ", expected:" << expectedValue << ")"; + QFAIL(""); + } + } + } + + // At this step validation is OK + qCInfo(LOG_FuzzingValidators()).noquote() << message << "OK"; + } +}; + +/// Creates the @sa DataValidatorHelper according to the server passed in parameter +std::unique_ptr createDataValidatorInstance(const QString &server) +{ + if (server == QString{"localhost"}) { + return std::make_unique(); + } + else { + return std::make_unique(); + } +} + +DataValidatorHelper &DataValidatorHelper::instance() +{ + // Creates instance depending on the SCIQLOP_AMDA_SERVER value at compile time + static auto instance = createDataValidatorInstance(SCIQLOP_AMDA_SERVER); + return *instance; +} + +// /////////////// // +// RANGE VALIDATOR // +// /////////////// // + +/** + * Checks that a range of a variable matches the expected range passed as a parameter + * @param variable the variable for which to check the range + * @param expectedRange the expected range + * @param getVariableRangeFun the function to retrieve the range from the variable + * @remarks if the variable is null, checks that the expected range is the invalid range + */ +void validateRange(std::shared_ptr variable, const SqpRange &expectedRange, + std::function getVariableRangeFun) +{ + auto compare = [](const auto &range, const auto &expectedRange, const auto &message) { + if (range == expectedRange) { + qCInfo(LOG_FuzzingValidators()).noquote() << message << "OK"; + } + else { + qCInfo(LOG_FuzzingValidators()).noquote() + << message << "FAIL (current range:" << range + << ", expected range:" << expectedRange << ")"; + QFAIL(""); + } + }; + + if (variable) { + compare(getVariableRangeFun(*variable), expectedRange, "Checking variable's range..."); + } + else { + compare(INVALID_RANGE, expectedRange, "Checking that there is no range set..."); + } +} + +/** + * Default implementation of @sa IFuzzingValidator. This validator takes as parameter of its + * construction a function of validation which is called in the validate() method + */ +class FuzzingValidator : public IFuzzingValidator { +public: + /// Signature of a validation function + using ValidationFunction = std::function; + + explicit FuzzingValidator(ValidationFunction fun) : m_Fun(std::move(fun)) {} + + void validate(const VariableState &variableState) const override { m_Fun(variableState); } + +private: + ValidationFunction m_Fun; +}; + +} // namespace + +std::unique_ptr FuzzingValidatorFactory::create(FuzzingValidatorType type) +{ + switch (type) { + case FuzzingValidatorType::DATA: + return std::make_unique([](const VariableState &variableState) { + DataValidatorHelper::instance().validate(variableState); + }); + case FuzzingValidatorType::RANGE: + return std::make_unique([](const VariableState &variableState) { + auto getVariableRange = [](const Variable &variable) { return variable.range(); }; + validateRange(variableState.m_Variable, variableState.m_Range, getVariableRange); + }); + default: + // Default case returns invalid validator + break; + } + + // Invalid validator + return std::make_unique( + [](const VariableState &) { QFAIL("Invalid validator"); }); +} diff --git a/plugins/amda/tests/FuzzingValidators.h b/plugins/amda/tests/FuzzingValidators.h new file mode 100644 index 0000000..33a538e --- /dev/null +++ b/plugins/amda/tests/FuzzingValidators.h @@ -0,0 +1,40 @@ +#ifndef SCIQLOP_FUZZINGVALIDATORS_H +#define SCIQLOP_FUZZINGVALIDATORS_H + +#include +#include + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(LOG_FuzzingValidators) + +class VariableState; + +/// Types of validators that can be defined +enum class FuzzingValidatorType { + DATA, ///< Validates variable's data + RANGE ///< Validates variable's range +}; + +/** + * Struct that represents a validator. A validator checks if the state of a variable is valid at the + * moment it is called during a fuzzing test + */ +struct IFuzzingValidator { + virtual ~IFuzzingValidator() noexcept = default; + + /// Validates the variable's state passed in parameter + virtual void validate(const VariableState &variableState) const = 0; +}; + +/// Factory of @sa IFuzzingValidator +struct FuzzingValidatorFactory { + /// Creates a validator according to the type passed in parameter + static std::unique_ptr create(FuzzingValidatorType type); +}; + +using ValidatorsTypes = std::vector; +Q_DECLARE_METATYPE(ValidatorsTypes) + +#endif // SCIQLOP_FUZZINGVALIDATORS_H diff --git a/plugins/amda/tests/TestAmdaFuzzing.cpp b/plugins/amda/tests/TestAmdaFuzzing.cpp index 41ace89..2dd6222 100644 --- a/plugins/amda/tests/TestAmdaFuzzing.cpp +++ b/plugins/amda/tests/TestAmdaFuzzing.cpp @@ -1,12 +1,16 @@ #include "FuzzingDefs.h" #include "FuzzingOperations.h" #include "FuzzingUtils.h" +#include "FuzzingValidators.h" #include "AmdaProvider.h" +#include #include +#include #include #include