xref: /openbmc/telemetry/tests/src/test_discrete_threshold.cpp (revision 89cf7d7dafe2c073d781a40d59fa9c46cb344be6)
1 #include "dbus_environment.hpp"
2 #include "discrete_threshold.hpp"
3 #include "helpers.hpp"
4 #include "mocks/clock_mock.hpp"
5 #include "mocks/sensor_mock.hpp"
6 #include "mocks/trigger_action_mock.hpp"
7 #include "types/duration_types.hpp"
8 #include "utils/conv_container.hpp"
9 
10 #include <gmock/gmock.h>
11 
12 using namespace testing;
13 using namespace std::chrono_literals;
14 
15 class TestDiscreteThreshold : public Test
16 {
17   public:
18     std::vector<std::shared_ptr<SensorMock>> sensorMocks = {
19         std::make_shared<NiceMock<SensorMock>>(),
20         std::make_shared<NiceMock<SensorMock>>()};
21     std::vector<std::string> sensorNames = {"Sensor1", "Sensor2"};
22     std::unique_ptr<TriggerActionMock> actionMockPtr =
23         std::make_unique<StrictMock<TriggerActionMock>>();
24     TriggerActionMock& actionMock = *actionMockPtr;
25     std::shared_ptr<DiscreteThreshold> sut;
26     std::string triggerId = "MyTrigger";
27     std::unique_ptr<NiceMock<ClockMock>> clockMockPtr =
28         std::make_unique<NiceMock<ClockMock>>();
29 
30     std::shared_ptr<DiscreteThreshold>
31         makeThreshold(Milliseconds dwellTime, std::string thresholdValue,
32                       discrete::Severity severity = discrete::Severity::ok,
33                       std::string thresholdName = "treshold name")
34     {
35         std::vector<std::unique_ptr<interfaces::TriggerAction>> actions;
36         actions.push_back(std::move(actionMockPtr));
37 
38         return std::make_shared<DiscreteThreshold>(
39             DbusEnvironment::getIoc(), triggerId,
40             utils::convContainer<std::shared_ptr<interfaces::Sensor>>(
41                 sensorMocks),
42             std::move(actions), dwellTime, thresholdValue, thresholdName,
43             severity, std::move(clockMockPtr));
44     }
45 
46     void SetUp() override
47     {
48         for (size_t idx = 0; idx < sensorMocks.size(); idx++)
49         {
50             ON_CALL(*sensorMocks.at(idx), getName())
51                 .WillByDefault(Return(sensorNames[idx]));
52         }
53 
54         sut = makeThreshold(0ms, "90.0", discrete::Severity::critical);
55     }
56 };
57 
58 TEST_F(TestDiscreteThreshold, initializeThresholdExpectAllSensorsAreRegistered)
59 {
60     for (auto& sensor : sensorMocks)
61     {
62         EXPECT_CALL(*sensor,
63                     registerForUpdates(Truly([sut = sut.get()](const auto& x) {
64                         return x.lock().get() == sut;
65                     })));
66     }
67 
68     sut->initialize();
69 }
70 
71 TEST_F(TestDiscreteThreshold, thresholdIsNotInitializeExpectNoActionCommit)
72 {
73     EXPECT_CALL(actionMock, commit(_, _, _, _, _)).Times(0);
74 }
75 
76 class TestDiscreteThresholdValues :
77     public TestDiscreteThreshold,
78     public WithParamInterface<std::string>
79 {};
80 
81 INSTANTIATE_TEST_SUITE_P(_, TestDiscreteThresholdValues,
82                          Values("90", ".90", "90.123", "0.0"));
83 
84 TEST_P(TestDiscreteThresholdValues, thresholdValueIsNumericAndStoredCorrectly)
85 {
86     sut = makeThreshold(0ms, GetParam(), discrete::Severity::critical);
87     LabeledThresholdParam expected = discrete::LabeledThresholdParam(
88         "treshold name", discrete::Severity::critical, 0, GetParam());
89     EXPECT_EQ(sut->getThresholdParam(), expected);
90 }
91 
92 class TestBadDiscreteThresholdValues :
93     public TestDiscreteThreshold,
94     public WithParamInterface<std::string>
95 {};
96 
97 INSTANTIATE_TEST_SUITE_P(_, TestBadDiscreteThresholdValues,
98                          Values("90ad", "ab.90", "x90", "On", "Off", ""));
99 
100 TEST_P(TestBadDiscreteThresholdValues, throwsWhenNotNumericValues)
101 {
102     EXPECT_THROW(makeThreshold(0ms, GetParam()), std::invalid_argument);
103 }
104 
105 class TestDiscreteThresholdInit : public TestDiscreteThreshold
106 {
107     void SetUp() override
108     {}
109 };
110 
111 TEST_F(TestDiscreteThresholdInit, nonEmptyNameIsNotChanged)
112 {
113     auto sut = makeThreshold(0ms, "12.3", discrete::Severity::ok, "non-empty");
114     EXPECT_THAT(
115         std::get<discrete::LabeledThresholdParam>(sut->getThresholdParam())
116             .at_label<utils::tstring::UserId>(),
117         Eq("non-empty"));
118 }
119 
120 TEST_F(TestDiscreteThresholdInit, emptyNameIsChanged)
121 {
122     auto sut = makeThreshold(0ms, "12.3", discrete::Severity::ok, "");
123     EXPECT_THAT(
124         std::get<discrete::LabeledThresholdParam>(sut->getThresholdParam())
125             .at_label<utils::tstring::UserId>(),
126         Not(Eq("")));
127 }
128 
129 struct DiscreteParams
130 {
131     struct UpdateParams
132     {
133         size_t sensor;
134         double value;
135         Milliseconds sleepAfter;
136 
137         UpdateParams(size_t sensor, double value,
138                      Milliseconds sleepAfter = 0ms) :
139             sensor(sensor),
140             value(value), sleepAfter(sleepAfter)
141         {}
142     };
143 
144     struct ExpectedParams
145     {
146         size_t sensor;
147         double value;
148         Milliseconds waitMin;
149 
150         ExpectedParams(size_t sensor, double value,
151                        Milliseconds waitMin = 0ms) :
152             sensor(sensor),
153             value(value), waitMin(waitMin)
154         {}
155     };
156 
157     DiscreteParams& Updates(std::vector<UpdateParams> val)
158     {
159         updates = std::move(val);
160         return *this;
161     }
162 
163     DiscreteParams& Expected(std::vector<ExpectedParams> val)
164     {
165         expected = std::move(val);
166         return *this;
167     }
168 
169     DiscreteParams& ThresholdValue(std::string val)
170     {
171         thresholdValue = std::move(val);
172         return *this;
173     }
174 
175     DiscreteParams& DwellTime(Milliseconds val)
176     {
177         dwellTime = std::move(val);
178         return *this;
179     }
180 
181     friend void PrintTo(const DiscreteParams& o, std::ostream* os)
182     {
183         *os << "{ DwellTime: " << o.dwellTime.count() << "ms ";
184         *os << ", ThresholdValue: " << o.thresholdValue;
185         *os << ", Updates: [ ";
186         for (const auto& [index, value, sleepAfter] : o.updates)
187         {
188             *os << "{ SensorIndex: " << index << ", Value: " << value
189                 << ", SleepAfter: " << sleepAfter.count() << "ms }, ";
190         }
191         *os << " ] Expected: [ ";
192         for (const auto& [index, value, waitMin] : o.expected)
193         {
194             *os << "{ SensorIndex: " << index << ", Value: " << value
195                 << ", waitMin: " << waitMin.count() << "ms }, ";
196         }
197         *os << " ] }";
198     }
199 
200     std::vector<UpdateParams> updates;
201     std::vector<ExpectedParams> expected;
202     std::string thresholdValue = "0.0";
203     Milliseconds dwellTime = 0ms;
204 };
205 
206 class TestDiscreteThresholdCommon :
207     public TestDiscreteThreshold,
208     public WithParamInterface<DiscreteParams>
209 {
210   public:
211     void sleep(Milliseconds duration)
212     {
213         if (duration != 0ms)
214         {
215             DbusEnvironment::sleepFor(duration);
216         }
217     }
218 
219     void testBodySensorIsUpdatedMultipleTimes()
220     {
221         std::vector<std::chrono::time_point<std::chrono::high_resolution_clock>>
222             timestamps(sensorMocks.size());
223 
224         sut->initialize();
225 
226         InSequence seq;
227 
228         for (const auto& [index, value, waitMin] : GetParam().expected)
229         {
230             EXPECT_CALL(actionMock,
231                         commit(triggerId, Optional(StrEq("treshold name")),
232                                sensorNames[index], _,
233                                TriggerValue(GetParam().thresholdValue)))
234                 .WillOnce(DoAll(
235                     InvokeWithoutArgs([idx = index, &timestamps] {
236                         timestamps[idx] =
237                             std::chrono::high_resolution_clock::now();
238                     }),
239                     InvokeWithoutArgs(DbusEnvironment::setPromise("commit"))));
240         }
241 
242         auto start = std::chrono::high_resolution_clock::now();
243 
244         for (const auto& [index, value, sleepAfter] : GetParam().updates)
245         {
246             sut->sensorUpdated(*sensorMocks[index], 42ms, value);
247             sleep(sleepAfter);
248         }
249 
250         EXPECT_THAT(DbusEnvironment::waitForFutures("commit"), true);
251         for (const auto& [index, value, waitMin] : GetParam().expected)
252         {
253             EXPECT_THAT(timestamps[index] - start, Ge(waitMin));
254         }
255     }
256 };
257 
258 class TestDiscreteThresholdNoDwellTime : public TestDiscreteThresholdCommon
259 {
260   public:
261     void SetUp() override
262     {
263         for (size_t idx = 0; idx < sensorMocks.size(); idx++)
264         {
265             ON_CALL(*sensorMocks.at(idx), getName())
266                 .WillByDefault(Return(sensorNames[idx]));
267         }
268 
269         sut = makeThreshold(0ms, GetParam().thresholdValue);
270     }
271 };
272 
273 INSTANTIATE_TEST_SUITE_P(
274     _, TestDiscreteThresholdNoDwellTime,
275     Values(DiscreteParams()
276                .ThresholdValue("90.0")
277                .Updates({{0, 80.0}, {0, 89.0}})
278                .Expected({}),
279            DiscreteParams()
280                .ThresholdValue("90.0")
281                .Updates({{0, 80.0}, {0, 90.0}, {0, 80.0}, {0, 90.0}})
282                .Expected({{0, 90.0}, {0, 90.0}}),
283            DiscreteParams()
284                .ThresholdValue("90.0")
285                .Updates({{0, 90.0}, {0, 99.0}, {1, 100.0}, {1, 90.0}})
286                .Expected({{0, 90.0}, {1, 90.0}})));
287 
288 TEST_P(TestDiscreteThresholdNoDwellTime, senorsIsUpdatedMultipleTimes)
289 {
290     testBodySensorIsUpdatedMultipleTimes();
291 }
292 
293 class TestDiscreteThresholdWithDwellTime : public TestDiscreteThresholdCommon
294 {
295   public:
296     void SetUp() override
297     {
298         for (size_t idx = 0; idx < sensorMocks.size(); idx++)
299         {
300             ON_CALL(*sensorMocks.at(idx), getName())
301                 .WillByDefault(Return(sensorNames[idx]));
302         }
303 
304         sut = makeThreshold(GetParam().dwellTime, GetParam().thresholdValue);
305     }
306 };
307 
308 INSTANTIATE_TEST_SUITE_P(
309     _, TestDiscreteThresholdWithDwellTime,
310     Values(DiscreteParams()
311                .DwellTime(200ms)
312                .ThresholdValue("90.0")
313                .Updates({{0, 90.0, 100ms}, {0, 91.0}, {0, 90.0}})
314                .Expected({{0, 90.0, 300ms}}),
315            DiscreteParams()
316                .DwellTime(100ms)
317                .ThresholdValue("90.0")
318                .Updates({{0, 90.0, 100ms}})
319                .Expected({{0, 90.0, 100ms}}),
320            DiscreteParams()
321                .DwellTime(1000ms)
322                .ThresholdValue("90.0")
323                .Updates({{0, 90.0, 700ms},
324                          {0, 91.0, 100ms},
325                          {0, 90.0, 300ms},
326                          {0, 91.0, 100ms}})
327                .Expected({}),
328            DiscreteParams()
329                .DwellTime(200ms)
330                .ThresholdValue("90.0")
331                .Updates({{0, 90.0},
332                          {1, 89.0, 100ms},
333                          {1, 90.0, 100ms},
334                          {1, 89.0, 100ms},
335                          {1, 90.0, 300ms},
336                          {1, 89.0, 100ms}})
337                .Expected({{0, 90, 200ms}, {1, 90, 500ms}})));
338 
339 TEST_P(TestDiscreteThresholdWithDwellTime, senorsIsUpdatedMultipleTimes)
340 {
341     testBodySensorIsUpdatedMultipleTimes();
342 }
343