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