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