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