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