blob: cd6a6ecd2650284228f7d9f4e71989fab69b444f [file] [log] [blame]
#include "fakes/clock_fake.hpp"
#include "helpers.hpp"
#include "metric.hpp"
#include "mocks/metric_listener_mock.hpp"
#include "mocks/sensor_mock.hpp"
#include "params/metric_params.hpp"
#include "utils/conv_container.hpp"
#include "utils/conversion.hpp"
#include "utils/tstring.hpp"
#include <gmock/gmock.h>
using namespace testing;
using namespace std::chrono_literals;
namespace tstring = utils::tstring;
constexpr Milliseconds systemTimestamp = 42ms;
class TestMetric : public Test
{
public:
TestMetric()
{
clockFake.steady.reset();
clockFake.system.set(systemTimestamp);
}
static std::vector<std::shared_ptr<SensorMock>>
makeSensorMocks(size_t amount)
{
std::vector<std::shared_ptr<SensorMock>> result;
for (size_t i = 0; i < amount; ++i)
{
auto& metricMock =
result.emplace_back(std::make_shared<NiceMock<SensorMock>>());
ON_CALL(*metricMock, metadata())
.WillByDefault(Return("metadata" + std::to_string(i)));
}
return result;
}
std::shared_ptr<Metric> makeSut(const MetricParams& p)
{
return std::make_shared<Metric>(
utils::convContainer<std::shared_ptr<interfaces::Sensor>>(
sensorMocks),
p.operationType(), p.id(), p.collectionTimeScope(),
p.collectionDuration(), std::move(clockFakePtr));
}
MetricParams params = MetricParams()
.id("id")
.operationType(OperationType::avg)
.collectionTimeScope(CollectionTimeScope::point)
.collectionDuration(CollectionDuration(0ms));
std::vector<std::shared_ptr<SensorMock>> sensorMocks = makeSensorMocks(1u);
std::unique_ptr<ClockFake> clockFakePtr = std::make_unique<ClockFake>();
ClockFake& clockFake = *clockFakePtr;
NiceMock<MetricListenerMock> listenerMock;
std::shared_ptr<Metric> sut;
};
TEST_F(TestMetric, subscribesForSensorDuringInitialization)
{
sut = makeSut(params);
EXPECT_CALL(*sensorMocks.front(),
registerForUpdates(Truly([sut = sut.get()](const auto& a0) {
return a0.lock().get() == sut;
})));
sut->initialize();
}
TEST_F(TestMetric, unsubscribesForSensorDuringDeinitialization)
{
sut = makeSut(params);
EXPECT_CALL(*sensorMocks.front(),
unregisterFromUpdates(Truly([sut = sut.get()](const auto& a0) {
return a0.lock().get() == sut;
})));
sut->deinitialize();
}
TEST_F(TestMetric, containsEmptyReadingAfterCreated)
{
sut = makeSut(params);
ASSERT_THAT(sut->getUpdatedReadings(), ElementsAre());
}
TEST_F(TestMetric,
notifiesRegisteredListenersOnManualUpdateWhenMetricValueChanges)
{
sut = makeSut(params.collectionTimeScope(CollectionTimeScope::startup));
sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
sut->registerForUpdates(listenerMock);
EXPECT_CALL(listenerMock, metricUpdated()).Times(2);
sut->updateReadings(Milliseconds{50u});
sut->updateReadings(Milliseconds{100u});
}
TEST_F(TestMetric,
doesntNotifyRegisteredListenersOnManualUpdateWhenMetricValueDoesntChange)
{
sut = makeSut(params.collectionTimeScope(CollectionTimeScope::startup)
.operationType(OperationType::max));
sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
sut->registerForUpdates(listenerMock);
EXPECT_CALL(listenerMock, metricUpdated()).Times(0);
sut->updateReadings(Milliseconds{50u});
sut->sensorUpdated(*sensorMocks.front(), Milliseconds{180}, 11.);
sut->updateReadings(Milliseconds{100u});
}
class TestMetricAfterInitialization : public TestMetric
{
public:
void SetUp() override
{
sut = makeSut(params);
sut->initialize();
}
};
TEST_F(TestMetricAfterInitialization, containsEmptyReading)
{
ASSERT_THAT(sut->getUpdatedReadings(), ElementsAre());
}
TEST_F(TestMetricAfterInitialization, updatesMetricValuesOnSensorUpdate)
{
sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
ASSERT_THAT(
sut->getUpdatedReadings(),
ElementsAre(MetricValue{"id", "metadata0", 31.2,
std::chrono::duration_cast<Milliseconds>(
clockFake.system.timestamp())
.count()}));
}
TEST_F(TestMetricAfterInitialization,
throwsWhenUpdateIsPerformedOnUnknownSensor)
{
auto sensor = std::make_shared<StrictMock<SensorMock>>();
EXPECT_THROW(sut->sensorUpdated(*sensor, Milliseconds{10}, 20.0),
std::out_of_range);
}
TEST_F(TestMetricAfterInitialization, dumpsConfiguration)
{
namespace ts = utils::tstring;
ON_CALL(*sensorMocks.front(), id())
.WillByDefault(Return(SensorMock::makeId("service1", "path1")));
ON_CALL(*sensorMocks.front(), metadata())
.WillByDefault(Return("metadata1"));
const auto conf = sut->dumpConfiguration();
LabeledMetricParameters expected = {};
expected.at_label<ts::Id>() = params.id();
expected.at_label<ts::OperationType>() = params.operationType();
expected.at_label<ts::CollectionTimeScope>() = params.collectionTimeScope();
expected.at_label<ts::CollectionDuration>() = params.collectionDuration();
expected.at_label<ts::SensorPath>() = {
LabeledSensorInfo("service1", "path1", "metadata1")};
EXPECT_THAT(conf, Eq(expected));
}
TEST_F(TestMetricAfterInitialization, notifiesRegisteredListeners)
{
EXPECT_CALL(listenerMock, metricUpdated());
sut->registerForUpdates(listenerMock);
sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
}
TEST_F(TestMetricAfterInitialization,
doesntNotifyRegisteredListenersWhenValueDoesntChange)
{
EXPECT_CALL(listenerMock, metricUpdated());
sut->registerForUpdates(listenerMock);
sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
sut->sensorUpdated(*sensorMocks.front(), Milliseconds{70}, 31.2);
}
TEST_F(TestMetricAfterInitialization, doesntNotifyAfterUnRegisterListener)
{
EXPECT_CALL(listenerMock, metricUpdated()).Times(0);
sut->registerForUpdates(listenerMock);
sut->unregisterFromUpdates(listenerMock);
sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
}
class TestMetricCalculationFunctions :
public TestMetric,
public WithParamInterface<MetricParams>
{
public:
void SetUp() override
{
sut = makeSut(params.operationType(GetParam().operationType())
.collectionTimeScope(GetParam().collectionTimeScope())
.collectionDuration(GetParam().collectionDuration()));
}
static std::vector<std::pair<Milliseconds, double>> defaultReadings()
{
std::vector<std::pair<Milliseconds, double>> ret;
ret.emplace_back(0ms, std::numeric_limits<double>::quiet_NaN());
ret.emplace_back(10ms, 14.);
ret.emplace_back(1ms, 3.);
ret.emplace_back(5ms, 7.);
return ret;
}
};
MetricParams defaultCollectionFunctionParams()
{
return MetricParams()
.readings(TestMetricCalculationFunctions::defaultReadings())
.expectedReading(systemTimestamp + 16ms, 7.0);
}
MetricParams defaultPointParams()
{
return defaultCollectionFunctionParams()
.collectionTimeScope(CollectionTimeScope::point)
.expectedIsTimerRequired(false);
}
INSTANTIATE_TEST_SUITE_P(
TimeScopePointReturnsLastReading, TestMetricCalculationFunctions,
Values(defaultPointParams().operationType(OperationType::min),
defaultPointParams().operationType(OperationType::max),
defaultPointParams().operationType(OperationType::sum),
defaultPointParams().operationType(OperationType::avg)));
MetricParams defaultMinParams()
{
return defaultCollectionFunctionParams().operationType(OperationType::min);
}
INSTANTIATE_TEST_SUITE_P(
ReturnsMinForGivenTimeScope, TestMetricCalculationFunctions,
Values(defaultMinParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(100ms))
.expectedReading(systemTimestamp + 16ms, 3.0),
defaultMinParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(3ms))
.expectedReading(systemTimestamp + 16ms, 7.0),
defaultMinParams()
.collectionTimeScope(CollectionTimeScope::startup)
.expectedReading(systemTimestamp + 16ms, 3.0)
.expectedIsTimerRequired(false)));
MetricParams defaultMaxParams()
{
return defaultCollectionFunctionParams().operationType(OperationType::max);
}
INSTANTIATE_TEST_SUITE_P(
ReturnsMaxForGivenTimeScope, TestMetricCalculationFunctions,
Values(defaultMaxParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(100ms))
.expectedReading(systemTimestamp + 16ms, 14.0),
defaultMaxParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(6ms))
.expectedReading(systemTimestamp + 16ms, 14.0),
defaultMaxParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(5ms))
.expectedReading(systemTimestamp + 16ms, 7.0),
defaultMaxParams()
.collectionTimeScope(CollectionTimeScope::startup)
.expectedReading(systemTimestamp + 16ms, 14.0)
.expectedIsTimerRequired(false)));
MetricParams defaultSumParams()
{
return defaultCollectionFunctionParams().operationType(OperationType::sum);
}
INSTANTIATE_TEST_SUITE_P(
ReturnsSumForGivenTimeScope, TestMetricCalculationFunctions,
Values(defaultSumParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(100ms))
.expectedReading(systemTimestamp + 16ms,
14. * 0.01 + 3. * 0.001 + 7. * 0.005),
defaultSumParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(8ms))
.expectedReading(systemTimestamp + 16ms,
14. * 0.002 + 3. * 0.001 + 7 * 0.005),
defaultSumParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(6ms))
.expectedReading(systemTimestamp + 16ms, 3. * 0.001 + 7 * 0.005),
defaultSumParams()
.collectionTimeScope(CollectionTimeScope::startup)
.expectedReading(systemTimestamp + 16ms,
14. * 0.01 + 3. * 0.001 + 7 * 0.005)));
MetricParams defaultAvgParams()
{
return defaultCollectionFunctionParams().operationType(OperationType::avg);
}
INSTANTIATE_TEST_SUITE_P(
ReturnsAvgForGivenTimeScope, TestMetricCalculationFunctions,
Values(defaultAvgParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(100ms))
.expectedReading(systemTimestamp + 16ms,
(14. * 10 + 3. * 1 + 7 * 5) / 16.),
defaultAvgParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(8ms))
.expectedReading(systemTimestamp + 16ms,
(14. * 2 + 3. * 1 + 7 * 5) / 8.),
defaultAvgParams()
.collectionTimeScope(CollectionTimeScope::interval)
.collectionDuration(CollectionDuration(6ms))
.expectedReading(systemTimestamp + 16ms, (3. * 1 + 7 * 5) / 6.),
defaultAvgParams()
.collectionTimeScope(CollectionTimeScope::startup)
.expectedReading(systemTimestamp + 16ms,
(14. * 10 + 3. * 1 + 7 * 5) / 16.)));
TEST_P(TestMetricCalculationFunctions, calculatesReadingValue)
{
for (auto [timestamp, reading] : GetParam().readings())
{
sut->sensorUpdated(*sensorMocks.front(), clockFake.steadyTimestamp(),
reading);
clockFake.advance(timestamp);
}
const auto [expectedTimestamp, expectedReading] =
GetParam().expectedReading();
const auto readings = sut->getUpdatedReadings();
EXPECT_THAT(readings,
ElementsAre(MetricValue{"id", "metadata0", expectedReading,
expectedTimestamp.count()}));
}
TEST_P(TestMetricCalculationFunctions,
calculatedReadingValueWithIntermediateCalculations)
{
for (auto [timestamp, reading] : GetParam().readings())
{
sut->sensorUpdated(*sensorMocks.front(), clockFake.steadyTimestamp(),
reading);
clockFake.advance(timestamp);
sut->getUpdatedReadings();
}
const auto [expectedTimestamp, expectedReading] =
GetParam().expectedReading();
const auto readings = sut->getUpdatedReadings();
EXPECT_THAT(readings,
ElementsAre(MetricValue{"id", "metadata0", expectedReading,
expectedTimestamp.count()}));
}
TEST_P(TestMetricCalculationFunctions, returnsIsTimerRequired)
{
EXPECT_THAT(sut->isTimerRequired(),
Eq(GetParam().expectedIsTimerRequired()));
}
class TestMetricWithMultipleSensors : public TestMetric
{
public:
TestMetricWithMultipleSensors()
{
sensorMocks = makeSensorMocks(7u);
sut = makeSut(params);
sut->initialize();
}
};
TEST_F(TestMetricWithMultipleSensors, readingsContainsAllReadingsInOrder)
{
for (size_t i = 0; i < sensorMocks.size(); ++i)
{
sut->sensorUpdated(*sensorMocks[i], Milliseconds{i + 100}, i + 10.0);
sut->getUpdatedReadings();
}
clockFake.system.set(Milliseconds{72});
EXPECT_THAT(sut->getUpdatedReadings(),
ElementsAre(MetricValue{"id", "metadata0", 10.0, 72},
MetricValue{"id", "metadata1", 11.0, 72},
MetricValue{"id", "metadata2", 12.0, 72},
MetricValue{"id", "metadata3", 13.0, 72},
MetricValue{"id", "metadata4", 14.0, 72},
MetricValue{"id", "metadata5", 15.0, 72},
MetricValue{"id", "metadata6", 16.0, 72}));
}
TEST_F(TestMetricWithMultipleSensors, readingsContainOnlyAvailableSensors)
{
for (auto i : {5u, 3u, 6u, 0u})
{
sut->sensorUpdated(*sensorMocks[i], Milliseconds{i + 100}, i + 10.0);
sut->getUpdatedReadings();
}
clockFake.system.set(Milliseconds{62});
EXPECT_THAT(sut->getUpdatedReadings(),
ElementsAre(MetricValue{"id", "metadata5", 15.0, 62},
MetricValue{"id", "metadata3", 13.0, 62},
MetricValue{"id", "metadata6", 16.0, 62},
MetricValue{"id", "metadata0", 10.0, 62}));
}
TEST_F(TestMetricWithMultipleSensors, readingsContainsAllReadingsOutOfOrder)
{
for (auto i : {6u, 5u, 3u, 4u, 0u, 2u, 1u})
{
sut->sensorUpdated(*sensorMocks[i], Milliseconds{i + 100}, i + 10.0);
sut->getUpdatedReadings();
}
clockFake.system.set(Milliseconds{52});
EXPECT_THAT(sut->getUpdatedReadings(),
ElementsAre(MetricValue{"id", "metadata6", 16.0, 52},
MetricValue{"id", "metadata5", 15.0, 52},
MetricValue{"id", "metadata3", 13.0, 52},
MetricValue{"id", "metadata4", 14.0, 52},
MetricValue{"id", "metadata0", 10.0, 52},
MetricValue{"id", "metadata2", 12.0, 52},
MetricValue{"id", "metadata1", 11.0, 52}));
}