xref: /openbmc/phosphor-logging/test/openpower-pels/src_test.cpp (revision 7b92372da378fa751c6596e5ea93936f61e61022)
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 
TEST_F(SRCTest,RegistryCalloutCantGetLocTest)778 TEST_F(SRCTest, RegistryCalloutCantGetLocTest)
779 {
780     message::Entry entry;
781     entry.src.type = 0xBD;
782     entry.src.reasonCode = 0xABCD;
783     entry.src.deconfigFlag = true;
784     entry.src.checkstopFlag = true;
785     entry.subsystem = 0x42;
786 
787     entry.callouts = R"(
788         [{
789             "CalloutList":
790             [
791                 {
792                     "Priority": "high",
793                     "LocCode": "P0-C8"
794                 },
795                 {
796                     "Priority": "medium",
797                     "LocCode": "P0-C9"
798                 }
799             ]
800         }])"_json;
801 
802     {
803         // The calls to expand the location codes will fail, but it should
804         // still create the callouts with the unexpanded values and no HW
805         // fields.
806         AdditionalData ad;
807         NiceMock<MockDataInterface> dataIface;
808         std::vector<std::string> names{"systemC"};
809 
810         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
811 
812         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
813             .WillRepeatedly(Throw(std::runtime_error("Fail")));
814 
815         EXPECT_CALL(dataIface, expandLocationCode("P0-C9", 0))
816             .WillRepeatedly(Throw(std::runtime_error("Fail")));
817 
818         EXPECT_CALL(dataIface, getInventoryFromLocCode(_, _, _)).Times(0);
819 
820         EXPECT_CALL(dataIface, getHWCalloutFields(_, _, _, _)).Times(0);
821 
822         SRC src{entry, ad, dataIface};
823 
824         auto& callouts = src.callouts()->callouts();
825         ASSERT_EQ(callouts.size(), 2);
826 
827         // Only unexpanded location codes
828         EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
829         EXPECT_EQ(callouts[0]->priority(), 'H');
830 
831         auto& fru1 = callouts[0]->fruIdentity();
832         EXPECT_EQ(fru1->getPN().value(), "");
833         EXPECT_EQ(fru1->getCCIN().value(), "");
834         EXPECT_EQ(fru1->getSN().value(), "");
835 
836         EXPECT_EQ(callouts[1]->locationCode(), "P0-C9");
837         EXPECT_EQ(callouts[1]->priority(), 'M');
838 
839         auto& fru2 = callouts[1]->fruIdentity();
840         EXPECT_EQ(fru2->getPN().value(), "");
841         EXPECT_EQ(fru2->getCCIN().value(), "");
842         EXPECT_EQ(fru2->getSN().value(), "");
843     }
844 }
845 
TEST_F(SRCTest,TrustedSymbolicFRUCantGetLocTest)846 TEST_F(SRCTest, TrustedSymbolicFRUCantGetLocTest)
847 {
848     message::Entry entry;
849     entry.src.type = 0xBD;
850     entry.src.reasonCode = 0xABCD;
851     entry.subsystem = 0x42;
852 
853     entry.callouts = R"(
854         [{
855             "CalloutList":
856             [
857                 {
858                     "Priority": "medium",
859                     "LocCode": "P0-C8",
860                     "SymbolicFRUTrusted": "pwrsply"
861                 }
862             ]
863         }])"_json;
864 
865     std::map<std::string, std::string> adData;
866     AdditionalData ad{adData};
867     NiceMock<MockDataInterface> dataIface;
868     std::vector<std::string> names{"systemA"};
869 
870     EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
871 
872     // The call to expand the location code will fail, but it should
873     // still create the callout with the unexpanded value and the
874     // symbolic FRU can't be trusted.
875     EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
876         .WillRepeatedly(Throw(std::runtime_error("Fail")));
877 
878     SRC src{entry, ad, dataIface};
879 
880     auto& callouts = src.callouts()->callouts();
881     ASSERT_EQ(callouts.size(), 1);
882 
883     EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
884     EXPECT_EQ(callouts[0]->priority(), 'M');
885     auto& fru = callouts[0]->fruIdentity();
886     EXPECT_EQ(fru->getPN().value(), "PWRSPLY");
887     EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
888 }
889 
890 // Test looking up device path fails in the callout jSON.
TEST_F(SRCTest,DevicePathCalloutTest)891 TEST_F(SRCTest, DevicePathCalloutTest)
892 {
893     message::Entry entry;
894     entry.src.type = 0xBD;
895     entry.src.reasonCode = 0xABCD;
896     entry.subsystem = 0x42;
897 
898     const auto calloutJSON = R"(
899     {
900         "I2C":
901         {
902             "14":
903             {
904                 "114":
905                 {
906                     "Callouts":[
907                     {
908                         "Name": "/chassis/motherboard/cpu0",
909                         "LocationCode": "P1-C40",
910                         "Priority": "H"
911                     },
912                     {
913                         "Name": "/chassis/motherboard",
914                         "LocationCode": "P1",
915                         "Priority": "M"
916                     },
917                     {
918                         "Name": "/chassis/motherboard/bmc",
919                         "LocationCode": "P1-C15",
920                         "Priority": "L"
921                     }
922                     ],
923                     "Dest": "proc 0 target"
924                 }
925             }
926         }
927     })";
928 
929     auto dataPath = getPELReadOnlyDataPath();
930     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
931     file << calloutJSON;
932     file.close();
933 
934     NiceMock<MockDataInterface> dataIface;
935     std::vector<std::string> names{"systemA"};
936 
937     EXPECT_CALL(dataIface, getSystemNames)
938         .Times(5)
939         .WillRepeatedly(Return(names));
940 
941     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false))
942         .Times(3)
943         .WillRepeatedly(Return(std::vector<std::string>{
944             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
945 
946     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
947         .Times(3)
948         .WillRepeatedly(Return(std::vector<std::string>{
949             "/xyz/openbmc_project/inventory/chassis/motherboard"}));
950 
951     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C15", 0, false))
952         .Times(3)
953         .WillRepeatedly(Return(std::vector<std::string>{
954             "/xyz/openbmc_project/inventory/chassis/motherboard/bmc"}));
955 
956     EXPECT_CALL(dataIface, expandLocationCode("P1-C40", 0))
957         .Times(3)
958         .WillRepeatedly(Return("Ufcs-P1-C40"));
959 
960     EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
961         .Times(3)
962         .WillRepeatedly(Return("Ufcs-P1"));
963 
964     EXPECT_CALL(dataIface, expandLocationCode("P1-C15", 0))
965         .Times(3)
966         .WillRepeatedly(Return("Ufcs-P1-C15"));
967 
968     EXPECT_CALL(dataIface,
969                 getHWCalloutFields(
970                     "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0",
971                     _, _, _))
972         .Times(3)
973         .WillRepeatedly(
974             DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
975                   SetArgReferee<3>("123456789ABC")));
976     EXPECT_CALL(
977         dataIface,
978         getHWCalloutFields("/xyz/openbmc_project/inventory/chassis/motherboard",
979                            _, _, _))
980         .Times(3)
981         .WillRepeatedly(
982             DoAll(SetArgReferee<1>("7654321"), SetArgReferee<2>("MMMM"),
983                   SetArgReferee<3>("CBA987654321")));
984     EXPECT_CALL(dataIface,
985                 getHWCalloutFields(
986                     "/xyz/openbmc_project/inventory/chassis/motherboard/bmc", _,
987                     _, _))
988         .Times(3)
989         .WillRepeatedly(
990             DoAll(SetArgReferee<1>("7123456"), SetArgReferee<2>("BBBB"),
991                   SetArgReferee<3>("C123456789AB")));
992 
993     // Call this below with different AdditionalData values that
994     // result in the same callouts.
995     auto checkCallouts = [&entry, &dataIface](const auto& items) {
996         AdditionalData ad{items};
997         SRC src{entry, ad, dataIface};
998 
999         ASSERT_TRUE(src.callouts());
1000         auto& callouts = src.callouts()->callouts();
1001 
1002         ASSERT_EQ(callouts.size(), 3);
1003 
1004         {
1005             EXPECT_EQ(callouts[0]->priority(), 'H');
1006             EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P1-C40");
1007 
1008             auto& fru = callouts[0]->fruIdentity();
1009             EXPECT_EQ(fru->getPN().value(), "1234567");
1010             EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1011             EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1012         }
1013         {
1014             EXPECT_EQ(callouts[1]->priority(), 'M');
1015             EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P1");
1016 
1017             auto& fru = callouts[1]->fruIdentity();
1018             EXPECT_EQ(fru->getPN().value(), "7654321");
1019             EXPECT_EQ(fru->getCCIN().value(), "MMMM");
1020             EXPECT_EQ(fru->getSN().value(), "CBA987654321");
1021         }
1022         {
1023             EXPECT_EQ(callouts[2]->priority(), 'L');
1024             EXPECT_EQ(callouts[2]->locationCode(), "Ufcs-P1-C15");
1025 
1026             auto& fru = callouts[2]->fruIdentity();
1027             EXPECT_EQ(fru->getPN().value(), "7123456");
1028             EXPECT_EQ(fru->getCCIN().value(), "BBBB");
1029             EXPECT_EQ(fru->getSN().value(), "C123456789AB");
1030         }
1031     };
1032 
1033     {
1034         // Callouts based on the device path
1035         std::map<std::string, std::string> items{
1036             {"CALLOUT_ERRNO", "5"},
1037             {"CALLOUT_DEVICE_PATH",
1038              "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}};
1039 
1040         checkCallouts(items);
1041     }
1042 
1043     {
1044         // Callouts based on the I2C bus and address
1045         std::map<std::string, std::string> items{{"CALLOUT_ERRNO", "5"},
1046                                                  {"CALLOUT_IIC_BUS", "14"},
1047                                                  {"CALLOUT_IIC_ADDR", "0x72"}};
1048         checkCallouts(items);
1049     }
1050 
1051     {
1052         // Also based on I2C bus and address, but with bus = /dev/i2c-14
1053         std::map<std::string, std::string> items{{"CALLOUT_ERRNO", "5"},
1054                                                  {"CALLOUT_IIC_BUS", "14"},
1055                                                  {"CALLOUT_IIC_ADDR", "0x72"}};
1056         checkCallouts(items);
1057     }
1058 
1059     {
1060         // Callout not found
1061         std::map<std::string, std::string> items{
1062             {"CALLOUT_ERRNO", "5"},
1063             {"CALLOUT_DEVICE_PATH",
1064              "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-24/24-0012"}};
1065 
1066         AdditionalData ad{items};
1067         SRC src{entry, ad, dataIface};
1068 
1069         EXPECT_FALSE(src.callouts());
1070         ASSERT_EQ(src.getDebugData().size(), 1);
1071         EXPECT_EQ(src.getDebugData()[0],
1072                   "Problem looking up I2C callouts on 24 18: "
1073                   "[json.exception.out_of_range.403] key '24' not found");
1074     }
1075 
1076     {
1077         // Callout not found
1078         std::map<std::string, std::string> items{{"CALLOUT_ERRNO", "5"},
1079                                                  {"CALLOUT_IIC_BUS", "22"},
1080                                                  {"CALLOUT_IIC_ADDR", "0x99"}};
1081         AdditionalData ad{items};
1082         SRC src{entry, ad, dataIface};
1083 
1084         EXPECT_FALSE(src.callouts());
1085         ASSERT_EQ(src.getDebugData().size(), 1);
1086         EXPECT_EQ(src.getDebugData()[0],
1087                   "Problem looking up I2C callouts on 22 153: "
1088                   "[json.exception.out_of_range.403] key '22' not found");
1089     }
1090 }
1091 
TEST_F(SRCTest,DevicePathCantGetLocTest)1092 TEST_F(SRCTest, DevicePathCantGetLocTest)
1093 {
1094     message::Entry entry;
1095     entry.src.type = 0xBD;
1096     entry.src.reasonCode = 0xABCD;
1097     entry.subsystem = 0x42;
1098 
1099     const auto calloutJSON = R"(
1100     {
1101         "I2C":
1102         {
1103             "14":
1104             {
1105                 "114":
1106                 {
1107                     "Callouts":[
1108                     {
1109                         "Name": "/chassis/motherboard/cpu0",
1110                         "LocationCode": "P1-C40",
1111                         "Priority": "H"
1112                     },
1113                     {
1114                         "Name": "/chassis/motherboard",
1115                         "LocationCode": "P1",
1116                         "Priority": "M"
1117                     }
1118                     ],
1119                     "Dest": "proc 0 target"
1120                 }
1121             }
1122         }
1123     })";
1124 
1125     auto dataPath = getPELReadOnlyDataPath();
1126     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
1127     file << calloutJSON;
1128     file.close();
1129 
1130     NiceMock<MockDataInterface> dataIface;
1131     std::vector<std::string> names{"systemA"};
1132 
1133     EXPECT_CALL(dataIface, getSystemNames).WillRepeatedly(Return(names));
1134 
1135     // The calls to expand the location codes will fail, so still create
1136     // the callouts with the unexpanded values and no HW fields
1137 
1138     EXPECT_CALL(dataIface, expandLocationCode("P1-C40", 0))
1139         .WillRepeatedly(Throw(std::runtime_error("Fail")));
1140 
1141     EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
1142         .WillRepeatedly(Throw(std::runtime_error("Fail")));
1143 
1144     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false))
1145         .Times(0);
1146     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false)).Times(0);
1147 
1148     std::map<std::string, std::string> items{
1149         {"CALLOUT_ERRNO", "5"},
1150         {"CALLOUT_DEVICE_PATH",
1151          "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}};
1152 
1153     AdditionalData ad{items};
1154     SRC src{entry, ad, dataIface};
1155 
1156     ASSERT_TRUE(src.callouts());
1157     auto& callouts = src.callouts()->callouts();
1158 
1159     ASSERT_EQ(callouts.size(), 2);
1160 
1161     // Should just contain the unexpanded location codes
1162     {
1163         EXPECT_EQ(callouts[0]->priority(), 'H');
1164         EXPECT_EQ(callouts[0]->locationCode(), "P1-C40");
1165 
1166         auto& fru = callouts[0]->fruIdentity();
1167         EXPECT_EQ(fru->getPN().value(), "");
1168         EXPECT_EQ(fru->getCCIN().value(), "");
1169         EXPECT_EQ(fru->getSN().value(), "");
1170     }
1171     {
1172         EXPECT_EQ(callouts[1]->priority(), 'M');
1173         EXPECT_EQ(callouts[1]->locationCode(), "P1");
1174 
1175         auto& fru = callouts[1]->fruIdentity();
1176         EXPECT_EQ(fru->getPN().value(), "");
1177         EXPECT_EQ(fru->getCCIN().value(), "");
1178         EXPECT_EQ(fru->getSN().value(), "");
1179     }
1180 
1181     fs::remove_all(dataPath);
1182 }
1183 
1184 // Test when callouts are passed in via JSON
TEST_F(SRCTest,JsonCalloutsTest)1185 TEST_F(SRCTest, JsonCalloutsTest)
1186 {
1187     const auto jsonCallouts = R"(
1188         [
1189             {
1190                 "LocationCode": "P0-C1",
1191                 "Priority": "H",
1192                 "MRUs": [
1193                     {
1194                         "ID": 42,
1195                         "Priority": "H"
1196                     },
1197                     {
1198                         "ID": 43,
1199                         "Priority": "M"
1200                     }
1201                 ]
1202             },
1203             {
1204                 "InventoryPath": "/inv/system/chassis/motherboard/cpu0",
1205                 "Priority": "M",
1206                 "Guarded": true,
1207                 "Deconfigured": true
1208             },
1209             {
1210                 "Procedure": "PROCEDU",
1211                 "Priority": "A"
1212             },
1213             {
1214                 "SymbolicFRU": "TRUSTED",
1215                 "Priority": "B",
1216                 "TrustedLocationCode": true,
1217                 "LocationCode": "P1-C23"
1218             },
1219             {
1220                 "SymbolicFRU": "FRUTST1",
1221                 "Priority": "C",
1222                 "LocationCode": "P1-C24"
1223             },
1224             {
1225                 "SymbolicFRU": "FRUTST2LONG",
1226                 "Priority": "L"
1227             },
1228             {
1229                 "Procedure": "fsi_path",
1230                 "Priority": "L"
1231             },
1232             {
1233                 "SymbolicFRU": "ambient_temp",
1234                 "Priority": "L"
1235             }
1236         ]
1237     )"_json;
1238 
1239     message::Entry entry;
1240     entry.src.type = 0xBD;
1241     entry.src.reasonCode = 0xABCD;
1242     entry.subsystem = 0x42;
1243 
1244     AdditionalData ad;
1245     NiceMock<MockDataInterface> dataIface;
1246 
1247     // Callout 0 mock calls
1248     {
1249         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1250             .Times(1)
1251             .WillOnce(Return("UXXX-P0-C1"));
1252         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1253             .Times(1)
1254             .WillOnce(Return(std::vector<std::string>{
1255                 "/inv/system/chassis/motherboard/bmc"}));
1256         EXPECT_CALL(
1257             dataIface,
1258             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1259             .Times(1)
1260             .WillOnce(
1261                 DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1262                       SetArgReferee<3>("123456789ABC")));
1263     }
1264     // Callout 1 mock calls
1265     {
1266         EXPECT_CALL(dataIface,
1267                     getLocationCode("/inv/system/chassis/motherboard/cpu0"))
1268             .WillOnce(Return("UYYY-P5"));
1269         EXPECT_CALL(
1270             dataIface,
1271             getHWCalloutFields("/inv/system/chassis/motherboard/cpu0", _, _, _))
1272             .Times(1)
1273             .WillOnce(
1274                 DoAll(SetArgReferee<1>("2345678"), SetArgReferee<2>("DDDD"),
1275                       SetArgReferee<3>("23456789ABCD")));
1276     }
1277     // Callout 3 mock calls
1278     {
1279         EXPECT_CALL(dataIface, expandLocationCode("P1-C23", 0))
1280             .Times(1)
1281             .WillOnce(Return("UXXX-P1-C23"));
1282     }
1283     // Callout 4 mock calls
1284     {
1285         EXPECT_CALL(dataIface, expandLocationCode("P1-C24", 0))
1286             .Times(1)
1287             .WillOnce(Return("UXXX-P1-C24"));
1288     }
1289 
1290     SRC src{entry, ad, jsonCallouts, dataIface};
1291     ASSERT_TRUE(src.callouts());
1292 
1293     // Check the guarded and deconfigured flags
1294     EXPECT_TRUE(src.hexwordData()[3] & 0x03000000);
1295 
1296     const auto& callouts = src.callouts()->callouts();
1297     ASSERT_EQ(callouts.size(), 8);
1298 
1299     // Check callout 0
1300     {
1301         EXPECT_EQ(callouts[0]->priority(), 'H');
1302         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1303 
1304         auto& fru = callouts[0]->fruIdentity();
1305         EXPECT_EQ(fru->getPN().value(), "1234567");
1306         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1307         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1308         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1309 
1310         auto& mruCallouts = callouts[0]->mru();
1311         ASSERT_TRUE(mruCallouts);
1312         auto& mrus = mruCallouts->mrus();
1313         ASSERT_EQ(mrus.size(), 2);
1314         EXPECT_EQ(mrus[0].id, 42);
1315         EXPECT_EQ(mrus[0].priority, 'H');
1316         EXPECT_EQ(mrus[1].id, 43);
1317         EXPECT_EQ(mrus[1].priority, 'M');
1318     }
1319 
1320     // Check callout 1
1321     {
1322         EXPECT_EQ(callouts[1]->priority(), 'M');
1323         EXPECT_EQ(callouts[1]->locationCode(), "UYYY-P5");
1324 
1325         auto& fru = callouts[1]->fruIdentity();
1326         EXPECT_EQ(fru->getPN().value(), "2345678");
1327         EXPECT_EQ(fru->getCCIN().value(), "DDDD");
1328         EXPECT_EQ(fru->getSN().value(), "23456789ABCD");
1329         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1330     }
1331 
1332     // Check callout 2
1333     {
1334         EXPECT_EQ(callouts[2]->priority(), 'A');
1335         EXPECT_EQ(callouts[2]->locationCode(), "");
1336 
1337         auto& fru = callouts[2]->fruIdentity();
1338         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1339         EXPECT_EQ(fru->failingComponentType(),
1340                   src::FRUIdentity::maintenanceProc);
1341     }
1342 
1343     // Check callout 3
1344     {
1345         EXPECT_EQ(callouts[3]->priority(), 'B');
1346         EXPECT_EQ(callouts[3]->locationCode(), "UXXX-P1-C23");
1347 
1348         auto& fru = callouts[3]->fruIdentity();
1349         EXPECT_EQ(fru->getPN().value(), "TRUSTED");
1350         EXPECT_EQ(fru->failingComponentType(),
1351                   src::FRUIdentity::symbolicFRUTrustedLocCode);
1352     }
1353 
1354     // Check callout 4
1355     {
1356         EXPECT_EQ(callouts[4]->priority(), 'C');
1357         EXPECT_EQ(callouts[4]->locationCode(), "UXXX-P1-C24");
1358 
1359         auto& fru = callouts[4]->fruIdentity();
1360         EXPECT_EQ(fru->getPN().value(), "FRUTST1");
1361         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1362     }
1363 
1364     // Check callout 5
1365     {
1366         EXPECT_EQ(callouts[5]->priority(), 'L');
1367         EXPECT_EQ(callouts[5]->locationCode(), "");
1368 
1369         auto& fru = callouts[5]->fruIdentity();
1370         EXPECT_EQ(fru->getPN().value(), "FRUTST2");
1371         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1372     }
1373 
1374     // Check callout 6
1375     {
1376         EXPECT_EQ(callouts[6]->priority(), 'L');
1377         EXPECT_EQ(callouts[6]->locationCode(), "");
1378 
1379         auto& fru = callouts[6]->fruIdentity();
1380         EXPECT_EQ(fru->getMaintProc().value(), "BMC0004");
1381         EXPECT_EQ(fru->failingComponentType(),
1382                   src::FRUIdentity::maintenanceProc);
1383     }
1384 
1385     // Check callout 7
1386     {
1387         EXPECT_EQ(callouts[7]->priority(), 'L');
1388         EXPECT_EQ(callouts[7]->locationCode(), "");
1389 
1390         auto& fru = callouts[7]->fruIdentity();
1391         EXPECT_EQ(fru->getPN().value(), "AMBTEMP");
1392         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1393     }
1394 
1395     // Check that it didn't find any errors
1396     const auto& data = src.getDebugData();
1397     EXPECT_TRUE(data.empty());
1398 }
1399 
TEST_F(SRCTest,JsonBadCalloutsTest)1400 TEST_F(SRCTest, JsonBadCalloutsTest)
1401 {
1402     // The first call will have a Throw in a mock call.
1403     // The second will have a different Throw in a mock call.
1404     // The others have issues with the Priority field.
1405     const auto jsonCallouts = R"(
1406         [
1407             {
1408                 "LocationCode": "P0-C1",
1409                 "Priority": "H"
1410             },
1411             {
1412                 "LocationCode": "P0-C2",
1413                 "Priority": "H"
1414             },
1415             {
1416                 "LocationCode": "P0-C3"
1417             },
1418             {
1419                 "LocationCode": "P0-C4",
1420                 "Priority": "X"
1421             }
1422         ]
1423     )"_json;
1424 
1425     message::Entry entry;
1426     entry.src.type = 0xBD;
1427     entry.src.reasonCode = 0xABCD;
1428     entry.subsystem = 0x42;
1429 
1430     AdditionalData ad;
1431     NiceMock<MockDataInterface> dataIface;
1432 
1433     // Callout 0 mock calls
1434     // Expand location code will fail, so the unexpanded location
1435     // code should show up in the callout instead.
1436     {
1437         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1438             .WillOnce(Throw(std::runtime_error("Fail")));
1439 
1440         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1441             .Times(1)
1442             .WillOnce(Return(std::vector<std::string>{
1443                 "/inv/system/chassis/motherboard/bmc"}));
1444         EXPECT_CALL(
1445             dataIface,
1446             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1447             .Times(1)
1448             .WillOnce(
1449                 DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1450                       SetArgReferee<3>("123456789ABC")));
1451     }
1452 
1453     // Callout 1 mock calls
1454     // getInventoryFromLocCode will fail, so a callout with just the
1455     // location code will be created.
1456     {
1457         EXPECT_CALL(dataIface, expandLocationCode("P0-C2", 0))
1458             .Times(1)
1459             .WillOnce(Return("UXXX-P0-C2"));
1460 
1461         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C2", 0, false))
1462             .Times(1)
1463             .WillOnce(Throw(std::runtime_error("Fail")));
1464     }
1465 
1466     SRC src{entry, ad, jsonCallouts, dataIface};
1467 
1468     ASSERT_TRUE(src.callouts());
1469 
1470     const auto& callouts = src.callouts()->callouts();
1471 
1472     // The first callout will have the unexpanded location code.
1473     ASSERT_EQ(callouts.size(), 2);
1474 
1475     EXPECT_EQ(callouts[0]->priority(), 'H');
1476     EXPECT_EQ(callouts[0]->locationCode(), "P0-C1");
1477 
1478     auto& fru0 = callouts[0]->fruIdentity();
1479     EXPECT_EQ(fru0->getPN().value(), "1234567");
1480     EXPECT_EQ(fru0->getCCIN().value(), "CCCC");
1481     EXPECT_EQ(fru0->getSN().value(), "123456789ABC");
1482     EXPECT_EQ(fru0->failingComponentType(), src::FRUIdentity::hardwareFRU);
1483 
1484     // The second callout will have empty HW details.
1485     EXPECT_EQ(callouts[1]->priority(), 'H');
1486     EXPECT_EQ(callouts[1]->locationCode(), "UXXX-P0-C2");
1487 
1488     auto& fru1 = callouts[1]->fruIdentity();
1489     EXPECT_EQ(fru1->getPN().value(), "");
1490     EXPECT_EQ(fru1->getCCIN().value(), "");
1491     EXPECT_EQ(fru1->getSN().value(), "");
1492     EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::hardwareFRU);
1493 
1494     const auto& data = src.getDebugData();
1495     ASSERT_EQ(data.size(), 4);
1496     EXPECT_STREQ(data[0].c_str(), "Unable to expand location code P0-C1: Fail");
1497     EXPECT_STREQ(
1498         data[1].c_str(),
1499         "Unable to get inventory path from location code: P0-C2: Fail");
1500     EXPECT_STREQ(data[2].c_str(),
1501                  "Failed extracting callout data from JSON: "
1502                  "[json.exception.out_of_range.403] key 'Priority' not found");
1503     EXPECT_STREQ(data[3].c_str(),
1504                  "Failed extracting callout data from JSON: Invalid "
1505                  "priority 'X' found in JSON callout");
1506 }
1507 
1508 // Test that an inventory path callout can have
1509 // a different priority than H.
TEST_F(SRCTest,InventoryCalloutTestPriority)1510 TEST_F(SRCTest, InventoryCalloutTestPriority)
1511 {
1512     message::Entry entry;
1513     entry.src.type = 0xBD;
1514     entry.src.reasonCode = 0xABCD;
1515     entry.subsystem = 0x42;
1516 
1517     std::map<std::string, std::string> adData{
1518         {"CALLOUT_INVENTORY_PATH", "motherboard"}, {"CALLOUT_PRIORITY", "M"}};
1519     AdditionalData ad{adData};
1520     NiceMock<MockDataInterface> dataIface;
1521 
1522     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
1523         .WillOnce(Return("UTMS-P1"));
1524 
1525     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
1526         .Times(1)
1527         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1528                         SetArgReferee<3>("123456789ABC")));
1529 
1530     SRC src{entry, ad, dataIface};
1531     EXPECT_TRUE(src.valid());
1532 
1533     ASSERT_TRUE(src.callouts());
1534 
1535     EXPECT_EQ(src.callouts()->callouts().size(), 1);
1536 
1537     auto& callout = src.callouts()->callouts().front();
1538 
1539     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
1540     EXPECT_EQ(callout->priority(), 'M');
1541 }
1542 
1543 // Test SRC with additional data - PEL_SUBSYSTEM
TEST_F(SRCTest,TestPELSubsystem)1544 TEST_F(SRCTest, TestPELSubsystem)
1545 {
1546     message::Entry entry;
1547     entry.src.type = 0xBD;
1548     entry.src.reasonCode = 0xABCD;
1549     entry.subsystem = 0x42;
1550 
1551     // Values for the SRC words pointed to above
1552     std::map<std::string, std::string> adData{{"PEL_SUBSYSTEM", "0x20"}};
1553     AdditionalData ad{adData};
1554     NiceMock<MockDataInterface> dataIface;
1555 
1556     EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD"));
1557 
1558     SRC src{entry, ad, dataIface};
1559 
1560     EXPECT_TRUE(src.valid());
1561 
1562     EXPECT_EQ(src.asciiString(), "BD20ABCD                        ");
1563 }
1564 
setAsciiString(std::vector<uint8_t> & src,const std::string & value)1565 void setAsciiString(std::vector<uint8_t>& src, const std::string& value)
1566 {
1567     assert(40 + value.size() <= src.size());
1568 
1569     for (size_t i = 0; i < value.size(); i++)
1570     {
1571         src[40 + i] = value[i];
1572     }
1573 }
1574 
TEST_F(SRCTest,TestGetProgressCode)1575 TEST_F(SRCTest, TestGetProgressCode)
1576 {
1577     {
1578         // A real SRC with CC009184
1579         std::vector<uint8_t> src{
1580             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1581             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1582             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1583             49, 56,  52, 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), 0xCC009184);
1587     }
1588 
1589     {
1590         // A real SRC with STANDBY
1591         std::vector<uint8_t> src{
1592             2,  0,  0,  1,  0,  0,  0,  72, 0,  0,  0,  0,  0,  0,  0,
1593             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
1594             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  83, 84, 65, 78, 68,
1595             66, 89, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
1596             32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
1597 
1598         EXPECT_EQ(SRC::getProgressCode(src), 0);
1599     }
1600 
1601     {
1602         // A real SRC with CC009184, but 1 byte too short
1603         std::vector<uint8_t> src{
1604             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1605             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1606             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1607             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1608             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32, 32};
1609         src.resize(71);
1610         EXPECT_EQ(SRC::getProgressCode(src), 0);
1611     }
1612 
1613     {
1614         // A few different ones
1615         const std::map<std::string, uint32_t> progressCodes{
1616             {"12345678", 0x12345678}, {"ABCDEF00", 0xABCDEF00},
1617             {"abcdef00", 0xABCDEF00}, {"X1234567", 0},
1618             {"1234567X", 0},          {"1       ", 0}};
1619 
1620         std::vector<uint8_t> src(72, 0x0);
1621 
1622         for (const auto& [code, expected] : progressCodes)
1623         {
1624             setAsciiString(src, code);
1625             EXPECT_EQ(SRC::getProgressCode(src), expected);
1626         }
1627 
1628         // empty
1629         src.clear();
1630         EXPECT_EQ(SRC::getProgressCode(src), 0);
1631     }
1632 }
1633 
1634 // Test progress is in right SRC hex data field
TEST_F(SRCTest,TestProgressCodeField)1635 TEST_F(SRCTest, TestProgressCodeField)
1636 {
1637     message::Entry entry;
1638     entry.src.type = 0xBD;
1639     entry.src.reasonCode = 0xABCD;
1640     entry.subsystem = 0x42;
1641 
1642     AdditionalData ad;
1643     NiceMock<MockDataInterface> dataIface;
1644     EXPECT_CALL(dataIface, getRawProgressSRC())
1645         .WillOnce(Return(std::vector<uint8_t>{
1646             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1647             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1648             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1649             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1650             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32, 32}));
1651 
1652     SRC src{entry, ad, dataIface};
1653     EXPECT_TRUE(src.valid());
1654 
1655     // Verify that the hex vlue is set at the right hexword
1656     EXPECT_EQ(src.hexwordData()[2], 0xCC009184);
1657 }
1658