blob: 38f7407fa388129ddea095fbcd6da814c41bf0e7 [file] [log] [blame]
Jason M. Bills3f7c5e42018-10-03 14:00:41 -07001#include <cmath>
2#include <sensorutils.hpp>
3
4#include "gtest/gtest.h"
5
Josh Lehan17e21c22019-11-18 17:57:37 -08006// There is a surprising amount of slop in the math,
7// thanks to all the rounding and conversion.
8// The "x" byte value can drift by up to 2 away, I have seen.
9static constexpr int8_t expectedSlopX = 2;
10
11// Unlike expectedSlopX, this is a ratio, not an integer
12// It scales based on the range of "y"
13static constexpr double expectedSlopY = 0.01;
14
15// The algorithm here was copied from ipmitool
16// sdr_convert_sensor_reading() function
17// https://github.com/ipmitool/ipmitool/blob/42a023ff0726c80e8cc7d30315b987fe568a981d/lib/ipmi_sdr.c#L360
18double ipmitool_y_from_x(uint8_t x, int m, int k2_rExp, int b, int k1_bExp,
19 bool bSigned)
20{
21 double result;
22
23 // Rename to exactly match names and types (except analog) from ipmitool
24 uint8_t val = x;
25 double k1 = k1_bExp;
26 double k2 = k2_rExp;
27 int analog = bSigned ? 2 : 0;
28
29 // Begin paste here
30 // Only change is to comment out complicated structure in switch statement
31
32 switch (/*sensor->cmn.unit.*/ analog)
33 {
34 case 0:
35 result = (double)(((m * val) + (b * pow(10, k1))) * pow(10, k2));
36 break;
37 case 1:
38 if (val & 0x80)
39 val++;
40 /* Deliberately fall through to case 2. */
41 case 2:
42 result =
43 (double)(((m * (int8_t)val) + (b * pow(10, k1))) * pow(10, k2));
44 break;
45 default:
46 /* Oops! This isn't an analog sensor. */
47 return 0.0;
48 }
49
50 // End paste here
51 // Ignoring linearization curves and postprocessing that follows,
52 // assuming all sensors are perfectly linear
53 return result;
54}
55
56void testValue(int x, double y, int16_t M, int8_t rExp, int16_t B, int8_t bExp,
57 bool bSigned, double yRange)
58{
59 double yRoundtrip;
60 int result;
61
62 // There is intentionally no exception catching here,
63 // because if getSensorAttributes() returned true,
64 // it is a promise that all of these should work.
65 if (bSigned)
66 {
67 int8_t expect = x;
68 int8_t actual =
69 ipmi::scaleIPMIValueFromDouble(y, M, rExp, B, bExp, bSigned);
70
71 result = actual;
72 yRoundtrip = ipmitool_y_from_x(actual, M, rExp, B, bExp, bSigned);
73
74 EXPECT_NEAR(actual, expect, expectedSlopX);
75 }
76 else
77 {
78 uint8_t expect = x;
79 uint8_t actual =
80 ipmi::scaleIPMIValueFromDouble(y, M, rExp, B, bExp, bSigned);
81
82 result = actual;
83 yRoundtrip = ipmitool_y_from_x(actual, M, rExp, B, bExp, bSigned);
84
85 EXPECT_NEAR(actual, expect, expectedSlopX);
86 }
87
88 // Scale the amount of allowed slop in y based on range, so ratio similar
89 double yTolerance = yRange * expectedSlopY;
90 double yError = std::abs(y - yRoundtrip);
91
92 EXPECT_NEAR(y, yRoundtrip, yTolerance);
93
94 char szFormat[1024];
95 sprintf(szFormat,
96 "Value | xExpect %4d | xResult %4d "
97 "| M %5d | rExp %3d "
98 "| B %5d | bExp %3d | bSigned %1d | y %18.3f | yRoundtrip %18.3f\n",
99 x, result, M, (int)rExp, B, (int)bExp, (int)bSigned, y, yRoundtrip);
100 std::cout << szFormat;
101}
102
103void testBounds(double yMin, double yMax, bool bExpectedOutcome = true)
104{
105 int16_t mValue;
106 int8_t rExp;
107 int16_t bValue;
108 int8_t bExp;
109 bool bSigned;
110 bool result;
111
112 result = ipmi::getSensorAttributes(yMax, yMin, mValue, rExp, bValue, bExp,
113 bSigned);
114 EXPECT_EQ(result, bExpectedOutcome);
115
116 if (!result)
117 {
118 return;
119 }
120
121 char szFormat[1024];
122 sprintf(szFormat,
123 "Bounds | yMin %18.3f | yMax %18.3f | M %5d"
124 " | rExp %3d | B %5d | bExp %3d | bSigned %1d\n",
125 yMin, yMax, mValue, (int)rExp, bValue, (int)bExp, (int)bSigned);
126 std::cout << szFormat;
127
128 double y50p = (yMin + yMax) / 2.0;
129
130 // Average the average
131 double y25p = (yMin + y50p) / 2.0;
132 double y75p = (y50p + yMax) / 2.0;
133
134 // This range value is only used for tolerance checking, not computation
135 double yRange = yMax - yMin;
136
137 if (bSigned)
138 {
139 int8_t xMin = -128;
140 int8_t x25p = -64;
141 int8_t x50p = 0;
142 int8_t x75p = 64;
143 int8_t xMax = 127;
144
145 testValue(xMin, yMin, mValue, rExp, bValue, bExp, bSigned, yRange);
146 testValue(x25p, y25p, mValue, rExp, bValue, bExp, bSigned, yRange);
147 testValue(x50p, y50p, mValue, rExp, bValue, bExp, bSigned, yRange);
148 testValue(x75p, y75p, mValue, rExp, bValue, bExp, bSigned, yRange);
149 testValue(xMax, yMax, mValue, rExp, bValue, bExp, bSigned, yRange);
150 }
151 else
152 {
153 uint8_t xMin = 0;
154 uint8_t x25p = 64;
155 uint8_t x50p = 128;
156 uint8_t x75p = 192;
157 uint8_t xMax = 255;
158
159 testValue(xMin, yMin, mValue, rExp, bValue, bExp, bSigned, yRange);
160 testValue(x25p, y25p, mValue, rExp, bValue, bExp, bSigned, yRange);
161 testValue(x50p, y50p, mValue, rExp, bValue, bExp, bSigned, yRange);
162 testValue(x75p, y75p, mValue, rExp, bValue, bExp, bSigned, yRange);
163 testValue(xMax, yMax, mValue, rExp, bValue, bExp, bSigned, yRange);
164 }
165}
166
167void testRanges(void)
168{
169 // The ranges from the main TEST function
170 testBounds(0x0, 0xFF);
171 testBounds(-128, 127);
172 testBounds(0, 16000);
173 testBounds(0, 20);
174 testBounds(8000, 16000);
175 testBounds(-10, 10);
176 testBounds(0, 277);
177 testBounds(0, 0, false);
178 testBounds(10, 12);
179
180 // Additional test cases recommended to me by hardware people
181 testBounds(-40, 150);
182 testBounds(0, 1);
183 testBounds(0, 2);
184 testBounds(0, 4);
185 testBounds(0, 8);
186 testBounds(35, 65);
187 testBounds(0, 18);
188 testBounds(0, 25);
189 testBounds(0, 80);
190 testBounds(0, 500);
191
192 // Additional sanity checks
193 testBounds(0, 255);
194 testBounds(-255, 0);
195 testBounds(-255, 255);
196 testBounds(0, 1000);
197 testBounds(-1000, 0);
198 testBounds(-1000, 1000);
199 testBounds(0, 255000);
200 testBounds(-128000000, 127000000);
201 testBounds(-50000, 0);
202 testBounds(-40000, 10000);
203 testBounds(-30000, 20000);
204 testBounds(-20000, 30000);
205 testBounds(-10000, 40000);
206 testBounds(0, 50000);
207 testBounds(-1e3, 1e6);
208 testBounds(-1e6, 1e3);
209
210 // Extreme ranges are now possible
211 testBounds(0, 1e10);
212 testBounds(0, 1e11);
213 testBounds(0, 1e12);
214 testBounds(0, 1e13, false);
215 testBounds(-1e10, 0);
216 testBounds(-1e11, 0);
217 testBounds(-1e12, 0);
218 testBounds(-1e13, 0, false);
219 testBounds(-1e9, 1e9);
220 testBounds(-1e10, 1e10);
221 testBounds(-1e11, 1e11);
222 testBounds(-1e12, 1e12, false);
223
224 // Large multiplier but small offset
225 testBounds(1e4, 1e4 + 255);
226 testBounds(1e5, 1e5 + 255);
227 testBounds(1e6, 1e6 + 255);
228 testBounds(1e7, 1e7 + 255);
229 testBounds(1e8, 1e8 + 255);
230 testBounds(1e9, 1e9 + 255);
231 testBounds(1e10, 1e10 + 255, false);
232
233 // Input validation against garbage
234 testBounds(0, INFINITY, false);
235 testBounds(-INFINITY, 0, false);
236 testBounds(-INFINITY, INFINITY, false);
237 testBounds(0, NAN, false);
238 testBounds(NAN, 0, false);
239 testBounds(NAN, NAN, false);
240
241 // Noteworthy binary integers
242 testBounds(0, std::pow(2.0, 32.0) - 1.0);
243 testBounds(0, std::pow(2.0, 32.0));
244 testBounds(0.0 - std::pow(2.0, 31.0), std::pow(2.0, 31.0));
245 testBounds((0.0 - std::pow(2.0, 31.0)) - 1.0, std::pow(2.0, 31.0));
246
247 // Similar but negative (note additional commented-out below)
248 testBounds(-1e1, (-1e1) + 255);
249 testBounds(-1e2, (-1e2) + 255);
250
251 // Ranges of negative numbers (note additional commented-out below)
252 testBounds(-10400, -10000);
253 testBounds(-15000, -14000);
254 testBounds(-10000, -9000);
255 testBounds(-1000, -900);
256 testBounds(-1000, -800);
257 testBounds(-1000, -700);
258 testBounds(-1000, -740);
259
260 // Very small ranges (note additional commented-out below)
261 testBounds(0, 0.1);
262 testBounds(0, 0.01);
263 testBounds(0, 0.001);
264 testBounds(0, 0.0001);
265 testBounds(0, 0.000001, false);
266
267#if 0
268 // TODO(): The algorithm in this module is better than it was before,
269 // but the resulting value of X is still wrong under certain conditions,
270 // such as when the range between min and max is around 255,
271 // and the offset is fairly extreme compared to the multiplier.
272 // Not sure why this is, but these ranges are contrived,
273 // and real-world examples would most likely never be this way.
274 testBounds(-10290, -10000);
275 testBounds(-10280, -10000);
276 testBounds(-10275,-10000);
277 testBounds(-10270,-10000);
278 testBounds(-10265,-10000);
279 testBounds(-10260,-10000);
280 testBounds(-10255,-10000);
281 testBounds(-10250,-10000);
282 testBounds(-10245,-10000);
283 testBounds(-10256,-10000);
284 testBounds(-10512, -10000);
285 testBounds(-11024, -10000);
286
287 // TODO(): This also fails, due to extreme small range, loss of precision
288 testBounds(0, 0.00001);
289
290 // TODO(): Interestingly, if bSigned is forced false,
291 // causing "x" to have range of (0,255) instead of (-128,127),
292 // these test cases change from failing to passing!
293 // Not sure why this is, perhaps a mathematician might know.
294 testBounds(-10300, -10000);
295 testBounds(-1000,-750);
296 testBounds(-1e3, (-1e3) + 255);
297 testBounds(-1e4, (-1e4) + 255);
298 testBounds(-1e5, (-1e5) + 255);
299 testBounds(-1e6, (-1e6) + 255);
300#endif
301}
302
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700303TEST(sensorutils, TranslateToIPMI)
304{
305 /*bool getSensorAttributes(double maxValue, double minValue, int16_t
306 &mValue, int8_t &rExp, int16_t &bValue, int8_t &bExp, bool &bSigned); */
307 // normal unsigned sensor
308 double maxValue = 0xFF;
309 double minValue = 0x0;
310 int16_t mValue;
311 int8_t rExp;
312 int16_t bValue;
313 int8_t bExp;
314 bool bSigned;
315 bool result;
316
317 uint8_t scaledVal;
318
319 result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
320 bExp, bSigned);
321 EXPECT_EQ(result, true);
322 if (result)
323 {
324 EXPECT_EQ(bSigned, false);
325 EXPECT_EQ(mValue, 1);
326 EXPECT_EQ(rExp, 0);
327 EXPECT_EQ(bValue, 0);
328 EXPECT_EQ(bExp, 0);
329 }
330 double expected = 0x50;
331 scaledVal = ipmi::scaleIPMIValueFromDouble(0x50, mValue, rExp, bValue, bExp,
332 bSigned);
333 EXPECT_NEAR(scaledVal, expected, expected * 0.01);
334
335 // normal signed sensor
336 maxValue = 127;
337 minValue = -128;
338
339 result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
340 bExp, bSigned);
341 EXPECT_EQ(result, true);
342
343 if (result)
344 {
345 EXPECT_EQ(bSigned, true);
346 EXPECT_EQ(mValue, 1);
347 EXPECT_EQ(rExp, 0);
348 EXPECT_EQ(bValue, 0);
349 EXPECT_EQ(bExp, 0);
350 }
351
James Feistf6a07832019-10-29 09:39:14 -0700352 // check negative values
353 expected = 236; // 2s compliment -20
354 scaledVal = ipmi::scaleIPMIValueFromDouble(-20, mValue, rExp, bValue, bExp,
355 bSigned);
356 EXPECT_NEAR(scaledVal, expected, expected * 0.01);
357
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700358 // fan example
359 maxValue = 16000;
360 minValue = 0;
361
362 result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
363 bExp, bSigned);
364 EXPECT_EQ(result, true);
365 if (result)
366 {
367 EXPECT_EQ(bSigned, false);
James Feistaecaef72019-04-26 10:30:32 -0700368 EXPECT_EQ(mValue, floor((16000.0 / 0xFF) + 0.5));
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700369 EXPECT_EQ(rExp, 0);
370 EXPECT_EQ(bValue, 0);
371 EXPECT_EQ(bExp, 0);
372 }
373
374 // voltage sensor example
375 maxValue = 20;
376 minValue = 0;
377
378 result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
379 bExp, bSigned);
380 EXPECT_EQ(result, true);
381 if (result)
382 {
383 EXPECT_EQ(bSigned, false);
Josh Lehan17e21c22019-11-18 17:57:37 -0800384 EXPECT_EQ(mValue, floor(((20.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700385 EXPECT_EQ(rExp, -3);
386 EXPECT_EQ(bValue, 0);
387 EXPECT_EQ(bExp, 0);
388 }
389 scaledVal = ipmi::scaleIPMIValueFromDouble(12.2, mValue, rExp, bValue, bExp,
390 bSigned);
391
392 expected = 12.2 / (mValue * std::pow(10, rExp));
393 EXPECT_NEAR(scaledVal, expected, expected * 0.01);
394
395 // shifted fan example
396 maxValue = 16000;
397 minValue = 8000;
398
399 result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
400 bExp, bSigned);
401 EXPECT_EQ(result, true);
402
403 if (result)
404 {
405 EXPECT_EQ(bSigned, false);
Josh Lehan17e21c22019-11-18 17:57:37 -0800406 EXPECT_EQ(mValue, floor(((8000.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
407 EXPECT_EQ(rExp, -1);
408 EXPECT_EQ(bValue, 8);
409 EXPECT_EQ(bExp, 4);
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700410 }
411
412 // signed voltage sensor example
413 maxValue = 10;
414 minValue = -10;
415
416 result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
417 bExp, bSigned);
418 EXPECT_EQ(result, true);
419 if (result)
420 {
421 EXPECT_EQ(bSigned, true);
Josh Lehan17e21c22019-11-18 17:57:37 -0800422 EXPECT_EQ(mValue, floor(((20.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700423 EXPECT_EQ(rExp, -3);
Josh Lehan17e21c22019-11-18 17:57:37 -0800424 // Although this seems like a weird magic number,
425 // it is because the range (-128,127) is not symmetrical about zero,
426 // unlike the range (-10,10), so this introduces some distortion.
427 EXPECT_EQ(bValue, 392);
428 EXPECT_EQ(bExp, -1);
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700429 }
430
431 scaledVal =
432 ipmi::scaleIPMIValueFromDouble(5, mValue, rExp, bValue, bExp, bSigned);
433
434 expected = 5 / (mValue * std::pow(10, rExp));
435 EXPECT_NEAR(scaledVal, expected, expected * 0.01);
436
James Feistaecaef72019-04-26 10:30:32 -0700437 // reading = max example
438 maxValue = 277;
439 minValue = 0;
440
441 result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
442 bExp, bSigned);
443 EXPECT_EQ(result, true);
444 if (result)
445 {
446 EXPECT_EQ(bSigned, false);
447 }
448
449 scaledVal = ipmi::scaleIPMIValueFromDouble(maxValue, mValue, rExp, bValue,
450 bExp, bSigned);
451
452 expected = 0xFF;
453 EXPECT_NEAR(scaledVal, expected, expected * 0.01);
454
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700455 // 0, 0 failure
456 maxValue = 0;
457 minValue = 0;
458 result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
459 bExp, bSigned);
460 EXPECT_EQ(result, false);
461
Josh Lehan17e21c22019-11-18 17:57:37 -0800462 // too close *success* (was previously failure!)
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700463 maxValue = 12;
464 minValue = 10;
465 result = ipmi::getSensorAttributes(maxValue, minValue, mValue, rExp, bValue,
466 bExp, bSigned);
Josh Lehan17e21c22019-11-18 17:57:37 -0800467 EXPECT_EQ(result, true);
468 if (result)
469 {
470 EXPECT_EQ(bSigned, false);
471 EXPECT_EQ(mValue, floor(((2.0 / 0xFF) / std::pow(10, rExp)) + 0.5));
472 EXPECT_EQ(rExp, -4);
473 EXPECT_EQ(bValue, 1);
474 EXPECT_EQ(bExp, 5);
475 }
476}
477
478TEST(sensorUtils, TestRanges)
479{
480 // Additional test ranges, each running through a series of values,
481 // to make sure the values of "x" and "y" go together and make sense,
482 // for the resulting scaling attributes from each range.
483 // Unlike the TranslateToIPMI test, exact matches of the
484 // getSensorAttributes() results (the coefficients) are not required,
485 // because they are tested through actual use, relating "x" to "y".
486 testRanges();
Jason M. Bills3f7c5e42018-10-03 14:00:41 -0700487}