PEL: LightPath: Choose callout location codes

LightPath uses the following rules to pick the location codes to turn on
LEDs for:

* If the PEL wasn't created by the BMC or Hostboot, and doesn't have the
  Service Action action flag set, then don't even check it and don't
  turn on the System Attention Indicator.

* Choose all location codes in the first group of callouts, where a
  group can be:
  * a single medium priority callout
  * one or more high priority callouts
  * one or more medium group A priority callouts

* All callouts in that group must be hardware callouts, meaning the FRU
  identity section's failing component type flag must either be hardware
  callout or symbolic FRU callout with trusted location code.  If there
  is a callout in the group that doesn't meet this requirement, then
  nothing in that group can be used.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ifbe7bddee14b69dc565a405e2f120fb5545f69e5
diff --git a/test/openpower-pels/service_indicators_test.cpp b/test/openpower-pels/service_indicators_test.cpp
new file mode 100644
index 0000000..c391e92
--- /dev/null
+++ b/test/openpower-pels/service_indicators_test.cpp
@@ -0,0 +1,259 @@
+/**
+ * Copyright © 2020 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "extensions/openpower-pels/service_indicators.hpp"
+#include "mocks.hpp"
+#include "pel_utils.hpp"
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+using CalloutVector = std::vector<std::unique_ptr<src::Callout>>;
+using ::testing::_;
+using ::testing::Return;
+using ::testing::Throw;
+
+// Test the ignore() function works
+TEST(ServiceIndicatorsTest, IgnoreTest)
+{
+    MockDataInterface dataIface;
+    service_indicators::LightPath lightPath{dataIface};
+
+    // PEL must have serviceable action flag set and be created
+    // by the BMC or Hostboot.
+    std::vector<std::tuple<char, uint16_t, bool>> testParams{
+        {'O', 0xA400, false}, // BMC serviceable, don't ignore
+        {'B', 0xA400, false}, // Hostboot serviceable, don't ignore
+        {'H', 0xA400, true},  // PHYP serviceable, ignore
+        {'O', 0x2400, true},  // BMC not serviceable, ignore
+        {'B', 0x2400, true},  // Hostboot not serviceable, ignore
+        {'H', 0x2400, true},  // PHYP not serviceable, ignore
+    };
+
+    for (const auto& test : testParams)
+    {
+        auto data = pelFactory(1, std::get<char>(test), 0x20,
+                               std::get<uint16_t>(test), 500);
+        PEL pel{data};
+
+        EXPECT_EQ(lightPath.ignore(pel), std::get<bool>(test));
+    }
+}
+
+// Test that only high, medium, and medium group A hardware
+// callouts have their location codes extracted.
+TEST(ServiceIndicatorsTest, OneCalloutPriorityTest)
+{
+    MockDataInterface dataIface;
+    service_indicators::LightPath lightPath{dataIface};
+
+    // The priorities to test, with the expected getLocationCodes results.
+    std::vector<std::tuple<CalloutPriority, std::vector<std::string>>>
+        testCallouts{{CalloutPriority::high, {"U27-P1"}},
+                     {CalloutPriority::medium, {"U27-P1"}},
+                     {CalloutPriority::mediumGroupA, {"U27-P1"}},
+                     {CalloutPriority::mediumGroupB, {}},
+                     {CalloutPriority::mediumGroupC, {}},
+                     {CalloutPriority::low, {}}};
+
+    for (const auto& test : testCallouts)
+    {
+        auto callout = std::make_unique<src::Callout>(
+            std::get<CalloutPriority>(test), "U27-P1", "1234567", "aaaa",
+            "123456789ABC");
+
+        CalloutVector callouts;
+        callouts.push_back(std::move(callout));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::get<std::vector<std::string>>(test));
+    }
+}
+
+// Test that only normal hardware callouts and symbolic FRU
+// callouts with trusted location codes have their location
+// codes extracted.
+TEST(ServiceIndicatorsTest, OneCalloutTypeTest)
+{
+    MockDataInterface dataIface;
+    service_indicators::LightPath lightPath{dataIface};
+
+    // Regular hardware callout
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::vector<std::string>{"U27-P1"});
+    }
+
+    // Symbolic FRU with trusted loc code callout
+    {
+        CalloutVector callouts;
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P1", true));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::vector<std::string>{"U27-P1"});
+    }
+
+    // Symbolic FRU without trusted loc code callout
+    {
+        CalloutVector callouts;
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P1", false));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::vector<std::string>{});
+    }
+
+    // Procedure callout
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "bmc_code"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::vector<std::string>{});
+    }
+}
+
+// Test that only the callouts in the first group have their location
+// codes extracted, where a group is one or more callouts listed
+// together with priorities of high, medium, or medium group A
+// (and medium is only ever contains 1 item).
+TEST(ServiceIndicatorsTest, CalloutGroupingTest)
+{
+    MockDataInterface dataIface;
+    service_indicators::LightPath lightPath{dataIface};
+
+    // high/high/medium/high just grabs the first 2
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P2",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::medium, "U27-P3",
+                                           "1234567", "aaaa", "123456789ABC"));
+        // This high priority callout after a medium isn't actually valid, since
+        // callouts are sorted, but test it anyway.
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P4",
+                                           "1234567", "aaaa", "123456789ABC"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{"U27-P1", "U27-P2"}));
+    }
+
+    // medium/medium just grabs the first medium
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::medium, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::medium, "U27-P2",
+                                           "1234567", "aaaa", "123456789ABC"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  std::vector<std::string>{"U27-P1"});
+    }
+
+    // mediumA/mediumA/medium just grabs the first 2
+    {
+        CalloutVector callouts;
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::mediumGroupA, "U27-P1", "1234567", "aaaa",
+            "123456789ABC"));
+
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::mediumGroupA, "U27-P2", "1234567", "aaaa",
+            "123456789ABC"));
+
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::medium, "U27-P3",
+                                           "1234567", "aaaa", "123456789ABC"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{"U27-P1", "U27-P2"}));
+    }
+}
+
+// Test that if any callouts in group are not HW/trusted symbolic
+// FRU callouts then no location codes will be extracted
+TEST(ServiceIndicatorsTest, CalloutMixedTypesTest)
+{
+    MockDataInterface dataIface;
+    service_indicators::LightPath lightPath{dataIface};
+
+    // Mixing FRU with trusted symbolic FRU is OK
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P2", true));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{"U27-P1", "U27-P2"}));
+    }
+
+    // Normal FRU callout with a non-trusted symbolic FRU callout not OK
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P2", false));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{}));
+    }
+
+    // Normal FRU callout with a procedure callout not OK
+    {
+        CalloutVector callouts;
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "U27-P1",
+                                           "1234567", "aaaa", "123456789ABC"));
+        callouts.push_back(
+            std::make_unique<src::Callout>(CalloutPriority::high, "bmc_code"));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{}));
+    }
+
+    // Trusted symbolic FRU callout with a non-trusted symbolic
+    // FRU callout not OK
+    {
+        CalloutVector callouts;
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P2", true));
+
+        callouts.push_back(std::make_unique<src::Callout>(
+            CalloutPriority::high, "service_docs", "U27-P2", false));
+
+        EXPECT_EQ(lightPath.getLocationCodes(callouts),
+                  (std::vector<std::string>{}));
+    }
+}