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 325 TEST_P(TestNumericThresholdNoDwellTime, senorsIsUpdatedMultipleTimes) 326 { 327 testBodySensorIsUpdatedMultipleTimes(); 328 } 329 330 class TestNumericThresholdWithDwellTime : public TestNumericThresholdCommon 331 { 332 public: 333 void SetUp() override 334 { 335 for (size_t idx = 0; idx < sensorMocks.size(); idx++) 336 { 337 ON_CALL(*sensorMocks.at(idx), getName()) 338 .WillByDefault(Return(sensorNames[idx])); 339 } 340 341 makeThreshold(GetParam().dwellTime, GetParam().direction, 342 GetParam().thresholdValue); 343 } 344 }; 345 346 INSTANTIATE_TEST_SUITE_P( 347 SleepAfterEveryUpdate, TestNumericThresholdWithDwellTime, 348 Values(NumericParams() 349 .DwellTime(200ms) 350 .ThresholdValue(90.0) 351 .Direction(numeric::Direction::increasing) 352 .InitialValues({80.0}) 353 .Updates({{0, 89.0, 200ms}}) 354 .Expected({}), 355 NumericParams() 356 .DwellTime(200ms) 357 .ThresholdValue(90.0) 358 .Direction(numeric::Direction::increasing) 359 .InitialValues({80.0}) 360 .Updates({{0, 91.0, 200ms}}) 361 .Expected({{0, 91.0, 200ms}}), 362 NumericParams() 363 .DwellTime(200ms) 364 .ThresholdValue(90.0) 365 .Direction(numeric::Direction::increasing) 366 .InitialValues({80.0}) 367 .Updates({{0, 99.0, 200ms}, {0, 80.0, 100ms}, {0, 98.0, 200ms}}) 368 .Expected({{0, 99.0, 200ms}, {0, 98.0, 500ms}}), 369 NumericParams() 370 .DwellTime(200ms) 371 .ThresholdValue(90.0) 372 .Direction(numeric::Direction::increasing) 373 .InitialValues({80.0, 99.0}) 374 .Updates({{0, 100.0, 100ms}, {1, 86.0, 100ms}}) 375 .Expected({{0, 100.0, 200ms}}), 376 NumericParams() 377 .DwellTime(200ms) 378 .ThresholdValue(90.0) 379 .Direction(numeric::Direction::decreasing) 380 .InitialValues({100.0}) 381 .Updates({{0, 91.0, 200ms}}) 382 .Expected({}), 383 NumericParams() 384 .DwellTime(200ms) 385 .ThresholdValue(90.0) 386 .Direction(numeric::Direction::decreasing) 387 .InitialValues({100.0}) 388 .Updates({{0, 80.0, 200ms}}) 389 .Expected({{0, 80.0, 200ms}}), 390 NumericParams() 391 .DwellTime(200ms) 392 .ThresholdValue(90.0) 393 .Direction(numeric::Direction::decreasing) 394 .InitialValues({100.0}) 395 .Updates({{0, 80.0, 200ms}, {0, 99.0, 100ms}, {0, 85.0, 200ms}}) 396 .Expected({{0, 80.0, 200ms}, {0, 85.0, 500ms}}), 397 NumericParams() 398 .DwellTime(200ms) 399 .ThresholdValue(90.0) 400 .Direction(numeric::Direction::decreasing) 401 .InitialValues({100.0, 99.0}) 402 .Updates({{0, 80.0, 200ms}, {1, 88.0, 200ms}}) 403 .Expected({{0, 80.0, 200ms}, {1, 88.0, 400ms}}), 404 NumericParams() 405 .DwellTime(200ms) 406 .ThresholdValue(90.0) 407 .Direction(numeric::Direction::either) 408 .InitialValues({98.0}) 409 .Updates({{0, 91.0, 200ms}}) 410 .Expected({}), 411 NumericParams() 412 .DwellTime(200ms) 413 .ThresholdValue(90.0) 414 .Direction(numeric::Direction::either) 415 .InitialValues({100.0}) 416 .Updates({{0, 80.0, 100ms}, {0, 85.0, 100ms}, {0, 91.0, 200ms}}) 417 .Expected({{0, 80.0, 200ms}, {0, 91.0, 400ms}}), 418 NumericParams() 419 .DwellTime(200ms) 420 .ThresholdValue(90.0) 421 .Direction(numeric::Direction::either) 422 .InitialValues({100.0, 80.0}) 423 .Updates({{0, 85.0, 100ms}, {1, 91.0, 200ms}}) 424 .Expected({{0, 85.0, 200ms}, {1, 91.0, 300ms}}))); 425 426 INSTANTIATE_TEST_SUITE_P( 427 SleepAfterLastUpdate, TestNumericThresholdWithDwellTime, 428 Values(NumericParams() 429 .DwellTime(200ms) 430 .ThresholdValue(90.0) 431 .Direction(numeric::Direction::increasing) 432 .InitialValues({80.0}) 433 .Updates({{0, 89.0, 300ms}}) 434 .Expected({}), 435 NumericParams() 436 .DwellTime(200ms) 437 .ThresholdValue(90.0) 438 .Direction(numeric::Direction::increasing) 439 .InitialValues({80.0}) 440 .Updates({{0, 91.0, 300ms}}) 441 .Expected({{0, 91.0, 200ms}}), 442 NumericParams() 443 .DwellTime(200ms) 444 .ThresholdValue(90.0) 445 .Direction(numeric::Direction::increasing) 446 .InitialValues({80.0}) 447 .Updates({{0, 99.0}, {0, 80.0}, {0, 98.0, 300ms}}) 448 .Expected({{0, 98.0, 200ms}}), 449 NumericParams() 450 .DwellTime(200ms) 451 .ThresholdValue(90.0) 452 .Direction(numeric::Direction::increasing) 453 .InitialValues({80.0, 99.0}) 454 .Updates({{0, 100.0}, {1, 98.0, 300ms}}) 455 .Expected({{0, 100.0, 200ms}}), 456 NumericParams() 457 .DwellTime(200ms) 458 .ThresholdValue(90.0) 459 .Direction(numeric::Direction::decreasing) 460 .InitialValues({100.0}) 461 .Updates({{0, 91.0, 300ms}}) 462 .Expected({}), 463 NumericParams() 464 .DwellTime(200ms) 465 .ThresholdValue(90.0) 466 .Direction(numeric::Direction::decreasing) 467 .InitialValues({100.0}) 468 .Updates({{0, 80.0, 300ms}}) 469 .Expected({{0, 80.0, 200ms}}), 470 NumericParams() 471 .DwellTime(200ms) 472 .ThresholdValue(90.0) 473 .Direction(numeric::Direction::decreasing) 474 .InitialValues({100.0}) 475 .Updates({{0, 80.0}, {0, 99.0}, {0, 85.0, 300ms}}) 476 .Expected({{0, 85.0, 200ms}}), 477 NumericParams() 478 .DwellTime(200ms) 479 .ThresholdValue(90.0) 480 .Direction(numeric::Direction::decreasing) 481 .InitialValues({100.0, 99.0}) 482 .Updates({{0, 80.0}, {1, 88.0, 300ms}}) 483 .Expected({{0, 80.0, 200ms}, {1, 88.0, 200ms}}), 484 NumericParams() 485 .DwellTime(200ms) 486 .ThresholdValue(90.0) 487 .Direction(numeric::Direction::either) 488 .InitialValues({98.0}) 489 .Updates({{0, 91.0, 300ms}}) 490 .Expected({}), 491 NumericParams() 492 .DwellTime(200ms) 493 .ThresholdValue(90.0) 494 .Direction(numeric::Direction::either) 495 .InitialValues({100.0}) 496 .Updates({{0, 80.0}, {0, 85.0}, {0, 91.0, 300ms}}) 497 .Expected({{0, 91.0, 200ms}}), 498 NumericParams() 499 .DwellTime(200ms) 500 .ThresholdValue(90.0) 501 .Direction(numeric::Direction::either) 502 .InitialValues({100.0, 80.0}) 503 .Updates({{0, 85.0}, {1, 91.0, 300ms}}) 504 .Expected({{0, 85.0, 200ms}, {1, 91.0, 200ms}}))); 505 506 TEST_P(TestNumericThresholdWithDwellTime, senorsIsUpdatedMultipleTimes) 507 { 508 testBodySensorIsUpdatedMultipleTimes(); 509 } 510