1 /** 2 * Copyright © 2020 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include <errno.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <sys/wait.h> 20 21 #include <fstream> 22 #include <nlohmann/json.hpp> 23 24 #include <gtest/gtest.h> 25 26 #define EXPECT_FILE_VALID(configFile) expectFileValid(configFile) 27 #define EXPECT_FILE_INVALID(configFile, expectedErrorMessage, \ 28 expectedOutputMessage) \ 29 expectFileInvalid(configFile, expectedErrorMessage, expectedOutputMessage) 30 #define EXPECT_JSON_VALID(configFileJson) expectJsonValid(configFileJson) 31 #define EXPECT_JSON_INVALID(configFileJson, expectedErrorMessage, \ 32 expectedOutputMessage) \ 33 expectJsonInvalid(configFileJson, expectedErrorMessage, \ 34 expectedOutputMessage) 35 36 using json = nlohmann::json; 37 38 const json validConfigFile = R"( 39 { 40 "comments": [ "Config file for a FooBar one-chassis system" ], 41 42 "rules": [ 43 { 44 "comments": [ "Sets output voltage for a PMBus regulator rail" ], 45 "id": "set_voltage_rule", 46 "actions": [ 47 { 48 "pmbus_write_vout_command": { 49 "format": "linear" 50 } 51 } 52 ] 53 } 54 ], 55 56 "chassis": [ 57 { 58 "comments": [ "Chassis number 1 containing CPUs and memory" ], 59 "number": 1, 60 "devices": [ 61 { 62 "comments": [ "IR35221 regulator producing the Vdd rail" ], 63 "id": "vdd_regulator", 64 "is_regulator": true, 65 "fru": "/system/chassis/motherboard/regulator1", 66 "i2c_interface": { 67 "bus": 1, 68 "address": "0x70" 69 }, 70 "rails": [ 71 { 72 "comments": [ "Vdd rail" ], 73 "id": "vdd", 74 "configuration": { 75 "volts": 1.03, 76 "rule_id": "set_voltage_rule" 77 }, 78 "sensor_monitoring": { 79 "rule_id": "read_sensors_rule" 80 } 81 } 82 ] 83 } 84 ] 85 } 86 ] 87 } 88 )"_json; 89 90 std::string createTmpFile() 91 { 92 // create temporary file using mkstemp under /tmp/. random name for XXXXXX 93 char fileName[] = "/tmp/temp-XXXXXX"; 94 int fd = mkstemp(fileName); 95 if (fd == -1) 96 { 97 perror("Can't create temporary file"); 98 } 99 close(fd); 100 return fileName; 101 } 102 103 std::string getValidationToolCommand(const std::string& configFileName) 104 { 105 std::string command = "python ../tools/validate-regulators-config.py -s \ 106 ../schema/config_schema.json -c "; 107 command += configFileName; 108 return command; 109 } 110 111 int runToolForOutput(const std::string& configFileName, std::string& output, 112 bool isReadingStderr = false) 113 { 114 // run the validation tool with the temporary file and return the output 115 // of the validation tool. 116 std::string command = getValidationToolCommand(configFileName); 117 // reading the stderr while isReadingStderr is true. 118 if (isReadingStderr == true) 119 { 120 command += " 2>&1 >/dev/null"; 121 } 122 // get the jsonschema print from validation tool. 123 char buffer[256]; 124 std::string result = ""; 125 // to get the stdout from the validation tool. 126 FILE* pipe = popen(command.c_str(), "r"); 127 if (!pipe) 128 { 129 throw std::runtime_error("popen() failed!"); 130 } 131 while (!std::feof(pipe)) 132 { 133 if (fgets(buffer, sizeof buffer, pipe) != NULL) 134 { 135 result += buffer; 136 } 137 } 138 int returnValue = pclose(pipe); 139 // Check if pclose() failed 140 if (returnValue == -1) 141 { 142 // unable to close pipe. Print error and exit function. 143 throw std::runtime_error("pclose() failed!"); 144 } 145 std::string firstLine = result.substr(0, result.find('\n')); 146 output = firstLine; 147 // Get command exit status from return value 148 int exitStatus = WEXITSTATUS(returnValue); 149 return exitStatus; 150 } 151 152 void expectFileValid(const std::string& configFileName) 153 { 154 std::string errorMessage = ""; 155 std::string outputMessage = ""; 156 EXPECT_EQ(runToolForOutput(configFileName, errorMessage, true), 0); 157 EXPECT_EQ(runToolForOutput(configFileName, outputMessage), 0); 158 EXPECT_EQ(errorMessage, ""); 159 EXPECT_EQ(outputMessage, ""); 160 } 161 162 void expectFileInvalid(const std::string& configFileName, 163 const std::string& expectedErrorMessage, 164 const std::string& expectedOutputMessage) 165 { 166 std::string errorMessage = ""; 167 std::string outputMessage = ""; 168 EXPECT_EQ(runToolForOutput(configFileName, errorMessage, true), 1); 169 EXPECT_EQ(runToolForOutput(configFileName, outputMessage), 1); 170 EXPECT_EQ(errorMessage, expectedErrorMessage); 171 EXPECT_EQ(outputMessage, expectedOutputMessage); 172 } 173 174 void expectJsonValid(const json configFileJson) 175 { 176 std::string fileName; 177 fileName = createTmpFile(); 178 std::string jsonData = configFileJson.dump(); 179 std::ofstream out(fileName); 180 out << jsonData; 181 out.close(); 182 183 EXPECT_FILE_VALID(fileName); 184 unlink(fileName.c_str()); 185 } 186 187 void expectJsonInvalid(const json configFileJson, 188 const std::string& expectedErrorMessage, 189 const std::string& expectedOutputMessage) 190 { 191 std::string fileName; 192 fileName = createTmpFile(); 193 std::string jsonData = configFileJson.dump(); 194 std::ofstream out(fileName); 195 out << jsonData; 196 out.close(); 197 198 EXPECT_FILE_INVALID(fileName, expectedErrorMessage, expectedOutputMessage); 199 unlink(fileName.c_str()); 200 } 201 202 TEST(ValidateRegulatorsConfigTest, Rule) 203 { 204 // valid test comments property, id property, 205 // action property specified. 206 { 207 json configFile = validConfigFile; 208 EXPECT_JSON_VALID(configFile); 209 } 210 211 // valid test rule with no comments 212 { 213 json configFile = validConfigFile; 214 configFile["rules"][0].erase("comments"); 215 EXPECT_JSON_VALID(configFile); 216 } 217 218 // invalid test comments property has invalid value type 219 { 220 json configFile = validConfigFile; 221 configFile["rules"][0]["comments"] = {1}; 222 EXPECT_JSON_INVALID(configFile, "Validation failed.", 223 "1 is not of type u'string'"); 224 } 225 226 // invalid test rule with no ID 227 { 228 json configFile = validConfigFile; 229 configFile["rules"][0].erase("id"); 230 EXPECT_JSON_INVALID(configFile, "Validation failed.", 231 "u'id' is a required property"); 232 } 233 234 // invalid test id property has invalid value type (not string) 235 { 236 json configFile = validConfigFile; 237 configFile["rules"][0]["id"] = true; 238 EXPECT_JSON_INVALID(configFile, "Validation failed.", 239 "True is not of type u'string'"); 240 } 241 242 // invalid test id property has invalid value 243 { 244 json configFile = validConfigFile; 245 configFile["rules"][0]["id"] = "foo%"; 246 EXPECT_JSON_INVALID(configFile, "Validation failed.", 247 "u'foo%' does not match u'^[A-Za-z0-9_]+$'"); 248 } 249 250 // invalid test rule with no actions property 251 { 252 json configFile = validConfigFile; 253 configFile["rules"][0].erase("actions"); 254 EXPECT_JSON_INVALID(configFile, "Validation failed.", 255 "u'actions' is a required property"); 256 } 257 258 // valid test rule with multiple actions 259 { 260 json configFile = validConfigFile; 261 configFile["rules"][0]["actions"][1]["run_rule"] = 262 "set_page0_voltage_rule"; 263 EXPECT_JSON_VALID(configFile); 264 } 265 266 // invalid test actions property has invalid value type (not an array) 267 { 268 json configFile = validConfigFile; 269 configFile["rules"][0]["actions"] = 1; 270 EXPECT_JSON_INVALID(configFile, "Validation failed.", 271 "1 is not of type u'array'"); 272 } 273 274 // invalid test actions property has invalid value of action 275 { 276 json configFile = validConfigFile; 277 configFile["rules"][0]["actions"][0] = "foo"; 278 EXPECT_JSON_INVALID(configFile, "Validation failed.", 279 "u'foo' is not of type u'object'"); 280 } 281 282 // invalid test actions property has empty array 283 { 284 json configFile = validConfigFile; 285 configFile["rules"][0]["actions"] = json::array(); 286 EXPECT_JSON_INVALID(configFile, "Validation failed.", 287 "[] is too short"); 288 } 289 }