Create validate-regulators-config_tests.cpp.

Create validate-regulators-config_tests.cpp and add test cases for JSON
object "rule". The validate-regulators-config_tests.cpp tests the cases
which validate-regulators-config.py checks.

Signed-off-by: Bob King <Bob_King@wistron.com>
Change-Id: I86ad7d78dac3617170dbd0a78213843f311b7123
diff --git a/phosphor-regulators/test/validate-regulators-config_tests.cpp b/phosphor-regulators/test/validate-regulators-config_tests.cpp
new file mode 100644
index 0000000..08e798d
--- /dev/null
+++ b/phosphor-regulators/test/validate-regulators-config_tests.cpp
@@ -0,0 +1,289 @@
+/**
+ * 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include <fstream>
+#include <nlohmann/json.hpp>
+
+#include <gtest/gtest.h>
+
+#define EXPECT_FILE_VALID(configFile) expectFileValid(configFile)
+#define EXPECT_FILE_INVALID(configFile, expectedErrorMessage,                  \
+                            expectedOutputMessage)                             \
+    expectFileInvalid(configFile, expectedErrorMessage, expectedOutputMessage)
+#define EXPECT_JSON_VALID(configFileJson) expectJsonValid(configFileJson)
+#define EXPECT_JSON_INVALID(configFileJson, expectedErrorMessage,              \
+                            expectedOutputMessage)                             \
+    expectJsonInvalid(configFileJson, expectedErrorMessage,                    \
+                      expectedOutputMessage)
+
+using json = nlohmann::json;
+
+const json validConfigFile = R"(
+    {
+      "comments": [ "Config file for a FooBar one-chassis system" ],
+
+      "rules": [
+        {
+          "comments": [ "Sets output voltage for a PMBus regulator rail" ],
+          "id": "set_voltage_rule",
+          "actions": [
+            {
+              "pmbus_write_vout_command": {
+                "format": "linear"
+              }
+            }
+          ]
+        }
+      ],
+
+      "chassis": [
+        {
+          "comments": [ "Chassis number 1 containing CPUs and memory" ],
+          "number": 1,
+          "devices": [
+            {
+              "comments": [ "IR35221 regulator producing the Vdd rail" ],
+              "id": "vdd_regulator",
+              "is_regulator": true,
+              "fru": "/system/chassis/motherboard/regulator1",
+              "i2c_interface": {
+                "bus": 1,
+                "address": "0x70"
+              },
+              "rails": [
+                {
+                  "comments": [ "Vdd rail" ],
+                  "id": "vdd",
+                  "configuration": {
+                    "volts": 1.03,
+                    "rule_id": "set_voltage_rule"
+                  },
+                  "sensor_monitoring": {
+                    "rule_id": "read_sensors_rule"
+                  }
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+)"_json;
+
+std::string createTmpFile()
+{
+    // create temporary file using mkstemp under /tmp/. random name for XXXXXX
+    char fileName[] = "/tmp/temp-XXXXXX";
+    int fd = mkstemp(fileName);
+    if (fd == -1)
+    {
+        perror("Can't create temporary file");
+    }
+    close(fd);
+    return fileName;
+}
+
+std::string getValidationToolCommand(const std::string& configFileName)
+{
+    std::string command = "python ../tools/validate-regulators-config.py -s \
+                           ../schema/config_schema.json -c ";
+    command += configFileName;
+    return command;
+}
+
+int runToolForOutput(const std::string& configFileName, std::string& output,
+                     bool isReadingStderr = false)
+{
+    // run the validation tool with the temporary file and return the output
+    // of the validation tool.
+    std::string command = getValidationToolCommand(configFileName);
+    // reading the stderr while isReadingStderr is true.
+    if (isReadingStderr == true)
+    {
+        command += " 2>&1 >/dev/null";
+    }
+    // get the jsonschema print from validation tool.
+    char buffer[256];
+    std::string result = "";
+    // to get the stdout from the validation tool.
+    FILE* pipe = popen(command.c_str(), "r");
+    if (!pipe)
+    {
+        throw std::runtime_error("popen() failed!");
+    }
+    while (!std::feof(pipe))
+    {
+        if (fgets(buffer, sizeof buffer, pipe) != NULL)
+        {
+            result += buffer;
+        }
+    }
+    int returnValue = pclose(pipe);
+    // Check if pclose() failed
+    if (returnValue == -1)
+    {
+        // unable to close pipe.  Print error and exit function.
+        throw std::runtime_error("pclose() failed!");
+    }
+    std::string firstLine = result.substr(0, result.find('\n'));
+    output = firstLine;
+    // Get command exit status from return value
+    int exitStatus = WEXITSTATUS(returnValue);
+    return exitStatus;
+}
+
+void expectFileValid(const std::string& configFileName)
+{
+    std::string errorMessage = "";
+    std::string outputMessage = "";
+    EXPECT_EQ(runToolForOutput(configFileName, errorMessage, true), 0);
+    EXPECT_EQ(runToolForOutput(configFileName, outputMessage), 0);
+    EXPECT_EQ(errorMessage, "");
+    EXPECT_EQ(outputMessage, "");
+}
+
+void expectFileInvalid(const std::string& configFileName,
+                       const std::string& expectedErrorMessage,
+                       const std::string& expectedOutputMessage)
+{
+    std::string errorMessage = "";
+    std::string outputMessage = "";
+    EXPECT_EQ(runToolForOutput(configFileName, errorMessage, true), 1);
+    EXPECT_EQ(runToolForOutput(configFileName, outputMessage), 1);
+    EXPECT_EQ(errorMessage, expectedErrorMessage);
+    EXPECT_EQ(outputMessage, expectedOutputMessage);
+}
+
+void expectJsonValid(const json configFileJson)
+{
+    std::string fileName;
+    fileName = createTmpFile();
+    std::string jsonData = configFileJson.dump();
+    std::ofstream out(fileName);
+    out << jsonData;
+    out.close();
+
+    EXPECT_FILE_VALID(fileName);
+    unlink(fileName.c_str());
+}
+
+void expectJsonInvalid(const json configFileJson,
+                       const std::string& expectedErrorMessage,
+                       const std::string& expectedOutputMessage)
+{
+    std::string fileName;
+    fileName = createTmpFile();
+    std::string jsonData = configFileJson.dump();
+    std::ofstream out(fileName);
+    out << jsonData;
+    out.close();
+
+    EXPECT_FILE_INVALID(fileName, expectedErrorMessage, expectedOutputMessage);
+    unlink(fileName.c_str());
+}
+
+TEST(ValidateRegulatorsConfigTest, Rule)
+{
+    // valid test comments property, id property,
+    // action property specified.
+    {
+        json configFile = validConfigFile;
+        EXPECT_JSON_VALID(configFile);
+    }
+
+    // valid test rule with no comments
+    {
+        json configFile = validConfigFile;
+        configFile["rules"][0].erase("comments");
+        EXPECT_JSON_VALID(configFile);
+    }
+
+    // invalid test comments property has invalid value type
+    {
+        json configFile = validConfigFile;
+        configFile["rules"][0]["comments"] = {1};
+        EXPECT_JSON_INVALID(configFile, "Validation failed.",
+                            "1 is not of type u'string'");
+    }
+
+    // invalid test rule with no ID
+    {
+        json configFile = validConfigFile;
+        configFile["rules"][0].erase("id");
+        EXPECT_JSON_INVALID(configFile, "Validation failed.",
+                            "u'id' is a required property");
+    }
+
+    // invalid test id property has invalid value type (not string)
+    {
+        json configFile = validConfigFile;
+        configFile["rules"][0]["id"] = true;
+        EXPECT_JSON_INVALID(configFile, "Validation failed.",
+                            "True is not of type u'string'");
+    }
+
+    // invalid test id property has invalid value
+    {
+        json configFile = validConfigFile;
+        configFile["rules"][0]["id"] = "foo%";
+        EXPECT_JSON_INVALID(configFile, "Validation failed.",
+                            "u'foo%' does not match u'^[A-Za-z0-9_]+$'");
+    }
+
+    // invalid test rule with no actions property
+    {
+        json configFile = validConfigFile;
+        configFile["rules"][0].erase("actions");
+        EXPECT_JSON_INVALID(configFile, "Validation failed.",
+                            "u'actions' is a required property");
+    }
+
+    // valid test rule with multiple actions
+    {
+        json configFile = validConfigFile;
+        configFile["rules"][0]["actions"][1]["run_rule"] =
+            "set_page0_voltage_rule";
+        EXPECT_JSON_VALID(configFile);
+    }
+
+    // invalid test actions property has invalid value type (not an array)
+    {
+        json configFile = validConfigFile;
+        configFile["rules"][0]["actions"] = 1;
+        EXPECT_JSON_INVALID(configFile, "Validation failed.",
+                            "1 is not of type u'array'");
+    }
+
+    // invalid test actions property has invalid value of action
+    {
+        json configFile = validConfigFile;
+        configFile["rules"][0]["actions"][0] = "foo";
+        EXPECT_JSON_INVALID(configFile, "Validation failed.",
+                            "u'foo' is not of type u'object'");
+    }
+
+    // invalid test actions property has empty array
+    {
+        json configFile = validConfigFile;
+        configFile["rules"][0]["actions"] = json::array();
+        EXPECT_JSON_INVALID(configFile, "Validation failed.",
+                            "[] is too short");
+    }
+}
\ No newline at end of file