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