From 73c43e70e2789bad9aec6aaedc5693be0b71c0a0 2017-10-31 08:18:24 From: Alexandre Leroux Date: 2017-10-31 08:18:24 Subject: [PATCH] Merge branch 'feature/SpectrogramSeries' into develop --- diff --git a/core/include/Data/ArrayData.h b/core/include/Data/ArrayData.h index ab14657..16f54fe 100644 --- a/core/include/Data/ArrayData.h +++ b/core/include/Data/ArrayData.h @@ -94,7 +94,6 @@ public: } int distance(const ArrayDataIteratorValue::Impl &other) const override try { - /// @todo ALX : validate const auto &otherImpl = dynamic_cast(other); return std::distance(otherImpl.m_It, m_It) / m_NbComponents; } diff --git a/core/include/Data/DataSeries.h b/core/include/Data/DataSeries.h index dff18f8..c6d2ce3 100644 --- a/core/include/Data/DataSeries.h +++ b/core/include/Data/DataSeries.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -120,9 +121,62 @@ private: /** * @brief The DataSeries class is the base (abstract) implementation of IDataSeries. * - * It proposes to set a dimension for the values ​​data. + * The DataSeries represents values on one or two axes, according to these rules: + * - the x-axis is always defined + * - an y-axis can be defined or not. If set, additional consistency checks apply to the values (see + * below) + * - the values are defined on one or two dimensions. In the case of 2-dim values, the data is + * distributed into components (for example, a vector defines three components) + * - New values can be added to the series, on the x-axis. + * - Once initialized to the series creation, the y-axis (if defined) is no longer modifiable + * - Data representing values and axes are associated with a unit + * - The data series is always sorted in ascending order on the x-axis. * - * A DataSeries is always sorted on its x-axis data. + * Consistency checks are carried out between the axes and the values. These controls are provided + * throughout the DataSeries lifecycle: + * - the number of data on the x-axis must be equal to the number of values (in the case of + * 2-dim ArrayData for values, the test is performed on the number of values per component) + * - if the y-axis is defined, the number of components of the ArrayData for values must equal the + * number of data on the y-axis. + * + * Examples: + * 1) + * - x-axis: [1 ; 2 ; 3] + * - y-axis: not defined + * - values: [10 ; 20 ; 30] (1-dim ArrayData) + * => the DataSeries is valid, as x-axis and values have the same number of data + * + * 2) + * - x-axis: [1 ; 2 ; 3] + * - y-axis: not defined + * - values: [10 ; 20 ; 30 ; 40] (1-dim ArrayData) + * => the DataSeries is invalid, as x-axis and values haven't the same number of data + * + * 3) + * - x-axis: [1 ; 2 ; 3] + * - y-axis: not defined + * - values: [10 ; 20 ; 30 + * 40 ; 50 ; 60] (2-dim ArrayData) + * => the DataSeries is valid, as x-axis has 3 data and values contains 2 components with 3 + * data each + * + * 4) + * - x-axis: [1 ; 2 ; 3] + * - y-axis: [1 ; 2] + * - values: [10 ; 20 ; 30 + * 40 ; 50 ; 60] (2-dim ArrayData) + * => the DataSeries is valid, as: + * - x-axis has 3 data and values contains 2 components with 3 data each AND + * - y-axis has 2 data and values contains 2 components + * + * 5) + * - x-axis: [1 ; 2 ; 3] + * - y-axis: [1 ; 2 ; 3] + * - values: [10 ; 20 ; 30 + * 40 ; 50 ; 60] (2-dim ArrayData) + * => the DataSeries is invalid, as: + * - x-axis has 3 data and values contains 2 components with 3 data each BUT + * - y-axis has 3 data and values contains only 2 components * * @tparam Dim The dimension of the values data * @@ -146,7 +200,7 @@ public: /// @sa IDataSeries::valuesUnit() Unit valuesUnit() const override { return m_ValuesUnit; } - int nbPoints() const override { return m_XAxisData->totalSize() + m_ValuesData->totalSize(); } + int nbPoints() const override { return m_ValuesData->totalSize(); } void clear() { @@ -156,7 +210,14 @@ public: bool isEmpty() const noexcept { return m_XAxisData->size() == 0; } - /// Merges into the data series an other data series + /// Merges into the data series an other data series. + /// + /// The two dataseries: + /// - must be of the same dimension + /// - must have the same y-axis (if defined) + /// + /// If the prerequisites are not valid, the method does nothing + /// /// @remarks the data series to merge with is cleared after the operation void merge(IDataSeries *dataSeries) override { @@ -164,7 +225,13 @@ public: lockWrite(); if (auto other = dynamic_cast *>(dataSeries)) { - DataSeriesMergeHelper::merge(*other, *this); + if (m_YAxis == other->m_YAxis) { + DataSeriesMergeHelper::merge(*other, *this); + } + else { + qCWarning(LOG_DataSeries()) + << QObject::tr("Can't merge data series that have not the same y-axis"); + } } else { qCWarning(LOG_DataSeries()) @@ -325,18 +392,31 @@ public: virtual void unlock() { m_Lock.unlock(); } protected: - /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a - /// DataSeries with no values will be created. + /// Protected ctor (DataSeries is abstract). + /// + /// Data vectors must be consistent with each other, otherwise an exception will be thrown (@sa + /// class description for consistent rules) /// @remarks data series is automatically sorted on its x-axis data + /// @throws std::invalid_argument if the data are inconsistent with each other explicit DataSeries(std::shared_ptr > xAxisData, const Unit &xAxisUnit, - std::shared_ptr > valuesData, const Unit &valuesUnit) + std::shared_ptr > valuesData, const Unit &valuesUnit, + OptionalAxis yAxis = OptionalAxis{}) : m_XAxisData{xAxisData}, m_XAxisUnit{xAxisUnit}, m_ValuesData{valuesData}, - m_ValuesUnit{valuesUnit} + m_ValuesUnit{valuesUnit}, + m_YAxis{std::move(yAxis)} { if (m_XAxisData->size() != m_ValuesData->size()) { - clear(); + throw std::invalid_argument{ + "The number of values by component must be equal to the number of x-axis data"}; + } + + // Validates y-axis (if defined) + if (yAxis.isDefined() && (yAxis.size() != m_ValuesData->componentCount())) { + throw std::invalid_argument{ + "As the y-axis is defined, the number of value components must be equal to the " + "number of y-axis data"}; } // Sorts data if it's not the case @@ -351,12 +431,16 @@ protected: : m_XAxisData{std::make_shared >(*other.m_XAxisData)}, m_XAxisUnit{other.m_XAxisUnit}, m_ValuesData{std::make_shared >(*other.m_ValuesData)}, - m_ValuesUnit{other.m_ValuesUnit} + m_ValuesUnit{other.m_ValuesUnit}, + m_YAxis{other.m_YAxis} { // Since a series is ordered from its construction and is always ordered, it is not // necessary to call the sort method here ('other' is sorted) } + /// @return the y-axis associated to the data series + OptionalAxis yAxis() const { return m_YAxis; } + /// Assignment operator template DataSeries &operator=(DataSeries other) @@ -365,6 +449,7 @@ protected: std::swap(m_XAxisUnit, other.m_XAxisUnit); std::swap(m_ValuesData, other.m_ValuesData); std::swap(m_ValuesUnit, other.m_ValuesUnit); + std::swap(m_YAxis, other.m_YAxis); return *this; } @@ -380,11 +465,17 @@ private: m_ValuesData = m_ValuesData->sort(permutation); } + // x-axis std::shared_ptr > m_XAxisData; Unit m_XAxisUnit; + + // values std::shared_ptr > m_ValuesData; Unit m_ValuesUnit; + // y-axis (optional) + OptionalAxis m_YAxis; + QReadWriteLock m_Lock; }; diff --git a/core/include/Data/IDataSeries.h b/core/include/Data/IDataSeries.h index 19778fb..44ed1dd 100644 --- a/core/include/Data/IDataSeries.h +++ b/core/include/Data/IDataSeries.h @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -12,22 +13,6 @@ template class ArrayData; -struct Unit { - explicit Unit(const QString &name = {}, bool timeUnit = false) - : m_Name{name}, m_TimeUnit{timeUnit} - { - } - - inline bool operator==(const Unit &other) const - { - return std::tie(m_Name, m_TimeUnit) == std::tie(other.m_Name, other.m_TimeUnit); - } - inline bool operator!=(const Unit &other) const { return !(*this == other); } - - QString m_Name; ///< Unit name - bool m_TimeUnit; ///< The unit is a unit of time (UTC) -}; - /** * @brief The IDataSeries aims to declare a data series. * diff --git a/core/include/Data/OptionalAxis.h b/core/include/Data/OptionalAxis.h new file mode 100644 index 0000000..1d3b198 --- /dev/null +++ b/core/include/Data/OptionalAxis.h @@ -0,0 +1,58 @@ +#ifndef SCIQLOP_OPTIONALAXIS_H +#define SCIQLOP_OPTIONALAXIS_H + +#include "CoreGlobal.h" +#include "Unit.h" + +#include + +template +class ArrayData; + +/** + * @brief The OptionalAxis class defines an optional data axis for a series of data. + * + * An optional data axis is an axis that can be defined or not for a data series. If defined, it + * contains a unit and data (1-dim ArrayData). It is then possible to access the data or the unit. + * In the case of an undefined axis, the axis has no data and no unit. The methods for accessing the + * data or the unit are always callable but will return undefined values. + * + * @sa DataSeries + * @sa ArrayData + */ +class SCIQLOP_CORE_EXPORT OptionalAxis { +public: + /// Ctor for an undefined axis + explicit OptionalAxis(); + /// Ctor for a defined axis + /// @param data the axis' data + /// @param unit the axis' unit + /// @throws std::invalid_argument if no data is associated to the axis + explicit OptionalAxis(std::shared_ptr > data, Unit unit); + + /// Copy ctor + OptionalAxis(const OptionalAxis &other); + /// Assignment operator + OptionalAxis &operator=(OptionalAxis other); + + /// @return the flag that indicates if the axis is defined or not + bool isDefined() const; + + /// @return gets the data at the index passed in parameter, NaN if the index is outside the + /// bounds of the axis, or if the axis is undefined + double at(int index) const; + /// @return the number of data on the axis, 0 if the axis is not defined + int size() const; + /// @return the unit of the axis, an empty unit if the axis is not defined + Unit unit() const; + + bool operator==(const OptionalAxis &other); + bool operator!=(const OptionalAxis &other); + +private: + bool m_Defined; ///< Axis is defined or not + std::shared_ptr > m_Data; ///< Axis' data + Unit m_Unit; ///< Axis' unit +}; + +#endif // SCIQLOP_OPTIONALAXIS_H diff --git a/core/include/Data/SpectrogramSeries.h b/core/include/Data/SpectrogramSeries.h new file mode 100644 index 0000000..a58bc74 --- /dev/null +++ b/core/include/Data/SpectrogramSeries.h @@ -0,0 +1,33 @@ +#ifndef SCIQLOP_SPECTROGRAMSERIES_H +#define SCIQLOP_SPECTROGRAMSERIES_H + +#include "CoreGlobal.h" + +#include + +/** + * @brief The SpectrogramSeries class is the implementation for a data series representing a + * spectrogram. + * + * It defines values on a x-axis and a y-axis. + */ +class SCIQLOP_CORE_EXPORT SpectrogramSeries : public DataSeries<2> { +public: + /// Ctor + explicit SpectrogramSeries(std::vector xAxisData, std::vector yAxisData, + std::vector valuesData, const Unit &xAxisUnit, + const Unit &yAxisUnit, const Unit &valuesUnit); + + /// Ctor directly with the y-axis + explicit SpectrogramSeries(std::shared_ptr > xAxisData, const Unit &xAxisUnit, + std::shared_ptr > valuesData, const Unit &valuesUnit, + OptionalAxis yAxis); + + /// @sa DataSeries::clone() + std::unique_ptr clone() const override; + + /// @sa DataSeries::subDataSeries() + std::shared_ptr subDataSeries(const SqpRange &range) override; +}; + +#endif // SCIQLOP_SPECTROGRAMSERIES_H diff --git a/core/include/Data/Unit.h b/core/include/Data/Unit.h new file mode 100644 index 0000000..24cdc47 --- /dev/null +++ b/core/include/Data/Unit.h @@ -0,0 +1,23 @@ +#ifndef SCIQLOP_UNIT_H +#define SCIQLOP_UNIT_H + +#include +#include + +struct Unit { + explicit Unit(const QString &name = {}, bool timeUnit = false) + : m_Name{name}, m_TimeUnit{timeUnit} + { + } + + inline bool operator==(const Unit &other) const + { + return std::tie(m_Name, m_TimeUnit) == std::tie(other.m_Name, other.m_TimeUnit); + } + inline bool operator!=(const Unit &other) const { return !(*this == other); } + + QString m_Name; ///< Unit name + bool m_TimeUnit; ///< The unit is a unit of time (UTC) +}; + +#endif // SCIQLOP_UNIT_H diff --git a/core/meson.build b/core/meson.build index 658e629..2256757 100644 --- a/core/meson.build +++ b/core/meson.build @@ -23,9 +23,11 @@ core_sources = [ 'src/Common/StringUtils.cpp', 'src/Common/MimeTypesDef.cpp', 'src/Data/ScalarSeries.cpp', + 'src/Data/SpectrogramSeries.cpp', 'src/Data/DataSeriesIterator.cpp', 'src/Data/ArrayDataIterator.cpp', 'src/Data/VectorSeries.cpp', + 'src/Data/OptionalAxis.cpp', 'src/DataSource/DataSourceController.cpp', 'src/DataSource/DataSourceItem.cpp', 'src/DataSource/DataSourceItemAction.cpp', diff --git a/core/src/Data/OptionalAxis.cpp b/core/src/Data/OptionalAxis.cpp new file mode 100644 index 0000000..8e25635 --- /dev/null +++ b/core/src/Data/OptionalAxis.cpp @@ -0,0 +1,74 @@ +#include + +#include "Data/ArrayData.h" + +OptionalAxis::OptionalAxis() : m_Defined{false}, m_Data{nullptr}, m_Unit{} +{ +} + +OptionalAxis::OptionalAxis(std::shared_ptr > data, Unit unit) + : m_Defined{true}, m_Data{data}, m_Unit{std::move(unit)} +{ + if (m_Data == nullptr) { + throw std::invalid_argument{"Data can't be null for a defined axis"}; + } +} + +OptionalAxis::OptionalAxis(const OptionalAxis &other) + : m_Defined{other.m_Defined}, + m_Data{other.m_Data ? std::make_shared >(*other.m_Data) : nullptr}, + m_Unit{other.m_Unit} +{ +} + +OptionalAxis &OptionalAxis::operator=(OptionalAxis other) +{ + std::swap(m_Defined, other.m_Defined); + std::swap(m_Data, other.m_Data); + std::swap(m_Unit, other.m_Unit); +} + +bool OptionalAxis::isDefined() const +{ + return m_Defined; +} + +double OptionalAxis::at(int index) const +{ + if (m_Defined) { + return (index >= 0 && index < m_Data->size()) ? m_Data->at(index) + : std::numeric_limits::quiet_NaN(); + } + else { + return std::numeric_limits::quiet_NaN(); + } +} + +int OptionalAxis::size() const +{ + return m_Defined ? m_Data->size() : 0; +} + +Unit OptionalAxis::unit() const +{ + return m_Defined ? m_Unit : Unit{}; +} + +bool OptionalAxis::operator==(const OptionalAxis &other) +{ + // Axis not defined + if (!m_Defined) { + return !other.m_Defined; + } + + // Axis defined + return m_Unit == other.m_Unit + && std::equal( + m_Data->cbegin(), m_Data->cend(), other.m_Data->cbegin(), other.m_Data->cend(), + [](const auto &it1, const auto &it2) { return it1.values() == it2.values(); }); +} + +bool OptionalAxis::operator!=(const OptionalAxis &other) +{ + return !(*this == other); +} diff --git a/core/src/Data/SpectrogramSeries.cpp b/core/src/Data/SpectrogramSeries.cpp new file mode 100644 index 0000000..2d91860 --- /dev/null +++ b/core/src/Data/SpectrogramSeries.cpp @@ -0,0 +1,45 @@ +#include + +SpectrogramSeries::SpectrogramSeries(std::vector xAxisData, std::vector yAxisData, + std::vector valuesData, const Unit &xAxisUnit, + const Unit &yAxisUnit, const Unit &valuesUnit) + : SpectrogramSeries{ + std::make_shared >(std::move(xAxisData)), xAxisUnit, + std::make_shared >(std::move(valuesData), yAxisData.size()), valuesUnit, + OptionalAxis{std::make_shared >(std::move(yAxisData)), yAxisUnit}} +{ +} + +SpectrogramSeries::SpectrogramSeries(std::shared_ptr > xAxisData, + const Unit &xAxisUnit, + std::shared_ptr > valuesData, + const Unit &valuesUnit, OptionalAxis yAxis) + : DataSeries{std::move(xAxisData), xAxisUnit, std::move(valuesData), valuesUnit, + std::move(yAxis)} +{ +} + +std::unique_ptr SpectrogramSeries::clone() const +{ + return std::make_unique(*this); +} + +std::shared_ptr SpectrogramSeries::subDataSeries(const SqpRange &range) +{ + auto subXAxisData = std::vector(); + auto subValuesData = QVector(); // Uses QVector to append easily values to it + this->lockRead(); + auto bounds = xAxisRange(range.m_TStart, range.m_TEnd); + for (auto it = bounds.first; it != bounds.second; ++it) { + subXAxisData.push_back(it->x()); + subValuesData.append(it->values()); + } + + auto yAxis = this->yAxis(); + this->unlock(); + + return std::make_shared( + std::make_shared >(std::move(subXAxisData)), this->xAxisUnit(), + std::make_shared >(subValuesData.toStdVector(), yAxis.size()), + this->valuesUnit(), std::move(yAxis)); +} diff --git a/core/tests/Data/DataSeriesBuilders.cpp b/core/tests/Data/DataSeriesBuilders.cpp new file mode 100644 index 0000000..7ce829e --- /dev/null +++ b/core/tests/Data/DataSeriesBuilders.cpp @@ -0,0 +1,90 @@ +#include "DataSeriesBuilders.h" + +#include +#include +#include +#include + +// ///////////// // +// ScalarBuilder // +// ///////////// // + +ScalarBuilder &ScalarBuilder::setX(std::vector xData) +{ + m_XAxisData = std::move(xData); + return *this; +} + +ScalarBuilder &ScalarBuilder::setValues(std::vector valuesData) +{ + m_ValuesData =std::move(valuesData); + return *this; +} + +std::shared_ptr ScalarBuilder::build() +{ + return std::make_shared(std::move(m_XAxisData), std::move(m_ValuesData), Unit{}, + Unit{}); +} + +// ////////////////// // +// SpectrogramBuilder // +// ////////////////// // + +SpectrogramBuilder &SpectrogramBuilder::setX(std::vector xData) +{ + m_XAxisData = std::move(xData); + return *this; +} + +SpectrogramBuilder &SpectrogramBuilder::setY(std::vector yData) +{ + m_YAxisData =std::move(yData); + return *this; +} + +SpectrogramBuilder &SpectrogramBuilder::setValues(std::vector valuesData) +{ + m_ValuesData =std::move(valuesData); + return *this; +} + +std::shared_ptr SpectrogramBuilder::build() +{ + return std::make_shared(std::move(m_XAxisData), std::move(m_YAxisData), std::move(m_ValuesData), Unit{}, + Unit{}, Unit{}); +} + +// ///////////// // +// VectorBuilder // +// ///////////// // + +VectorBuilder &VectorBuilder::setX(std::vector xData) +{ + m_XAxisData = std::move(xData); + return *this; +} + +VectorBuilder &VectorBuilder::setXValues(std::vector xValuesData) +{ + m_XValuesData =std::move(xValuesData); + return *this; +} + +VectorBuilder &VectorBuilder::setYValues(std::vector yValuesData) +{ + m_YValuesData =std::move(yValuesData); + return *this; +} + +VectorBuilder &VectorBuilder::setZValues(std::vector zValuesData) +{ + m_ZValuesData =std::move(zValuesData); + return *this; +} + +std::shared_ptr VectorBuilder::build() +{ + return std::make_shared(std::move(m_XAxisData), std::move(m_XValuesData), std::move(m_YValuesData), std::move(m_ZValuesData), Unit{}, + Unit{}); +} diff --git a/core/tests/Data/DataSeriesBuilders.h b/core/tests/Data/DataSeriesBuilders.h new file mode 100644 index 0000000..38894f3 --- /dev/null +++ b/core/tests/Data/DataSeriesBuilders.h @@ -0,0 +1,74 @@ +#ifndef SCIQLOP_DATASERIESBUILDERS_H +#define SCIQLOP_DATASERIESBUILDERS_H + +#include +#include + +class ScalarSeries; +class SpectrogramSeries; +class VectorSeries; + +/** + * @brief The ScalarBuilder class aims to facilitate the creation of a ScalarSeries for unit tests + * @sa ScalarSeries + */ +class ScalarBuilder { +public: + /// Sets x-axis data of the series + ScalarBuilder & setX(std::vector xData); + /// Sets values data of the series + ScalarBuilder & setValues(std::vector valuesData); + /// Creates the series + std::shared_ptr build(); + +private: + std::vector m_XAxisData{}; + std::vector m_ValuesData{}; +}; + +/** + * @brief The SpectrogramBuilder class aims to facilitate the creation of a SpectrogramSeries for unit tests + * @sa SpectrogramSeries + */ +class SpectrogramBuilder { +public: + /// Sets x-axis data of the series + SpectrogramBuilder & setX(std::vector xData); + /// Sets y-axis data of the series + SpectrogramBuilder & setY(std::vector yData); + /// Sets values data of the series + SpectrogramBuilder & setValues(std::vector valuesData); + /// Creates the series + std::shared_ptr build(); + +private: + std::vector m_XAxisData{}; + std::vector m_YAxisData{}; + std::vector m_ValuesData{}; +}; + +/** + * @brief The VectorBuilder class aims to facilitate the creation of a VectorSeries for unit tests + * @sa VectorSeries + */ +class VectorBuilder { +public: + /// Sets x-axis data of the series + VectorBuilder & setX(std::vector xData); + /// Sets x-values data of the series + VectorBuilder & setXValues(std::vector xValuesData); + /// Sets y-values data of the series + VectorBuilder & setYValues(std::vector yValuesData); + /// Sets z-values data of the series + VectorBuilder & setZValues(std::vector zValuesData); + /// Creates the series + std::shared_ptr build(); + +private: + std::vector m_XAxisData{}; + std::vector m_XValuesData{}; + std::vector m_YValuesData{}; + std::vector m_ZValuesData{}; +}; + +#endif // SCIQLOP_DATASERIESBUILDERS_H diff --git a/core/tests/Data/DataSeriesUtils.cpp b/core/tests/Data/DataSeriesUtils.cpp new file mode 100644 index 0000000..b4162e8 --- /dev/null +++ b/core/tests/Data/DataSeriesUtils.cpp @@ -0,0 +1,24 @@ +#include "DataSeriesUtils.h" + +void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData, + const DataContainer &valuesData) +{ + QVERIFY(std::equal(first, last, xData.cbegin(), xData.cend(), + [](const auto &it, const auto &expectedX) { return it.x() == expectedX; })); + QVERIFY(std::equal( + first, last, valuesData.cbegin(), valuesData.cend(), + [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; })); +} + +void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData, const std::vector &valuesData) +{ + QVERIFY(std::equal(first, last, xData.cbegin(), xData.cend(), + [](const auto &it, const auto &expectedX) { return it.x() == expectedX; })); + for (auto i = 0; i < valuesData.size(); ++i) { + auto componentData = valuesData.at(i); + + QVERIFY(std::equal( + first, last, componentData.cbegin(), componentData.cend(), + [i](const auto &it, const auto &expectedVal) { return it.value(i) == expectedVal; })); + } +} diff --git a/core/tests/Data/DataSeriesUtils.h b/core/tests/Data/DataSeriesUtils.h new file mode 100644 index 0000000..d12657f --- /dev/null +++ b/core/tests/Data/DataSeriesUtils.h @@ -0,0 +1,373 @@ +/** + * The DataSeriesUtils file contains a set of utility methods that can be used to test the operations on a DataSeries. + * + * Most of these methods are template methods to adapt to any series (scalars, vectors, spectrograms...) + * + * @sa DataSeries + */ +#ifndef SCIQLOP_DATASERIESUTILS_H +#define SCIQLOP_DATASERIESUTILS_H + +#include +#include +#include +#include + +#include +#include + +/// Underlying data in ArrayData +using DataContainer = std::vector; + +Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(std::shared_ptr) + +/** + * Checks that the range of a 1-dim data series contains the expected x-axis data and values data + * @param first the iterator on the beginning of the range to check + * @param last the iterator on the end of the range to check + * @param xData expected x-axis data for the range + * @param valuesData expected values data for the range + */ +void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData, + const DataContainer &valuesData); + +/** + * Checks that the range of a 2-dim data series contains the expected x-axis data and values data + * @param first the iterator on the beginning of the range to check + * @param last the iterator on the end of the range to check + * @param xData expected x-axis data for the range + * @param valuesData expected values data for the range + */ +void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData, + const std::vector &valuesData); + +/** + * Sets the structure of unit tests concerning merge of two data series + * @tparam DataSeriesType the type of data series to merge + * @tparam ExpectedValuesType the type of values expected after merge + * @sa testMerge_t() + */ +template +void testMerge_struct() { + // Data series to merge + QTest::addColumn >("dataSeries"); + QTest::addColumn >("dataSeries2"); + + // Expected values in the first data series after merge + QTest::addColumn("expectedXAxisData"); + QTest::addColumn("expectedValuesData"); +} + +/** + * Unit test concerning merge of two data series + * @sa testMerge_struct() + */ +template +void testMerge_t(){ + // Merges series + QFETCH(std::shared_ptr, dataSeries); + QFETCH(std::shared_ptr, dataSeries2); + + dataSeries->merge(dataSeries2.get()); + + // Validates results : we check that the merge is valid + QFETCH(DataContainer, expectedXAxisData); + QFETCH(ExpectedValuesType, expectedValuesData); + + validateRange(dataSeries->cbegin(), dataSeries->cend(), expectedXAxisData, expectedValuesData); +} + +/** + * Sets the structure of unit tests concerning merge of two data series that are of a different type + * @tparam SourceType the type of data series with which to make the merge + * @tparam DestType the type of data series in which to make the merge + * @sa testMergeDifferentTypes_t() + */ +template +void testMergeDifferentTypes_struct() +{ + // Data series to merge + QTest::addColumn >("dest"); + QTest::addColumn >("source"); + + // Expected values in the dest data series after merge + QTest::addColumn("expectedXAxisData"); + QTest::addColumn("expectedValuesData"); +} + +/** + * Unit test concerning merge of two data series that are of a different type + * @sa testMergeDifferentTypes_struct() + */ +template +void testMergeDifferentTypes_t() +{ + // Merges series + QFETCH(std::shared_ptr, source); + QFETCH(std::shared_ptr, dest); + + dest->merge(source.get()); + + // Validates results : we check that the merge is valid and the data series is sorted on its + // x-axis data + QFETCH(DataContainer, expectedXAxisData); + QFETCH(DataContainer, expectedValuesData); + + validateRange(dest->cbegin(), dest->cend(), expectedXAxisData, expectedValuesData); +} + +/** + * Sets the structure of unit tests concerning getting the min x-axis data of a data series + * @tparam T the type of data series on which to make the operation + * @sa testMinXAxisData_t() + */ +template +void testMinXAxisData_struct(){ + // Data series to get min data + QTest::addColumn >("dataSeries"); + + // Min data + QTest::addColumn("min"); + + // Expected results + QTest::addColumn( + "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator) + QTest::addColumn( + "expectedMin"); // Expected value when method doesn't return end iterator +} + +/** + * Unit test concerning getting the min x-axis data of a data series + * @sa testMinXAxisData_struct() + */ +template +void testMinXAxisData_t() +{ + QFETCH(std::shared_ptr, dataSeries); + QFETCH(double, min); + + QFETCH(bool, expectedOK); + QFETCH(double, expectedMin); + + auto it = dataSeries->minXAxisData(min); + + QCOMPARE(expectedOK, it != dataSeries->cend()); + + // If the method doesn't return a end iterator, checks with expected value + if (expectedOK) { + QCOMPARE(expectedMin, it->x()); + } +} + +/** + * Sets the structure of unit tests concerning getting the max x-axis data of a data series + * @tparam T the type of data series on which to make the operation + * @sa testMaxXAxisData_t() + */ +template +void testMaxXAxisData_struct(){ + // Data series to get max data + QTest::addColumn >("dataSeries"); + + // Max data + QTest::addColumn("max"); + + // Expected results + QTest::addColumn( + "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator) + QTest::addColumn( + "expectedMax"); // Expected value when method doesn't return end iterator + +} + +/** + * Unit test concerning getting the max x-axis data of a data series + * @sa testMaxXAxisData_struct() + */ +template +void testMaxXAxisData_t() +{ + QFETCH(std::shared_ptr, dataSeries); + QFETCH(double, max); + + QFETCH(bool, expectedOK); + QFETCH(double, expectedMax); + + auto it = dataSeries->maxXAxisData(max); + + QCOMPARE(expectedOK, it != dataSeries->cend()); + + // If the method doesn't return a end iterator, checks with expected value + if (expectedOK) { + QCOMPARE(expectedMax, it->x()); + } +} + +/** + * Sets the structure of unit tests concerning getting the purge of a data series + * @tparam T the type of data series on which to make the operation + * @sa testMinXAxisData_t() + */ +template +void testPurge_struct() +{ + // Data series to purge + QTest::addColumn >("dataSeries"); + QTest::addColumn("min"); + QTest::addColumn("max"); + + // Expected values after purge + QTest::addColumn("expectedXAxisData"); + QTest::addColumn >("expectedValuesData"); +} + +/** + * Unit test concerning getting the purge of a data series + * @sa testPurge_struct() + */ +template +void testPurge_t(){ + QFETCH(std::shared_ptr, dataSeries); + QFETCH(double, min); + QFETCH(double, max); + + dataSeries->purge(min, max); + + // Validates results + QFETCH(DataContainer, expectedXAxisData); + QFETCH(std::vector, expectedValuesData); + + validateRange(dataSeries->cbegin(), dataSeries->cend(), expectedXAxisData, + expectedValuesData); +} + +/** + * Sets the structure of unit tests concerning getting subdata of a data series + * @tparam DataSeriesType the type of data series on which to make the operation + * @tparam ExpectedValuesType the type of values expected after the operation + * @sa testSubDataSeries_t() + */ +template +void testSubDataSeries_struct() { + // Data series from which extract the subdata series + QTest::addColumn >("dataSeries"); + // Range to extract + QTest::addColumn("range"); + + // Expected values for the subdata series + QTest::addColumn("expectedXAxisData"); + QTest::addColumn("expectedValuesData"); +} + +/** + * Unit test concerning getting subdata of a data series + * @sa testSubDataSeries_struct() + */ +template +void testSubDataSeries_t(){ + QFETCH(std::shared_ptr, dataSeries); + QFETCH(SqpRange, range); + + // Makes the operation + auto subDataSeries = std::dynamic_pointer_cast(dataSeries->subDataSeries(range)); + QVERIFY(subDataSeries != nullptr); + + // Validates results + QFETCH(DataContainer, expectedXAxisData); + QFETCH(ExpectedValuesType, expectedValuesData); + + validateRange(subDataSeries->cbegin(), subDataSeries->cend(), expectedXAxisData, expectedValuesData); +} + +/** + * Sets the structure of unit tests concerning getting the range of a data series + * @tparam T the type of data series on which to make the operation + * @sa testXAxisRange_t() + */ +template +void testXAxisRange_struct(){ + // Data series to get x-axis range + QTest::addColumn >("dataSeries"); + + // Min/max values + QTest::addColumn("min"); + QTest::addColumn("max"); + + // Expected values + QTest::addColumn("expectedXAxisData"); + QTest::addColumn("expectedValuesData"); +} + +/** + * Unit test concerning getting the range of a data series + * @sa testXAxisRange_struct() + */ +template +void testXAxisRange_t(){ + QFETCH(std::shared_ptr, dataSeries); + QFETCH(double, min); + QFETCH(double, max); + + QFETCH(DataContainer, expectedXAxisData); + QFETCH(DataContainer, expectedValuesData); + + auto bounds = dataSeries->xAxisRange(min, max); + validateRange(bounds.first, bounds.second, expectedXAxisData, expectedValuesData); +} + +/** + * Sets the structure of unit tests concerning getting values bounds of a data series + * @tparam T the type of data series on which to make the operation + * @sa testValuesBounds_t() + */ +template +void testValuesBounds_struct() +{ + // Data series to get values bounds + QTest::addColumn >("dataSeries"); + + // x-axis range + QTest::addColumn("minXAxis"); + QTest::addColumn("maxXAxis"); + + // Expected results + QTest::addColumn( + "expectedOK"); // Test is expected to be ok (i.e. method doesn't return end iterators) + QTest::addColumn("expectedMinValue"); + QTest::addColumn("expectedMaxValue"); +} + +/** + * Unit test concerning getting values bounds of a data series + * @sa testValuesBounds_struct() + */ +template +void testValuesBounds_t() +{ + QFETCH(std::shared_ptr, dataSeries); + QFETCH(double, minXAxis); + QFETCH(double, maxXAxis); + + QFETCH(bool, expectedOK); + QFETCH(double, expectedMinValue); + QFETCH(double, expectedMaxValue); + + auto minMaxIts = dataSeries->valuesBounds(minXAxis, maxXAxis); + auto end = dataSeries->cend(); + + // Checks iterators with expected result + QCOMPARE(expectedOK, minMaxIts.first != end && minMaxIts.second != end); + + if (expectedOK) { + auto compare = [](const auto &v1, const auto &v2) { + return (std::isnan(v1) && std::isnan(v2)) || v1 == v2; + }; + + QVERIFY(compare(expectedMinValue, minMaxIts.first->minValue())); + QVERIFY(compare(expectedMaxValue, minMaxIts.second->maxValue())); + } +} + +#endif // SCIQLOP_DATASERIESUTILS_H diff --git a/core/tests/Data/TestDataSeries.cpp b/core/tests/Data/TestDataSeries.cpp deleted file mode 100644 index 15a9f10..0000000 --- a/core/tests/Data/TestDataSeries.cpp +++ /dev/null @@ -1,707 +0,0 @@ -#include "Data/DataSeries.h" -#include "Data/ScalarSeries.h" -#include "Data/VectorSeries.h" - -#include - -#include -#include - -Q_DECLARE_METATYPE(std::shared_ptr) -Q_DECLARE_METATYPE(std::shared_ptr) - -namespace { - -using DataContainer = std::vector; - -void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData, - const DataContainer &valuesData) -{ - QVERIFY(std::equal(first, last, xData.cbegin(), xData.cend(), - [](const auto &it, const auto &expectedX) { return it.x() == expectedX; })); - QVERIFY(std::equal( - first, last, valuesData.cbegin(), valuesData.cend(), - [](const auto &it, const auto &expectedVal) { return it.value() == expectedVal; })); -} - -void validateRange(DataSeriesIterator first, DataSeriesIterator last, const DataContainer &xData, - const std::vector &valuesData) -{ - QVERIFY(std::equal(first, last, xData.cbegin(), xData.cend(), - [](const auto &it, const auto &expectedX) { return it.x() == expectedX; })); - for (auto i = 0; i < valuesData.size(); ++i) { - auto componentData = valuesData.at(i); - - QVERIFY(std::equal( - first, last, componentData.cbegin(), componentData.cend(), - [i](const auto &it, const auto &expectedVal) { return it.value(i) == expectedVal; })); - } -} - -} // namespace - -class TestDataSeries : public QObject { - Q_OBJECT -private: - template - void testValuesBoundsStructure() - { - // ////////////// // - // Test structure // - // ////////////// // - - // Data series to get values bounds - QTest::addColumn >("dataSeries"); - - // x-axis range - QTest::addColumn("minXAxis"); - QTest::addColumn("maxXAxis"); - - // Expected results - QTest::addColumn( - "expectedOK"); // Test is expected to be ok (i.e. method doesn't return end iterators) - QTest::addColumn("expectedMinValue"); - QTest::addColumn("expectedMaxValue"); - } - - template - void testValuesBounds() - { - QFETCH(std::shared_ptr, dataSeries); - QFETCH(double, minXAxis); - QFETCH(double, maxXAxis); - - QFETCH(bool, expectedOK); - QFETCH(double, expectedMinValue); - QFETCH(double, expectedMaxValue); - - auto minMaxIts = dataSeries->valuesBounds(minXAxis, maxXAxis); - auto end = dataSeries->cend(); - - // Checks iterators with expected result - QCOMPARE(expectedOK, minMaxIts.first != end && minMaxIts.second != end); - - if (expectedOK) { - auto compare = [](const auto &v1, const auto &v2) { - return (std::isnan(v1) && std::isnan(v2)) || v1 == v2; - }; - - QVERIFY(compare(expectedMinValue, minMaxIts.first->minValue())); - QVERIFY(compare(expectedMaxValue, minMaxIts.second->maxValue())); - } - } - - template - void testPurgeStructure() - { - // ////////////// // - // Test structure // - // ////////////// // - - // Data series to purge - QTest::addColumn >("dataSeries"); - QTest::addColumn("min"); - QTest::addColumn("max"); - - // Expected values after purge - QTest::addColumn("expectedXAxisData"); - QTest::addColumn >("expectedValuesData"); - } - - template - void testPurge() - { - QFETCH(std::shared_ptr, dataSeries); - QFETCH(double, min); - QFETCH(double, max); - - dataSeries->purge(min, max); - - // Validates results - QFETCH(DataContainer, expectedXAxisData); - QFETCH(std::vector, expectedValuesData); - - validateRange(dataSeries->cbegin(), dataSeries->cend(), expectedXAxisData, - expectedValuesData); - } - - template - void testMergeDifferentTypesStructure() - { - // ////////////// // - // Test structure // - // ////////////// // - - // Data series to merge - QTest::addColumn >("dest"); - QTest::addColumn >("source"); - - // Expected values in the dest data series after merge - QTest::addColumn("expectedXAxisData"); - QTest::addColumn("expectedValuesData"); - } - - template - void testMergeDifferentTypes() - { - // Merges series - QFETCH(std::shared_ptr, source); - QFETCH(std::shared_ptr, dest); - - dest->merge(source.get()); - - // Validates results : we check that the merge is valid and the data series is sorted on its - // x-axis data - QFETCH(DataContainer, expectedXAxisData); - QFETCH(DataContainer, expectedValuesData); - - validateRange(dest->cbegin(), dest->cend(), expectedXAxisData, expectedValuesData); - } - -private slots: - - /// Input test data - /// @sa testCtor() - void testCtor_data(); - - /// Tests construction of a data series - void testCtor(); - - /// Input test data - /// @sa testMerge() - void testMerge_data(); - - /// Tests merge of two data series - void testMerge(); - - /// Input test data - /// @sa testMergeVectorInScalar() - void testMergeVectorInScalar_data(); - - /// Tests merge of vector series in scalar series - void testMergeVectorInScalar(); - - /// Input test data - /// @sa testPurgeScalar() - void testPurgeScalar_data(); - - /// Tests purge of a scalar series - void testPurgeScalar(); - - /// Input test data - /// @sa testPurgeVector() - void testPurgeVector_data(); - - /// Tests purge of a vector series - void testPurgeVector(); - - /// Input test data - /// @sa testMinXAxisData() - void testMinXAxisData_data(); - - /// Tests get min x-axis data of a data series - void testMinXAxisData(); - - /// Input test data - /// @sa testMaxXAxisData() - void testMaxXAxisData_data(); - - /// Tests get max x-axis data of a data series - void testMaxXAxisData(); - - /// Input test data - /// @sa testXAxisRange() - void testXAxisRange_data(); - - /// Tests get x-axis range of a data series - void testXAxisRange(); - - /// Input test data - /// @sa testValuesBoundsScalar() - void testValuesBoundsScalar_data(); - - /// Tests get values bounds of a scalar series - void testValuesBoundsScalar(); - - /// Input test data - /// @sa testValuesBoundsVector() - void testValuesBoundsVector_data(); - - /// Tests get values bounds of a vector series - void testValuesBoundsVector(); -}; - -void TestDataSeries::testCtor_data() -{ - // ////////////// // - // Test structure // - // ////////////// // - - // x-axis data - QTest::addColumn("xAxisData"); - // values data - QTest::addColumn("valuesData"); - - // expected x-axis data - QTest::addColumn("expectedXAxisData"); - // expected values data - QTest::addColumn("expectedValuesData"); - - // ////////// // - // Test cases // - // ////////// // - - QTest::newRow("invalidData (different sizes of vectors)") - << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 200., 300.} << DataContainer{} - << DataContainer{}; - - QTest::newRow("sortedData") << DataContainer{1., 2., 3., 4., 5.} - << DataContainer{100., 200., 300., 400., 500.} - << DataContainer{1., 2., 3., 4., 5.} - << DataContainer{100., 200., 300., 400., 500.}; - - QTest::newRow("unsortedData") << DataContainer{5., 4., 3., 2., 1.} - << DataContainer{100., 200., 300., 400., 500.} - << DataContainer{1., 2., 3., 4., 5.} - << DataContainer{500., 400., 300., 200., 100.}; - - QTest::newRow("unsortedData2") - << DataContainer{1., 4., 3., 5., 2.} << DataContainer{100., 200., 300., 400., 500.} - << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 500., 300., 200., 400.}; -} - -void TestDataSeries::testCtor() -{ - // Creates series - QFETCH(DataContainer, xAxisData); - QFETCH(DataContainer, valuesData); - - auto series = std::make_shared(std::move(xAxisData), std::move(valuesData), - Unit{}, Unit{}); - - // Validates results : we check that the data series is sorted on its x-axis data - QFETCH(DataContainer, expectedXAxisData); - QFETCH(DataContainer, expectedValuesData); - - validateRange(series->cbegin(), series->cend(), expectedXAxisData, expectedValuesData); -} - -namespace { - -std::shared_ptr createScalarSeries(DataContainer xAxisData, DataContainer valuesData) -{ - return std::make_shared(std::move(xAxisData), std::move(valuesData), Unit{}, - Unit{}); -} - -std::shared_ptr createVectorSeries(DataContainer xAxisData, DataContainer xValuesData, - DataContainer yValuesData, - DataContainer zValuesData) -{ - return std::make_shared(std::move(xAxisData), std::move(xValuesData), - std::move(yValuesData), std::move(zValuesData), Unit{}, - Unit{}); -} - -} // namespace - -void TestDataSeries::testMerge_data() -{ - // ////////////// // - // Test structure // - // ////////////// // - - // Data series to merge - QTest::addColumn >("dataSeries"); - QTest::addColumn >("dataSeries2"); - - // Expected values in the first data series after merge - QTest::addColumn("expectedXAxisData"); - QTest::addColumn("expectedValuesData"); - - // ////////// // - // Test cases // - // ////////// // - - QTest::newRow("sortedMerge") - << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.}) - << DataContainer{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} - << DataContainer{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.}; - - QTest::newRow("unsortedMerge") - << createScalarSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.}) - << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << DataContainer{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} - << DataContainer{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.}; - - QTest::newRow("unsortedMerge2 (merge not made because source is in the bounds of dest)") - << createScalarSeries({1., 2., 8., 9., 10}, {100., 200., 800., 900., 1000.}) - << createScalarSeries({3., 4., 5., 6., 7.}, {300., 400., 500., 600., 700.}) - << DataContainer{1., 2., 8., 9., 10.} << DataContainer{100., 200., 800., 900., 1000.}; - - QTest::newRow("unsortedMerge3") - << createScalarSeries({3., 4., 5., 7., 8}, {300., 400., 500., 700., 800.}) - << createScalarSeries({1., 2., 3., 7., 10.}, {100., 200., 333., 777., 1000.}) - << DataContainer{1., 2., 3., 4., 5., 7., 8., 10.} - << DataContainer{100., 200., 300., 400., 500., 700., 800., 1000.}; - - QTest::newRow("emptySource") << createScalarSeries({3., 4., 5., 7., 8}, - {300., 400., 500., 700., 800.}) - << createScalarSeries({}, {}) << DataContainer{3., 4., 5., 7., 8.} - << DataContainer{300., 400., 500., 700., 800.}; -} - -void TestDataSeries::testMerge() -{ - // Merges series - QFETCH(std::shared_ptr, dataSeries); - QFETCH(std::shared_ptr, dataSeries2); - - dataSeries->merge(dataSeries2.get()); - - // Validates results : we check that the merge is valid and the data series is sorted on its - // x-axis data - QFETCH(DataContainer, expectedXAxisData); - QFETCH(DataContainer, expectedValuesData); - - validateRange(dataSeries->cbegin(), dataSeries->cend(), expectedXAxisData, expectedValuesData); -} - -void TestDataSeries::testMergeVectorInScalar_data() -{ - testMergeDifferentTypesStructure(); - - // ////////// // - // Test cases // - // ////////// // - - QTest::newRow("purgeVectorInScalar") - << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) - << createVectorSeries({6., 7., 8., 9., 10.}, {600., 700., 800., 900., 1000.}, - {610., 710., 810., 910., 1010.}, {620., 720., 820., 920., 1020.}) - << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 200., 300., 400., 500.}; -} - -void TestDataSeries::testMergeVectorInScalar() -{ - testMergeDifferentTypes(); -} - -void TestDataSeries::testPurgeScalar_data() -{ - testPurgeStructure(); - - // ////////// // - // Test cases // - // ////////// // - - QTest::newRow("purgeScalar") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 2. << 4. << DataContainer{2., 3., 4.} - << std::vector{{200., 300., 400.}}; - QTest::newRow("purgeScalar1 (min/max swap)") - << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 4. << 2. - << DataContainer{2., 3., 4.} << std::vector{{200., 300., 400.}}; - QTest::newRow("purgeScalar2") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 0. << 2.5 << DataContainer{1., 2.} - << std::vector{{100., 200.}}; - QTest::newRow("purgeScalar3") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 3.5 << 7. << DataContainer{4., 5.} - << std::vector{{400., 500.}}; - QTest::newRow("purgeScalar4") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 0. << 7. << DataContainer{1., 2., 3., 4., 5.} - << std::vector{{100., 200., 300., 400., 500.}}; - QTest::newRow("purgeScalar5") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 5.5 << 7. << DataContainer{} << std::vector{{}}; -} - -void TestDataSeries::testPurgeScalar() -{ - testPurge(); -} - -void TestDataSeries::testPurgeVector_data() -{ - testPurgeStructure(); - - // ////////// // - // Test cases // - // ////////// // - - QTest::newRow("purgeVector") << createVectorSeries({1., 2., 3., 4., 5.}, {6., 7., 8., 9., 10.}, - {11., 12., 13., 14., 15.}, - {16., 17., 18., 19., 20.}) - << 2. << 4. << DataContainer{2., 3., 4.} - << std::vector{ - {7., 8., 9.}, {12., 13., 14.}, {17., 18., 19.}}; -} - -void TestDataSeries::testPurgeVector() -{ - testPurge(); -} - -void TestDataSeries::testMinXAxisData_data() -{ - // ////////////// // - // Test structure // - // ////////////// // - - // Data series to get min data - QTest::addColumn >("dataSeries"); - - // Min data - QTest::addColumn("min"); - - // Expected results - QTest::addColumn( - "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator) - QTest::addColumn( - "expectedMin"); // Expected value when method doesn't return end iterator - - // ////////// // - // Test cases // - // ////////// // - - QTest::newRow("minData1") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 0. << true << 1.; - QTest::newRow("minData2") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 1. << true << 1.; - QTest::newRow("minData3") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 1.1 << true << 2.; - QTest::newRow("minData4") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 5. << true << 5.; - QTest::newRow("minData5") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 5.1 << false << std::numeric_limits::quiet_NaN(); - QTest::newRow("minData6") << createScalarSeries({}, {}) << 1.1 << false - << std::numeric_limits::quiet_NaN(); -} - -void TestDataSeries::testMinXAxisData() -{ - QFETCH(std::shared_ptr, dataSeries); - QFETCH(double, min); - - QFETCH(bool, expectedOK); - QFETCH(double, expectedMin); - - auto it = dataSeries->minXAxisData(min); - - QCOMPARE(expectedOK, it != dataSeries->cend()); - - // If the method doesn't return a end iterator, checks with expected value - if (expectedOK) { - QCOMPARE(expectedMin, it->x()); - } -} - -void TestDataSeries::testMaxXAxisData_data() -{ - // ////////////// // - // Test structure // - // ////////////// // - - // Data series to get max data - QTest::addColumn >("dataSeries"); - - // Max data - QTest::addColumn("max"); - - // Expected results - QTest::addColumn( - "expectedOK"); // if true, expects to have a result (i.e. the iterator != end iterator) - QTest::addColumn( - "expectedMax"); // Expected value when method doesn't return end iterator - - // ////////// // - // Test cases // - // ////////// // - - QTest::newRow("maxData1") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 6. << true << 5.; - QTest::newRow("maxData2") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 5. << true << 5.; - QTest::newRow("maxData3") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 4.9 << true << 4.; - QTest::newRow("maxData4") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 1.1 << true << 1.; - QTest::newRow("maxData5") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 1. << true << 1.; - QTest::newRow("maxData6") << createScalarSeries({}, {}) << 1.1 << false - << std::numeric_limits::quiet_NaN(); -} - -void TestDataSeries::testMaxXAxisData() -{ - QFETCH(std::shared_ptr, dataSeries); - QFETCH(double, max); - - QFETCH(bool, expectedOK); - QFETCH(double, expectedMax); - - auto it = dataSeries->maxXAxisData(max); - - QCOMPARE(expectedOK, it != dataSeries->cend()); - - // If the method doesn't return a end iterator, checks with expected value - if (expectedOK) { - QCOMPARE(expectedMax, it->x()); - } -} - -void TestDataSeries::testXAxisRange_data() -{ - // ////////////// // - // Test structure // - // ////////////// // - - // Data series to get x-axis range - QTest::addColumn >("dataSeries"); - - // Min/max values - QTest::addColumn("min"); - QTest::addColumn("max"); - - // Expected values - QTest::addColumn("expectedXAxisData"); - QTest::addColumn("expectedValuesData"); - - // ////////// // - // Test cases // - // ////////// // - - QTest::newRow("xAxisRange") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << -1. << 3.2 << DataContainer{1., 2., 3.} - << DataContainer{100., 200., 300.}; - QTest::newRow("xAxisRange1 (min/max swap)") - << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 3.2 << -1. - << DataContainer{1., 2., 3.} << DataContainer{100., 200., 300.}; - QTest::newRow("xAxisRange2") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 1. << 4. << DataContainer{1., 2., 3., 4.} - << DataContainer{100., 200., 300., 400.}; - QTest::newRow("xAxisRange3") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 1. << 3.9 << DataContainer{1., 2., 3.} - << DataContainer{100., 200., 300.}; - QTest::newRow("xAxisRange4") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 0. << 0.9 << DataContainer{} << DataContainer{}; - QTest::newRow("xAxisRange5") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 0. << 1. << DataContainer{1.} << DataContainer{100.}; - QTest::newRow("xAxisRange6") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 2.1 << 6. << DataContainer{3., 4., 5.} - << DataContainer{300., 400., 500.}; - QTest::newRow("xAxisRange7") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 6. << 9. << DataContainer{} << DataContainer{}; - QTest::newRow("xAxisRange8") << createScalarSeries({1., 2., 3., 4., 5.}, - {100., 200., 300., 400., 500.}) - << 5. << 9. << DataContainer{5.} << DataContainer{500.}; -} - -void TestDataSeries::testXAxisRange() -{ - QFETCH(std::shared_ptr, dataSeries); - QFETCH(double, min); - QFETCH(double, max); - - QFETCH(DataContainer, expectedXAxisData); - QFETCH(DataContainer, expectedValuesData); - - auto bounds = dataSeries->xAxisRange(min, max); - validateRange(bounds.first, bounds.second, expectedXAxisData, expectedValuesData); -} - -void TestDataSeries::testValuesBoundsScalar_data() -{ - testValuesBoundsStructure(); - - // ////////// // - // Test cases // - // ////////// // - auto nan = std::numeric_limits::quiet_NaN(); - - QTest::newRow("scalarBounds1") - << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 6. - << true << 100. << 500.; - QTest::newRow("scalarBounds2") - << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 2. << 4. - << true << 200. << 400.; - QTest::newRow("scalarBounds3") - << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 0. << 0.5 - << false << nan << nan; - QTest::newRow("scalarBounds4") - << createScalarSeries({1., 2., 3., 4., 5.}, {100., 200., 300., 400., 500.}) << 5.1 << 6. - << false << nan << nan; - QTest::newRow("scalarBounds5") << createScalarSeries({1.}, {100.}) << 0. << 2. << true << 100. - << 100.; - QTest::newRow("scalarBounds6") << createScalarSeries({}, {}) << 0. << 2. << false << nan << nan; - - // Tests with NaN values: NaN values are not included in min/max search - QTest::newRow("scalarBounds7") - << createScalarSeries({1., 2., 3., 4., 5.}, {nan, 200., 300., 400., nan}) << 0. << 6. - << true << 200. << 400.; - QTest::newRow("scalarBounds8") - << createScalarSeries({1., 2., 3., 4., 5.}, {nan, nan, nan, nan, nan}) << 0. << 6. << true - << std::numeric_limits::quiet_NaN() << std::numeric_limits::quiet_NaN(); -} - -void TestDataSeries::testValuesBoundsScalar() -{ - testValuesBounds(); -} - -void TestDataSeries::testValuesBoundsVector_data() -{ - testValuesBoundsStructure(); - - // ////////// // - // Test cases // - // ////////// // - auto nan = std::numeric_limits::quiet_NaN(); - - QTest::newRow("vectorBounds1") - << createVectorSeries({1., 2., 3., 4., 5.}, {10., 15., 20., 13., 12.}, - {35., 24., 10., 9., 0.3}, {13., 14., 12., 9., 24.}) - << 0. << 6. << true << 0.3 << 35.; // min/max in same component - QTest::newRow("vectorBounds2") - << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.}, - {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.}) - << 0. << 6. << true << 2.3 << 35.; // min/max in same entry - QTest::newRow("vectorBounds3") - << createVectorSeries({1., 2., 3., 4., 5.}, {2.3, 15., 20., 13., 12.}, - {35., 24., 10., 9., 4.}, {13., 14., 12., 9., 24.}) - << 2. << 3. << true << 10. << 24.; - - // Tests with NaN values: NaN values are not included in min/max search - QTest::newRow("vectorBounds4") - << createVectorSeries({1., 2.}, {nan, nan}, {nan, nan}, {nan, nan}) << 0. << 6. << true - << nan << nan; -} - -void TestDataSeries::testValuesBoundsVector() -{ - testValuesBounds(); -} - -QTEST_MAIN(TestDataSeries) -#include "TestDataSeries.moc" diff --git a/core/tests/Data/TestOptionalAxis.cpp b/core/tests/Data/TestOptionalAxis.cpp new file mode 100644 index 0000000..ad4959b --- /dev/null +++ b/core/tests/Data/TestOptionalAxis.cpp @@ -0,0 +1,151 @@ +#include +#include + +#include +#include + +Q_DECLARE_METATYPE(OptionalAxis) +Q_DECLARE_METATYPE(Unit) + +class TestOptionalAxis : public QObject { + Q_OBJECT + +private slots: + /// Tests the creation of a undefined axis + void testNotDefinedAxisCtor(); + + /// Tests the creation of a undefined axis + void testDefinedAxisCtor_data(); + void testDefinedAxisCtor(); + + /// Tests @sa OptionalAxis::at() method + void testAt_data(); + void testAt(); + + /// Tests @sa OptionalAxis::size() method + void testSize_data(); + void testSize(); + + /// Tests @sa OptionalAxis::unit() method + void testUnit_data(); + void testUnit(); +}; + +void TestOptionalAxis::testNotDefinedAxisCtor() +{ + OptionalAxis notDefinedAxis{}; + QVERIFY(!notDefinedAxis.isDefined()); +} + +void TestOptionalAxis::testDefinedAxisCtor_data() +{ + QTest::addColumn("noData"); // If set to true, nullptr is passed as data of the axis + QTest::addColumn >( + "data"); // Values assigned to the axis when 'noData' flag is set to false + QTest::addColumn("unit"); // Unit assigned to the axis + + QTest::newRow("validData") << false << std::vector{1, 2, 3} << Unit{"Hz"}; + QTest::newRow("invalidData") << true << std::vector{} << Unit{"Hz"}; +} + +void TestOptionalAxis::testDefinedAxisCtor() +{ + QFETCH(bool, noData); + QFETCH(Unit, unit); + + // When there is no data, we expect that the constructor returns exception + if (noData) { + QVERIFY_EXCEPTION_THROWN(OptionalAxis(nullptr, unit), std::invalid_argument); + } + else { + QFETCH(std::vector, data); + + OptionalAxis axis{std::make_shared >(data), unit}; + QVERIFY(axis.isDefined()); + } +} + +void TestOptionalAxis::testAt_data() +{ + QTest::addColumn("axis"); // Axis used for test case (defined or not) + QTest::addColumn("index"); // Index to test in the axis + QTest::addColumn("expectedValue"); // Expected axis value for the index + + OptionalAxis definedAxis{std::make_shared >(std::vector{1, 2, 3}), + Unit{"Hz"}}; + + QTest::newRow("data1") << definedAxis << 0 << 1.; + QTest::newRow("data2") << definedAxis << 1 << 2.; + QTest::newRow("data3") << definedAxis << 2 << 3.; + QTest::newRow("data4 (index out of bounds)") + << definedAxis << 3 + << std::numeric_limits::quiet_NaN(); // Expects NaN for out of bounds index + QTest::newRow("data5 (index out of bounds)") + << definedAxis << -1 + << std::numeric_limits::quiet_NaN(); // Expects NaN for out of bounds index + QTest::newRow("data6 (axis not defined)") + << OptionalAxis{} << 0 + << std::numeric_limits::quiet_NaN(); // Expects NaN for undefined axis +} + +void TestOptionalAxis::testAt() +{ + QFETCH(OptionalAxis, axis); + QFETCH(int, index); + QFETCH(double, expectedValue); + + auto value = axis.at(index); + QVERIFY((std::isnan(value) && std::isnan(expectedValue)) || value == expectedValue); +} + +void TestOptionalAxis::testSize_data() +{ + QTest::addColumn("axis"); // Axis used for test case (defined or not) + QTest::addColumn("expectedSize"); // Expected number of data in the axis + + // Lambda that creates default defined axis (with the values passed in parameter) + auto axis = [](std::vector values) { + return OptionalAxis{std::make_shared >(std::move(values)), Unit{"Hz"}}; + }; + + QTest::newRow("data1") << axis({}) << 0; + QTest::newRow("data2") << axis({1, 2, 3}) << 3; + QTest::newRow("data3") << axis({1, 2, 3, 4}) << 4; + QTest::newRow("data4 (axis not defined)") + << OptionalAxis{} << 0; // Expects 0 for undefined axis +} + +void TestOptionalAxis::testSize() +{ + QFETCH(OptionalAxis, axis); + QFETCH(int, expectedSize); + + QCOMPARE(axis.size(), expectedSize); +} + +void TestOptionalAxis::testUnit_data() +{ + QTest::addColumn("axis"); // Axis used for test case (defined or not) + QTest::addColumn("expectedUnit"); // Expected unit for the axis + + // Lambda that creates default defined axis (with the unit passed in parameter) + auto axis = [](Unit unit) { + return OptionalAxis{std::make_shared >(std::vector{1, 2, 3}), unit}; + }; + + QTest::newRow("data1") << axis(Unit{"Hz"}) << Unit{"Hz"}; + QTest::newRow("data2") << axis(Unit{"t", true}) << Unit{"t", true}; + QTest::newRow("data3 (axis not defined)") + << OptionalAxis{} << Unit{}; // Expects default unit for undefined axis +} + +void TestOptionalAxis::testUnit() +{ + QFETCH(OptionalAxis, axis); + QFETCH(Unit, expectedUnit); + + QCOMPARE(axis.unit(), expectedUnit); +} + +QTEST_MAIN(TestOptionalAxis) +#include "TestOptionalAxis.moc" diff --git a/core/tests/Data/TestScalarSeries.cpp b/core/tests/Data/TestScalarSeries.cpp new file mode 100644 index 0000000..75b1bb6 --- /dev/null +++ b/core/tests/Data/TestScalarSeries.cpp @@ -0,0 +1,426 @@ +#include "Data/ScalarSeries.h" + +#include "DataSeriesBuilders.h" +#include "DataSeriesUtils.h" + +#include +#include + +/** + * @brief The TestScalarSeries class defines unit tests on scalar series. + * + * Most of these unit tests use generic tests defined for DataSeries (@sa DataSeriesUtils) + */ +class TestScalarSeries : public QObject { + Q_OBJECT +private slots: + /// Tests construction of a scalar series + void testCtor_data(); + void testCtor(); + + /// Tests merge of two scalar series + void testMerge_data(); + void testMerge(); + + /// Tests merge of a vector series in a scalar series + void testMergeWithVector_data(); + void testMergeWithVector(); + + /// Tests get min x-axis data of a scalar series + void testMinXAxisData_data(); + void testMinXAxisData(); + + /// Tests get max x-axis data of a scalar series + void testMaxXAxisData_data(); + void testMaxXAxisData(); + + /// Tests purge of a scalar series + void testPurge_data(); + void testPurge(); + + /// Tests get x-axis range of a scalar series + void testXAxisRange_data(); + void testXAxisRange(); + + /// Tests get values bounds of a scalar series + void testValuesBounds_data(); + void testValuesBounds(); +}; + +void TestScalarSeries::testCtor_data() +{ + // x-axis data + QTest::addColumn("xAxisData"); + // values data + QTest::addColumn("valuesData"); + + // construction expected to be valid + QTest::addColumn("expectOK"); + // expected x-axis data (when construction is valid) + QTest::addColumn("expectedXAxisData"); + // expected values data (when construction is valid) + QTest::addColumn("expectedValuesData"); + + QTest::newRow("invalidData (different sizes of vectors)") + << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 200., 300.} << false + << DataContainer{} << DataContainer{}; + + QTest::newRow("sortedData") << DataContainer{1., 2., 3., 4., 5.} + << DataContainer{100., 200., 300., 400., 500.} << true + << DataContainer{1., 2., 3., 4., 5.} + << DataContainer{100., 200., 300., 400., 500.}; + + QTest::newRow("unsortedData") << DataContainer{5., 4., 3., 2., 1.} + << DataContainer{100., 200., 300., 400., 500.} << true + << DataContainer{1., 2., 3., 4., 5.} + << DataContainer{500., 400., 300., 200., 100.}; + + QTest::newRow("unsortedData2") + << DataContainer{1., 4., 3., 5., 2.} << DataContainer{100., 200., 300., 400., 500.} << true + << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 500., 300., 200., 400.}; +} + +void TestScalarSeries::testCtor() +{ + // Creates series + QFETCH(DataContainer, xAxisData); + QFETCH(DataContainer, valuesData); + QFETCH(bool, expectOK); + + if (expectOK) { + auto series = std::make_shared(std::move(xAxisData), std::move(valuesData), + Unit{}, Unit{}); + + // Validates results : we check that the data series is sorted on its x-axis data + QFETCH(DataContainer, expectedXAxisData); + QFETCH(DataContainer, expectedValuesData); + + validateRange(series->cbegin(), series->cend(), expectedXAxisData, expectedValuesData); + } + else { + QVERIFY_EXCEPTION_THROWN(std::make_shared( + std::move(xAxisData), std::move(valuesData), Unit{}, Unit{}), + std::invalid_argument); + } +} + +void TestScalarSeries::testMerge_data() +{ + testMerge_struct(); + + QTest::newRow("sortedMerge") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << ScalarBuilder{} + .setX({6., 7., 8., 9., 10.}) + .setValues({600., 700., 800., 900., 1000.}) + .build() + << DataContainer{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} + << DataContainer{100., 200., 300., 400., 500., + 600., 700., 800., 900., 1000.}; + + QTest::newRow("unsortedMerge") + << ScalarBuilder{} + .setX({6., 7., 8., 9., 10.}) + .setValues({600., 700., 800., 900., 1000.}) + .build() + << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << DataContainer{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} + << DataContainer{100., 200., 300., 400., 500., 600., 700., 800., 900., 1000.}; + + QTest::newRow("unsortedMerge2 (merge not made because source is in the bounds of dest)") + << ScalarBuilder{} + .setX({1., 2., 8., 9., 10}) + .setValues({100., 200., 800., 900., 1000.}) + .build() + << ScalarBuilder{} + .setX({3., 4., 5., 6., 7.}) + .setValues({300., 400., 500., 600., 700.}) + .build() + << DataContainer{1., 2., 8., 9., 10.} << DataContainer{100., 200., 800., 900., 1000.}; + + QTest::newRow("unsortedMerge3") + << ScalarBuilder{} + .setX({3., 4., 5., 7., 8}) + .setValues({300., 400., 500., 700., 800.}) + .build() + << ScalarBuilder{} + .setX({1., 2., 3., 7., 10.}) + .setValues({100., 200., 333., 777., 1000.}) + .build() + << DataContainer{1., 2., 3., 4., 5., 7., 8., 10.} + << DataContainer{100., 200., 300., 400., 500., 700., 800., 1000.}; + + QTest::newRow("emptySource") << ScalarBuilder{} + .setX({3., 4., 5., 7., 8}) + .setValues({300., 400., 500., 700., 800.}) + .build() + << ScalarBuilder{}.setX({}).setValues({}).build() + << DataContainer{3., 4., 5., 7., 8.} + << DataContainer{300., 400., 500., 700., 800.}; +} + +void TestScalarSeries::testMerge() +{ + testMerge_t(); +} + +void TestScalarSeries::testMergeWithVector_data() +{ + testMergeDifferentTypes_struct(); + + QTest::newRow("mergeVectorInScalar") + << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << VectorBuilder{} + .setX({6., 7., 8., 9., 10.}) + .setXValues({600., 700., 800., 900., 1000.}) + .setYValues({610., 710., 810., 910., 1010.}) + .setZValues({620., 720., 820., 920., 1020.}) + .build() + << DataContainer{1., 2., 3., 4., 5.} << DataContainer{100., 200., 300., 400., 500.}; +} + +void TestScalarSeries::testMergeWithVector() +{ + testMergeDifferentTypes_t(); +} + +void TestScalarSeries::testMinXAxisData_data() +{ + testMinXAxisData_struct(); + + QTest::newRow("minData1") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 0. << true << 1.; + QTest::newRow("minData2") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 1. << true << 1.; + QTest::newRow("minData3") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 1.1 << true << 2.; + QTest::newRow("minData4") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 5. << true << 5.; + QTest::newRow("minData5") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 5.1 << false << std::numeric_limits::quiet_NaN(); + QTest::newRow("minData6") << ScalarBuilder{}.setX({}).setValues({}).build() << 1.1 << false + << std::numeric_limits::quiet_NaN(); +} + +void TestScalarSeries::testMinXAxisData() +{ + testMinXAxisData_t(); +} + +void TestScalarSeries::testMaxXAxisData_data() +{ + testMaxXAxisData_struct(); + + QTest::newRow("maxData1") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 6. << true << 5.; + QTest::newRow("maxData2") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 5. << true << 5.; + QTest::newRow("maxData3") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 4.9 << true << 4.; + QTest::newRow("maxData4") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 1.1 << true << 1.; + QTest::newRow("maxData5") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 1. << true << 1.; + QTest::newRow("maxData6") << ScalarBuilder{}.setX({}).setValues({}).build() << 1.1 << false + << std::numeric_limits::quiet_NaN(); +} + +void TestScalarSeries::testMaxXAxisData() +{ + testMaxXAxisData_t(); +} + +void TestScalarSeries::testPurge_data() +{ + testPurge_struct(); + + QTest::newRow("purgeScalar") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 2. << 4. << DataContainer{2., 3., 4.} + << std::vector{{200., 300., 400.}}; + QTest::newRow("purgeScalar1 (min/max swap)") + << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 4. << 2. << DataContainer{2., 3., 4.} << std::vector{{200., 300., 400.}}; + QTest::newRow("purgeScalar2") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 0. << 2.5 << DataContainer{1., 2.} + << std::vector{{100., 200.}}; + QTest::newRow("purgeScalar3") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 3.5 << 7. << DataContainer{4., 5.} + << std::vector{{400., 500.}}; + QTest::newRow("purgeScalar4") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 0. << 7. << DataContainer{1., 2., 3., 4., 5.} + << std::vector{{100., 200., 300., 400., 500.}}; + QTest::newRow("purgeScalar5") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 5.5 << 7. << DataContainer{} << std::vector{{}}; +} + +void TestScalarSeries::testPurge() +{ + testPurge_t(); +} + +void TestScalarSeries::testXAxisRange_data() +{ + testXAxisRange_struct(); + + QTest::newRow("xAxisRange") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << -1. << 3.2 << DataContainer{1., 2., 3.} + << DataContainer{100., 200., 300.}; + QTest::newRow("xAxisRange1 (min/max swap)") + << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 3.2 << -1. << DataContainer{1., 2., 3.} << DataContainer{100., 200., 300.}; + QTest::newRow("xAxisRange2") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 1. << 4. << DataContainer{1., 2., 3., 4.} + << DataContainer{100., 200., 300., 400.}; + QTest::newRow("xAxisRange3") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 1. << 3.9 << DataContainer{1., 2., 3.} + << DataContainer{100., 200., 300.}; + QTest::newRow("xAxisRange4") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 0. << 0.9 << DataContainer{} << DataContainer{}; + QTest::newRow("xAxisRange5") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 0. << 1. << DataContainer{1.} << DataContainer{100.}; + QTest::newRow("xAxisRange6") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 2.1 << 6. << DataContainer{3., 4., 5.} + << DataContainer{300., 400., 500.}; + QTest::newRow("xAxisRange7") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 6. << 9. << DataContainer{} << DataContainer{}; + QTest::newRow("xAxisRange8") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 5. << 9. << DataContainer{5.} << DataContainer{500.}; +} + +void TestScalarSeries::testXAxisRange() +{ + testXAxisRange_t(); +} + +void TestScalarSeries::testValuesBounds_data() +{ + testValuesBounds_struct(); + + auto nan = std::numeric_limits::quiet_NaN(); + + QTest::newRow("scalarBounds1") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 0. << 6. << true << 100. << 500.; + QTest::newRow("scalarBounds2") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 2. << 4. << true << 200. << 400.; + QTest::newRow("scalarBounds3") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 0. << 0.5 << false << nan << nan; + QTest::newRow("scalarBounds4") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({100., 200., 300., 400., 500.}) + .build() + << 5.1 << 6. << false << nan << nan; + QTest::newRow("scalarBounds5") + << ScalarBuilder{}.setX({1.}).setValues({100.}).build() << 0. << 2. << true << 100. << 100.; + QTest::newRow("scalarBounds6") + << ScalarBuilder{}.setX({}).setValues({}).build() << 0. << 2. << false << nan << nan; + + // Tests with NaN values: NaN values are not included in min/max search + QTest::newRow("scalarBounds7") << ScalarBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setValues({nan, 200., 300., 400., nan}) + .build() + << 0. << 6. << true << 200. << 400.; + QTest::newRow("scalarBounds8") + << ScalarBuilder{}.setX({1., 2., 3., 4., 5.}).setValues({nan, nan, nan, nan, nan}).build() + << 0. << 6. << true << nan << nan; +} + +void TestScalarSeries::testValuesBounds() +{ + testValuesBounds_t(); +} + +QTEST_MAIN(TestScalarSeries) +#include "TestScalarSeries.moc" diff --git a/core/tests/Data/TestSpectrogramSeries.cpp b/core/tests/Data/TestSpectrogramSeries.cpp new file mode 100644 index 0000000..8954231 --- /dev/null +++ b/core/tests/Data/TestSpectrogramSeries.cpp @@ -0,0 +1,199 @@ +#include "Data/SpectrogramSeries.h" + +#include "DataSeriesBuilders.h" +#include "DataSeriesUtils.h" + +#include +#include + +namespace { + +// Aliases used to facilitate reading of test inputs +using X = DataContainer; +using Y = DataContainer; +using Values = DataContainer; +using Components = std::vector; + +} // namespace + +/** + * @brief The TestSpectrogramSeries class defines unit tests on spectrogram series. + * + * Most of these unit tests use generic tests defined for DataSeries (@sa DataSeriesUtils) + */ +class TestSpectrogramSeries : public QObject { + Q_OBJECT +private slots: + + /// Tests construction of a spectrogram series + void testCtor_data(); + void testCtor(); + + /// Tests merge of two spectrogram series + void testMerge_data(); + void testMerge(); + + /// @todo ALX: test subdataseries + /// Tests get subdata of a spectrogram series + void testSubDataSeries_data(); + void testSubDataSeries(); +}; + +void TestSpectrogramSeries::testCtor_data() +{ + // x-axis data + QTest::addColumn("xAxisData"); + // y-axis data + QTest::addColumn("yAxisData"); + // values data + QTest::addColumn("valuesData"); + + // construction expected to be valid + QTest::addColumn("expectOK"); + // expected x-axis data (when construction is valid) + QTest::addColumn("expectedXAxisData"); + // expected components data (when construction is valid) + QTest::addColumn("expectedComponentsData"); + + QTest::newRow( + "invalidData (number of values by component aren't equal to the number of x-axis data)") + << X{1., 2., 3., 4., 5.} << Y{1., 2., 3.} << Values{1., 2., 3.} << false << X{} + << Components{}; + + QTest::newRow("invalidData (number of components aren't equal to the number of y-axis data)") + << X{1., 2., 3., 4., 5.} << Y{1., 2.} // 2 y-axis data + << Values{1., 2., 3., 4., 5.} // 1 component + << false << X{} << Components{}; + + QTest::newRow("sortedData") << X{1., 2., 3., 4., 5.} << Y{1., 2.} // 2 y-axis data + << Values{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} // 2 components + << true << X{1., 2., 3., 4., 5.} + << Components{{1., 3., 5., 7., 9.}, {2., 4., 6., 8., 10.}}; + + QTest::newRow("unsortedData") << X{5., 4., 3., 2., 1.} << Y{1., 2.} + << Values{1., 2., 3., 4., 5., 6., 7., 8., 9., 10.} << true + << X{1., 2., 3., 4., 5.} + << Components{{9., 7., 5., 3., 1.}, {10., 8., 6., 4., 2.}}; +} + +void TestSpectrogramSeries::testCtor() +{ + // Creates series + QFETCH(X, xAxisData); + QFETCH(Y, yAxisData); + QFETCH(Values, valuesData); + QFETCH(bool, expectOK); + + if (expectOK) { + auto series = SpectrogramBuilder{} + .setX(std::move(xAxisData)) + .setY(std::move(yAxisData)) + .setValues(std::move(valuesData)) + .build(); + + // Validates results + QFETCH(X, expectedXAxisData); + QFETCH(Components, expectedComponentsData); + validateRange(series->cbegin(), series->cend(), expectedXAxisData, expectedComponentsData); + } + else { + QVERIFY_EXCEPTION_THROWN(SpectrogramBuilder{} + .setX(std::move(xAxisData)) + .setY(std::move(yAxisData)) + .setValues(std::move(valuesData)) + .build(), + std::invalid_argument); + } +} + +void TestSpectrogramSeries::testMerge_data() +{ + testMerge_struct(); + + QTest::newRow("sortedMerge") << SpectrogramBuilder{} + .setX({1., 2., 3.}) + .setY({1., 2.}) + .setValues({10., 11., 20., 21., 30., 31}) + .build() + << SpectrogramBuilder{} + .setX({4., 5., 6.}) + .setY({1., 2.}) + .setValues({40., 41., 50., 51., 60., 61}) + .build() + << DataContainer{1., 2., 3., 4., 5., 6.} + << Components{{10., 20., 30., 40., 50., 60.}, + {11., 21., 31., 41., 51., 61}}; + + QTest::newRow( + "unsortedMerge (merge not made because the two data series have different y-axes)") + << SpectrogramBuilder{} + .setX({4., 5., 6.}) + .setY({1., 2.}) + .setValues({40., 41., 50., 51., 60., 61}) + .build() + << SpectrogramBuilder{} + .setX({1., 2., 3.}) + .setY({3., 4.}) + .setValues({10., 11., 20., 21., 30., 31}) + .build() + << DataContainer{4., 5., 6.} << Components{{40., 50., 60.}, {41., 51., 61}}; + + QTest::newRow( + "unsortedMerge (unsortedMerge (merge is made because the two data series have the same " + "y-axis)") + << SpectrogramBuilder{} + .setX({4., 5., 6.}) + .setY({1., 2.}) + .setValues({40., 41., 50., 51., 60., 61}) + .build() + << SpectrogramBuilder{} + .setX({1., 2., 3.}) + .setY({1., 2.}) + .setValues({10., 11., 20., 21., 30., 31}) + .build() + << DataContainer{1., 2., 3., 4., 5., 6.} + << Components{{10., 20., 30., 40., 50., 60.}, {11., 21., 31., 41., 51., 61}}; +} + +void TestSpectrogramSeries::testMerge() +{ + testMerge_t(); +} + +void TestSpectrogramSeries::testSubDataSeries_data() +{ + testSubDataSeries_struct(); + + QTest::newRow("subDataSeries (the range includes all data)") + << SpectrogramBuilder{} + .setX({1., 2., 3.}) + .setY({1., 2.}) + .setValues({10., 11., 20., 21., 30., 31}) + .build() + << SqpRange{0., 5.} << DataContainer{1., 2., 3.} + << Components{{10., 20., 30.}, {11., 21., 31.}}; + + QTest::newRow("subDataSeries (the range includes no data)") + << SpectrogramBuilder{} + .setX({1., 2., 3.}) + .setY({1., 2.}) + .setValues({10., 11., 20., 21., 30., 31}) + .build() + << SqpRange{4., 5.} << DataContainer{} << Components{{}, {}}; + + QTest::newRow("subDataSeries (the range includes some data)") + << SpectrogramBuilder{} + .setX({1., 2., 3.}) + .setY({1., 2.}) + .setValues({10., 11., 20., 21., 30., 31}) + .build() + << SqpRange{1.1, 3} << DataContainer{2., 3.} << Components{{20., 30.}, {21., 31.}}; +} + +void TestSpectrogramSeries::testSubDataSeries() +{ + testSubDataSeries_t(); +} + +QTEST_MAIN(TestSpectrogramSeries) +#include "TestSpectrogramSeries.moc" diff --git a/core/tests/Data/TestVectorSeries.cpp b/core/tests/Data/TestVectorSeries.cpp new file mode 100644 index 0000000..3542fd9 --- /dev/null +++ b/core/tests/Data/TestVectorSeries.cpp @@ -0,0 +1,90 @@ +#include "Data/VectorSeries.h" + +#include "DataSeriesBuilders.h" +#include "DataSeriesUtils.h" + +#include +#include + +/** + * @brief The TestVectorSeries class defines unit tests on vector series. + * + * Most of these unit tests use generic tests defined for DataSeries (@sa DataSeriesUtils) + */ +class TestVectorSeries : public QObject { + Q_OBJECT +private slots: + /// Tests purge of a vector series + void testPurge_data(); + void testPurge(); + + /// Tests get values bounds of a vector series + void testValuesBounds_data(); + void testValuesBounds(); +}; + +void TestVectorSeries::testPurge_data() +{ + testPurge_struct(); + + QTest::newRow("purgeVector") << VectorBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setXValues({6., 7., 8., 9., 10.}) + .setYValues({11., 12., 13., 14., 15.}) + .setZValues({16., 17., 18., 19., 20.}) + .build() + << 2. << 4. << DataContainer{2., 3., 4.} + << std::vector{ + {7., 8., 9.}, {12., 13., 14.}, {17., 18., 19.}}; +} + +void TestVectorSeries::testPurge() +{ + testPurge_t(); +} + +void TestVectorSeries::testValuesBounds_data() +{ + testValuesBounds_struct(); + + auto nan = std::numeric_limits::quiet_NaN(); + + QTest::newRow("vectorBounds1") << VectorBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setXValues({10., 15., 20., 13., 12.}) + .setYValues({35., 24., 10., 9., 0.3}) + .setZValues({13., 14., 12., 9., 24.}) + .build() + << 0. << 6. << true << 0.3 << 35.; // min/max in same component + QTest::newRow("vectorBounds2") << VectorBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setXValues({2.3, 15., 20., 13., 12.}) + .setYValues({35., 24., 10., 9., 4.}) + .setZValues({13., 14., 12., 9., 24.}) + .build() + << 0. << 6. << true << 2.3 << 35.; // min/max in same entry + QTest::newRow("vectorBounds3") << VectorBuilder{} + .setX({1., 2., 3., 4., 5.}) + .setXValues({2.3, 15., 20., 13., 12.}) + .setYValues({35., 24., 10., 9., 4.}) + .setZValues({13., 14., 12., 9., 24.}) + .build() + << 2. << 3. << true << 10. << 24.; + + // Tests with NaN values: NaN values are not included in min/max search + QTest::newRow("vectorBounds4") << VectorBuilder{} + .setX({1., 2.}) + .setXValues({nan, nan}) + .setYValues({nan, nan}) + .setZValues({nan, nan}) + .build() + << 0. << 6. << true << nan << nan; +} + +void TestVectorSeries::testValuesBounds() +{ + testValuesBounds_t(); +} + +QTEST_MAIN(TestVectorSeries) +#include "TestVectorSeries.moc" diff --git a/core/tests/Variable/TestVariable.cpp b/core/tests/Variable/TestVariable.cpp index e3389e8..a8fca16 100644 --- a/core/tests/Variable/TestVariable.cpp +++ b/core/tests/Variable/TestVariable.cpp @@ -296,21 +296,21 @@ void TestVariable::testNbPoints_data() // ////////// // NbPointsOperations operations{}; - // Sets cache range (expected nb points = series xAxis data + series values data) + // Sets cache range (expected nb points = values data) auto cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 0), date(2017, 1, 1, 12, 0, 9)}; - operations.push_back({cacheRange, dataSeries(cacheRange), 20}); + operations.push_back({cacheRange, dataSeries(cacheRange), 10}); // Doubles cache but don't add data series (expected nb points don't change) cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 0), date(2017, 1, 1, 12, 0, 19)}; - operations.push_back({cacheRange, dataSeries(INVALID_RANGE), 20}); + operations.push_back({cacheRange, dataSeries(INVALID_RANGE), 10}); // Doubles cache and data series (expected nb points change) cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 0), date(2017, 1, 1, 12, 0, 19)}; - operations.push_back({cacheRange, dataSeries(cacheRange), 40}); + operations.push_back({cacheRange, dataSeries(cacheRange), 20}); // Decreases cache (expected nb points decreases as the series is purged) cacheRange = SqpRange{date(2017, 1, 1, 12, 0, 5), date(2017, 1, 1, 12, 0, 9)}; - operations.push_back({cacheRange, dataSeries(INVALID_RANGE), 10}); + operations.push_back({cacheRange, dataSeries(INVALID_RANGE), 5}); QTest::newRow("nbPoints1") << operations; } diff --git a/core/tests/meson.build b/core/tests/meson.build index 5476049..6847e97 100644 --- a/core/tests/meson.build +++ b/core/tests/meson.build @@ -2,8 +2,11 @@ tests = [ [['Common/TestStringUtils.cpp'],'test_string_utils','StringUtils test'], - [['Data/TestDataSeries.cpp'],'test_data','DataSeries test'], + [['Data/TestScalarSeries.cpp'],'test_scalar','ScalarSeries test'], + [['Data/TestSpectrogramSeries.cpp'],'test_spectrogram','SpectrogramSeries test'], + [['Data/TestVectorSeries.cpp'],'test_vector','VectorSeries test'], [['Data/TestOneDimArrayData.cpp'],'test_1d','One Dim Array test'], + [['Data/TestOptionalAxis.cpp'],'test_optional_axis','OptionalAxis test'], [['Data/TestTwoDimArrayData.cpp'],'test_2d','Two Dim Array test'], [['DataSource/TestDataSourceController.cpp'],'test_data_source','DataSourceController test'], [['Variable/TestVariableCacheController.cpp'],'test_variable_cache','VariableCacheController test'],