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