PEL: Add PEL testcase for device callouts

Add a PEL class unit test where device callouts are included in the PEL.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I22567c29c7e2a8dd0a024a459756ee2cde8239fb
diff --git a/test/openpower-pels/pel_test.cpp b/test/openpower-pels/pel_test.cpp
index c6a29b6..d643076 100644
--- a/test/openpower-pels/pel_test.cpp
+++ b/test/openpower-pels/pel_test.cpp
@@ -26,8 +26,11 @@
 
 namespace fs = std::filesystem;
 using namespace openpower::pels;
+using ::testing::_;
 using ::testing::NiceMock;
 using ::testing::Return;
+using ::testing::ReturnRef;
+using ::testing::SetArgReferee;
 
 class PELTest : public CleanLogID
 {
@@ -767,3 +770,153 @@
 
     fs::remove_all(dir);
 }
+
+// Create a PEL with device callouts
+TEST_F(PELTest, CreateWithDevCalloutsTest)
+{
+    message::Entry regEntry;
+    uint64_t timestamp = 5;
+
+    regEntry.name = "test";
+    regEntry.subsystem = 5;
+    regEntry.actionFlags = 0xC000;
+    regEntry.src.type = 0xBD;
+    regEntry.src.reasonCode = 0x1234;
+
+    NiceMock<MockDataInterface> dataIface;
+    PelFFDC ffdc;
+
+    const auto calloutJSON = R"(
+    {
+        "I2C":
+        {
+            "14":
+            {
+                "114":
+                {
+                    "Callouts":[
+                        {
+                        "Name": "/chassis/motherboard/cpu0",
+                        "LocationCode": "P1",
+                        "Priority": "H"
+                        }
+                    ],
+                    "Dest": "proc 0 target"
+                }
+            }
+        }
+    })";
+
+    std::vector<std::string> names{"systemA"};
+    EXPECT_CALL(dataIface, getSystemNames)
+        .Times(2)
+        .WillRepeatedly(ReturnRef(names));
+
+    EXPECT_CALL(dataIface,
+                getLocationCode(
+                    "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"))
+        .WillOnce(Return("UXXX-P1"));
+
+    EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0))
+        .WillOnce(
+            Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
+
+    EXPECT_CALL(
+        dataIface,
+        getHWCalloutFields(
+            "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
+        .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
+                        SetArgReferee<3>("123456789ABC")));
+
+    auto dataPath = getPELReadOnlyDataPath();
+    std::ofstream file{dataPath / "systemA_dev_callouts.json"};
+    file << calloutJSON;
+    file.close();
+
+    {
+        std::vector<std::string> data{
+            "CALLOUT_ERRNO=5",
+            "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
+            "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
+
+        AdditionalData ad{data};
+
+        PEL pel{
+            regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
+            ad,       ffdc, dataIface};
+
+        ASSERT_TRUE(pel.primarySRC().value()->callouts());
+        auto& callouts = pel.primarySRC().value()->callouts()->callouts();
+        ASSERT_EQ(callouts.size(), 1);
+
+        EXPECT_EQ(callouts[0]->priority(), 'H');
+        EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1");
+
+        auto& fru = callouts[0]->fruIdentity();
+        EXPECT_EQ(fru->getPN().value(), "1234567");
+        EXPECT_EQ(fru->getCCIN().value(), "CCCC");
+        EXPECT_EQ(fru->getSN().value(), "123456789ABC");
+
+        const auto& section = pel.optionalSections().back();
+
+        ASSERT_EQ(section->header().id, 0x5544); // UD
+        auto ud = static_cast<UserData*>(section.get());
+
+        // Check that there was a UserData section added that
+        // contains debug details about the device.
+        const auto& d = ud->data();
+        std::string jsonString{d.begin(), d.end()};
+        auto actualJSON = nlohmann::json::parse(jsonString);
+
+        auto expectedJSON = R"(
+            {
+                "PEL Internal Debug Data": {
+                    "SRC": [
+                      "I2C: bus: 14 address: 114 dest: proc 0 target"
+                    ]
+                }
+            }
+        )"_json;
+
+        EXPECT_EQ(actualJSON, expectedJSON);
+    }
+
+    {
+        // Device path not found (wrong i2c addr), so no callouts
+        std::vector<std::string> data{
+            "CALLOUT_ERRNO=5",
+            "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
+            "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"};
+
+        AdditionalData ad{data};
+
+        PEL pel{
+            regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
+            ad,       ffdc, dataIface};
+
+        // no callouts
+        EXPECT_FALSE(pel.primarySRC().value()->callouts());
+
+        // Now check that there was a UserData section
+        // that contains the lookup error.
+        const auto& section = pel.optionalSections().back();
+
+        ASSERT_EQ(section->header().id, 0x5544); // UD
+        auto ud = static_cast<UserData*>(section.get());
+
+        const auto& d = ud->data();
+
+        std::string jsonString{d.begin(), d.end()};
+
+        auto actualJSON = nlohmann::json::parse(jsonString);
+
+        auto expectedJSON =
+            "{\"PEL Internal Debug Data\":{\"SRC\":"
+            "[\"Problem looking up I2C callouts on 14 153: "
+            "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
+
+        EXPECT_EQ(actualJSON, expectedJSON);
+    }
+
+    fs::remove_all(dataPath);
+}