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), value(value), sleepAfter(sleepAfter) 93 {} 94 }; 95 96 struct ExpectedParams 97 { 98 size_t sensor; 99 double value; 100 Milliseconds waitMin; 101 102 ExpectedParams(size_t sensor, double value, 103 Milliseconds waitMin = 0ms) : 104 sensor(sensor), value(value), waitMin(waitMin) 105 {} 106 }; 107 108 NumericParams& Direction(numeric::Direction val) 109 { 110 direction = val; 111 return *this; 112 } 113 114 NumericParams& Updates(std::vector<UpdateParams> val) 115 { 116 updates = std::move(val); 117 return *this; 118 } 119 120 NumericParams& Expected(std::vector<ExpectedParams> val) 121 { 122 expected = std::move(val); 123 return *this; 124 } 125 126 NumericParams& ThresholdValue(double val) 127 { 128 thresholdValue = val; 129 return *this; 130 } 131 132 NumericParams& DwellTime(Milliseconds val) 133 { 134 dwellTime = std::move(val); 135 return *this; 136 } 137 138 NumericParams& InitialValues(std::vector<double> val) 139 { 140 initialValues = std::move(val); 141 return *this; 142 } 143 144 friend void PrintTo(const NumericParams& o, std::ostream* os) 145 { 146 *os << "{ DwellTime: " << o.dwellTime.count() << "ms "; 147 *os << ", ThresholdValue: " << o.thresholdValue; 148 *os << ", Direction: " << static_cast<int>(o.direction); 149 *os << ", InitialValues: [ "; 150 size_t idx = 0; 151 for (const double value : o.initialValues) 152 { 153 *os << "{ SensorIndex: " << idx << ", Value: " << value << " }, "; 154 idx++; 155 } 156 *os << " ], Updates: [ "; 157 for (const auto& [index, value, sleepAfter] : o.updates) 158 { 159 *os << "{ SensorIndex: " << index << ", Value: " << value 160 << ", SleepAfter: " << sleepAfter.count() << "ms }, "; 161 } 162 *os << " ], Expected: [ "; 163 for (const auto& [index, value, waitMin] : o.expected) 164 { 165 *os << "{ SensorIndex: " << index << ", Value: " << value 166 << ", waitMin: " << waitMin.count() << "ms }, "; 167 } 168 *os << " ] }"; 169 } 170 171 numeric::Direction direction; 172 double thresholdValue = 0.0; 173 Milliseconds dwellTime = 0ms; 174 std::vector<UpdateParams> updates; 175 std::vector<ExpectedParams> expected; 176 std::vector<double> initialValues; 177 }; 178 179 class TestNumericThresholdCommon : 180 public TestNumericThreshold, 181 public WithParamInterface<NumericParams> 182 { 183 public: 184 void sleep(Milliseconds duration) 185 { 186 if (duration != 0ms) 187 { 188 DbusEnvironment::sleepFor(duration); 189 } 190 } 191 192 void testBodySensorIsUpdatedMultipleTimes() 193 { 194 std::vector<std::chrono::time_point<std::chrono::high_resolution_clock>> 195 timestamps(sensorMocks.size()); 196 197 sut->initialize(); 198 199 InSequence seq; 200 201 for (const auto& [index, value, waitMin] : GetParam().expected) 202 { 203 EXPECT_CALL(actionMock, 204 commit(triggerId, Eq(std::nullopt), sensorNames[index], 205 _, TriggerValue(value))) 206 .WillOnce(DoAll( 207 InvokeWithoutArgs([idx = index, ×tamps] { 208 timestamps[idx] = std::chrono::high_resolution_clock::now(); 209 }), 210 InvokeWithoutArgs(DbusEnvironment::setPromise("commit")))); 211 } 212 213 auto start = std::chrono::high_resolution_clock::now(); 214 215 size_t idx = 0; 216 for (const double value : GetParam().initialValues) 217 { 218 sut->sensorUpdated(*sensorMocks[idx], 0ms, value); 219 idx++; 220 } 221 222 for (const auto& [index, value, sleepAfter] : GetParam().updates) 223 { 224 ASSERT_LT(index, GetParam().initialValues.size()) 225 << "Initial value was not specified for sensor with index: " 226 << index; 227 sut->sensorUpdated(*sensorMocks[index], 42ms, value); 228 sleep(sleepAfter); 229 } 230 231 EXPECT_THAT(DbusEnvironment::waitForFutures("commit"), true); 232 for (const auto& [index, value, waitMin] : GetParam().expected) 233 { 234 EXPECT_THAT(timestamps[index] - start, Ge(waitMin)); 235 } 236 } 237 }; 238 239 class TestNumericThresholdNoDwellTime : public TestNumericThresholdCommon 240 { 241 public: 242 void SetUp() override 243 { 244 for (size_t idx = 0; idx < sensorMocks.size(); idx++) 245 { 246 ON_CALL(*sensorMocks.at(idx), getName()) 247 .WillByDefault(Return(sensorNames[idx])); 248 } 249 250 makeThreshold(0ms, GetParam().direction, GetParam().thresholdValue); 251 } 252 }; 253 254 INSTANTIATE_TEST_SUITE_P(_, TestNumericThresholdNoDwellTime, 255 Values(NumericParams() 256 .ThresholdValue(90.0) 257 .Direction(numeric::Direction::increasing) 258 .InitialValues({80.0}) 259 .Updates({{0, 89.0}}) 260 .Expected({}), 261 NumericParams() 262 .ThresholdValue(90.0) 263 .Direction(numeric::Direction::increasing) 264 .InitialValues({80.0}) 265 .Updates({{0, 91.0}}) 266 .Expected({{0, 91.0}}), 267 NumericParams() 268 .ThresholdValue(90.0) 269 .Direction(numeric::Direction::increasing) 270 .InitialValues({80.0}) 271 .Updates({{0, 99.0}, {0, 80.0}, {0, 98.0}}) 272 .Expected({{0, 99.0}, {0, 98.0}}), 273 NumericParams() 274 .ThresholdValue(90.0) 275 .Direction(numeric::Direction::increasing) 276 .InitialValues({80.0, 100.0}) 277 .Updates({{0, 99.0}, {1, 98.0}}) 278 .Expected({{0, 99.0}}), 279 NumericParams() 280 .ThresholdValue(90.0) 281 .Direction(numeric::Direction::decreasing) 282 .InitialValues({100.0}) 283 .Updates({{0, 91.0}}) 284 .Expected({}), 285 NumericParams() 286 .ThresholdValue(90.0) 287 .Direction(numeric::Direction::decreasing) 288 .InitialValues({100.0}) 289 .Updates({{0, 80.0}}) 290 .Expected({{0, 80.0}}), 291 NumericParams() 292 .ThresholdValue(90.0) 293 .Direction(numeric::Direction::decreasing) 294 .InitialValues({100.0}) 295 .Updates({{0, 80.0}, {0, 99.0}, {0, 85.0}}) 296 .Expected({{0, 80.0}, {0, 85.0}}), 297 NumericParams() 298 .ThresholdValue(90.0) 299 .Direction(numeric::Direction::decreasing) 300 .InitialValues({100.0, 99.0}) 301 .Updates({{0, 80.0}, {1, 88.0}}) 302 .Expected({{0, 80.0}, {1, 88.0}}), 303 NumericParams() 304 .ThresholdValue(90.0) 305 .Direction(numeric::Direction::either) 306 .InitialValues({98.0}) 307 .Updates({{0, 91.0}}) 308 .Expected({}), 309 NumericParams() 310 .ThresholdValue(90.0) 311 .Direction(numeric::Direction::either) 312 .InitialValues({100.0}) 313 .Updates({{0, 80.0}, {0, 85.0}, {0, 91.0}}) 314 .Expected({{0, 80.0}, {0, 91.0}}), 315 NumericParams() 316 .ThresholdValue(90.0) 317 .Direction(numeric::Direction::either) 318 .InitialValues({100.0, 80.0}) 319 .Updates({{0, 85.0}, {1, 91.0}}) 320 .Expected({{0, 85.0}, {1, 91.0}}), 321 NumericParams() 322 .ThresholdValue(30.0) 323 .Direction(numeric::Direction::decreasing) 324 .InitialValues({40.0}) 325 .Updates({{0, 30.0}, {0, 20.0}}) 326 .Expected({{0, 20.0}}), 327 NumericParams() 328 .ThresholdValue(30.0) 329 .Direction(numeric::Direction::decreasing) 330 .InitialValues({20.0}) 331 .Updates({{0, 30.0}, {0, 20.0}}) 332 .Expected({}), 333 NumericParams() 334 .ThresholdValue(30.0) 335 .Direction(numeric::Direction::either) 336 .InitialValues({20.0}) 337 .Updates({{0, 30.0}, {0, 20.0}}) 338 .Expected({}), 339 NumericParams() 340 .ThresholdValue(30.0) 341 .Direction(numeric::Direction::increasing) 342 .InitialValues({20.0}) 343 .Updates({{0, 30.0}, {0, 40.0}}) 344 .Expected({{0, 40.0}}), 345 NumericParams() 346 .ThresholdValue(30.0) 347 .Direction(numeric::Direction::increasing) 348 .InitialValues({40.0}) 349 .Updates({{0, 30.0}, {0, 40.0}}) 350 .Expected({}), 351 NumericParams() 352 .ThresholdValue(30.0) 353 .Direction(numeric::Direction::either) 354 .InitialValues({40.0}) 355 .Updates({{0, 30.0}, {0, 40.0}}) 356 .Expected({}))); 357 358 TEST_P(TestNumericThresholdNoDwellTime, senorsIsUpdatedMultipleTimes) 359 { 360 testBodySensorIsUpdatedMultipleTimes(); 361 } 362 363 class TestNumericThresholdWithDwellTime : public TestNumericThresholdCommon 364 { 365 public: 366 void SetUp() override 367 { 368 for (size_t idx = 0; idx < sensorMocks.size(); idx++) 369 { 370 ON_CALL(*sensorMocks.at(idx), getName()) 371 .WillByDefault(Return(sensorNames[idx])); 372 } 373 374 makeThreshold(GetParam().dwellTime, GetParam().direction, 375 GetParam().thresholdValue); 376 } 377 }; 378 379 INSTANTIATE_TEST_SUITE_P( 380 SleepAfterEveryUpdate, TestNumericThresholdWithDwellTime, 381 Values(NumericParams() 382 .DwellTime(200ms) 383 .ThresholdValue(90.0) 384 .Direction(numeric::Direction::increasing) 385 .InitialValues({80.0}) 386 .Updates({{0, 89.0, 200ms}}) 387 .Expected({}), 388 NumericParams() 389 .DwellTime(200ms) 390 .ThresholdValue(90.0) 391 .Direction(numeric::Direction::increasing) 392 .InitialValues({80.0}) 393 .Updates({{0, 91.0, 200ms}}) 394 .Expected({{0, 91.0, 200ms}}), 395 NumericParams() 396 .DwellTime(200ms) 397 .ThresholdValue(90.0) 398 .Direction(numeric::Direction::increasing) 399 .InitialValues({80.0}) 400 .Updates({{0, 99.0, 200ms}, {0, 80.0, 100ms}, {0, 98.0, 200ms}}) 401 .Expected({{0, 99.0, 200ms}, {0, 98.0, 500ms}}), 402 NumericParams() 403 .DwellTime(200ms) 404 .ThresholdValue(90.0) 405 .Direction(numeric::Direction::increasing) 406 .InitialValues({80.0, 99.0}) 407 .Updates({{0, 100.0, 100ms}, {1, 86.0, 100ms}}) 408 .Expected({{0, 100.0, 200ms}}), 409 NumericParams() 410 .DwellTime(200ms) 411 .ThresholdValue(90.0) 412 .Direction(numeric::Direction::decreasing) 413 .InitialValues({100.0}) 414 .Updates({{0, 91.0, 200ms}}) 415 .Expected({}), 416 NumericParams() 417 .DwellTime(200ms) 418 .ThresholdValue(90.0) 419 .Direction(numeric::Direction::decreasing) 420 .InitialValues({100.0}) 421 .Updates({{0, 80.0, 200ms}}) 422 .Expected({{0, 80.0, 200ms}}), 423 NumericParams() 424 .DwellTime(200ms) 425 .ThresholdValue(90.0) 426 .Direction(numeric::Direction::decreasing) 427 .InitialValues({100.0}) 428 .Updates({{0, 80.0, 200ms}, {0, 99.0, 100ms}, {0, 85.0, 200ms}}) 429 .Expected({{0, 80.0, 200ms}, {0, 85.0, 500ms}}), 430 NumericParams() 431 .DwellTime(200ms) 432 .ThresholdValue(90.0) 433 .Direction(numeric::Direction::decreasing) 434 .InitialValues({100.0, 99.0}) 435 .Updates({{0, 80.0, 200ms}, {1, 88.0, 200ms}}) 436 .Expected({{0, 80.0, 200ms}, {1, 88.0, 400ms}}), 437 NumericParams() 438 .DwellTime(200ms) 439 .ThresholdValue(90.0) 440 .Direction(numeric::Direction::either) 441 .InitialValues({98.0}) 442 .Updates({{0, 91.0, 200ms}}) 443 .Expected({}), 444 NumericParams() 445 .DwellTime(200ms) 446 .ThresholdValue(90.0) 447 .Direction(numeric::Direction::either) 448 .InitialValues({100.0}) 449 .Updates({{0, 80.0, 100ms}, {0, 85.0, 100ms}, {0, 91.0, 200ms}}) 450 .Expected({{0, 80.0, 200ms}, {0, 91.0, 400ms}}), 451 NumericParams() 452 .DwellTime(200ms) 453 .ThresholdValue(90.0) 454 .Direction(numeric::Direction::either) 455 .InitialValues({100.0, 80.0}) 456 .Updates({{0, 85.0, 100ms}, {1, 91.0, 200ms}}) 457 .Expected({{0, 85.0, 200ms}, {1, 91.0, 300ms}}))); 458 459 INSTANTIATE_TEST_SUITE_P( 460 SleepAfterLastUpdate, TestNumericThresholdWithDwellTime, 461 Values(NumericParams() 462 .DwellTime(200ms) 463 .ThresholdValue(90.0) 464 .Direction(numeric::Direction::increasing) 465 .InitialValues({80.0}) 466 .Updates({{0, 89.0, 300ms}}) 467 .Expected({}), 468 NumericParams() 469 .DwellTime(200ms) 470 .ThresholdValue(90.0) 471 .Direction(numeric::Direction::increasing) 472 .InitialValues({80.0}) 473 .Updates({{0, 91.0, 300ms}}) 474 .Expected({{0, 91.0, 200ms}}), 475 NumericParams() 476 .DwellTime(200ms) 477 .ThresholdValue(90.0) 478 .Direction(numeric::Direction::increasing) 479 .InitialValues({80.0}) 480 .Updates({{0, 99.0}, {0, 80.0}, {0, 98.0, 300ms}}) 481 .Expected({{0, 98.0, 200ms}}), 482 NumericParams() 483 .DwellTime(200ms) 484 .ThresholdValue(90.0) 485 .Direction(numeric::Direction::increasing) 486 .InitialValues({80.0, 99.0}) 487 .Updates({{0, 100.0}, {1, 98.0, 300ms}}) 488 .Expected({{0, 100.0, 200ms}}), 489 NumericParams() 490 .DwellTime(200ms) 491 .ThresholdValue(90.0) 492 .Direction(numeric::Direction::decreasing) 493 .InitialValues({100.0}) 494 .Updates({{0, 91.0, 300ms}}) 495 .Expected({}), 496 NumericParams() 497 .DwellTime(200ms) 498 .ThresholdValue(90.0) 499 .Direction(numeric::Direction::decreasing) 500 .InitialValues({100.0}) 501 .Updates({{0, 80.0, 300ms}}) 502 .Expected({{0, 80.0, 200ms}}), 503 NumericParams() 504 .DwellTime(200ms) 505 .ThresholdValue(90.0) 506 .Direction(numeric::Direction::decreasing) 507 .InitialValues({100.0}) 508 .Updates({{0, 80.0}, {0, 99.0}, {0, 85.0, 300ms}}) 509 .Expected({{0, 85.0, 200ms}}), 510 NumericParams() 511 .DwellTime(200ms) 512 .ThresholdValue(90.0) 513 .Direction(numeric::Direction::decreasing) 514 .InitialValues({100.0, 99.0}) 515 .Updates({{0, 80.0}, {1, 88.0, 300ms}}) 516 .Expected({{0, 80.0, 200ms}, {1, 88.0, 200ms}}), 517 NumericParams() 518 .DwellTime(200ms) 519 .ThresholdValue(90.0) 520 .Direction(numeric::Direction::either) 521 .InitialValues({98.0}) 522 .Updates({{0, 91.0, 300ms}}) 523 .Expected({}), 524 NumericParams() 525 .DwellTime(200ms) 526 .ThresholdValue(90.0) 527 .Direction(numeric::Direction::either) 528 .InitialValues({100.0}) 529 .Updates({{0, 80.0}, {0, 85.0}, {0, 91.0, 300ms}}) 530 .Expected({{0, 91.0, 200ms}}), 531 NumericParams() 532 .DwellTime(200ms) 533 .ThresholdValue(90.0) 534 .Direction(numeric::Direction::either) 535 .InitialValues({100.0, 80.0}) 536 .Updates({{0, 85.0}, {1, 91.0, 300ms}}) 537 .Expected({{0, 85.0, 200ms}, {1, 91.0, 200ms}}))); 538 539 TEST_P(TestNumericThresholdWithDwellTime, senorsIsUpdatedMultipleTimes) 540 { 541 testBodySensorIsUpdatedMultipleTimes(); 542 } 543