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