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