xref: /openbmc/telemetry/tests/src/test_report.cpp (revision 9f9ff90a)
1 #include "dbus_environment.hpp"
2 #include "helpers.hpp"
3 #include "mocks/json_storage_mock.hpp"
4 #include "mocks/metric_mock.hpp"
5 #include "mocks/report_manager_mock.hpp"
6 #include "params/report_params.hpp"
7 #include "report.hpp"
8 #include "report_manager.hpp"
9 #include "utils/conv_container.hpp"
10 #include "utils/set_exception.hpp"
11 #include "utils/tstring.hpp"
12 
13 #include <sdbusplus/exception.hpp>
14 
15 using namespace testing;
16 using namespace std::literals::string_literals;
17 using namespace std::chrono_literals;
18 namespace tstring = utils::tstring;
19 
20 class TestReport : public Test
21 {
22   public:
23     ReportParams defaultParams;
24 
25     std::unique_ptr<ReportManagerMock> reportManagerMock =
26         std::make_unique<NiceMock<ReportManagerMock>>();
27     testing::NiceMock<StorageMock> storageMock;
28     std::vector<std::shared_ptr<MetricMock>> metricMocks = {
29         std::make_shared<NiceMock<MetricMock>>(),
30         std::make_shared<NiceMock<MetricMock>>(),
31         std::make_shared<NiceMock<MetricMock>>()};
32     std::unique_ptr<Report> sut;
33 
34     MockFunction<void()> checkPoint;
35 
36     TestReport()
37     {
38         ON_CALL(*metricMocks[0], getReading())
39             .WillByDefault(ReturnRefOfCopy(MetricValue{"a", "b", 17.1, 114}));
40         ON_CALL(*metricMocks[1], getReading())
41             .WillByDefault(ReturnRefOfCopy(MetricValue{"aa", "bb", 42.0, 74}));
42         ON_CALL(*metricMocks[2], getReading())
43             .WillByDefault(
44                 ReturnRefOfCopy(MetricValue{"aaa", "bbb", 100.7, 21}));
45 
46         for (size_t i = 0; i < metricMocks.size(); ++i)
47         {
48             using namespace std::string_literals;
49 
50             auto id = std::to_string(i);
51 
52             auto sensorParameters =
53                 LabeledSensorParameters("service"s + id, "path"s + id);
54             auto metricParameters = LabeledMetricParameters(
55                 std::move(sensorParameters), utils::toOperationType(i),
56                 "id"s + id, "metadata"s + id);
57 
58             ON_CALL(*metricMocks[i], dumpConfiguration())
59                 .WillByDefault(Return(std::move(metricParameters)));
60         }
61     }
62 
63     void SetUp() override
64     {
65         sut = makeReport(ReportParams());
66     }
67 
68     static interfaces::JsonStorage::FilePath to_file_path(std::string name)
69     {
70         return interfaces::JsonStorage::FilePath(
71             std::to_string(std::hash<std::string>{}(name)));
72     }
73 
74     std::unique_ptr<Report> makeReport(const ReportParams& params)
75     {
76         return std::make_unique<Report>(
77             DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(),
78             params.reportName(), params.reportingType(),
79             params.emitReadingUpdate(), params.logToMetricReportCollection(),
80             params.interval(), params.readingParameters(), *reportManagerMock,
81             storageMock,
82             utils::convContainer<std::shared_ptr<interfaces::Metric>>(
83                 metricMocks));
84     }
85 
86     template <class T>
87     static T getProperty(const std::string& path, const std::string& property)
88     {
89         auto propertyPromise = std::promise<T>();
90         auto propertyFuture = propertyPromise.get_future();
91         sdbusplus::asio::getProperty<T>(
92             *DbusEnvironment::getBus(), DbusEnvironment::serviceName(), path,
93             Report::reportIfaceName, property,
94             [&propertyPromise](const boost::system::error_code& ec, T t) {
95                 if (ec)
96                 {
97                     utils::setException(propertyPromise, "GetProperty failed");
98                     return;
99                 }
100                 propertyPromise.set_value(t);
101             });
102         return DbusEnvironment::waitForFuture(std::move(propertyFuture));
103     }
104 
105     boost::system::error_code call(const std::string& path,
106                                    const std::string& interface,
107                                    const std::string& method)
108     {
109         std::promise<boost::system::error_code> methodPromise;
110         DbusEnvironment::getBus()->async_method_call(
111             [&methodPromise](boost::system::error_code ec) {
112                 methodPromise.set_value(ec);
113             },
114             DbusEnvironment::serviceName(), path, interface, method);
115         return DbusEnvironment::waitForFuture(methodPromise.get_future());
116     }
117 
118     boost::system::error_code update(const std::string& path)
119     {
120         return call(path, Report::reportIfaceName, "Update");
121     }
122 
123     template <class T>
124     static boost::system::error_code setProperty(const std::string& path,
125                                                  const std::string& property,
126                                                  const T& newValue)
127     {
128         auto setPromise = std::promise<boost::system::error_code>();
129         auto setFuture = setPromise.get_future();
130         sdbusplus::asio::setProperty(
131             *DbusEnvironment::getBus(), DbusEnvironment::serviceName(), path,
132             Report::reportIfaceName, property, std::move(newValue),
133             [setPromise =
134                  std::move(setPromise)](boost::system::error_code ec) mutable {
135                 setPromise.set_value(ec);
136             });
137         return DbusEnvironment::waitForFuture(std::move(setFuture));
138     }
139 
140     boost::system::error_code deleteReport(const std::string& path)
141     {
142         return call(path, Report::deleteIfaceName, "Delete");
143     }
144 };
145 
146 TEST_F(TestReport, verifyIfPropertiesHaveValidValue)
147 {
148     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
149                 Eq(defaultParams.interval().count()));
150     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistency"), Eq(true));
151     EXPECT_THAT(getProperty<bool>(sut->getPath(), "EmitsReadingsUpdate"),
152                 Eq(defaultParams.emitReadingUpdate()));
153     EXPECT_THAT(
154         getProperty<bool>(sut->getPath(), "LogToMetricReportsCollection"),
155         Eq(defaultParams.logToMetricReportCollection()));
156     EXPECT_THAT(
157         getProperty<ReadingParameters>(sut->getPath(), "ReadingParameters"),
158         Eq(defaultParams.readingParameters()));
159 }
160 
161 TEST_F(TestReport, readingsAreInitialyEmpty)
162 {
163     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
164                 Eq(Readings{}));
165 }
166 
167 TEST_F(TestReport, setIntervalWithValidValue)
168 {
169     uint64_t newValue = defaultParams.interval().count() + 1;
170     EXPECT_THAT(setProperty(sut->getPath(), "Interval", newValue).value(),
171                 Eq(boost::system::errc::success));
172     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
173                 Eq(newValue));
174 }
175 
176 TEST_F(TestReport, settingIntervalWithInvalidValueDoesNotChangeProperty)
177 {
178     uint64_t newValue = defaultParams.interval().count() - 1;
179     EXPECT_THAT(setProperty(sut->getPath(), "Interval", newValue).value(),
180                 Eq(boost::system::errc::success));
181     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
182                 Eq(defaultParams.interval().count()));
183 }
184 
185 TEST_F(TestReport, settingEmitsReadingsUpdateHaveNoEffect)
186 {
187     EXPECT_THAT(setProperty(sut->getPath(), "EmitsReadingsUpdate",
188                             !defaultParams.emitReadingUpdate())
189                     .value(),
190                 Eq(boost::system::errc::read_only_file_system));
191     EXPECT_THAT(getProperty<bool>(sut->getPath(), "EmitsReadingsUpdate"),
192                 Eq(defaultParams.emitReadingUpdate()));
193 }
194 
195 TEST_F(TestReport, settingLogToMetricReportCollectionHaveNoEffect)
196 {
197     EXPECT_THAT(setProperty(sut->getPath(), "LogToMetricReportsCollection",
198                             !defaultParams.logToMetricReportCollection())
199                     .value(),
200                 Eq(boost::system::errc::read_only_file_system));
201     EXPECT_THAT(
202         getProperty<bool>(sut->getPath(), "LogToMetricReportsCollection"),
203         Eq(defaultParams.logToMetricReportCollection()));
204 }
205 
206 TEST_F(TestReport, settingPersistencyToFalseRemovesReportFromStorage)
207 {
208     EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
209 
210     bool persistency = false;
211     EXPECT_THAT(setProperty(sut->getPath(), "Persistency", persistency).value(),
212                 Eq(boost::system::errc::success));
213     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistency"),
214                 Eq(persistency));
215 }
216 
217 TEST_F(TestReport, deleteReport)
218 {
219     EXPECT_CALL(*reportManagerMock, removeReport(sut.get()));
220     auto ec = deleteReport(sut->getPath());
221     EXPECT_THAT(ec, Eq(boost::system::errc::success));
222 }
223 
224 TEST_F(TestReport, deletingNonExistingReportReturnInvalidRequestDescriptor)
225 {
226     auto ec = deleteReport(Report::reportDir + "NonExisting"s);
227     EXPECT_THAT(ec.value(), Eq(EBADR));
228 }
229 
230 TEST_F(TestReport, deleteReportExpectThatFileIsRemoveFromStorage)
231 {
232     EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
233     auto ec = deleteReport(sut->getPath());
234     EXPECT_THAT(ec, Eq(boost::system::errc::success));
235 }
236 
237 class TestReportStore :
238     public TestReport,
239     public WithParamInterface<std::pair<std::string, nlohmann::json>>
240 {
241   public:
242     void SetUp() override
243     {}
244 
245     nlohmann::json storedConfiguration;
246 };
247 
248 INSTANTIATE_TEST_SUITE_P(
249     _, TestReportStore,
250     Values(std::make_pair("Version"s, nlohmann::json(3)),
251            std::make_pair("Name"s, nlohmann::json(ReportParams().reportName())),
252            std::make_pair("ReportingType",
253                           nlohmann::json(ReportParams().reportingType())),
254            std::make_pair("EmitsReadingsUpdate",
255                           nlohmann::json(ReportParams().emitReadingUpdate())),
256            std::make_pair(
257                "LogToMetricReportsCollection",
258                nlohmann::json(ReportParams().logToMetricReportCollection())),
259            std::make_pair("Interval",
260                           nlohmann::json(ReportParams().interval().count())),
261            std::make_pair(
262                "ReadingParameters",
263                nlohmann::json(
264                    {{{tstring::SensorPath::str(),
265                       {{"service", "service0"}, {"path", "path0"}}},
266                      {tstring::OperationType::str(), OperationType::single},
267                      {tstring::Id::str(), "id0"},
268                      {tstring::MetricMetadata::str(), "metadata0"}},
269                     {{tstring::SensorPath::str(),
270                       {{"service", "service1"}, {"path", "path1"}}},
271                      {tstring::OperationType::str(), OperationType::max},
272                      {tstring::Id::str(), "id1"},
273                      {tstring::MetricMetadata::str(), "metadata1"}},
274                     {{tstring::SensorPath::str(),
275                       {{"service", "service2"}, {"path", "path2"}}},
276                      {tstring::OperationType::str(), OperationType::min},
277                      {tstring::Id::str(), "id2"},
278                      {tstring::MetricMetadata::str(), "metadata2"}}}))));
279 
280 TEST_P(TestReportStore, settingPersistencyToTrueStoresReport)
281 {
282     sut = makeReport(ReportParams());
283 
284     {
285         InSequence seq;
286         EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
287         EXPECT_CALL(checkPoint, Call());
288         EXPECT_CALL(storageMock, store(to_file_path(sut->getName()), _))
289             .WillOnce(SaveArg<1>(&storedConfiguration));
290     }
291 
292     setProperty(sut->getPath(), "Persistency", false);
293     checkPoint.Call();
294     setProperty(sut->getPath(), "Persistency", true);
295 
296     const auto& [key, value] = GetParam();
297 
298     ASSERT_THAT(storedConfiguration.at(key), Eq(value));
299 }
300 
301 TEST_P(TestReportStore, reportIsSavedToStorageAfterCreated)
302 {
303     EXPECT_CALL(storageMock,
304                 store(to_file_path(ReportParams().reportName()), _))
305         .WillOnce(SaveArg<1>(&storedConfiguration));
306 
307     sut = makeReport(ReportParams());
308 
309     const auto& [key, value] = GetParam();
310 
311     ASSERT_THAT(storedConfiguration.at(key), Eq(value));
312 }
313 
314 class TestReportValidNames :
315     public TestReport,
316     public WithParamInterface<ReportParams>
317 {
318   public:
319     void SetUp() override
320     {}
321 };
322 
323 INSTANTIATE_TEST_SUITE_P(
324     ValidNames, TestReportValidNames,
325     Values(ReportParams().reportName("Valid_1"),
326            ReportParams().reportName("Valid_1/Valid_2"),
327            ReportParams().reportName("Valid_1/Valid_2/Valid_3")));
328 
329 TEST_P(TestReportValidNames, reportCtorDoesNotThrowOnValidName)
330 {
331     EXPECT_NO_THROW(makeReport(GetParam()));
332 }
333 
334 class TestReportInvalidNames :
335     public TestReport,
336     public WithParamInterface<ReportParams>
337 {
338   public:
339     void SetUp() override
340     {}
341 };
342 
343 INSTANTIATE_TEST_SUITE_P(InvalidNames, TestReportInvalidNames,
344                          Values(ReportParams().reportName("/"),
345                                 ReportParams().reportName("/Invalid"),
346                                 ReportParams().reportName("Invalid/"),
347                                 ReportParams().reportName("Invalid/Invalid/"),
348                                 ReportParams().reportName("Invalid?")));
349 
350 TEST_P(TestReportInvalidNames, reportCtorThrowOnInvalidName)
351 {
352     EXPECT_THROW(makeReport(GetParam()), sdbusplus::exception::SdBusError);
353 }
354 
355 TEST_F(TestReportInvalidNames, reportCtorThrowOnInvalidNameAndNoStoreIsCalled)
356 {
357     EXPECT_CALL(storageMock, store).Times(0);
358     EXPECT_THROW(makeReport(ReportParams().reportName("/Invalid")),
359                  sdbusplus::exception::SdBusError);
360 }
361 
362 class TestReportAllReportTypes :
363     public TestReport,
364     public WithParamInterface<ReportParams>
365 {
366   public:
367     void SetUp() override
368     {
369         sut = makeReport(GetParam());
370     }
371 };
372 
373 INSTANTIATE_TEST_SUITE_P(_, TestReportAllReportTypes,
374                          Values(ReportParams().reportingType("OnRequest"),
375                                 ReportParams().reportingType("OnChange"),
376                                 ReportParams().reportingType("Periodic")));
377 
378 TEST_P(TestReportAllReportTypes, returnPropertValueOfReportType)
379 {
380     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "ReportingType"),
381                 Eq(GetParam().reportingType()));
382 }
383 
384 TEST_P(TestReportAllReportTypes, updateReadingsCallUpdateReadingsProperty)
385 {
386     const uint64_t expectedTime = std::time(0);
387 
388     sut->updateReadings();
389 
390     const auto [timestamp, readings] =
391         getProperty<Readings>(sut->getPath(), "Readings");
392 
393     EXPECT_THAT(timestamp, Ge(expectedTime));
394 }
395 
396 class TestReportOnRequestType : public TestReport
397 {
398     void SetUp() override
399     {
400         sut = makeReport(ReportParams().reportingType("OnRequest"));
401     }
402 };
403 
404 TEST_F(TestReportOnRequestType, updatesReadingTimestamp)
405 {
406     const uint64_t expectedTime = std::time(0);
407 
408     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
409 
410     const auto [timestamp, readings] =
411         getProperty<Readings>(sut->getPath(), "Readings");
412 
413     EXPECT_THAT(timestamp, Ge(expectedTime));
414 }
415 
416 TEST_F(TestReportOnRequestType, updatesReadingWhenUpdateIsCalled)
417 {
418     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
419 
420     const auto [timestamp, readings] =
421         getProperty<Readings>(sut->getPath(), "Readings");
422 
423     EXPECT_THAT(readings,
424                 ElementsAre(std::make_tuple("a"s, "b"s, 17.1, 114u),
425                             std::make_tuple("aa"s, "bb"s, 42.0, 74u),
426                             std::make_tuple("aaa"s, "bbb"s, 100.7, 21u)));
427 }
428 
429 class TestReportNonOnRequestType :
430     public TestReport,
431     public WithParamInterface<ReportParams>
432 {
433     void SetUp() override
434     {
435         sut = makeReport(GetParam());
436     }
437 };
438 
439 INSTANTIATE_TEST_SUITE_P(_, TestReportNonOnRequestType,
440                          Values(ReportParams().reportingType("Periodic"),
441                                 ReportParams().reportingType("OnChange")));
442 
443 TEST_P(TestReportNonOnRequestType, readingsAreNotUpdateOnUpdateCall)
444 {
445     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
446 
447     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
448                 Eq(Readings{}));
449 }
450 
451 class TestReportNonPeriodicReport :
452     public TestReport,
453     public WithParamInterface<ReportParams>
454 {
455   public:
456     void SetUp() override
457     {
458         sut = makeReport(GetParam());
459     }
460 };
461 
462 INSTANTIATE_TEST_SUITE_P(_, TestReportNonPeriodicReport,
463                          Values(ReportParams().reportingType("OnRequest"),
464                                 ReportParams().reportingType("OnChange")));
465 
466 TEST_P(TestReportNonPeriodicReport, readingsAreNotUpdatedAfterIntervalExpires)
467 {
468     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
469 
470     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
471                 Eq(Readings{}));
472 }
473 
474 class TestReportPeriodicReport : public TestReport
475 {
476     void SetUp() override
477     {
478         sut = makeReport(ReportParams().reportingType("Periodic"));
479     }
480 };
481 
482 TEST_F(TestReportPeriodicReport, readingTimestampIsUpdatedAfterIntervalExpires)
483 {
484     const uint64_t expectedTime = std::time(0);
485     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
486 
487     const auto [timestamp, readings] =
488         getProperty<Readings>(sut->getPath(), "Readings");
489 
490     EXPECT_THAT(timestamp, Ge(expectedTime));
491 }
492 
493 TEST_F(TestReportPeriodicReport, readingsAreUpdatedAfterIntervalExpires)
494 {
495     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
496 
497     const auto [timestamp, readings] =
498         getProperty<Readings>(sut->getPath(), "Readings");
499 
500     EXPECT_THAT(readings,
501                 ElementsAre(std::make_tuple("a"s, "b"s, 17.1, 114u),
502                             std::make_tuple("aa"s, "bb"s, 42.0, 74u),
503                             std::make_tuple("aaa"s, "bbb"s, 100.7, 21u)));
504 }
505 
506 class TestReportInitialization : public TestReport
507 {
508   public:
509     void SetUp() override
510     {}
511 
512     void monitorProc(sdbusplus::message::message& msg)
513     {
514         std::string iface;
515         std::vector<std::pair<std::string, std::variant<Readings>>>
516             changed_properties;
517         std::vector<std::string> invalidated_properties;
518 
519         msg.read(iface, changed_properties, invalidated_properties);
520 
521         if (iface == Report::reportIfaceName)
522         {
523             for (const auto& [name, value] : changed_properties)
524             {
525                 if (name == "Readings")
526                 {
527                     readingsUpdated.Call();
528                 }
529             }
530         }
531     }
532 
533     void makeMonitor()
534     {
535         monitor = std::make_unique<sdbusplus::bus::match::match>(
536             *DbusEnvironment::getBus(),
537             sdbusplus::bus::match::rules::propertiesChanged(
538                 sut->getPath(), Report::reportIfaceName),
539             [this](auto& msg) { monitorProc(msg); });
540     }
541 
542     std::unique_ptr<sdbusplus::bus::match::match> monitor;
543     MockFunction<void()> readingsUpdated;
544 };
545 
546 TEST_F(TestReportInitialization, metricsAreInitializedWhenConstructed)
547 {
548     for (auto& metric : metricMocks)
549     {
550         EXPECT_CALL(*metric, initialize());
551     }
552 
553     sut = makeReport(ReportParams());
554 }
555 
556 TEST_F(TestReportInitialization,
557        emitReadingsUpdateIsTrueReadingsPropertiesChangedSingalEmits)
558 {
559     EXPECT_CALL(readingsUpdated, Call())
560         .WillOnce(
561             InvokeWithoutArgs(DbusEnvironment::setPromise("readingsUpdated")));
562 
563     const auto elapsed = DbusEnvironment::measureTime([this] {
564         sut = makeReport(
565             defaultParams.reportingType("Periodic").emitReadingUpdate(true));
566         makeMonitor();
567         EXPECT_TRUE(DbusEnvironment::waitForFuture("readingsUpdated"));
568     });
569 
570     EXPECT_THAT(elapsed, AllOf(Ge(defaultParams.interval()),
571                                Lt(defaultParams.interval() * 2)));
572 }
573 
574 TEST_F(TestReportInitialization,
575        emitReadingsUpdateIsFalseReadingsPropertiesChangesSigalDoesNotEmits)
576 {
577     EXPECT_CALL(readingsUpdated, Call()).Times(0);
578 
579     sut = makeReport(
580         defaultParams.reportingType("Periodic").emitReadingUpdate(false));
581     makeMonitor();
582     DbusEnvironment::sleepFor(defaultParams.interval() * 2);
583 }
584