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