#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 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> makeSensorMocks(size_t amount) { std::vector> result; for (size_t i = 0; i < amount; ++i) { auto& metricMock = result.emplace_back(std::make_shared>()); ON_CALL(*metricMock, metadata()) .WillByDefault(Return("metadata" + std::to_string(i))); } return result; } std::shared_ptr makeSut(const MetricParams& p) { return std::make_shared( utils::convContainer>( sensorMocks), p.operationType(), p.collectionTimeScope(), p.collectionDuration(), std::move(clockFakePtr)); } MetricParams params = MetricParams() .operationType(OperationType::avg) .collectionTimeScope(CollectionTimeScope::point) .collectionDuration(CollectionDuration(0ms)); std::vector> sensorMocks = makeSensorMocks(1u); std::unique_ptr clockFakePtr = std::make_unique(); ClockFake& clockFake = *clockFakePtr; NiceMock listenerMock; std::shared_ptr 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{"metadata0", 31.2, std::chrono::duration_cast( clockFake.system.timestamp()) .count()})); } TEST_F(TestMetricAfterInitialization, throwsWhenUpdateIsPerformedOnUnknownSensor) { auto sensor = std::make_shared>(); 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() = params.operationType(); expected.at_label() = params.collectionTimeScope(); expected.at_label() = params.collectionDuration(); expected.at_label() = { 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 { public: void SetUp() override { sut = makeSut(params.operationType(GetParam().operationType()) .collectionTimeScope(GetParam().collectionTimeScope()) .collectionDuration(GetParam().collectionDuration())); } static std::vector> defaultReadings() { std::vector> ret; ret.emplace_back(0ms, std::numeric_limits::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{"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{"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{"metadata0", 10.0, 72}, MetricValue{"metadata1", 11.0, 72}, MetricValue{"metadata2", 12.0, 72}, MetricValue{"metadata3", 13.0, 72}, MetricValue{"metadata4", 14.0, 72}, MetricValue{"metadata5", 15.0, 72}, MetricValue{"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{"metadata5", 15.0, 62}, MetricValue{"metadata3", 13.0, 62}, MetricValue{"metadata6", 16.0, 62}, MetricValue{"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{"metadata6", 16.0, 52}, MetricValue{"metadata5", 15.0, 52}, MetricValue{"metadata3", 13.0, 52}, MetricValue{"metadata4", 14.0, 52}, MetricValue{"metadata0", 10.0, 52}, MetricValue{"metadata2", 12.0, 52}, MetricValue{"metadata1", 11.0, 52})); }