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] =
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