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