| /** |
| * 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"); |
| } |
| } |