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