xref: /openbmc/phosphor-power/phosphor-regulators/test/validate-regulators-config_tests.cpp (revision 5cc0128068ef5fcfd49e99099a5d6339cd69f272)
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 }