xref: /openbmc/phosphor-power/phosphor-regulators/test/error_logging_utils_tests.cpp (revision 38f8500414fe5c1be6f5159c563937289fe557c2)
1 /**
2  * Copyright © 2021 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "config_file_parser_error.hpp"
17 #include "error_logging.hpp"
18 #include "error_logging_utils.hpp"
19 #include "i2c_interface.hpp"
20 #include "journal.hpp"
21 #include "mock_error_logging.hpp"
22 #include "mock_journal.hpp"
23 #include "mock_services.hpp"
24 #include "pmbus_error.hpp"
25 #include "test_sdbus_error.hpp"
26 #include "write_verification_error.hpp"
27 
28 #include <errno.h>
29 
30 #include <sdbusplus/exception.hpp>
31 
32 #include <exception>
33 #include <filesystem>
34 #include <stdexcept>
35 #include <string>
36 
37 #include <gmock/gmock.h>
38 #include <gtest/gtest.h>
39 
40 using namespace phosphor::power::regulators;
41 using ConfigFileParserError = phosphor::power::util::ConfigFileParserError;
42 
43 namespace fs = std::filesystem;
44 
45 using ::testing::Ref;
46 
TEST(ErrorLoggingUtilsTests,LogError_3Parameters)47 TEST(ErrorLoggingUtilsTests, LogError_3Parameters)
48 {
49     // Create exception with two nesting levels; top priority is inner
50     // PMBusError
51     std::exception_ptr eptr;
52     try
53     {
54         try
55         {
56             throw PMBusError{"VOUT_MODE contains unsupported data format",
57                              "reg1",
58                              "/xyz/openbmc_project/inventory/system/chassis/"
59                              "motherboard/reg1"};
60         }
61         catch (...)
62         {
63             std::throw_with_nested(
64                 std::runtime_error{"Unable to set output voltage"});
65         }
66     }
67     catch (...)
68     {
69         eptr = std::current_exception();
70     }
71 
72     // Create MockServices.  Expect logPMBusError() to be called.
73     MockServices services{};
74     MockErrorLogging& errorLogging = services.getMockErrorLogging();
75     MockJournal& journal = services.getMockJournal();
76     EXPECT_CALL(
77         errorLogging,
78         logPMBusError(
79             Entry::Level::Error, Ref(journal),
80             "/xyz/openbmc_project/inventory/system/chassis/motherboard/reg1"))
81         .Times(1);
82 
83     // Log error based on the nested exception
84     error_logging_utils::logError(eptr, Entry::Level::Error, services);
85 }
86 
TEST(ErrorLoggingUtilsTests,LogError_4Parameters)87 TEST(ErrorLoggingUtilsTests, LogError_4Parameters)
88 {
89     // Test where exception pointer is null
90     {
91         std::exception_ptr eptr;
92 
93         // Create MockServices.  Don't expect any log*() methods to be called.
94         MockServices services{};
95 
96         ErrorHistory history{};
97         error_logging_utils::logError(eptr, Entry::Level::Error, services,
98                                       history);
99     }
100 
101     // Test where exception is not nested
102     {
103         std::exception_ptr eptr;
104         try
105         {
106             throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8",
107                                     0x30, ENODEV};
108         }
109         catch (...)
110         {
111             eptr = std::current_exception();
112         }
113 
114         // Create MockServices.  Expect logI2CError() to be called.
115         MockServices services{};
116         MockErrorLogging& errorLogging = services.getMockErrorLogging();
117         MockJournal& journal = services.getMockJournal();
118         EXPECT_CALL(errorLogging,
119                     logI2CError(Entry::Level::Critical, Ref(journal),
120                                 "/dev/i2c-8", 0x30, ENODEV))
121             .Times(1);
122 
123         // Log error based on the nested exception
124         ErrorHistory history{};
125         error_logging_utils::logError(eptr, Entry::Level::Critical, services,
126                                       history);
127     }
128 
129     // Test where exception is nested
130     {
131         std::exception_ptr eptr;
132         try
133         {
134             try
135             {
136                 throw std::invalid_argument{"JSON element is not an array"};
137             }
138             catch (...)
139             {
140                 std::throw_with_nested(ConfigFileParserError{
141                     fs::path{"/etc/phosphor-regulators/config.json"},
142                     "Unable to parse JSON configuration file"});
143             }
144         }
145         catch (...)
146         {
147             eptr = std::current_exception();
148         }
149 
150         // Create MockServices.  Expect logConfigFileError() to be called.
151         MockServices services{};
152         MockErrorLogging& errorLogging = services.getMockErrorLogging();
153         MockJournal& journal = services.getMockJournal();
154         EXPECT_CALL(errorLogging,
155                     logConfigFileError(Entry::Level::Warning, Ref(journal)))
156             .Times(1);
157 
158         // Log error based on the nested exception
159         ErrorHistory history{};
160         error_logging_utils::logError(eptr, Entry::Level::Warning, services,
161                                       history);
162     }
163 
164     // Test where exception is a ConfigFileParserError
165     {
166         std::exception_ptr eptr;
167         try
168         {
169             throw ConfigFileParserError{
170                 fs::path{"/etc/phosphor-regulators/config.json"},
171                 "Unable to parse JSON configuration file"};
172         }
173         catch (...)
174         {
175             eptr = std::current_exception();
176         }
177 
178         // Create MockServices.  Expect logConfigFileError() to be called once.
179         MockServices services{};
180         MockErrorLogging& errorLogging = services.getMockErrorLogging();
181         MockJournal& journal = services.getMockJournal();
182         EXPECT_CALL(errorLogging,
183                     logConfigFileError(Entry::Level::Error, Ref(journal)))
184             .Times(1);
185 
186         // Log error based on the nested exception
187         ErrorHistory history{};
188         error_logging_utils::logError(eptr, Entry::Level::Error, services,
189                                       history);
190 
191         // Try to log error again.  Should not happen due to ErrorHistory.
192         error_logging_utils::logError(eptr, Entry::Level::Error, services,
193                                       history);
194     }
195 
196     // Test where exception is a PMBusError
197     {
198         std::exception_ptr eptr;
199         try
200         {
201             throw PMBusError{"VOUT_MODE contains unsupported data format",
202                              "reg1",
203                              "/xyz/openbmc_project/inventory/system/chassis/"
204                              "motherboard/reg1"};
205         }
206         catch (...)
207         {
208             eptr = std::current_exception();
209         }
210 
211         // Create MockServices.  Expect logPMBusError() to be called once.
212         MockServices services{};
213         MockErrorLogging& errorLogging = services.getMockErrorLogging();
214         MockJournal& journal = services.getMockJournal();
215         EXPECT_CALL(errorLogging,
216                     logPMBusError(Entry::Level::Error, Ref(journal),
217                                   "/xyz/openbmc_project/inventory/system/"
218                                   "chassis/motherboard/reg1"))
219             .Times(1);
220 
221         // Log error based on the nested exception
222         ErrorHistory history{};
223         error_logging_utils::logError(eptr, Entry::Level::Error, services,
224                                       history);
225 
226         // Try to log error again.  Should not happen due to ErrorHistory.
227         error_logging_utils::logError(eptr, Entry::Level::Error, services,
228                                       history);
229     }
230 
231     // Test where exception is a WriteVerificationError
232     {
233         std::exception_ptr eptr;
234         try
235         {
236             throw WriteVerificationError{
237                 "value_written: 0xDEAD, value_read: 0xBEEF", "reg1",
238                 "/xyz/openbmc_project/inventory/system/chassis/motherboard/"
239                 "reg1"};
240         }
241         catch (...)
242         {
243             eptr = std::current_exception();
244         }
245 
246         // Create MockServices.  Expect logWriteVerificationError() to be
247         // called once.
248         MockServices services{};
249         MockErrorLogging& errorLogging = services.getMockErrorLogging();
250         MockJournal& journal = services.getMockJournal();
251         EXPECT_CALL(errorLogging, logWriteVerificationError(
252                                       Entry::Level::Warning, Ref(journal),
253                                       "/xyz/openbmc_project/inventory/system/"
254                                       "chassis/motherboard/reg1"))
255             .Times(1);
256 
257         // Log error based on the nested exception
258         ErrorHistory history{};
259         error_logging_utils::logError(eptr, Entry::Level::Warning, services,
260                                       history);
261 
262         // Try to log error again.  Should not happen due to ErrorHistory.
263         error_logging_utils::logError(eptr, Entry::Level::Warning, services,
264                                       history);
265     }
266 
267     // Test where exception is a I2CException
268     {
269         std::exception_ptr eptr;
270         try
271         {
272             throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8",
273                                     0x30, ENODEV};
274         }
275         catch (...)
276         {
277             eptr = std::current_exception();
278         }
279 
280         // Create MockServices.  Expect logI2CError() to be called once.
281         MockServices services{};
282         MockErrorLogging& errorLogging = services.getMockErrorLogging();
283         MockJournal& journal = services.getMockJournal();
284         EXPECT_CALL(errorLogging,
285                     logI2CError(Entry::Level::Informational, Ref(journal),
286                                 "/dev/i2c-8", 0x30, ENODEV))
287             .Times(1);
288 
289         // Log error based on the nested exception
290         ErrorHistory history{};
291         error_logging_utils::logError(eptr, Entry::Level::Informational,
292                                       services, history);
293 
294         // Try to log error again.  Should not happen due to ErrorHistory.
295         error_logging_utils::logError(eptr, Entry::Level::Informational,
296                                       services, history);
297     }
298 
299     // Test where exception is a sdbusplus::exception_t
300     {
301         std::exception_ptr eptr;
302         try
303         {
304             // Throw TestSDBusError; exception_t is a pure virtual base class
305             throw TestSDBusError{"DBusError: Invalid object path."};
306         }
307         catch (...)
308         {
309             eptr = std::current_exception();
310         }
311 
312         // Create MockServices.  Expect logDBusError() to be called once.
313         MockServices services{};
314         MockErrorLogging& errorLogging = services.getMockErrorLogging();
315         MockJournal& journal = services.getMockJournal();
316         EXPECT_CALL(errorLogging,
317                     logDBusError(Entry::Level::Debug, Ref(journal)))
318             .Times(1);
319 
320         // Log error based on the nested exception
321         ErrorHistory history{};
322         error_logging_utils::logError(eptr, Entry::Level::Debug, services,
323                                       history);
324 
325         // Try to log error again.  Should not happen due to ErrorHistory.
326         error_logging_utils::logError(eptr, Entry::Level::Debug, services,
327                                       history);
328     }
329 
330     // Test where exception is a std::exception
331     {
332         std::exception_ptr eptr;
333         try
334         {
335             throw std::runtime_error{
336                 "Unable to read configuration file: No such file or directory"};
337         }
338         catch (...)
339         {
340             eptr = std::current_exception();
341         }
342 
343         // Create MockServices.  Expect logInternalError() to be called once.
344         MockServices services{};
345         MockErrorLogging& errorLogging = services.getMockErrorLogging();
346         MockJournal& journal = services.getMockJournal();
347         EXPECT_CALL(errorLogging,
348                     logInternalError(Entry::Level::Error, Ref(journal)))
349             .Times(1);
350 
351         // Log error based on the nested exception
352         ErrorHistory history{};
353         error_logging_utils::logError(eptr, Entry::Level::Error, services,
354                                       history);
355 
356         // Try to log error again.  Should not happen due to ErrorHistory.
357         error_logging_utils::logError(eptr, Entry::Level::Error, services,
358                                       history);
359     }
360 
361     // Test where exception is unknown type
362     {
363         std::exception_ptr eptr;
364         try
365         {
366             throw 23;
367         }
368         catch (...)
369         {
370             eptr = std::current_exception();
371         }
372 
373         // Create MockServices.  Expect logInternalError() to be called once.
374         MockServices services{};
375         MockErrorLogging& errorLogging = services.getMockErrorLogging();
376         MockJournal& journal = services.getMockJournal();
377         EXPECT_CALL(errorLogging,
378                     logInternalError(Entry::Level::Warning, Ref(journal)))
379             .Times(1);
380 
381         // Log error based on the nested exception
382         ErrorHistory history{};
383         error_logging_utils::logError(eptr, Entry::Level::Warning, services,
384                                       history);
385 
386         // Try to log error again.  Should not happen due to ErrorHistory.
387         error_logging_utils::logError(eptr, Entry::Level::Warning, services,
388                                       history);
389     }
390 }
391 
TEST(ErrorLoggingUtilsTests,GetExceptionToLog)392 TEST(ErrorLoggingUtilsTests, GetExceptionToLog)
393 {
394     // Test where exception is not nested
395     {
396         std::exception_ptr eptr;
397         try
398         {
399             throw i2c::I2CException{"Unable to open device reg1", "/dev/i2c-8",
400                                     0x30, ENODEV};
401         }
402         catch (...)
403         {
404             eptr = std::current_exception();
405         }
406 
407         std::exception_ptr exceptionToLog =
408             error_logging_utils::internal::getExceptionToLog(eptr);
409         EXPECT_EQ(eptr, exceptionToLog);
410     }
411 
412     // Test where exception is nested: Highest priority is innermost exception
413     {
414         std::exception_ptr inner, outer;
415         try
416         {
417             try
418             {
419                 throw PMBusError{
420                     "VOUT_MODE contains unsupported data format", "reg1",
421                     "/xyz/openbmc_project/inventory/system/chassis/"
422                     "motherboard/reg1"};
423             }
424             catch (...)
425             {
426                 inner = std::current_exception();
427                 std::throw_with_nested(
428                     std::runtime_error{"Unable to set output voltage"});
429             }
430         }
431         catch (...)
432         {
433             outer = std::current_exception();
434         }
435 
436         std::exception_ptr exceptionToLog =
437             error_logging_utils::internal::getExceptionToLog(outer);
438         EXPECT_EQ(inner, exceptionToLog);
439     }
440 
441     // Test where exception is nested: Highest priority is middle exception
442     {
443         std::exception_ptr inner, middle, outer;
444         try
445         {
446             try
447             {
448                 try
449                 {
450                     throw std::invalid_argument{"JSON element is not an array"};
451                 }
452                 catch (...)
453                 {
454                     inner = std::current_exception();
455                     std::throw_with_nested(ConfigFileParserError{
456                         fs::path{"/etc/phosphor-regulators/config.json"},
457                         "Unable to parse JSON configuration file"});
458                 }
459             }
460             catch (...)
461             {
462                 middle = std::current_exception();
463                 std::throw_with_nested(
464                     std::runtime_error{"Unable to load config file"});
465             }
466         }
467         catch (...)
468         {
469             outer = std::current_exception();
470         }
471 
472         std::exception_ptr exceptionToLog =
473             error_logging_utils::internal::getExceptionToLog(outer);
474         EXPECT_EQ(middle, exceptionToLog);
475     }
476 
477     // Test where exception is nested: Highest priority is outermost exception
478     {
479         std::exception_ptr inner, outer;
480         try
481         {
482             try
483             {
484                 throw std::invalid_argument{"JSON element is not an array"};
485             }
486             catch (...)
487             {
488                 inner = std::current_exception();
489                 std::throw_with_nested(ConfigFileParserError{
490                     fs::path{"/etc/phosphor-regulators/config.json"},
491                     "Unable to parse JSON configuration file"});
492             }
493         }
494         catch (...)
495         {
496             outer = std::current_exception();
497         }
498 
499         std::exception_ptr exceptionToLog =
500             error_logging_utils::internal::getExceptionToLog(outer);
501         EXPECT_EQ(outer, exceptionToLog);
502     }
503 
504     // Test where exception is nested: Two exceptions have same priority.
505     // Should return outermost exception with that priority.
506     {
507         std::exception_ptr inner, outer;
508         try
509         {
510             try
511             {
512                 throw std::invalid_argument{"JSON element is not an array"};
513             }
514             catch (...)
515             {
516                 inner = std::current_exception();
517                 std::throw_with_nested(
518                     std::runtime_error{"Unable to load config file"});
519             }
520         }
521         catch (...)
522         {
523             outer = std::current_exception();
524         }
525 
526         std::exception_ptr exceptionToLog =
527             error_logging_utils::internal::getExceptionToLog(outer);
528         EXPECT_EQ(outer, exceptionToLog);
529     }
530 
531     // Test where exception is nested: Highest priority is ConfigFileParserError
532     {
533         std::exception_ptr inner, outer;
534         try
535         {
536             try
537             {
538                 throw ConfigFileParserError{
539                     fs::path{"/etc/phosphor-regulators/config.json"},
540                     "Unable to parse JSON configuration file"};
541             }
542             catch (...)
543             {
544                 inner = std::current_exception();
545                 std::throw_with_nested(
546                     std::runtime_error{"Unable to load config file"});
547             }
548         }
549         catch (...)
550         {
551             outer = std::current_exception();
552         }
553 
554         std::exception_ptr exceptionToLog =
555             error_logging_utils::internal::getExceptionToLog(outer);
556         EXPECT_EQ(inner, exceptionToLog);
557     }
558 
559     // Test where exception is nested: Highest priority is PMBusError
560     {
561         std::exception_ptr inner, outer;
562         try
563         {
564             try
565             {
566                 throw std::invalid_argument{"Invalid VOUT_MODE value"};
567             }
568             catch (...)
569             {
570                 inner = std::current_exception();
571                 std::throw_with_nested(PMBusError{
572                     "VOUT_MODE contains unsupported data format", "reg1",
573                     "/xyz/openbmc_project/inventory/system/chassis/motherboard/"
574                     "reg1"});
575             }
576         }
577         catch (...)
578         {
579             outer = std::current_exception();
580         }
581 
582         std::exception_ptr exceptionToLog =
583             error_logging_utils::internal::getExceptionToLog(outer);
584         EXPECT_EQ(outer, exceptionToLog);
585     }
586 
587     // Test where exception is nested: Highest priority is
588     // WriteVerificationError
589     {
590         std::exception_ptr inner, outer;
591         try
592         {
593             try
594             {
595                 throw WriteVerificationError{
596                     "value_written: 0xDEAD, value_read: 0xBEEF", "reg1",
597                     "/xyz/openbmc_project/inventory/system/chassis/motherboard/"
598                     "reg1"};
599             }
600             catch (...)
601             {
602                 inner = std::current_exception();
603                 std::throw_with_nested(
604                     std::runtime_error{"Unable set voltage"});
605             }
606         }
607         catch (...)
608         {
609             outer = std::current_exception();
610         }
611 
612         std::exception_ptr exceptionToLog =
613             error_logging_utils::internal::getExceptionToLog(outer);
614         EXPECT_EQ(inner, exceptionToLog);
615     }
616 
617     // Test where exception is nested: Highest priority is I2CException
618     {
619         std::exception_ptr inner, outer;
620         try
621         {
622             try
623             {
624                 throw std::invalid_argument{"No such device"};
625             }
626             catch (...)
627             {
628                 inner = std::current_exception();
629                 std::throw_with_nested(i2c::I2CException{
630                     "Unable to open device reg1", "/dev/i2c-8", 0x30, ENODEV});
631             }
632         }
633         catch (...)
634         {
635             outer = std::current_exception();
636         }
637 
638         std::exception_ptr exceptionToLog =
639             error_logging_utils::internal::getExceptionToLog(outer);
640         EXPECT_EQ(outer, exceptionToLog);
641     }
642 
643     // Test where exception is nested: Highest priority is
644     // sdbusplus::exception_t
645     {
646         std::exception_ptr inner, outer;
647         try
648         {
649             try
650             {
651                 // Throw TestSDBusError; exception_t is pure virtual class
652                 throw TestSDBusError{"DBusError: Invalid object path."};
653             }
654             catch (...)
655             {
656                 inner = std::current_exception();
657                 std::throw_with_nested(
658                     std::runtime_error{"Unable to call D-Bus method"});
659             }
660         }
661         catch (...)
662         {
663             outer = std::current_exception();
664         }
665 
666         std::exception_ptr exceptionToLog =
667             error_logging_utils::internal::getExceptionToLog(outer);
668         EXPECT_EQ(inner, exceptionToLog);
669     }
670 
671     // Test where exception is nested: Highest priority is std::exception
672     {
673         std::exception_ptr inner, outer;
674         try
675         {
676             try
677             {
678                 throw std::invalid_argument{"No such file or directory"};
679             }
680             catch (...)
681             {
682                 inner = std::current_exception();
683                 std::throw_with_nested(
684                     std::runtime_error{"Unable load config file"});
685             }
686         }
687         catch (...)
688         {
689             outer = std::current_exception();
690         }
691 
692         std::exception_ptr exceptionToLog =
693             error_logging_utils::internal::getExceptionToLog(outer);
694         EXPECT_EQ(outer, exceptionToLog);
695     }
696 
697     // Test where exception is nested: Highest priority is unknown type
698     {
699         std::exception_ptr inner, outer;
700         try
701         {
702             try
703             {
704                 throw 23;
705             }
706             catch (...)
707             {
708                 inner = std::current_exception();
709                 std::throw_with_nested(std::string{"Unable load config file"});
710             }
711         }
712         catch (...)
713         {
714             outer = std::current_exception();
715         }
716 
717         std::exception_ptr exceptionToLog =
718             error_logging_utils::internal::getExceptionToLog(outer);
719         EXPECT_EQ(outer, exceptionToLog);
720     }
721 }
722