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