blob: 08e798ddbc707c19fffb94ff68e96dbe135291dc [file] [log] [blame]
/**
* 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");
}
}