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