xref: /openbmc/telemetry/tests/src/test_report.cpp (revision 6ccfcbf5)
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, settingPersistencyToFalseRemovesReportFromStorage)
171 {
172     EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
173 
174     bool persistency = false;
175     EXPECT_THAT(setProperty(sut->getPath(), "Persistency", persistency).value(),
176                 Eq(boost::system::errc::success));
177     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistency"),
178                 Eq(persistency));
179 }
180 
181 TEST_F(TestReport, deleteReport)
182 {
183     EXPECT_CALL(*reportManagerMock, removeReport(sut.get()));
184     auto ec = deleteReport(sut->getPath());
185     EXPECT_THAT(ec, Eq(boost::system::errc::success));
186 }
187 
188 TEST_F(TestReport, deletingNonExistingReportReturnInvalidRequestDescriptor)
189 {
190     auto ec = deleteReport(Report::reportDir + "NonExisting"s);
191     EXPECT_THAT(ec.value(), Eq(EBADR));
192 }
193 
194 TEST_F(TestReport, deleteReportExpectThatFileIsRemoveFromStorage)
195 {
196     EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
197     auto ec = deleteReport(sut->getPath());
198     EXPECT_THAT(ec, Eq(boost::system::errc::success));
199 }
200 
201 class TestReportStore :
202     public TestReport,
203     public WithParamInterface<std::pair<std::string, nlohmann::json>>
204 {
205   public:
206     void SetUp() override
207     {}
208 
209     nlohmann::json storedConfiguration;
210 };
211 
212 INSTANTIATE_TEST_SUITE_P(
213     _, TestReportStore,
214     Values(std::make_pair("Version"s, nlohmann::json(1)),
215            std::make_pair("Name"s, nlohmann::json(ReportParams().reportName())),
216            std::make_pair("ReportingType",
217                           nlohmann::json(ReportParams().reportingType())),
218            std::make_pair("EmitsReadingsUpdate",
219                           nlohmann::json(ReportParams().emitReadingUpdate())),
220            std::make_pair(
221                "LogToMetricReportsCollection",
222                nlohmann::json(ReportParams().logToMetricReportCollection())),
223            std::make_pair("Interval",
224                           nlohmann::json(ReportParams().interval().count())),
225            std::make_pair("ReadingParameters",
226                           nlohmann::json({"metric0", "metric1", "metric2"}))));
227 
228 TEST_P(TestReportStore, settingPersistencyToTrueStoresReport)
229 {
230     sut = makeReport(ReportParams());
231 
232     {
233         InSequence seq;
234         EXPECT_CALL(storageMock, remove(to_file_path(sut->getName())));
235         EXPECT_CALL(checkPoint, Call());
236         EXPECT_CALL(storageMock, store(to_file_path(sut->getName()), _))
237             .WillOnce(SaveArg<1>(&storedConfiguration));
238     }
239 
240     setProperty(sut->getPath(), "Persistency", false);
241     checkPoint.Call();
242     setProperty(sut->getPath(), "Persistency", true);
243 
244     const auto& [key, value] = GetParam();
245 
246     ASSERT_THAT(storedConfiguration.at(key), Eq(value));
247 }
248 
249 TEST_P(TestReportStore, reportIsSavedToStorageAfterCreated)
250 {
251     EXPECT_CALL(storageMock,
252                 store(to_file_path(ReportParams().reportName()), _))
253         .WillOnce(SaveArg<1>(&storedConfiguration));
254 
255     sut = makeReport(ReportParams());
256 
257     const auto& [key, value] = GetParam();
258 
259     ASSERT_THAT(storedConfiguration.at(key), Eq(value));
260 }
261 
262 class TestReportValidNames :
263     public TestReport,
264     public WithParamInterface<ReportParams>
265 {
266   public:
267     void SetUp() override
268     {}
269 };
270 
271 INSTANTIATE_TEST_SUITE_P(
272     ValidNames, TestReportValidNames,
273     Values(ReportParams().reportName("Valid_1"),
274            ReportParams().reportName("Valid_1/Valid_2"),
275            ReportParams().reportName("Valid_1/Valid_2/Valid_3")));
276 
277 TEST_P(TestReportValidNames, reportCtorDoesNotThrowOnValidName)
278 {
279     EXPECT_NO_THROW(makeReport(GetParam()));
280 }
281 
282 class TestReportInvalidNames :
283     public TestReport,
284     public WithParamInterface<ReportParams>
285 {
286   public:
287     void SetUp() override
288     {}
289 };
290 
291 INSTANTIATE_TEST_SUITE_P(InvalidNames, TestReportInvalidNames,
292                          Values(ReportParams().reportName("/"),
293                                 ReportParams().reportName("/Invalid"),
294                                 ReportParams().reportName("Invalid/"),
295                                 ReportParams().reportName("Invalid/Invalid/"),
296                                 ReportParams().reportName("Invalid?")));
297 
298 TEST_P(TestReportInvalidNames, reportCtorThrowOnInvalidName)
299 {
300     EXPECT_THROW(makeReport(GetParam()), sdbusplus::exception::SdBusError);
301 }
302 
303 TEST_F(TestReportInvalidNames, reportCtorThrowOnInvalidNameAndNoStoreIsCalled)
304 {
305     EXPECT_CALL(storageMock, store).Times(0);
306     EXPECT_THROW(makeReport(ReportParams().reportName("/Invalid")),
307                  sdbusplus::exception::SdBusError);
308 }
309 
310 class TestReportAllReportTypes :
311     public TestReport,
312     public WithParamInterface<ReportParams>
313 {
314     void SetUp() override
315     {
316         sut = makeReport(GetParam());
317     }
318 };
319 
320 INSTANTIATE_TEST_SUITE_P(_, TestReportAllReportTypes,
321                          Values(ReportParams().reportingType("OnRequest"),
322                                 ReportParams().reportingType("OnChange"),
323                                 ReportParams().reportingType("Periodic")));
324 
325 TEST_P(TestReportAllReportTypes, returnPropertValueOfReportType)
326 {
327     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "ReportingType"),
328                 Eq(GetParam().reportingType()));
329 }
330 
331 class TestReportOnRequestType : public TestReport
332 {
333     void SetUp() override
334     {
335         sut = makeReport(ReportParams().reportingType("OnRequest"));
336     }
337 };
338 
339 TEST_F(TestReportOnRequestType, updatesReadingTimestamp)
340 {
341     const uint64_t expectedTime = std::time(0);
342 
343     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
344 
345     const auto [timestamp, readings] =
346         getProperty<Readings>(sut->getPath(), "Readings");
347 
348     EXPECT_THAT(timestamp, Ge(expectedTime));
349 }
350 
351 TEST_F(TestReportOnRequestType, updatesReadingWhenUpdateIsCalled)
352 {
353     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
354 
355     const auto [timestamp, readings] =
356         getProperty<Readings>(sut->getPath(), "Readings");
357 
358     EXPECT_THAT(readings,
359                 ElementsAre(std::make_tuple("a"s, "b"s, 17.1, 114u),
360                             std::make_tuple("aaa"s, "bbb"s, 21.7, 100u),
361                             std::make_tuple("aa"s, "bb"s, 42.0, 74u)));
362 }
363 
364 class TestReportNonOnRequestType :
365     public TestReport,
366     public WithParamInterface<ReportParams>
367 {
368     void SetUp() override
369     {
370         sut = makeReport(GetParam());
371     }
372 };
373 
374 INSTANTIATE_TEST_SUITE_P(_, TestReportNonOnRequestType,
375                          Values(ReportParams().reportingType("Periodic"),
376                                 ReportParams().reportingType("OnChange")));
377 
378 TEST_P(TestReportNonOnRequestType, readingsAreNotUpdateOnUpdateCall)
379 {
380     ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));
381 
382     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
383                 Eq(Readings{}));
384 }
385 
386 class TestReportNonPeriodicReport :
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(_, TestReportNonPeriodicReport,
397                          Values(ReportParams().reportingType("OnRequest"),
398                                 ReportParams().reportingType("OnChange")));
399 
400 TEST_P(TestReportNonPeriodicReport, readingsAreNotUpdatedAfterIntervalExpires)
401 {
402     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
403 
404     EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
405                 Eq(Readings{}));
406 }
407 
408 class TestReportPeriodicReport : public TestReport
409 {
410     void SetUp() override
411     {
412         sut = makeReport(ReportParams().reportingType("Periodic"));
413     }
414 };
415 
416 TEST_F(TestReportPeriodicReport, readingTimestampIsUpdatedAfterIntervalExpires)
417 {
418     const uint64_t expectedTime = std::time(0);
419     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
420 
421     const auto [timestamp, readings] =
422         getProperty<Readings>(sut->getPath(), "Readings");
423 
424     EXPECT_THAT(timestamp, Ge(expectedTime));
425 }
426 
427 TEST_F(TestReportPeriodicReport, readingsAreUpdatedAfterIntervalExpires)
428 {
429     DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);
430 
431     const auto [timestamp, readings] =
432         getProperty<Readings>(sut->getPath(), "Readings");
433 
434     EXPECT_THAT(readings,
435                 ElementsAre(std::make_tuple("a"s, "b"s, 17.1, 114u),
436                             std::make_tuple("aaa"s, "bbb"s, 21.7, 100u),
437                             std::make_tuple("aa"s, "bb"s, 42.0, 74u)));
438 }
439 
440 class TestReportInitialization : public TestReport
441 {
442   public:
443     void SetUp() override
444     {}
445 
446     void monitorProc(sdbusplus::message::message& msg)
447     {
448         std::string iface;
449         std::vector<std::pair<std::string, std::variant<Readings>>>
450             changed_properties;
451         std::vector<std::string> invalidated_properties;
452 
453         msg.read(iface, changed_properties, invalidated_properties);
454 
455         if (iface == Report::reportIfaceName)
456         {
457             for (const auto& [name, value] : changed_properties)
458             {
459                 if (name == "Readings")
460                 {
461                     readingsUpdated.Call();
462                 }
463             }
464         }
465     }
466 
467     void makeMonitor()
468     {
469         monitor = std::make_unique<sdbusplus::bus::match::match>(
470             *DbusEnvironment::getBus(),
471             sdbusplus::bus::match::rules::propertiesChanged(
472                 sut->getPath(), Report::reportIfaceName),
473             [this](auto& msg) { monitorProc(msg); });
474     }
475 
476     std::unique_ptr<sdbusplus::bus::match::match> monitor;
477     MockFunction<void()> readingsUpdated;
478 };
479 
480 TEST_F(TestReportInitialization, metricsAreInitializedWhenConstructed)
481 {
482     for (auto& metric : metricMocks)
483     {
484         EXPECT_CALL(*metric, initialize());
485     }
486 
487     sut = makeReport(ReportParams());
488 }
489 
490 TEST_F(TestReportInitialization, readingsPropertiesChangedSingalEmits)
491 {
492     sut = makeReport(defaultParams.reportingType("Periodic"));
493     EXPECT_CALL(readingsUpdated, Call());
494     makeMonitor();
495     DbusEnvironment::sleepFor(defaultParams.interval() + 1ms);
496 }
497