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, &timestamps] {
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