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