xref: /openbmc/phosphor-logging/test/openpower-pels/src_test.cpp (revision 40fb54935ce7367636a7156039396ee91cc4d5e2)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2019 IBM Corporation
3 
4 #include "extensions/openpower-pels/src.hpp"
5 #include "mocks.hpp"
6 #include "pel_utils.hpp"
7 
8 #include <fstream>
9 
10 #include <gtest/gtest.h>
11 
12 using namespace openpower::pels;
13 using ::testing::_;
14 using ::testing::DoAll;
15 using ::testing::InvokeWithoutArgs;
16 using ::testing::NiceMock;
17 using ::testing::Return;
18 using ::testing::SetArgReferee;
19 using ::testing::Throw;
20 namespace fs = std::filesystem;
21 
22 const auto testRegistry = R"(
23 {
24 "PELs":
25 [
26     {
27         "Name": "xyz.openbmc_project.Error.Test",
28         "Subsystem": "bmc_firmware",
29         "SRC":
30         {
31             "ReasonCode": "0xABCD",
32             "Words6To9":
33             {
34                 "6":
35                 {
36                     "Description": "Component ID",
37                     "AdditionalDataPropSource": "COMPID"
38                 },
39                 "7":
40                 {
41                     "Description": "Failure count",
42                     "AdditionalDataPropSource": "FREQUENCY"
43                 },
44                 "8":
45                 {
46                     "Description": "Time period",
47                     "AdditionalDataPropSource": "DURATION"
48                 },
49                 "9":
50                 {
51                     "Description": "Error code",
52                     "AdditionalDataPropSource": "ERRORCODE"
53                 }
54             }
55         },
56         "Documentation":
57         {
58             "Description": "A Component Fault",
59             "Message": "Comp %1 failed %2 times over %3 secs with ErrorCode %4",
60             "MessageArgSources":
61             [
62                 "SRCWord6", "SRCWord7", "SRCWord8", "SRCWord9"
63             ]
64         }
65     }
66 ]
67 }
68 )";
69 
70 class SRCTest : public ::testing::Test
71 {
72   protected:
SetUpTestCase()73     static void SetUpTestCase()
74     {
75         char path[] = "/tmp/srctestXXXXXX";
76         regDir = mkdtemp(path);
77     }
78 
TearDownTestCase()79     static void TearDownTestCase()
80     {
81         fs::remove_all(regDir);
82     }
83 
writeData(const char * data)84     static std::string writeData(const char* data)
85     {
86         fs::path path = regDir / "registry.json";
87         std::ofstream stream{path};
88         stream << data;
89         return path;
90     }
91 
92     static fs::path regDir;
93 };
94 
95 fs::path SRCTest::regDir{};
96 
TEST_F(SRCTest,UnflattenFlattenTestNoCallouts)97 TEST_F(SRCTest, UnflattenFlattenTestNoCallouts)
98 {
99     auto data = pelDataFactory(TestPELType::primarySRCSection);
100 
101     Stream stream{data};
102     SRC src{stream};
103 
104     EXPECT_TRUE(src.valid());
105 
106     EXPECT_EQ(src.header().id, 0x5053);
107     EXPECT_EQ(src.header().size, 0x50);
108     EXPECT_EQ(src.header().version, 0x01);
109     EXPECT_EQ(src.header().subType, 0x01);
110     EXPECT_EQ(src.header().componentID, 0x0202);
111 
112     EXPECT_EQ(src.version(), 0x02);
113     EXPECT_EQ(src.flags(), 0x00);
114     EXPECT_EQ(src.hexWordCount(), 9);
115     EXPECT_EQ(src.size(), 0x48);
116 
117     const auto& hexwords = src.hexwordData();
118     EXPECT_EQ(0x02020255, hexwords[0]);
119     EXPECT_EQ(0x03030310, hexwords[1]);
120     EXPECT_EQ(0x04040404, hexwords[2]);
121     EXPECT_EQ(0x05050505, hexwords[3]);
122     EXPECT_EQ(0x06060606, hexwords[4]);
123     EXPECT_EQ(0x07070707, hexwords[5]);
124     EXPECT_EQ(0x08080808, hexwords[6]);
125     EXPECT_EQ(0x09090909, hexwords[7]);
126 
127     EXPECT_EQ(src.asciiString(), "BD8D5678                        ");
128     EXPECT_FALSE(src.callouts());
129 
130     // Flatten
131     std::vector<uint8_t> newData;
132     Stream newStream{newData};
133 
134     src.flatten(newStream);
135     EXPECT_EQ(data, newData);
136 }
137 
TEST_F(SRCTest,UnflattenFlattenTest2Callouts)138 TEST_F(SRCTest, UnflattenFlattenTest2Callouts)
139 {
140     auto data = pelDataFactory(TestPELType::primarySRCSection2Callouts);
141 
142     Stream stream{data};
143     SRC src{stream};
144 
145     EXPECT_TRUE(src.valid());
146     EXPECT_EQ(src.flags(), 0x01); // Additional sections within the SRC.
147 
148     // Spot check the SRC fields, but they're the same as above
149     EXPECT_EQ(src.asciiString(), "BD8D5678                        ");
150 
151     // There should be 2 callouts
152     const auto& calloutsSection = src.callouts();
153     ASSERT_TRUE(calloutsSection);
154     const auto& callouts = calloutsSection->callouts();
155     EXPECT_EQ(callouts.size(), 2);
156 
157     // spot check that each callout has the right substructures
158     EXPECT_TRUE(callouts.front()->fruIdentity());
159     EXPECT_FALSE(callouts.front()->pceIdentity());
160     EXPECT_FALSE(callouts.front()->mru());
161 
162     EXPECT_TRUE(callouts.back()->fruIdentity());
163     EXPECT_TRUE(callouts.back()->pceIdentity());
164     EXPECT_TRUE(callouts.back()->mru());
165 
166     // Flatten
167     std::vector<uint8_t> newData;
168     Stream newStream{newData};
169 
170     src.flatten(newStream);
171     EXPECT_EQ(data, newData);
172 }
173 
174 // Create an SRC from the message registry
TEST_F(SRCTest,CreateTestNoCallouts)175 TEST_F(SRCTest, CreateTestNoCallouts)
176 {
177     message::Entry entry;
178     entry.src.type = 0xBD;
179     entry.src.reasonCode = 0xABCD;
180     entry.subsystem = 0x42;
181     entry.src.hexwordADFields = {
182         {5, {"TEST1", "DESCR1"}}, // Not a user defined word
183         {6, {"TEST1", "DESCR1"}},
184         {7, {"TEST2", "DESCR2"}},
185         {8, {"TEST3", "DESCR3"}},
186         {9, {"TEST4", "DESCR4"}}};
187 
188     // Values for the SRC words pointed to above
189     std::map<std::string, std::string> adData{
190         {"TEST1", "0x12345678"},
191         {"TEST2", "12345678"},
192         {"TEST3", "0XDEF"},
193         {"TEST4", "Z"}};
194     AdditionalData ad{adData};
195     NiceMock<MockDataInterface> dataIface;
196 
197     EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD"));
198 
199     SRC src{entry, ad, dataIface};
200 
201     EXPECT_TRUE(src.valid());
202     EXPECT_FALSE(src.isPowerFaultEvent());
203     EXPECT_EQ(src.size(), baseSRCSize);
204 
205     const auto& hexwords = src.hexwordData();
206 
207     // The spec always refers to SRC words 2 - 9, and as the hexwordData()
208     // array index starts at 0 use the math in the [] below to make it easier
209     // to tell what is being accessed.
210     EXPECT_EQ(hexwords[2 - 2] & 0xF0000000, 0);    // Partition dump status
211     EXPECT_EQ(hexwords[2 - 2] & 0x00F00000, 0);    // Partition boot type
212     EXPECT_EQ(hexwords[2 - 2] & 0x000000FF, 0x55); // SRC format
213     EXPECT_EQ(hexwords[3 - 2] & 0x000000FF, 0x10); // BMC position
214     EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0xABCD0000); // Motherboard CCIN
215 
216     // Validate more fields here as the code starts filling them in.
217 
218     // Ensure hex word 5 wasn't allowed to be set to TEST1's contents
219     // And that none of the error status flags are set
220     EXPECT_EQ(hexwords[5 - 2], 0);
221 
222     // The user defined hex word fields specifed in the additional data.
223     EXPECT_EQ(hexwords[6 - 2], 0x12345678); // TEST1
224     EXPECT_EQ(hexwords[7 - 2], 12345678);   // TEST2
225     EXPECT_EQ(hexwords[8 - 2], 0xdef);      // TEST3
226     EXPECT_EQ(hexwords[9 - 2], 0);          // TEST4, but can't convert a 'Z'
227 
228     EXPECT_EQ(src.asciiString(), "BD42ABCD                        ");
229 
230     // No callouts
231     EXPECT_FALSE(src.callouts());
232 
233     // May as well spot check the flatten/unflatten
234     std::vector<uint8_t> data;
235     Stream stream{data};
236     src.flatten(stream);
237 
238     stream.offset(0);
239     SRC newSRC{stream};
240 
241     EXPECT_TRUE(newSRC.valid());
242     EXPECT_EQ(newSRC.asciiString(), src.asciiString());
243     EXPECT_FALSE(newSRC.callouts());
244 }
245 
246 // Test when the CCIN string isn't a 4 character number
TEST_F(SRCTest,BadCCINTest)247 TEST_F(SRCTest, BadCCINTest)
248 {
249     message::Entry entry;
250     entry.src.type = 0xBD;
251     entry.src.reasonCode = 0xABCD;
252     entry.subsystem = 0x42;
253 
254     std::map<std::string, std::string> adData{};
255     AdditionalData ad{adData};
256     NiceMock<MockDataInterface> dataIface;
257 
258     // First it isn't a number, then it is too long,
259     // then it is empty.
260     EXPECT_CALL(dataIface, getMotherboardCCIN)
261         .WillOnce(Return("X"))
262         .WillOnce(Return("12345"))
263         .WillOnce(Return(""));
264 
265     // The CCIN in the first half should still be 0 each time.
266     {
267         SRC src{entry, ad, dataIface};
268         EXPECT_TRUE(src.valid());
269         const auto& hexwords = src.hexwordData();
270         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
271     }
272 
273     {
274         SRC src{entry, ad, dataIface};
275         EXPECT_TRUE(src.valid());
276         const auto& hexwords = src.hexwordData();
277         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
278     }
279 
280     {
281         SRC src{entry, ad, dataIface};
282         EXPECT_TRUE(src.valid());
283         const auto& hexwords = src.hexwordData();
284         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
285     }
286 }
287 
288 // Test the getErrorDetails function
TEST_F(SRCTest,MessageSubstitutionTest)289 TEST_F(SRCTest, MessageSubstitutionTest)
290 {
291     auto path = SRCTest::writeData(testRegistry);
292     message::Registry registry{path};
293     auto entry = registry.lookup("0xABCD", message::LookupType::reasonCode);
294 
295     std::map<std::string, std::string> adData{
296         {"COMPID", "0x1"},
297         {"FREQUENCY", "0x4"},
298         {"DURATION", "30"},
299         {"ERRORCODE", "0x01ABCDEF"}};
300     AdditionalData ad{adData};
301     NiceMock<MockDataInterface> dataIface;
302 
303     SRC src{*entry, ad, dataIface};
304     EXPECT_TRUE(src.valid());
305 
306     auto errorDetails = src.getErrorDetails(registry, DetailLevel::message);
307     ASSERT_TRUE(errorDetails);
308     EXPECT_EQ(errorDetails.value(),
309               "Comp 0x00000001 failed 0x00000004 times over 0x0000001E secs "
310               "with ErrorCode 0x01ABCDEF");
311 }
312 // Test that an inventory path callout string is
313 // converted into the appropriate FRU callout.
TEST_F(SRCTest,InventoryCalloutTest)314 TEST_F(SRCTest, InventoryCalloutTest)
315 {
316     message::Entry entry;
317     entry.src.type = 0xBD;
318     entry.src.reasonCode = 0xABCD;
319     entry.subsystem = 0x42;
320 
321     std::map<std::string, std::string> adData{
322         {"CALLOUT_INVENTORY_PATH", "motherboard"}};
323     AdditionalData ad{adData};
324     NiceMock<MockDataInterface> dataIface;
325 
326     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
327         .WillOnce(Return("UTMS-P1"));
328 
329     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
330         .Times(1)
331         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
332                         SetArgReferee<3>("123456789ABC")));
333 
334     SRC src{entry, ad, dataIface};
335     EXPECT_TRUE(src.valid());
336 
337     ASSERT_TRUE(src.callouts());
338 
339     EXPECT_EQ(src.callouts()->callouts().size(), 1);
340 
341     auto& callout = src.callouts()->callouts().front();
342 
343     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
344     EXPECT_EQ(callout->priority(), 'H');
345 
346     auto& fru = callout->fruIdentity();
347 
348     EXPECT_EQ(fru->getPN().value(), "1234567");
349     EXPECT_EQ(fru->getCCIN().value(), "CCCC");
350     EXPECT_EQ(fru->getSN().value(), "123456789ABC");
351 
352     // flatten and unflatten
353     std::vector<uint8_t> data;
354     Stream stream{data};
355     src.flatten(stream);
356 
357     stream.offset(0);
358     SRC newSRC{stream};
359     EXPECT_TRUE(newSRC.valid());
360     ASSERT_TRUE(src.callouts());
361     EXPECT_EQ(src.callouts()->callouts().size(), 1);
362 }
363 
364 // Test that when the location code can't be obtained that
365 // no callout is added.
TEST_F(SRCTest,InventoryCalloutNoLocCodeTest)366 TEST_F(SRCTest, InventoryCalloutNoLocCodeTest)
367 {
368     message::Entry entry;
369     entry.src.type = 0xBD;
370     entry.src.reasonCode = 0xABCD;
371     entry.subsystem = 0x42;
372 
373     std::map<std::string, std::string> adData{
374         {"CALLOUT_INVENTORY_PATH", "motherboard"}};
375     AdditionalData ad{adData};
376     NiceMock<MockDataInterface> dataIface;
377 
378     auto func = []() {
379         throw sdbusplus::exception::SdBusError(5, "Error");
380         return std::string{};
381     };
382 
383     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
384         .Times(1)
385         .WillOnce(InvokeWithoutArgs(func));
386 
387     EXPECT_CALL(dataIface, getHWCalloutFields(_, _, _, _)).Times(0);
388 
389     SRC src{entry, ad, dataIface};
390     EXPECT_TRUE(src.valid());
391 
392     ASSERT_FALSE(src.callouts());
393 
394     // flatten and unflatten
395     std::vector<uint8_t> data;
396     Stream stream{data};
397     src.flatten(stream);
398 
399     stream.offset(0);
400     SRC newSRC{stream};
401     EXPECT_TRUE(newSRC.valid());
402     ASSERT_FALSE(src.callouts());
403 }
404 
405 // Test that when the VPD can't be obtained that
406 // a callout is still created.
TEST_F(SRCTest,InventoryCalloutNoVPDTest)407 TEST_F(SRCTest, InventoryCalloutNoVPDTest)
408 {
409     message::Entry entry;
410     entry.src.type = 0xBD;
411     entry.src.reasonCode = 0xABCD;
412     entry.subsystem = 0x42;
413 
414     std::map<std::string, std::string> adData{
415         {"CALLOUT_INVENTORY_PATH", "motherboard"}};
416     AdditionalData ad{adData};
417     NiceMock<MockDataInterface> dataIface;
418 
419     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
420         .Times(1)
421         .WillOnce(Return("UTMS-P10"));
422 
423     auto func = []() { throw sdbusplus::exception::SdBusError(5, "Error"); };
424 
425     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
426         .Times(1)
427         .WillOnce(InvokeWithoutArgs(func));
428 
429     SRC src{entry, ad, dataIface};
430     EXPECT_TRUE(src.valid());
431     ASSERT_TRUE(src.callouts());
432     EXPECT_EQ(src.callouts()->callouts().size(), 1);
433 
434     auto& callout = src.callouts()->callouts().front();
435     EXPECT_EQ(callout->locationCode(), "UTMS-P10");
436     EXPECT_EQ(callout->priority(), 'H');
437 
438     auto& fru = callout->fruIdentity();
439 
440     EXPECT_EQ(fru->getPN(), "");
441     EXPECT_EQ(fru->getCCIN(), "");
442     EXPECT_EQ(fru->getSN(), "");
443     EXPECT_FALSE(fru->getMaintProc());
444 
445     // flatten and unflatten
446     std::vector<uint8_t> data;
447     Stream stream{data};
448     src.flatten(stream);
449 
450     stream.offset(0);
451     SRC newSRC{stream};
452     EXPECT_TRUE(newSRC.valid());
453     ASSERT_TRUE(src.callouts());
454     EXPECT_EQ(src.callouts()->callouts().size(), 1);
455 }
456 
TEST_F(SRCTest,RegistryCalloutTest)457 TEST_F(SRCTest, RegistryCalloutTest)
458 {
459     message::Entry entry;
460     entry.src.type = 0xBD;
461     entry.src.reasonCode = 0xABCD;
462     entry.src.deconfigFlag = true;
463     entry.src.checkstopFlag = true;
464     entry.subsystem = 0x42;
465 
466     entry.callouts = R"(
467         [
468         {
469             "System": "systemA",
470             "CalloutList":
471             [
472                 {
473                     "Priority": "high",
474                     "SymbolicFRU": "service_docs"
475                 },
476                 {
477                     "Priority": "medium",
478                     "Procedure": "BMC0001"
479                 }
480             ]
481         },
482         {
483             "System": "systemB",
484             "CalloutList":
485             [
486                 {
487                     "Priority": "high",
488                     "LocCode": "P0-C8",
489                     "SymbolicFRUTrusted": "service_docs"
490                 },
491                 {
492                     "Priority": "medium",
493                     "SymbolicFRUTrusted": "service_docs"
494                 }
495             ]
496         },
497         {
498             "System": "systemC",
499             "CalloutList":
500             [
501                 {
502                     "Priority": "high",
503                     "LocCode": "P0-C8"
504                 },
505                 {
506                     "Priority": "medium",
507                     "LocCode": "P0-C9"
508                 }
509             ]
510         }
511         ])"_json;
512 
513     {
514         // Call out a symbolic FRU and a procedure
515         AdditionalData ad;
516         NiceMock<MockDataInterface> dataIface;
517         std::vector<std::string> names{"systemA"};
518 
519         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
520 
521         SRC src{entry, ad, dataIface};
522 
523         EXPECT_TRUE(
524             src.getErrorStatusFlag(SRC::ErrorStatusFlags::deconfigured));
525         EXPECT_TRUE(src.getErrorStatusFlag(SRC::ErrorStatusFlags::hwCheckstop));
526 
527         const auto& hexwords = src.hexwordData();
528         auto mask = static_cast<uint32_t>(SRC::ErrorStatusFlags::deconfigured) |
529                     static_cast<uint32_t>(SRC::ErrorStatusFlags::hwCheckstop);
530         EXPECT_EQ(hexwords[5 - 2] & mask, mask);
531 
532         auto& callouts = src.callouts()->callouts();
533         ASSERT_EQ(callouts.size(), 2);
534 
535         EXPECT_EQ(callouts[0]->locationCodeSize(), 0);
536         EXPECT_EQ(callouts[0]->priority(), 'H');
537 
538         EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
539         EXPECT_EQ(callouts[1]->priority(), 'M');
540 
541         auto& fru1 = callouts[0]->fruIdentity();
542         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
543         EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
544         EXPECT_FALSE(fru1->getMaintProc());
545         EXPECT_FALSE(fru1->getSN());
546         EXPECT_FALSE(fru1->getCCIN());
547 
548         auto& fru2 = callouts[1]->fruIdentity();
549         EXPECT_EQ(fru2->getMaintProc().value(), "BMC0001");
550         EXPECT_EQ(fru2->failingComponentType(),
551                   src::FRUIdentity::maintenanceProc);
552         EXPECT_FALSE(fru2->getPN());
553         EXPECT_FALSE(fru2->getSN());
554         EXPECT_FALSE(fru2->getCCIN());
555     }
556 
557     {
558         // Call out a trusted symbolic FRU with a location code, and
559         // another one without.
560         AdditionalData ad;
561         NiceMock<MockDataInterface> dataIface;
562         std::vector<std::string> names{"systemB"};
563 
564         EXPECT_CALL(dataIface, expandLocationCode).WillOnce(Return("P0-C8"));
565         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
566 
567         SRC src{entry, ad, dataIface};
568 
569         auto& callouts = src.callouts()->callouts();
570         EXPECT_EQ(callouts.size(), 2);
571 
572         EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
573         EXPECT_EQ(callouts[0]->priority(), 'H');
574 
575         EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
576         EXPECT_EQ(callouts[1]->priority(), 'M');
577 
578         auto& fru1 = callouts[0]->fruIdentity();
579         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
580         EXPECT_EQ(fru1->failingComponentType(),
581                   src::FRUIdentity::symbolicFRUTrustedLocCode);
582         EXPECT_FALSE(fru1->getMaintProc());
583         EXPECT_FALSE(fru1->getSN());
584         EXPECT_FALSE(fru1->getCCIN());
585 
586         // It asked for a trusted symbolic FRU, but no location code
587         // was provided so it is switched back to a normal one
588         auto& fru2 = callouts[1]->fruIdentity();
589         EXPECT_EQ(fru2->getPN().value(), "SVCDOCS");
590         EXPECT_EQ(fru2->failingComponentType(), src::FRUIdentity::symbolicFRU);
591         EXPECT_FALSE(fru2->getMaintProc());
592         EXPECT_FALSE(fru2->getSN());
593         EXPECT_FALSE(fru2->getCCIN());
594     }
595 
596     {
597         // Two hardware callouts
598         AdditionalData ad;
599         NiceMock<MockDataInterface> dataIface;
600         std::vector<std::string> names{"systemC"};
601 
602         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
603 
604         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
605             .WillOnce(Return("UXXX-P0-C8"));
606 
607         EXPECT_CALL(dataIface, expandLocationCode("P0-C9", 0))
608             .WillOnce(Return("UXXX-P0-C9"));
609 
610         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C8", 0, false))
611             .WillOnce(Return(std::vector<std::string>{
612                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
613 
614         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C9", 0, false))
615             .WillOnce(Return(std::vector<std::string>{
616                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1"}));
617 
618         EXPECT_CALL(
619             dataIface,
620             getHWCalloutFields(
621                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _,
622                 _))
623             .Times(1)
624             .WillOnce(
625                 DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
626                       SetArgReferee<3>("123456789ABC")));
627 
628         EXPECT_CALL(
629             dataIface,
630             getHWCalloutFields(
631                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1", _, _,
632                 _))
633             .Times(1)
634             .WillOnce(
635                 DoAll(SetArgReferee<1>("2345678"), SetArgReferee<2>("DDDD"),
636                       SetArgReferee<3>("23456789ABCD")));
637 
638         SRC src{entry, ad, dataIface};
639 
640         auto& callouts = src.callouts()->callouts();
641         EXPECT_EQ(callouts.size(), 2);
642 
643         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C8");
644         EXPECT_EQ(callouts[0]->priority(), 'H');
645 
646         auto& fru1 = callouts[0]->fruIdentity();
647         EXPECT_EQ(fru1->getPN().value(), "1234567");
648         EXPECT_EQ(fru1->getCCIN().value(), "CCCC");
649         EXPECT_EQ(fru1->getSN().value(), "123456789ABC");
650 
651         EXPECT_EQ(callouts[1]->locationCode(), "UXXX-P0-C9");
652         EXPECT_EQ(callouts[1]->priority(), 'M');
653 
654         auto& fru2 = callouts[1]->fruIdentity();
655         EXPECT_EQ(fru2->getPN().value(), "2345678");
656         EXPECT_EQ(fru2->getCCIN().value(), "DDDD");
657         EXPECT_EQ(fru2->getSN().value(), "23456789ABCD");
658     }
659 }
660 
661 // Test that a symbolic FRU with a trusted location code callout
662 // from the registry can get its location from the
663 // CALLOUT_INVENTORY_PATH AdditionalData entry.
TEST_F(SRCTest,SymbolicFRUWithInvPathTest)664 TEST_F(SRCTest, SymbolicFRUWithInvPathTest)
665 {
666     message::Entry entry;
667     entry.src.type = 0xBD;
668     entry.src.reasonCode = 0xABCD;
669     entry.subsystem = 0x42;
670 
671     entry.callouts = R"(
672         [{
673             "CalloutList":
674             [
675                 {
676                     "Priority": "high",
677                     "SymbolicFRUTrusted": "service_docs",
678                     "UseInventoryLocCode": true
679                 },
680                 {
681                     "Priority": "medium",
682                     "LocCode": "P0-C8",
683                     "SymbolicFRUTrusted": "pwrsply"
684                 }
685             ]
686         }])"_json;
687 
688     {
689         // The location code for the first symbolic FRU callout will
690         // come from this inventory path since UseInventoryLocCode is set.
691         // In this case there will be no normal FRU callout for the motherboard.
692         std::map<std::string, std::string> adData{
693             {"CALLOUT_INVENTORY_PATH", "motherboard"}};
694         AdditionalData ad{adData};
695         NiceMock<MockDataInterface> dataIface;
696         std::vector<std::string> names{"systemA"};
697 
698         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
699 
700         EXPECT_CALL(dataIface, getLocationCode("motherboard"))
701             .Times(1)
702             .WillOnce(Return("Ufcs-P10"));
703 
704         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
705             .WillOnce(Return("Ufcs-P0-C8"));
706 
707         SRC src{entry, ad, dataIface};
708 
709         auto& callouts = src.callouts()->callouts();
710         EXPECT_EQ(callouts.size(), 2);
711 
712         // The location code for the first symbolic FRU callout with a
713         // trusted location code comes from the motherboard.
714         EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P10");
715         EXPECT_EQ(callouts[0]->priority(), 'H');
716         auto& fru1 = callouts[0]->fruIdentity();
717         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
718         EXPECT_EQ(fru1->failingComponentType(),
719                   src::FRUIdentity::symbolicFRUTrustedLocCode);
720 
721         // The second trusted symbolic FRU callouts uses the location
722         // code in the registry as usual.
723         EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P0-C8");
724         EXPECT_EQ(callouts[1]->priority(), 'M');
725         auto& fru2 = callouts[1]->fruIdentity();
726         EXPECT_EQ(fru2->getPN().value(), "PWRSPLY");
727         EXPECT_EQ(fru2->failingComponentType(),
728                   src::FRUIdentity::symbolicFRUTrustedLocCode);
729     }
730 
731     {
732         // This time say we want to use the location code from
733         // the inventory, but don't pass it in and the callout should
734         // end up a regular symbolic FRU
735         entry.callouts = R"(
736         [{
737             "CalloutList":
738             [
739                 {
740                     "Priority": "high",
741                     "SymbolicFRUTrusted": "service_docs",
742                     "UseInventoryLocCode": true
743                 }
744             ]
745         }])"_json;
746 
747         AdditionalData ad;
748         NiceMock<MockDataInterface> dataIface;
749         std::vector<std::string> names{"systemA"};
750 
751         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
752 
753         SRC src{entry, ad, dataIface};
754 
755         auto& callouts = src.callouts()->callouts();
756         EXPECT_EQ(callouts.size(), 1);
757 
758         EXPECT_EQ(callouts[0]->locationCode(), "");
759         EXPECT_EQ(callouts[0]->priority(), 'H');
760         auto& fru1 = callouts[0]->fruIdentity();
761         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
762         EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
763     }
764 }
765 
TEST_F(SRCTest,RegistryCalloutCantGetLocTest)766 TEST_F(SRCTest, RegistryCalloutCantGetLocTest)
767 {
768     message::Entry entry;
769     entry.src.type = 0xBD;
770     entry.src.reasonCode = 0xABCD;
771     entry.src.deconfigFlag = true;
772     entry.src.checkstopFlag = true;
773     entry.subsystem = 0x42;
774 
775     entry.callouts = R"(
776         [{
777             "CalloutList":
778             [
779                 {
780                     "Priority": "high",
781                     "LocCode": "P0-C8"
782                 },
783                 {
784                     "Priority": "medium",
785                     "LocCode": "P0-C9"
786                 }
787             ]
788         }])"_json;
789 
790     {
791         // The calls to expand the location codes will fail, but it should
792         // still create the callouts with the unexpanded values and no HW
793         // fields.
794         AdditionalData ad;
795         NiceMock<MockDataInterface> dataIface;
796         std::vector<std::string> names{"systemC"};
797 
798         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
799 
800         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
801             .WillRepeatedly(Throw(std::runtime_error("Fail")));
802 
803         EXPECT_CALL(dataIface, expandLocationCode("P0-C9", 0))
804             .WillRepeatedly(Throw(std::runtime_error("Fail")));
805 
806         EXPECT_CALL(dataIface, getInventoryFromLocCode(_, _, _)).Times(0);
807 
808         EXPECT_CALL(dataIface, getHWCalloutFields(_, _, _, _)).Times(0);
809 
810         SRC src{entry, ad, dataIface};
811 
812         auto& callouts = src.callouts()->callouts();
813         ASSERT_EQ(callouts.size(), 2);
814 
815         // Only unexpanded location codes
816         EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
817         EXPECT_EQ(callouts[0]->priority(), 'H');
818 
819         auto& fru1 = callouts[0]->fruIdentity();
820         EXPECT_EQ(fru1->getPN().value(), "");
821         EXPECT_EQ(fru1->getCCIN().value(), "");
822         EXPECT_EQ(fru1->getSN().value(), "");
823 
824         EXPECT_EQ(callouts[1]->locationCode(), "P0-C9");
825         EXPECT_EQ(callouts[1]->priority(), 'M');
826 
827         auto& fru2 = callouts[1]->fruIdentity();
828         EXPECT_EQ(fru2->getPN().value(), "");
829         EXPECT_EQ(fru2->getCCIN().value(), "");
830         EXPECT_EQ(fru2->getSN().value(), "");
831     }
832 }
833 
TEST_F(SRCTest,TrustedSymbolicFRUCantGetLocTest)834 TEST_F(SRCTest, TrustedSymbolicFRUCantGetLocTest)
835 {
836     message::Entry entry;
837     entry.src.type = 0xBD;
838     entry.src.reasonCode = 0xABCD;
839     entry.subsystem = 0x42;
840 
841     entry.callouts = R"(
842         [{
843             "CalloutList":
844             [
845                 {
846                     "Priority": "medium",
847                     "LocCode": "P0-C8",
848                     "SymbolicFRUTrusted": "pwrsply"
849                 }
850             ]
851         }])"_json;
852 
853     std::map<std::string, std::string> adData;
854     AdditionalData ad{adData};
855     NiceMock<MockDataInterface> dataIface;
856     std::vector<std::string> names{"systemA"};
857 
858     EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
859 
860     // The call to expand the location code will fail, but it should
861     // still create the callout with the unexpanded value and the
862     // symbolic FRU can't be trusted.
863     EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
864         .WillRepeatedly(Throw(std::runtime_error("Fail")));
865 
866     SRC src{entry, ad, dataIface};
867 
868     auto& callouts = src.callouts()->callouts();
869     ASSERT_EQ(callouts.size(), 1);
870 
871     EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
872     EXPECT_EQ(callouts[0]->priority(), 'M');
873     auto& fru = callouts[0]->fruIdentity();
874     EXPECT_EQ(fru->getPN().value(), "PWRSPLY");
875     EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
876 }
877 
878 // Test looking up device path fails in the callout jSON.
TEST_F(SRCTest,DevicePathCalloutTest)879 TEST_F(SRCTest, DevicePathCalloutTest)
880 {
881     message::Entry entry;
882     entry.src.type = 0xBD;
883     entry.src.reasonCode = 0xABCD;
884     entry.subsystem = 0x42;
885 
886     const auto calloutJSON = R"(
887     {
888         "I2C":
889         {
890             "14":
891             {
892                 "114":
893                 {
894                     "Callouts":[
895                     {
896                         "Name": "/chassis/motherboard/cpu0",
897                         "LocationCode": "P1-C40",
898                         "Priority": "H"
899                     },
900                     {
901                         "Name": "/chassis/motherboard",
902                         "LocationCode": "P1",
903                         "Priority": "M"
904                     },
905                     {
906                         "Name": "/chassis/motherboard/bmc",
907                         "LocationCode": "P1-C15",
908                         "Priority": "L"
909                     }
910                     ],
911                     "Dest": "proc 0 target"
912                 }
913             }
914         }
915     })";
916 
917     auto dataPath = getPELReadOnlyDataPath();
918     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
919     file << calloutJSON;
920     file.close();
921 
922     NiceMock<MockDataInterface> dataIface;
923     std::vector<std::string> names{"systemA"};
924 
925     EXPECT_CALL(dataIface, getSystemNames)
926         .Times(5)
927         .WillRepeatedly(Return(names));
928 
929     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false))
930         .Times(3)
931         .WillRepeatedly(Return(std::vector<std::string>{
932             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
933 
934     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
935         .Times(3)
936         .WillRepeatedly(Return(std::vector<std::string>{
937             "/xyz/openbmc_project/inventory/chassis/motherboard"}));
938 
939     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C15", 0, false))
940         .Times(3)
941         .WillRepeatedly(Return(std::vector<std::string>{
942             "/xyz/openbmc_project/inventory/chassis/motherboard/bmc"}));
943 
944     EXPECT_CALL(dataIface, expandLocationCode("P1-C40", 0))
945         .Times(3)
946         .WillRepeatedly(Return("Ufcs-P1-C40"));
947 
948     EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
949         .Times(3)
950         .WillRepeatedly(Return("Ufcs-P1"));
951 
952     EXPECT_CALL(dataIface, expandLocationCode("P1-C15", 0))
953         .Times(3)
954         .WillRepeatedly(Return("Ufcs-P1-C15"));
955 
956     EXPECT_CALL(dataIface,
957                 getHWCalloutFields(
958                     "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0",
959                     _, _, _))
960         .Times(3)
961         .WillRepeatedly(
962             DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
963                   SetArgReferee<3>("123456789ABC")));
964     EXPECT_CALL(
965         dataIface,
966         getHWCalloutFields("/xyz/openbmc_project/inventory/chassis/motherboard",
967                            _, _, _))
968         .Times(3)
969         .WillRepeatedly(
970             DoAll(SetArgReferee<1>("7654321"), SetArgReferee<2>("MMMM"),
971                   SetArgReferee<3>("CBA987654321")));
972     EXPECT_CALL(dataIface,
973                 getHWCalloutFields(
974                     "/xyz/openbmc_project/inventory/chassis/motherboard/bmc", _,
975                     _, _))
976         .Times(3)
977         .WillRepeatedly(
978             DoAll(SetArgReferee<1>("7123456"), SetArgReferee<2>("BBBB"),
979                   SetArgReferee<3>("C123456789AB")));
980 
981     // Call this below with different AdditionalData values that
982     // result in the same callouts.
983     auto checkCallouts = [&entry, &dataIface](const auto& items) {
984         AdditionalData ad{items};
985         SRC src{entry, ad, dataIface};
986 
987         ASSERT_TRUE(src.callouts());
988         auto& callouts = src.callouts()->callouts();
989 
990         ASSERT_EQ(callouts.size(), 3);
991 
992         {
993             EXPECT_EQ(callouts[0]->priority(), 'H');
994             EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P1-C40");
995 
996             auto& fru = callouts[0]->fruIdentity();
997             EXPECT_EQ(fru->getPN().value(), "1234567");
998             EXPECT_EQ(fru->getCCIN().value(), "CCCC");
999             EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1000         }
1001         {
1002             EXPECT_EQ(callouts[1]->priority(), 'M');
1003             EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P1");
1004 
1005             auto& fru = callouts[1]->fruIdentity();
1006             EXPECT_EQ(fru->getPN().value(), "7654321");
1007             EXPECT_EQ(fru->getCCIN().value(), "MMMM");
1008             EXPECT_EQ(fru->getSN().value(), "CBA987654321");
1009         }
1010         {
1011             EXPECT_EQ(callouts[2]->priority(), 'L');
1012             EXPECT_EQ(callouts[2]->locationCode(), "Ufcs-P1-C15");
1013 
1014             auto& fru = callouts[2]->fruIdentity();
1015             EXPECT_EQ(fru->getPN().value(), "7123456");
1016             EXPECT_EQ(fru->getCCIN().value(), "BBBB");
1017             EXPECT_EQ(fru->getSN().value(), "C123456789AB");
1018         }
1019     };
1020 
1021     {
1022         // Callouts based on the device path
1023         std::map<std::string, std::string> items{
1024             {"CALLOUT_ERRNO", "5"},
1025             {"CALLOUT_DEVICE_PATH",
1026              "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}};
1027 
1028         checkCallouts(items);
1029     }
1030 
1031     {
1032         // Callouts based on the I2C bus and address
1033         std::map<std::string, std::string> items{{"CALLOUT_ERRNO", "5"},
1034                                                  {"CALLOUT_IIC_BUS", "14"},
1035                                                  {"CALLOUT_IIC_ADDR", "0x72"}};
1036         checkCallouts(items);
1037     }
1038 
1039     {
1040         // Also based on I2C bus and address, but with bus = /dev/i2c-14
1041         std::map<std::string, std::string> items{{"CALLOUT_ERRNO", "5"},
1042                                                  {"CALLOUT_IIC_BUS", "14"},
1043                                                  {"CALLOUT_IIC_ADDR", "0x72"}};
1044         checkCallouts(items);
1045     }
1046 
1047     {
1048         // Callout not found
1049         std::map<std::string, std::string> items{
1050             {"CALLOUT_ERRNO", "5"},
1051             {"CALLOUT_DEVICE_PATH",
1052              "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-24/24-0012"}};
1053 
1054         AdditionalData ad{items};
1055         SRC src{entry, ad, dataIface};
1056 
1057         EXPECT_FALSE(src.callouts());
1058         ASSERT_EQ(src.getDebugData().size(), 1);
1059         EXPECT_EQ(src.getDebugData()[0],
1060                   "Problem looking up I2C callouts on 24 18: "
1061                   "[json.exception.out_of_range.403] key '24' not found");
1062     }
1063 
1064     {
1065         // Callout not found
1066         std::map<std::string, std::string> items{{"CALLOUT_ERRNO", "5"},
1067                                                  {"CALLOUT_IIC_BUS", "22"},
1068                                                  {"CALLOUT_IIC_ADDR", "0x99"}};
1069         AdditionalData ad{items};
1070         SRC src{entry, ad, dataIface};
1071 
1072         EXPECT_FALSE(src.callouts());
1073         ASSERT_EQ(src.getDebugData().size(), 1);
1074         EXPECT_EQ(src.getDebugData()[0],
1075                   "Problem looking up I2C callouts on 22 153: "
1076                   "[json.exception.out_of_range.403] key '22' not found");
1077     }
1078 }
1079 
TEST_F(SRCTest,DevicePathCantGetLocTest)1080 TEST_F(SRCTest, DevicePathCantGetLocTest)
1081 {
1082     message::Entry entry;
1083     entry.src.type = 0xBD;
1084     entry.src.reasonCode = 0xABCD;
1085     entry.subsystem = 0x42;
1086 
1087     const auto calloutJSON = R"(
1088     {
1089         "I2C":
1090         {
1091             "14":
1092             {
1093                 "114":
1094                 {
1095                     "Callouts":[
1096                     {
1097                         "Name": "/chassis/motherboard/cpu0",
1098                         "LocationCode": "P1-C40",
1099                         "Priority": "H"
1100                     },
1101                     {
1102                         "Name": "/chassis/motherboard",
1103                         "LocationCode": "P1",
1104                         "Priority": "M"
1105                     }
1106                     ],
1107                     "Dest": "proc 0 target"
1108                 }
1109             }
1110         }
1111     })";
1112 
1113     auto dataPath = getPELReadOnlyDataPath();
1114     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
1115     file << calloutJSON;
1116     file.close();
1117 
1118     NiceMock<MockDataInterface> dataIface;
1119     std::vector<std::string> names{"systemA"};
1120 
1121     EXPECT_CALL(dataIface, getSystemNames).WillRepeatedly(Return(names));
1122 
1123     // The calls to expand the location codes will fail, so still create
1124     // the callouts with the unexpanded values and no HW fields
1125 
1126     EXPECT_CALL(dataIface, expandLocationCode("P1-C40", 0))
1127         .WillRepeatedly(Throw(std::runtime_error("Fail")));
1128 
1129     EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
1130         .WillRepeatedly(Throw(std::runtime_error("Fail")));
1131 
1132     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false))
1133         .Times(0);
1134     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false)).Times(0);
1135 
1136     std::map<std::string, std::string> items{
1137         {"CALLOUT_ERRNO", "5"},
1138         {"CALLOUT_DEVICE_PATH",
1139          "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}};
1140 
1141     AdditionalData ad{items};
1142     SRC src{entry, ad, dataIface};
1143 
1144     ASSERT_TRUE(src.callouts());
1145     auto& callouts = src.callouts()->callouts();
1146 
1147     ASSERT_EQ(callouts.size(), 2);
1148 
1149     // Should just contain the unexpanded location codes
1150     {
1151         EXPECT_EQ(callouts[0]->priority(), 'H');
1152         EXPECT_EQ(callouts[0]->locationCode(), "P1-C40");
1153 
1154         auto& fru = callouts[0]->fruIdentity();
1155         EXPECT_EQ(fru->getPN().value(), "");
1156         EXPECT_EQ(fru->getCCIN().value(), "");
1157         EXPECT_EQ(fru->getSN().value(), "");
1158     }
1159     {
1160         EXPECT_EQ(callouts[1]->priority(), 'M');
1161         EXPECT_EQ(callouts[1]->locationCode(), "P1");
1162 
1163         auto& fru = callouts[1]->fruIdentity();
1164         EXPECT_EQ(fru->getPN().value(), "");
1165         EXPECT_EQ(fru->getCCIN().value(), "");
1166         EXPECT_EQ(fru->getSN().value(), "");
1167     }
1168 
1169     fs::remove_all(dataPath);
1170 }
1171 
1172 // Test when callouts are passed in via JSON
TEST_F(SRCTest,JsonCalloutsTest)1173 TEST_F(SRCTest, JsonCalloutsTest)
1174 {
1175     const auto jsonCallouts = R"(
1176         [
1177             {
1178                 "LocationCode": "P0-C1",
1179                 "Priority": "H",
1180                 "MRUs": [
1181                     {
1182                         "ID": 42,
1183                         "Priority": "H"
1184                     },
1185                     {
1186                         "ID": 43,
1187                         "Priority": "M"
1188                     }
1189                 ]
1190             },
1191             {
1192                 "InventoryPath": "/inv/system/chassis/motherboard/cpu0",
1193                 "Priority": "M",
1194                 "Guarded": true,
1195                 "Deconfigured": true
1196             },
1197             {
1198                 "Procedure": "PROCEDU",
1199                 "Priority": "A"
1200             },
1201             {
1202                 "SymbolicFRU": "TRUSTED",
1203                 "Priority": "B",
1204                 "TrustedLocationCode": true,
1205                 "LocationCode": "P1-C23"
1206             },
1207             {
1208                 "SymbolicFRU": "FRUTST1",
1209                 "Priority": "C",
1210                 "LocationCode": "P1-C24"
1211             },
1212             {
1213                 "SymbolicFRU": "FRUTST2LONG",
1214                 "Priority": "L"
1215             },
1216             {
1217                 "Procedure": "fsi_path",
1218                 "Priority": "L"
1219             },
1220             {
1221                 "SymbolicFRU": "ambient_temp",
1222                 "Priority": "L"
1223             }
1224         ]
1225     )"_json;
1226 
1227     message::Entry entry;
1228     entry.src.type = 0xBD;
1229     entry.src.reasonCode = 0xABCD;
1230     entry.subsystem = 0x42;
1231 
1232     AdditionalData ad;
1233     NiceMock<MockDataInterface> dataIface;
1234 
1235     // Callout 0 mock calls
1236     {
1237         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1238             .Times(1)
1239             .WillOnce(Return("UXXX-P0-C1"));
1240         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1241             .Times(1)
1242             .WillOnce(Return(std::vector<std::string>{
1243                 "/inv/system/chassis/motherboard/bmc"}));
1244         EXPECT_CALL(
1245             dataIface,
1246             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1247             .Times(1)
1248             .WillOnce(
1249                 DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1250                       SetArgReferee<3>("123456789ABC")));
1251     }
1252     // Callout 1 mock calls
1253     {
1254         EXPECT_CALL(dataIface,
1255                     getLocationCode("/inv/system/chassis/motherboard/cpu0"))
1256             .WillOnce(Return("UYYY-P5"));
1257         EXPECT_CALL(
1258             dataIface,
1259             getHWCalloutFields("/inv/system/chassis/motherboard/cpu0", _, _, _))
1260             .Times(1)
1261             .WillOnce(
1262                 DoAll(SetArgReferee<1>("2345678"), SetArgReferee<2>("DDDD"),
1263                       SetArgReferee<3>("23456789ABCD")));
1264     }
1265     // Callout 3 mock calls
1266     {
1267         EXPECT_CALL(dataIface, expandLocationCode("P1-C23", 0))
1268             .Times(1)
1269             .WillOnce(Return("UXXX-P1-C23"));
1270     }
1271     // Callout 4 mock calls
1272     {
1273         EXPECT_CALL(dataIface, expandLocationCode("P1-C24", 0))
1274             .Times(1)
1275             .WillOnce(Return("UXXX-P1-C24"));
1276     }
1277 
1278     SRC src{entry, ad, jsonCallouts, dataIface};
1279     ASSERT_TRUE(src.callouts());
1280 
1281     // Check the guarded and deconfigured flags
1282     EXPECT_TRUE(src.hexwordData()[3] & 0x03000000);
1283 
1284     const auto& callouts = src.callouts()->callouts();
1285     ASSERT_EQ(callouts.size(), 8);
1286 
1287     // Check callout 0
1288     {
1289         EXPECT_EQ(callouts[0]->priority(), 'H');
1290         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1291 
1292         auto& fru = callouts[0]->fruIdentity();
1293         EXPECT_EQ(fru->getPN().value(), "1234567");
1294         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1295         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1296         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1297 
1298         auto& mruCallouts = callouts[0]->mru();
1299         ASSERT_TRUE(mruCallouts);
1300         auto& mrus = mruCallouts->mrus();
1301         ASSERT_EQ(mrus.size(), 2);
1302         EXPECT_EQ(mrus[0].id, 42);
1303         EXPECT_EQ(mrus[0].priority, 'H');
1304         EXPECT_EQ(mrus[1].id, 43);
1305         EXPECT_EQ(mrus[1].priority, 'M');
1306     }
1307 
1308     // Check callout 1
1309     {
1310         EXPECT_EQ(callouts[1]->priority(), 'M');
1311         EXPECT_EQ(callouts[1]->locationCode(), "UYYY-P5");
1312 
1313         auto& fru = callouts[1]->fruIdentity();
1314         EXPECT_EQ(fru->getPN().value(), "2345678");
1315         EXPECT_EQ(fru->getCCIN().value(), "DDDD");
1316         EXPECT_EQ(fru->getSN().value(), "23456789ABCD");
1317         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1318     }
1319 
1320     // Check callout 2
1321     {
1322         EXPECT_EQ(callouts[2]->priority(), 'A');
1323         EXPECT_EQ(callouts[2]->locationCode(), "");
1324 
1325         auto& fru = callouts[2]->fruIdentity();
1326         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1327         EXPECT_EQ(fru->failingComponentType(),
1328                   src::FRUIdentity::maintenanceProc);
1329     }
1330 
1331     // Check callout 3
1332     {
1333         EXPECT_EQ(callouts[3]->priority(), 'B');
1334         EXPECT_EQ(callouts[3]->locationCode(), "UXXX-P1-C23");
1335 
1336         auto& fru = callouts[3]->fruIdentity();
1337         EXPECT_EQ(fru->getPN().value(), "TRUSTED");
1338         EXPECT_EQ(fru->failingComponentType(),
1339                   src::FRUIdentity::symbolicFRUTrustedLocCode);
1340     }
1341 
1342     // Check callout 4
1343     {
1344         EXPECT_EQ(callouts[4]->priority(), 'C');
1345         EXPECT_EQ(callouts[4]->locationCode(), "UXXX-P1-C24");
1346 
1347         auto& fru = callouts[4]->fruIdentity();
1348         EXPECT_EQ(fru->getPN().value(), "FRUTST1");
1349         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1350     }
1351 
1352     // Check callout 5
1353     {
1354         EXPECT_EQ(callouts[5]->priority(), 'L');
1355         EXPECT_EQ(callouts[5]->locationCode(), "");
1356 
1357         auto& fru = callouts[5]->fruIdentity();
1358         EXPECT_EQ(fru->getPN().value(), "FRUTST2");
1359         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1360     }
1361 
1362     // Check callout 6
1363     {
1364         EXPECT_EQ(callouts[6]->priority(), 'L');
1365         EXPECT_EQ(callouts[6]->locationCode(), "");
1366 
1367         auto& fru = callouts[6]->fruIdentity();
1368         EXPECT_EQ(fru->getMaintProc().value(), "BMC0004");
1369         EXPECT_EQ(fru->failingComponentType(),
1370                   src::FRUIdentity::maintenanceProc);
1371     }
1372 
1373     // Check callout 7
1374     {
1375         EXPECT_EQ(callouts[7]->priority(), 'L');
1376         EXPECT_EQ(callouts[7]->locationCode(), "");
1377 
1378         auto& fru = callouts[7]->fruIdentity();
1379         EXPECT_EQ(fru->getPN().value(), "AMBTEMP");
1380         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1381     }
1382 
1383     // Check that it didn't find any errors
1384     const auto& data = src.getDebugData();
1385     EXPECT_TRUE(data.empty());
1386 }
1387 
TEST_F(SRCTest,JsonBadCalloutsTest)1388 TEST_F(SRCTest, JsonBadCalloutsTest)
1389 {
1390     // The first call will have a Throw in a mock call.
1391     // The second will have a different Throw in a mock call.
1392     // The others have issues with the Priority field.
1393     const auto jsonCallouts = R"(
1394         [
1395             {
1396                 "LocationCode": "P0-C1",
1397                 "Priority": "H"
1398             },
1399             {
1400                 "LocationCode": "P0-C2",
1401                 "Priority": "H"
1402             },
1403             {
1404                 "LocationCode": "P0-C3"
1405             },
1406             {
1407                 "LocationCode": "P0-C4",
1408                 "Priority": "X"
1409             }
1410         ]
1411     )"_json;
1412 
1413     message::Entry entry;
1414     entry.src.type = 0xBD;
1415     entry.src.reasonCode = 0xABCD;
1416     entry.subsystem = 0x42;
1417 
1418     AdditionalData ad;
1419     NiceMock<MockDataInterface> dataIface;
1420 
1421     // Callout 0 mock calls
1422     // Expand location code will fail, so the unexpanded location
1423     // code should show up in the callout instead.
1424     {
1425         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1426             .WillOnce(Throw(std::runtime_error("Fail")));
1427 
1428         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1429             .Times(1)
1430             .WillOnce(Return(std::vector<std::string>{
1431                 "/inv/system/chassis/motherboard/bmc"}));
1432         EXPECT_CALL(
1433             dataIface,
1434             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1435             .Times(1)
1436             .WillOnce(
1437                 DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1438                       SetArgReferee<3>("123456789ABC")));
1439     }
1440 
1441     // Callout 1 mock calls
1442     // getInventoryFromLocCode will fail, so a callout with just the
1443     // location code will be created.
1444     {
1445         EXPECT_CALL(dataIface, expandLocationCode("P0-C2", 0))
1446             .Times(1)
1447             .WillOnce(Return("UXXX-P0-C2"));
1448 
1449         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C2", 0, false))
1450             .Times(1)
1451             .WillOnce(Throw(std::runtime_error("Fail")));
1452     }
1453 
1454     SRC src{entry, ad, jsonCallouts, dataIface};
1455 
1456     ASSERT_TRUE(src.callouts());
1457 
1458     const auto& callouts = src.callouts()->callouts();
1459 
1460     // The first callout will have the unexpanded location code.
1461     ASSERT_EQ(callouts.size(), 2);
1462 
1463     EXPECT_EQ(callouts[0]->priority(), 'H');
1464     EXPECT_EQ(callouts[0]->locationCode(), "P0-C1");
1465 
1466     auto& fru0 = callouts[0]->fruIdentity();
1467     EXPECT_EQ(fru0->getPN().value(), "1234567");
1468     EXPECT_EQ(fru0->getCCIN().value(), "CCCC");
1469     EXPECT_EQ(fru0->getSN().value(), "123456789ABC");
1470     EXPECT_EQ(fru0->failingComponentType(), src::FRUIdentity::hardwareFRU);
1471 
1472     // The second callout will have empty HW details.
1473     EXPECT_EQ(callouts[1]->priority(), 'H');
1474     EXPECT_EQ(callouts[1]->locationCode(), "UXXX-P0-C2");
1475 
1476     auto& fru1 = callouts[1]->fruIdentity();
1477     EXPECT_EQ(fru1->getPN().value(), "");
1478     EXPECT_EQ(fru1->getCCIN().value(), "");
1479     EXPECT_EQ(fru1->getSN().value(), "");
1480     EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::hardwareFRU);
1481 
1482     const auto& data = src.getDebugData();
1483     ASSERT_EQ(data.size(), 4);
1484     EXPECT_STREQ(data[0].c_str(), "Unable to expand location code P0-C1: Fail");
1485     EXPECT_STREQ(
1486         data[1].c_str(),
1487         "Unable to get inventory path from location code: P0-C2: Fail");
1488     EXPECT_STREQ(data[2].c_str(),
1489                  "Failed extracting callout data from JSON: "
1490                  "[json.exception.out_of_range.403] key 'Priority' not found");
1491     EXPECT_STREQ(data[3].c_str(),
1492                  "Failed extracting callout data from JSON: Invalid "
1493                  "priority 'X' found in JSON callout");
1494 }
1495 
1496 // Test that an inventory path callout can have
1497 // a different priority than H.
TEST_F(SRCTest,InventoryCalloutTestPriority)1498 TEST_F(SRCTest, InventoryCalloutTestPriority)
1499 {
1500     message::Entry entry;
1501     entry.src.type = 0xBD;
1502     entry.src.reasonCode = 0xABCD;
1503     entry.subsystem = 0x42;
1504 
1505     std::map<std::string, std::string> adData{
1506         {"CALLOUT_INVENTORY_PATH", "motherboard"}, {"CALLOUT_PRIORITY", "M"}};
1507     AdditionalData ad{adData};
1508     NiceMock<MockDataInterface> dataIface;
1509 
1510     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
1511         .WillOnce(Return("UTMS-P1"));
1512 
1513     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
1514         .Times(1)
1515         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1516                         SetArgReferee<3>("123456789ABC")));
1517 
1518     SRC src{entry, ad, dataIface};
1519     EXPECT_TRUE(src.valid());
1520 
1521     ASSERT_TRUE(src.callouts());
1522 
1523     EXPECT_EQ(src.callouts()->callouts().size(), 1);
1524 
1525     auto& callout = src.callouts()->callouts().front();
1526 
1527     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
1528     EXPECT_EQ(callout->priority(), 'M');
1529 }
1530 
1531 // Test SRC with additional data - PEL_SUBSYSTEM
TEST_F(SRCTest,TestPELSubsystem)1532 TEST_F(SRCTest, TestPELSubsystem)
1533 {
1534     message::Entry entry;
1535     entry.src.type = 0xBD;
1536     entry.src.reasonCode = 0xABCD;
1537     entry.subsystem = 0x42;
1538 
1539     // Values for the SRC words pointed to above
1540     std::map<std::string, std::string> adData{{"PEL_SUBSYSTEM", "0x20"}};
1541     AdditionalData ad{adData};
1542     NiceMock<MockDataInterface> dataIface;
1543 
1544     EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD"));
1545 
1546     SRC src{entry, ad, dataIface};
1547 
1548     EXPECT_TRUE(src.valid());
1549 
1550     EXPECT_EQ(src.asciiString(), "BD20ABCD                        ");
1551 }
1552 
setAsciiString(std::vector<uint8_t> & src,const std::string & value)1553 void setAsciiString(std::vector<uint8_t>& src, const std::string& value)
1554 {
1555     assert(40 + value.size() <= src.size());
1556 
1557     for (size_t i = 0; i < value.size(); i++)
1558     {
1559         src[40 + i] = value[i];
1560     }
1561 }
1562 
TEST_F(SRCTest,TestGetProgressCode)1563 TEST_F(SRCTest, TestGetProgressCode)
1564 {
1565     {
1566         // A real SRC with CC009184
1567         std::vector<uint8_t> src{
1568             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1569             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1570             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1571             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1572             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32, 32};
1573 
1574         EXPECT_EQ(SRC::getProgressCode(src), 0xCC009184);
1575     }
1576 
1577     {
1578         // A real SRC with STANDBY
1579         std::vector<uint8_t> src{
1580             2,  0,  0,  1,  0,  0,  0,  72, 0,  0,  0,  0,  0,  0,  0,
1581             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
1582             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  83, 84, 65, 78, 68,
1583             66, 89, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
1584             32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
1585 
1586         EXPECT_EQ(SRC::getProgressCode(src), 0);
1587     }
1588 
1589     {
1590         // A real SRC with CC009184, but 1 byte too short
1591         std::vector<uint8_t> src{
1592             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1593             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1594             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1595             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1596             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32};
1597 
1598         EXPECT_EQ(SRC::getProgressCode(src), 0);
1599     }
1600 
1601     {
1602         // A few different ones
1603         const std::map<std::string, uint32_t> progressCodes{
1604             {"12345678", 0x12345678}, {"ABCDEF00", 0xABCDEF00},
1605             {"abcdef00", 0xABCDEF00}, {"X1234567", 0},
1606             {"1234567X", 0},          {"1       ", 0}};
1607 
1608         std::vector<uint8_t> src(72, 0x0);
1609 
1610         for (const auto& [code, expected] : progressCodes)
1611         {
1612             setAsciiString(src, code);
1613             EXPECT_EQ(SRC::getProgressCode(src), expected);
1614         }
1615 
1616         // empty
1617         src.clear();
1618         EXPECT_EQ(SRC::getProgressCode(src), 0);
1619     }
1620 }
1621 
1622 // Test progress is in right SRC hex data field
TEST_F(SRCTest,TestProgressCodeField)1623 TEST_F(SRCTest, TestProgressCodeField)
1624 {
1625     message::Entry entry;
1626     entry.src.type = 0xBD;
1627     entry.src.reasonCode = 0xABCD;
1628     entry.subsystem = 0x42;
1629 
1630     AdditionalData ad;
1631     NiceMock<MockDataInterface> dataIface;
1632     EXPECT_CALL(dataIface, getRawProgressSRC())
1633         .WillOnce(Return(std::vector<uint8_t>{
1634             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1635             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1636             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1637             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1638             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32, 32}));
1639 
1640     SRC src{entry, ad, dataIface};
1641     EXPECT_TRUE(src.valid());
1642 
1643     // Verify that the hex vlue is set at the right hexword
1644     EXPECT_EQ(src.hexwordData()[2], 0xCC009184);
1645 }
1646