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] = 211 std::chrono::high_resolution_clock::now(); 212 }), 213 InvokeWithoutArgs(DbusEnvironment::setPromise("commit")))); 214 } 215 216 auto start = std::chrono::high_resolution_clock::now(); 217 218 size_t idx = 0; 219 for (const double value : GetParam().initialValues) 220 { 221 sut->sensorUpdated(*sensorMocks[idx], 0ms, value); 222 idx++; 223 } 224 225 for (const auto& [index, value, sleepAfter] : GetParam().updates) 226 { 227 ASSERT_LT(index, GetParam().initialValues.size()) 228 << "Initial value was not specified for sensor with index: " 229 << index; 230 sut->sensorUpdated(*sensorMocks[index], 42ms, value); 231 sleep(sleepAfter); 232 } 233 234 EXPECT_THAT(DbusEnvironment::waitForFutures("commit"), true); 235 for (const auto& [index, value, waitMin] : GetParam().expected) 236 { 237 EXPECT_THAT(timestamps[index] - start, Ge(waitMin)); 238 } 239 } 240 }; 241 242 class TestNumericThresholdNoDwellTime : public TestNumericThresholdCommon 243 { 244 public: 245 void SetUp() override 246 { 247 for (size_t idx = 0; idx < sensorMocks.size(); idx++) 248 { 249 ON_CALL(*sensorMocks.at(idx), getName()) 250 .WillByDefault(Return(sensorNames[idx])); 251 } 252 253 makeThreshold(0ms, GetParam().direction, GetParam().thresholdValue); 254 } 255 }; 256 257 INSTANTIATE_TEST_SUITE_P(_, TestNumericThresholdNoDwellTime, 258 Values(NumericParams() 259 .ThresholdValue(90.0) 260 .Direction(numeric::Direction::increasing) 261 .InitialValues({80.0}) 262 .Updates({{0, 89.0}}) 263 .Expected({}), 264 NumericParams() 265 .ThresholdValue(90.0) 266 .Direction(numeric::Direction::increasing) 267 .InitialValues({80.0}) 268 .Updates({{0, 91.0}}) 269 .Expected({{0, 91.0}}), 270 NumericParams() 271 .ThresholdValue(90.0) 272 .Direction(numeric::Direction::increasing) 273 .InitialValues({80.0}) 274 .Updates({{0, 99.0}, {0, 80.0}, {0, 98.0}}) 275 .Expected({{0, 99.0}, {0, 98.0}}), 276 NumericParams() 277 .ThresholdValue(90.0) 278 .Direction(numeric::Direction::increasing) 279 .InitialValues({80.0, 100.0}) 280 .Updates({{0, 99.0}, {1, 98.0}}) 281 .Expected({{0, 99.0}}), 282 NumericParams() 283 .ThresholdValue(90.0) 284 .Direction(numeric::Direction::decreasing) 285 .InitialValues({100.0}) 286 .Updates({{0, 91.0}}) 287 .Expected({}), 288 NumericParams() 289 .ThresholdValue(90.0) 290 .Direction(numeric::Direction::decreasing) 291 .InitialValues({100.0}) 292 .Updates({{0, 80.0}}) 293 .Expected({{0, 80.0}}), 294 NumericParams() 295 .ThresholdValue(90.0) 296 .Direction(numeric::Direction::decreasing) 297 .InitialValues({100.0}) 298 .Updates({{0, 80.0}, {0, 99.0}, {0, 85.0}}) 299 .Expected({{0, 80.0}, {0, 85.0}}), 300 NumericParams() 301 .ThresholdValue(90.0) 302 .Direction(numeric::Direction::decreasing) 303 .InitialValues({100.0, 99.0}) 304 .Updates({{0, 80.0}, {1, 88.0}}) 305 .Expected({{0, 80.0}, {1, 88.0}}), 306 NumericParams() 307 .ThresholdValue(90.0) 308 .Direction(numeric::Direction::either) 309 .InitialValues({98.0}) 310 .Updates({{0, 91.0}}) 311 .Expected({}), 312 NumericParams() 313 .ThresholdValue(90.0) 314 .Direction(numeric::Direction::either) 315 .InitialValues({100.0}) 316 .Updates({{0, 80.0}, {0, 85.0}, {0, 91.0}}) 317 .Expected({{0, 80.0}, {0, 91.0}}), 318 NumericParams() 319 .ThresholdValue(90.0) 320 .Direction(numeric::Direction::either) 321 .InitialValues({100.0, 80.0}) 322 .Updates({{0, 85.0}, {1, 91.0}}) 323 .Expected({{0, 85.0}, {1, 91.0}}), 324 NumericParams() 325 .ThresholdValue(30.0) 326 .Direction(numeric::Direction::decreasing) 327 .InitialValues({40.0}) 328 .Updates({{0, 30.0}, {0, 20.0}}) 329 .Expected({{0, 20.0}}), 330 NumericParams() 331 .ThresholdValue(30.0) 332 .Direction(numeric::Direction::decreasing) 333 .InitialValues({20.0}) 334 .Updates({{0, 30.0}, {0, 20.0}}) 335 .Expected({}), 336 NumericParams() 337 .ThresholdValue(30.0) 338 .Direction(numeric::Direction::either) 339 .InitialValues({20.0}) 340 .Updates({{0, 30.0}, {0, 20.0}}) 341 .Expected({}), 342 NumericParams() 343 .ThresholdValue(30.0) 344 .Direction(numeric::Direction::increasing) 345 .InitialValues({20.0}) 346 .Updates({{0, 30.0}, {0, 40.0}}) 347 .Expected({{0, 40.0}}), 348 NumericParams() 349 .ThresholdValue(30.0) 350 .Direction(numeric::Direction::increasing) 351 .InitialValues({40.0}) 352 .Updates({{0, 30.0}, {0, 40.0}}) 353 .Expected({}), 354 NumericParams() 355 .ThresholdValue(30.0) 356 .Direction(numeric::Direction::either) 357 .InitialValues({40.0}) 358 .Updates({{0, 30.0}, {0, 40.0}}) 359 .Expected({}))); 360 361 TEST_P(TestNumericThresholdNoDwellTime, senorsIsUpdatedMultipleTimes) 362 { 363 testBodySensorIsUpdatedMultipleTimes(); 364 } 365 366 class TestNumericThresholdWithDwellTime : public TestNumericThresholdCommon 367 { 368 public: 369 void SetUp() override 370 { 371 for (size_t idx = 0; idx < sensorMocks.size(); idx++) 372 { 373 ON_CALL(*sensorMocks.at(idx), getName()) 374 .WillByDefault(Return(sensorNames[idx])); 375 } 376 377 makeThreshold(GetParam().dwellTime, GetParam().direction, 378 GetParam().thresholdValue); 379 } 380 }; 381 382 INSTANTIATE_TEST_SUITE_P( 383 SleepAfterEveryUpdate, TestNumericThresholdWithDwellTime, 384 Values(NumericParams() 385 .DwellTime(200ms) 386 .ThresholdValue(90.0) 387 .Direction(numeric::Direction::increasing) 388 .InitialValues({80.0}) 389 .Updates({{0, 89.0, 200ms}}) 390 .Expected({}), 391 NumericParams() 392 .DwellTime(200ms) 393 .ThresholdValue(90.0) 394 .Direction(numeric::Direction::increasing) 395 .InitialValues({80.0}) 396 .Updates({{0, 91.0, 200ms}}) 397 .Expected({{0, 91.0, 200ms}}), 398 NumericParams() 399 .DwellTime(200ms) 400 .ThresholdValue(90.0) 401 .Direction(numeric::Direction::increasing) 402 .InitialValues({80.0}) 403 .Updates({{0, 99.0, 200ms}, {0, 80.0, 100ms}, {0, 98.0, 200ms}}) 404 .Expected({{0, 99.0, 200ms}, {0, 98.0, 500ms}}), 405 NumericParams() 406 .DwellTime(200ms) 407 .ThresholdValue(90.0) 408 .Direction(numeric::Direction::increasing) 409 .InitialValues({80.0, 99.0}) 410 .Updates({{0, 100.0, 100ms}, {1, 86.0, 100ms}}) 411 .Expected({{0, 100.0, 200ms}}), 412 NumericParams() 413 .DwellTime(200ms) 414 .ThresholdValue(90.0) 415 .Direction(numeric::Direction::decreasing) 416 .InitialValues({100.0}) 417 .Updates({{0, 91.0, 200ms}}) 418 .Expected({}), 419 NumericParams() 420 .DwellTime(200ms) 421 .ThresholdValue(90.0) 422 .Direction(numeric::Direction::decreasing) 423 .InitialValues({100.0}) 424 .Updates({{0, 80.0, 200ms}}) 425 .Expected({{0, 80.0, 200ms}}), 426 NumericParams() 427 .DwellTime(200ms) 428 .ThresholdValue(90.0) 429 .Direction(numeric::Direction::decreasing) 430 .InitialValues({100.0}) 431 .Updates({{0, 80.0, 200ms}, {0, 99.0, 100ms}, {0, 85.0, 200ms}}) 432 .Expected({{0, 80.0, 200ms}, {0, 85.0, 500ms}}), 433 NumericParams() 434 .DwellTime(200ms) 435 .ThresholdValue(90.0) 436 .Direction(numeric::Direction::decreasing) 437 .InitialValues({100.0, 99.0}) 438 .Updates({{0, 80.0, 200ms}, {1, 88.0, 200ms}}) 439 .Expected({{0, 80.0, 200ms}, {1, 88.0, 400ms}}), 440 NumericParams() 441 .DwellTime(200ms) 442 .ThresholdValue(90.0) 443 .Direction(numeric::Direction::either) 444 .InitialValues({98.0}) 445 .Updates({{0, 91.0, 200ms}}) 446 .Expected({}), 447 NumericParams() 448 .DwellTime(200ms) 449 .ThresholdValue(90.0) 450 .Direction(numeric::Direction::either) 451 .InitialValues({100.0}) 452 .Updates({{0, 80.0, 100ms}, {0, 85.0, 100ms}, {0, 91.0, 200ms}}) 453 .Expected({{0, 80.0, 200ms}, {0, 91.0, 400ms}}), 454 NumericParams() 455 .DwellTime(200ms) 456 .ThresholdValue(90.0) 457 .Direction(numeric::Direction::either) 458 .InitialValues({100.0, 80.0}) 459 .Updates({{0, 85.0, 100ms}, {1, 91.0, 200ms}}) 460 .Expected({{0, 85.0, 200ms}, {1, 91.0, 300ms}}))); 461 462 INSTANTIATE_TEST_SUITE_P( 463 SleepAfterLastUpdate, TestNumericThresholdWithDwellTime, 464 Values(NumericParams() 465 .DwellTime(200ms) 466 .ThresholdValue(90.0) 467 .Direction(numeric::Direction::increasing) 468 .InitialValues({80.0}) 469 .Updates({{0, 89.0, 300ms}}) 470 .Expected({}), 471 NumericParams() 472 .DwellTime(200ms) 473 .ThresholdValue(90.0) 474 .Direction(numeric::Direction::increasing) 475 .InitialValues({80.0}) 476 .Updates({{0, 91.0, 300ms}}) 477 .Expected({{0, 91.0, 200ms}}), 478 NumericParams() 479 .DwellTime(200ms) 480 .ThresholdValue(90.0) 481 .Direction(numeric::Direction::increasing) 482 .InitialValues({80.0}) 483 .Updates({{0, 99.0}, {0, 80.0}, {0, 98.0, 300ms}}) 484 .Expected({{0, 98.0, 200ms}}), 485 NumericParams() 486 .DwellTime(200ms) 487 .ThresholdValue(90.0) 488 .Direction(numeric::Direction::increasing) 489 .InitialValues({80.0, 99.0}) 490 .Updates({{0, 100.0}, {1, 98.0, 300ms}}) 491 .Expected({{0, 100.0, 200ms}}), 492 NumericParams() 493 .DwellTime(200ms) 494 .ThresholdValue(90.0) 495 .Direction(numeric::Direction::decreasing) 496 .InitialValues({100.0}) 497 .Updates({{0, 91.0, 300ms}}) 498 .Expected({}), 499 NumericParams() 500 .DwellTime(200ms) 501 .ThresholdValue(90.0) 502 .Direction(numeric::Direction::decreasing) 503 .InitialValues({100.0}) 504 .Updates({{0, 80.0, 300ms}}) 505 .Expected({{0, 80.0, 200ms}}), 506 NumericParams() 507 .DwellTime(200ms) 508 .ThresholdValue(90.0) 509 .Direction(numeric::Direction::decreasing) 510 .InitialValues({100.0}) 511 .Updates({{0, 80.0}, {0, 99.0}, {0, 85.0, 300ms}}) 512 .Expected({{0, 85.0, 200ms}}), 513 NumericParams() 514 .DwellTime(200ms) 515 .ThresholdValue(90.0) 516 .Direction(numeric::Direction::decreasing) 517 .InitialValues({100.0, 99.0}) 518 .Updates({{0, 80.0}, {1, 88.0, 300ms}}) 519 .Expected({{0, 80.0, 200ms}, {1, 88.0, 200ms}}), 520 NumericParams() 521 .DwellTime(200ms) 522 .ThresholdValue(90.0) 523 .Direction(numeric::Direction::either) 524 .InitialValues({98.0}) 525 .Updates({{0, 91.0, 300ms}}) 526 .Expected({}), 527 NumericParams() 528 .DwellTime(200ms) 529 .ThresholdValue(90.0) 530 .Direction(numeric::Direction::either) 531 .InitialValues({100.0}) 532 .Updates({{0, 80.0}, {0, 85.0}, {0, 91.0, 300ms}}) 533 .Expected({{0, 91.0, 200ms}}), 534 NumericParams() 535 .DwellTime(200ms) 536 .ThresholdValue(90.0) 537 .Direction(numeric::Direction::either) 538 .InitialValues({100.0, 80.0}) 539 .Updates({{0, 85.0}, {1, 91.0, 300ms}}) 540 .Expected({{0, 85.0, 200ms}, {1, 91.0, 200ms}}))); 541 542 TEST_P(TestNumericThresholdWithDwellTime, senorsIsUpdatedMultipleTimes) 543 { 544 testBodySensorIsUpdatedMultipleTimes(); 545 } 546