1 #include "dbus_environment.hpp" 2 #include "helpers.hpp" 3 #include "mocks/clock_mock.hpp" 4 #include "mocks/sensor_mock.hpp" 5 #include "mocks/trigger_action_mock.hpp" 6 #include "numeric_threshold.hpp" 7 #include "utils/conv_container.hpp" 8 9 #include <gmock/gmock.h> 10 11 using namespace testing; 12 using namespace std::chrono_literals; 13 14 class TestNumericThreshold : public Test 15 { 16 public: 17 std::vector<std::shared_ptr<SensorMock>> sensorMocks = { 18 std::make_shared<NiceMock<SensorMock>>(), 19 std::make_shared<NiceMock<SensorMock>>()}; 20 std::vector<std::string> sensorNames = {"Sensor1", "Sensor2"}; 21 std::unique_ptr<TriggerActionMock> actionMockPtr = 22 std::make_unique<StrictMock<TriggerActionMock>>(); 23 TriggerActionMock& actionMock = *actionMockPtr; 24 std::shared_ptr<NumericThreshold> sut; 25 std::string triggerId = "MyTrigger"; 26 std::unique_ptr<NiceMock<ClockMock>> clockMockPtr = 27 std::make_unique<NiceMock<ClockMock>>(); 28 29 void makeThreshold(Milliseconds dwellTime, numeric::Direction direction, 30 double thresholdValue, 31 numeric::Type type = numeric::Type::lowerWarning) 32 { 33 std::vector<std::unique_ptr<interfaces::TriggerAction>> actions; 34 actions.push_back(std::move(actionMockPtr)); 35 36 sut = std::make_shared<NumericThreshold>( 37 DbusEnvironment::getIoc(), triggerId, 38 utils::convContainer<std::shared_ptr<interfaces::Sensor>>( 39 sensorMocks), 40 std::move(actions), dwellTime, direction, thresholdValue, type, 41 std::move(clockMockPtr)); 42 } 43 44 void SetUp() override 45 { 46 for (size_t idx = 0; idx < sensorMocks.size(); idx++) 47 { 48 ON_CALL(*sensorMocks.at(idx), getName()) 49 .WillByDefault(Return(sensorNames[idx])); 50 } 51 52 makeThreshold(0ms, numeric::Direction::increasing, 90.0, 53 numeric::Type::upperCritical); 54 } 55 }; 56 57 TEST_F(TestNumericThreshold, initializeThresholdExpectAllSensorsAreRegistered) 58 { 59 for (auto& sensor : sensorMocks) 60 { 61 EXPECT_CALL(*sensor, 62 registerForUpdates(Truly([sut = sut.get()](const auto& x) { 63 return x.lock().get() == sut; 64 }))); 65 } 66 67 sut->initialize(); 68 } 69 70 TEST_F(TestNumericThreshold, thresholdIsNotInitializeExpectNoActionCommit) 71 { 72 EXPECT_CALL(actionMock, commit(_, _, _, _, _)).Times(0); 73 } 74 75 TEST_F(TestNumericThreshold, getLabeledParamsReturnsCorrectly) 76 { 77 LabeledThresholdParam expected = numeric::LabeledThresholdParam( 78 numeric::Type::upperCritical, 0, numeric::Direction::increasing, 90.0); 79 EXPECT_EQ(sut->getThresholdParam(), expected); 80 } 81 82 struct NumericParams 83 { 84 struct UpdateParams 85 { 86 size_t sensor; 87 double value; 88 Milliseconds sleepAfter; 89 90 UpdateParams(size_t sensor, double value, 91 Milliseconds sleepAfter = 0ms) : 92 sensor(sensor), 93 value(value), sleepAfter(sleepAfter) 94 {} 95 }; 96 97 struct ExpectedParams 98 { 99 size_t sensor; 100 double value; 101 Milliseconds waitMin; 102 103 ExpectedParams(size_t sensor, double value, 104 Milliseconds waitMin = 0ms) : 105 sensor(sensor), 106 value(value), waitMin(waitMin) 107 {} 108 }; 109 110 NumericParams& Direction(numeric::Direction val) 111 { 112 direction = val; 113 return *this; 114 } 115 116 NumericParams& Updates(std::vector<UpdateParams> val) 117 { 118 updates = std::move(val); 119 return *this; 120 } 121 122 NumericParams& Expected(std::vector<ExpectedParams> val) 123 { 124 expected = std::move(val); 125 return *this; 126 } 127 128 NumericParams& ThresholdValue(double val) 129 { 130 thresholdValue = val; 131 return *this; 132 } 133 134 NumericParams& DwellTime(Milliseconds val) 135 { 136 dwellTime = std::move(val); 137 return *this; 138 } 139 140 NumericParams& InitialValues(std::vector<double> val) 141 { 142 initialValues = std::move(val); 143 return *this; 144 } 145 146 friend void PrintTo(const NumericParams& o, std::ostream* os) 147 { 148 *os << "{ DwellTime: " << o.dwellTime.count() << "ms "; 149 *os << ", ThresholdValue: " << o.thresholdValue; 150 *os << ", Direction: " << static_cast<int>(o.direction); 151 *os << ", InitialValues: [ "; 152 size_t idx = 0; 153 for (const double value : o.initialValues) 154 { 155 *os << "{ SensorIndex: " << idx << ", Value: " << value << " }, "; 156 idx++; 157 } 158 *os << " ], Updates: [ "; 159 for (const auto& [index, value, sleepAfter] : o.updates) 160 { 161 *os << "{ SensorIndex: " << index << ", Value: " << value 162 << ", SleepAfter: " << sleepAfter.count() << "ms }, "; 163 } 164 *os << " ], Expected: [ "; 165 for (const auto& [index, value, waitMin] : o.expected) 166 { 167 *os << "{ SensorIndex: " << index << ", Value: " << value 168 << ", waitMin: " << waitMin.count() << "ms }, "; 169 } 170 *os << " ] }"; 171 } 172 173 numeric::Direction direction; 174 double thresholdValue = 0.0; 175 Milliseconds dwellTime = 0ms; 176 std::vector<UpdateParams> updates; 177 std::vector<ExpectedParams> expected; 178 std::vector<double> initialValues; 179 }; 180 181 class TestNumericThresholdCommon : 182 public TestNumericThreshold, 183 public WithParamInterface<NumericParams> 184 { 185 public: 186 void sleep(Milliseconds duration) 187 { 188 if (duration != 0ms) 189 { 190 DbusEnvironment::sleepFor(duration); 191 } 192 } 193 194 void testBodySensorIsUpdatedMultipleTimes() 195 { 196 std::vector<std::chrono::time_point<std::chrono::high_resolution_clock>> 197 timestamps(sensorMocks.size()); 198 199 sut->initialize(); 200 201 InSequence seq; 202 203 for (const auto& [index, value, waitMin] : GetParam().expected) 204 { 205 EXPECT_CALL(actionMock, 206 commit(triggerId, Eq(std::nullopt), sensorNames[index], 207 _, TriggerValue(value))) 208 .WillOnce(DoAll( 209 InvokeWithoutArgs([idx = index, ×tamps] { 210 timestamps[idx] = std::chrono::high_resolution_clock::now(); 211 }), 212 InvokeWithoutArgs(DbusEnvironment::setPromise("commit")))); 213 } 214 215 auto start = std::chrono::high_resolution_clock::now(); 216 217 size_t idx = 0; 218 for (const double value : GetParam().initialValues) 219 { 220 sut->sensorUpdated(*sensorMocks[idx], 0ms, value); 221 idx++; 222 } 223 224 for (const auto& [index, value, sleepAfter] : GetParam().updates) 225 { 226 ASSERT_LT(index, GetParam().initialValues.size()) 227 << "Initial value was not specified for sensor with index: " 228 << index; 229 sut->sensorUpdated(*sensorMocks[index], 42ms, value); 230 sleep(sleepAfter); 231 } 232 233 EXPECT_THAT(DbusEnvironment::waitForFutures("commit"), true); 234 for (const auto& [index, value, waitMin] : GetParam().expected) 235 { 236 EXPECT_THAT(timestamps[index] - start, Ge(waitMin)); 237 } 238 } 239 }; 240 241 class TestNumericThresholdNoDwellTime : public TestNumericThresholdCommon 242 { 243 public: 244 void SetUp() override 245 { 246 for (size_t idx = 0; idx < sensorMocks.size(); idx++) 247 { 248 ON_CALL(*sensorMocks.at(idx), getName()) 249 .WillByDefault(Return(sensorNames[idx])); 250 } 251 252 makeThreshold(0ms, GetParam().direction, GetParam().thresholdValue); 253 } 254 }; 255 256 INSTANTIATE_TEST_SUITE_P(_, TestNumericThresholdNoDwellTime, 257 Values(NumericParams() 258 .ThresholdValue(90.0) 259 .Direction(numeric::Direction::increasing) 260 .InitialValues({80.0}) 261 .Updates({{0, 89.0}}) 262 .Expected({}), 263 NumericParams() 264 .ThresholdValue(90.0) 265 .Direction(numeric::Direction::increasing) 266 .InitialValues({80.0}) 267 .Updates({{0, 91.0}}) 268 .Expected({{0, 91.0}}), 269 NumericParams() 270 .ThresholdValue(90.0) 271 .Direction(numeric::Direction::increasing) 272 .InitialValues({80.0}) 273 .Updates({{0, 99.0}, {0, 80.0}, {0, 98.0}}) 274 .Expected({{0, 99.0}, {0, 98.0}}), 275 NumericParams() 276 .ThresholdValue(90.0) 277 .Direction(numeric::Direction::increasing) 278 .InitialValues({80.0, 100.0}) 279 .Updates({{0, 99.0}, {1, 98.0}}) 280 .Expected({{0, 99.0}}), 281 NumericParams() 282 .ThresholdValue(90.0) 283 .Direction(numeric::Direction::decreasing) 284 .InitialValues({100.0}) 285 .Updates({{0, 91.0}}) 286 .Expected({}), 287 NumericParams() 288 .ThresholdValue(90.0) 289 .Direction(numeric::Direction::decreasing) 290 .InitialValues({100.0}) 291 .Updates({{0, 80.0}}) 292 .Expected({{0, 80.0}}), 293 NumericParams() 294 .ThresholdValue(90.0) 295 .Direction(numeric::Direction::decreasing) 296 .InitialValues({100.0}) 297 .Updates({{0, 80.0}, {0, 99.0}, {0, 85.0}}) 298 .Expected({{0, 80.0}, {0, 85.0}}), 299 NumericParams() 300 .ThresholdValue(90.0) 301 .Direction(numeric::Direction::decreasing) 302 .InitialValues({100.0, 99.0}) 303 .Updates({{0, 80.0}, {1, 88.0}}) 304 .Expected({{0, 80.0}, {1, 88.0}}), 305 NumericParams() 306 .ThresholdValue(90.0) 307 .Direction(numeric::Direction::either) 308 .InitialValues({98.0}) 309 .Updates({{0, 91.0}}) 310 .Expected({}), 311 NumericParams() 312 .ThresholdValue(90.0) 313 .Direction(numeric::Direction::either) 314 .InitialValues({100.0}) 315 .Updates({{0, 80.0}, {0, 85.0}, {0, 91.0}}) 316 .Expected({{0, 80.0}, {0, 91.0}}), 317 NumericParams() 318 .ThresholdValue(90.0) 319 .Direction(numeric::Direction::either) 320 .InitialValues({100.0, 80.0}) 321 .Updates({{0, 85.0}, {1, 91.0}}) 322 .Expected({{0, 85.0}, {1, 91.0}}), 323 NumericParams() 324 .ThresholdValue(30.0) 325 .Direction(numeric::Direction::decreasing) 326 .InitialValues({40.0}) 327 .Updates({{0, 30.0}, {0, 20.0}}) 328 .Expected({{0, 20.0}}), 329 NumericParams() 330 .ThresholdValue(30.0) 331 .Direction(numeric::Direction::decreasing) 332 .InitialValues({20.0}) 333 .Updates({{0, 30.0}, {0, 20.0}}) 334 .Expected({}), 335 NumericParams() 336 .ThresholdValue(30.0) 337 .Direction(numeric::Direction::either) 338 .InitialValues({20.0}) 339 .Updates({{0, 30.0}, {0, 20.0}}) 340 .Expected({}), 341 NumericParams() 342 .ThresholdValue(30.0) 343 .Direction(numeric::Direction::increasing) 344 .InitialValues({20.0}) 345 .Updates({{0, 30.0}, {0, 40.0}}) 346 .Expected({{0, 40.0}}), 347 NumericParams() 348 .ThresholdValue(30.0) 349 .Direction(numeric::Direction::increasing) 350 .InitialValues({40.0}) 351 .Updates({{0, 30.0}, {0, 40.0}}) 352 .Expected({}), 353 NumericParams() 354 .ThresholdValue(30.0) 355 .Direction(numeric::Direction::either) 356 .InitialValues({40.0}) 357 .Updates({{0, 30.0}, {0, 40.0}}) 358 .Expected({}))); 359 360 TEST_P(TestNumericThresholdNoDwellTime, senorsIsUpdatedMultipleTimes) 361 { 362 testBodySensorIsUpdatedMultipleTimes(); 363 } 364 365 class TestNumericThresholdWithDwellTime : public TestNumericThresholdCommon 366 { 367 public: 368 void SetUp() override 369 { 370 for (size_t idx = 0; idx < sensorMocks.size(); idx++) 371 { 372 ON_CALL(*sensorMocks.at(idx), getName()) 373 .WillByDefault(Return(sensorNames[idx])); 374 } 375 376 makeThreshold(GetParam().dwellTime, GetParam().direction, 377 GetParam().thresholdValue); 378 } 379 }; 380 381 INSTANTIATE_TEST_SUITE_P( 382 SleepAfterEveryUpdate, TestNumericThresholdWithDwellTime, 383 Values(NumericParams() 384 .DwellTime(200ms) 385 .ThresholdValue(90.0) 386 .Direction(numeric::Direction::increasing) 387 .InitialValues({80.0}) 388 .Updates({{0, 89.0, 200ms}}) 389 .Expected({}), 390 NumericParams() 391 .DwellTime(200ms) 392 .ThresholdValue(90.0) 393 .Direction(numeric::Direction::increasing) 394 .InitialValues({80.0}) 395 .Updates({{0, 91.0, 200ms}}) 396 .Expected({{0, 91.0, 200ms}}), 397 NumericParams() 398 .DwellTime(200ms) 399 .ThresholdValue(90.0) 400 .Direction(numeric::Direction::increasing) 401 .InitialValues({80.0}) 402 .Updates({{0, 99.0, 200ms}, {0, 80.0, 100ms}, {0, 98.0, 200ms}}) 403 .Expected({{0, 99.0, 200ms}, {0, 98.0, 500ms}}), 404 NumericParams() 405 .DwellTime(200ms) 406 .ThresholdValue(90.0) 407 .Direction(numeric::Direction::increasing) 408 .InitialValues({80.0, 99.0}) 409 .Updates({{0, 100.0, 100ms}, {1, 86.0, 100ms}}) 410 .Expected({{0, 100.0, 200ms}}), 411 NumericParams() 412 .DwellTime(200ms) 413 .ThresholdValue(90.0) 414 .Direction(numeric::Direction::decreasing) 415 .InitialValues({100.0}) 416 .Updates({{0, 91.0, 200ms}}) 417 .Expected({}), 418 NumericParams() 419 .DwellTime(200ms) 420 .ThresholdValue(90.0) 421 .Direction(numeric::Direction::decreasing) 422 .InitialValues({100.0}) 423 .Updates({{0, 80.0, 200ms}}) 424 .Expected({{0, 80.0, 200ms}}), 425 NumericParams() 426 .DwellTime(200ms) 427 .ThresholdValue(90.0) 428 .Direction(numeric::Direction::decreasing) 429 .InitialValues({100.0}) 430 .Updates({{0, 80.0, 200ms}, {0, 99.0, 100ms}, {0, 85.0, 200ms}}) 431 .Expected({{0, 80.0, 200ms}, {0, 85.0, 500ms}}), 432 NumericParams() 433 .DwellTime(200ms) 434 .ThresholdValue(90.0) 435 .Direction(numeric::Direction::decreasing) 436 .InitialValues({100.0, 99.0}) 437 .Updates({{0, 80.0, 200ms}, {1, 88.0, 200ms}}) 438 .Expected({{0, 80.0, 200ms}, {1, 88.0, 400ms}}), 439 NumericParams() 440 .DwellTime(200ms) 441 .ThresholdValue(90.0) 442 .Direction(numeric::Direction::either) 443 .InitialValues({98.0}) 444 .Updates({{0, 91.0, 200ms}}) 445 .Expected({}), 446 NumericParams() 447 .DwellTime(200ms) 448 .ThresholdValue(90.0) 449 .Direction(numeric::Direction::either) 450 .InitialValues({100.0}) 451 .Updates({{0, 80.0, 100ms}, {0, 85.0, 100ms}, {0, 91.0, 200ms}}) 452 .Expected({{0, 80.0, 200ms}, {0, 91.0, 400ms}}), 453 NumericParams() 454 .DwellTime(200ms) 455 .ThresholdValue(90.0) 456 .Direction(numeric::Direction::either) 457 .InitialValues({100.0, 80.0}) 458 .Updates({{0, 85.0, 100ms}, {1, 91.0, 200ms}}) 459 .Expected({{0, 85.0, 200ms}, {1, 91.0, 300ms}}))); 460 461 INSTANTIATE_TEST_SUITE_P( 462 SleepAfterLastUpdate, TestNumericThresholdWithDwellTime, 463 Values(NumericParams() 464 .DwellTime(200ms) 465 .ThresholdValue(90.0) 466 .Direction(numeric::Direction::increasing) 467 .InitialValues({80.0}) 468 .Updates({{0, 89.0, 300ms}}) 469 .Expected({}), 470 NumericParams() 471 .DwellTime(200ms) 472 .ThresholdValue(90.0) 473 .Direction(numeric::Direction::increasing) 474 .InitialValues({80.0}) 475 .Updates({{0, 91.0, 300ms}}) 476 .Expected({{0, 91.0, 200ms}}), 477 NumericParams() 478 .DwellTime(200ms) 479 .ThresholdValue(90.0) 480 .Direction(numeric::Direction::increasing) 481 .InitialValues({80.0}) 482 .Updates({{0, 99.0}, {0, 80.0}, {0, 98.0, 300ms}}) 483 .Expected({{0, 98.0, 200ms}}), 484 NumericParams() 485 .DwellTime(200ms) 486 .ThresholdValue(90.0) 487 .Direction(numeric::Direction::increasing) 488 .InitialValues({80.0, 99.0}) 489 .Updates({{0, 100.0}, {1, 98.0, 300ms}}) 490 .Expected({{0, 100.0, 200ms}}), 491 NumericParams() 492 .DwellTime(200ms) 493 .ThresholdValue(90.0) 494 .Direction(numeric::Direction::decreasing) 495 .InitialValues({100.0}) 496 .Updates({{0, 91.0, 300ms}}) 497 .Expected({}), 498 NumericParams() 499 .DwellTime(200ms) 500 .ThresholdValue(90.0) 501 .Direction(numeric::Direction::decreasing) 502 .InitialValues({100.0}) 503 .Updates({{0, 80.0, 300ms}}) 504 .Expected({{0, 80.0, 200ms}}), 505 NumericParams() 506 .DwellTime(200ms) 507 .ThresholdValue(90.0) 508 .Direction(numeric::Direction::decreasing) 509 .InitialValues({100.0}) 510 .Updates({{0, 80.0}, {0, 99.0}, {0, 85.0, 300ms}}) 511 .Expected({{0, 85.0, 200ms}}), 512 NumericParams() 513 .DwellTime(200ms) 514 .ThresholdValue(90.0) 515 .Direction(numeric::Direction::decreasing) 516 .InitialValues({100.0, 99.0}) 517 .Updates({{0, 80.0}, {1, 88.0, 300ms}}) 518 .Expected({{0, 80.0, 200ms}, {1, 88.0, 200ms}}), 519 NumericParams() 520 .DwellTime(200ms) 521 .ThresholdValue(90.0) 522 .Direction(numeric::Direction::either) 523 .InitialValues({98.0}) 524 .Updates({{0, 91.0, 300ms}}) 525 .Expected({}), 526 NumericParams() 527 .DwellTime(200ms) 528 .ThresholdValue(90.0) 529 .Direction(numeric::Direction::either) 530 .InitialValues({100.0}) 531 .Updates({{0, 80.0}, {0, 85.0}, {0, 91.0, 300ms}}) 532 .Expected({{0, 91.0, 200ms}}), 533 NumericParams() 534 .DwellTime(200ms) 535 .ThresholdValue(90.0) 536 .Direction(numeric::Direction::either) 537 .InitialValues({100.0, 80.0}) 538 .Updates({{0, 85.0}, {1, 91.0, 300ms}}) 539 .Expected({{0, 85.0, 200ms}, {1, 91.0, 200ms}}))); 540 541 TEST_P(TestNumericThresholdWithDwellTime, senorsIsUpdatedMultipleTimes) 542 { 543 testBodySensorIsUpdatedMultipleTimes(); 544 } 545