blob: c2637fd682d9cce618ca86a0640ef50a924ef59b [file] [log] [blame]
Bob King386d33f2019-12-26 17:28:56 +08001/**
Bob King0dcbdf52020-01-20 17:19:39 +08002 * Copyright c 2020 IBM Corporation
Bob King386d33f2019-12-26 17:28:56 +08003 *
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
Bob King386d33f2019-12-26 17:28:56 +080021#include <nlohmann/json.hpp>
22
Bob King0dcbdf52020-01-20 17:19:39 +080023#include <fstream>
24
Bob King386d33f2019-12-26 17:28:56 +080025#include <gtest/gtest.h>
26
27#define EXPECT_FILE_VALID(configFile) expectFileValid(configFile)
28#define EXPECT_FILE_INVALID(configFile, expectedErrorMessage, \
29 expectedOutputMessage) \
30 expectFileInvalid(configFile, expectedErrorMessage, expectedOutputMessage)
31#define EXPECT_JSON_VALID(configFileJson) expectJsonValid(configFileJson)
32#define EXPECT_JSON_INVALID(configFileJson, expectedErrorMessage, \
33 expectedOutputMessage) \
34 expectJsonInvalid(configFileJson, expectedErrorMessage, \
35 expectedOutputMessage)
36
37using json = nlohmann::json;
38
39const json validConfigFile = R"(
40 {
41 "comments": [ "Config file for a FooBar one-chassis system" ],
42
43 "rules": [
44 {
45 "comments": [ "Sets output voltage for a PMBus regulator rail" ],
46 "id": "set_voltage_rule",
47 "actions": [
48 {
49 "pmbus_write_vout_command": {
50 "format": "linear"
51 }
52 }
53 ]
54 }
55 ],
56
57 "chassis": [
58 {
59 "comments": [ "Chassis number 1 containing CPUs and memory" ],
60 "number": 1,
61 "devices": [
62 {
63 "comments": [ "IR35221 regulator producing the Vdd rail" ],
64 "id": "vdd_regulator",
65 "is_regulator": true,
66 "fru": "/system/chassis/motherboard/regulator1",
67 "i2c_interface": {
68 "bus": 1,
69 "address": "0x70"
70 },
71 "rails": [
72 {
73 "comments": [ "Vdd rail" ],
74 "id": "vdd",
75 "configuration": {
76 "volts": 1.03,
77 "rule_id": "set_voltage_rule"
78 },
79 "sensor_monitoring": {
80 "rule_id": "read_sensors_rule"
81 }
82 }
83 ]
84 }
85 ]
86 }
87 ]
88 }
89)"_json;
90
91std::string createTmpFile()
92{
93 // create temporary file using mkstemp under /tmp/. random name for XXXXXX
94 char fileName[] = "/tmp/temp-XXXXXX";
95 int fd = mkstemp(fileName);
96 if (fd == -1)
97 {
98 perror("Can't create temporary file");
99 }
100 close(fd);
101 return fileName;
102}
103
104std::string getValidationToolCommand(const std::string& configFileName)
105{
106 std::string command = "python ../tools/validate-regulators-config.py -s \
107 ../schema/config_schema.json -c ";
108 command += configFileName;
109 return command;
110}
111
112int runToolForOutput(const std::string& configFileName, std::string& output,
113 bool isReadingStderr = false)
114{
115 // run the validation tool with the temporary file and return the output
116 // of the validation tool.
117 std::string command = getValidationToolCommand(configFileName);
118 // reading the stderr while isReadingStderr is true.
119 if (isReadingStderr == true)
120 {
121 command += " 2>&1 >/dev/null";
122 }
123 // get the jsonschema print from validation tool.
124 char buffer[256];
125 std::string result = "";
126 // to get the stdout from the validation tool.
127 FILE* pipe = popen(command.c_str(), "r");
128 if (!pipe)
129 {
130 throw std::runtime_error("popen() failed!");
131 }
132 while (!std::feof(pipe))
133 {
134 if (fgets(buffer, sizeof buffer, pipe) != NULL)
135 {
136 result += buffer;
137 }
138 }
139 int returnValue = pclose(pipe);
140 // Check if pclose() failed
141 if (returnValue == -1)
142 {
143 // unable to close pipe. Print error and exit function.
144 throw std::runtime_error("pclose() failed!");
145 }
146 std::string firstLine = result.substr(0, result.find('\n'));
147 output = firstLine;
148 // Get command exit status from return value
149 int exitStatus = WEXITSTATUS(returnValue);
150 return exitStatus;
151}
152
153void expectFileValid(const std::string& configFileName)
154{
155 std::string errorMessage = "";
156 std::string outputMessage = "";
157 EXPECT_EQ(runToolForOutput(configFileName, errorMessage, true), 0);
158 EXPECT_EQ(runToolForOutput(configFileName, outputMessage), 0);
159 EXPECT_EQ(errorMessage, "");
160 EXPECT_EQ(outputMessage, "");
161}
162
163void expectFileInvalid(const std::string& configFileName,
164 const std::string& expectedErrorMessage,
165 const std::string& expectedOutputMessage)
166{
167 std::string errorMessage = "";
168 std::string outputMessage = "";
169 EXPECT_EQ(runToolForOutput(configFileName, errorMessage, true), 1);
170 EXPECT_EQ(runToolForOutput(configFileName, outputMessage), 1);
171 EXPECT_EQ(errorMessage, expectedErrorMessage);
172 EXPECT_EQ(outputMessage, expectedOutputMessage);
173}
174
175void expectJsonValid(const json configFileJson)
176{
177 std::string fileName;
178 fileName = createTmpFile();
179 std::string jsonData = configFileJson.dump();
180 std::ofstream out(fileName);
181 out << jsonData;
182 out.close();
183
184 EXPECT_FILE_VALID(fileName);
185 unlink(fileName.c_str());
186}
187
188void expectJsonInvalid(const json configFileJson,
189 const std::string& expectedErrorMessage,
190 const std::string& expectedOutputMessage)
191{
192 std::string fileName;
193 fileName = createTmpFile();
194 std::string jsonData = configFileJson.dump();
195 std::ofstream out(fileName);
196 out << jsonData;
197 out.close();
198
199 EXPECT_FILE_INVALID(fileName, expectedErrorMessage, expectedOutputMessage);
200 unlink(fileName.c_str());
201}
202
203TEST(ValidateRegulatorsConfigTest, Rule)
204{
205 // valid test comments property, id property,
206 // action property specified.
207 {
208 json configFile = validConfigFile;
209 EXPECT_JSON_VALID(configFile);
210 }
211
212 // valid test rule with no comments
213 {
214 json configFile = validConfigFile;
215 configFile["rules"][0].erase("comments");
216 EXPECT_JSON_VALID(configFile);
217 }
218
219 // invalid test comments property has invalid value type
220 {
221 json configFile = validConfigFile;
222 configFile["rules"][0]["comments"] = {1};
223 EXPECT_JSON_INVALID(configFile, "Validation failed.",
224 "1 is not of type u'string'");
225 }
226
227 // invalid test rule with no ID
228 {
229 json configFile = validConfigFile;
230 configFile["rules"][0].erase("id");
231 EXPECT_JSON_INVALID(configFile, "Validation failed.",
232 "u'id' is a required property");
233 }
234
235 // invalid test id property has invalid value type (not string)
236 {
237 json configFile = validConfigFile;
238 configFile["rules"][0]["id"] = true;
239 EXPECT_JSON_INVALID(configFile, "Validation failed.",
240 "True is not of type u'string'");
241 }
242
243 // invalid test id property has invalid value
244 {
245 json configFile = validConfigFile;
246 configFile["rules"][0]["id"] = "foo%";
247 EXPECT_JSON_INVALID(configFile, "Validation failed.",
248 "u'foo%' does not match u'^[A-Za-z0-9_]+$'");
249 }
250
251 // invalid test rule with no actions property
252 {
253 json configFile = validConfigFile;
254 configFile["rules"][0].erase("actions");
255 EXPECT_JSON_INVALID(configFile, "Validation failed.",
256 "u'actions' is a required property");
257 }
258
259 // valid test rule with multiple actions
260 {
261 json configFile = validConfigFile;
262 configFile["rules"][0]["actions"][1]["run_rule"] =
263 "set_page0_voltage_rule";
264 EXPECT_JSON_VALID(configFile);
265 }
266
267 // invalid test actions property has invalid value type (not an array)
268 {
269 json configFile = validConfigFile;
270 configFile["rules"][0]["actions"] = 1;
271 EXPECT_JSON_INVALID(configFile, "Validation failed.",
272 "1 is not of type u'array'");
273 }
274
275 // invalid test actions property has invalid value of action
276 {
277 json configFile = validConfigFile;
278 configFile["rules"][0]["actions"][0] = "foo";
279 EXPECT_JSON_INVALID(configFile, "Validation failed.",
280 "u'foo' is not of type u'object'");
281 }
282
283 // invalid test actions property has empty array
284 {
285 json configFile = validConfigFile;
286 configFile["rules"][0]["actions"] = json::array();
287 EXPECT_JSON_INVALID(configFile, "Validation failed.",
288 "[] is too short");
289 }
Bob King0dcbdf52020-01-20 17:19:39 +0800290}
Bob Kingbeaf6532020-01-21 11:03:49 +0800291TEST(ValidateRegulatorsConfigTest, And)
292{
293 // Valid.
294 {
295 json configFile = validConfigFile;
296 json andAction =
297 R"(
298 {
299 "and": [
300 { "i2c_compare_byte": { "register": "0xA0", "value": "0x00" } },
301 { "i2c_compare_byte": { "register": "0xA1", "value": "0x00" } }
302 ]
303 }
304 )"_json;
305 configFile["rules"][0]["actions"].push_back(andAction);
306 EXPECT_JSON_VALID(configFile);
307 }
308
309 // Invalid: actions property value is an empty array.
310 {
311 json configFile = validConfigFile;
312 json andAction =
313 R"(
314 {
315 "and": []
316 }
317 )"_json;
318 configFile["rules"][0]["actions"].push_back(andAction);
319 EXPECT_JSON_INVALID(configFile, "Validation failed.",
320 "[] is too short");
321 }
322
323 // Invalid: actions property has incorrect value data type.
324 {
325 json configFile = validConfigFile;
326 json andAction =
327 R"(
328 {
329 "and": true
330 }
331 )"_json;
332 configFile["rules"][0]["actions"].push_back(andAction);
333 EXPECT_JSON_INVALID(configFile, "Validation failed.",
334 "True is not of type u'array'");
335 }
336
337 // Invalid: actions property value contains wrong element type
338 {
339 json configFile = validConfigFile;
340 json andAction =
341 R"(
342 {
343 "and": ["foo"]
344 }
345 )"_json;
346 configFile["rules"][0]["actions"].push_back(andAction);
347 EXPECT_JSON_INVALID(configFile, "Validation failed.",
348 "u'foo' is not of type u'object'");
349 }
350}
Bob Kingbf1cbea2020-01-21 11:08:50 +0800351TEST(ValidateRegulatorsConfigTest, ComparePresence)
352{
353 json comparePresenceFile = validConfigFile;
354 comparePresenceFile["rules"][0]["actions"][1]["compare_presence"]["fru"] =
355 "/system/chassis/motherboard/regulator2";
356 comparePresenceFile["rules"][0]["actions"][1]["compare_presence"]["value"] =
357 true;
358 // Valid.
359 {
360 json configFile = comparePresenceFile;
361 EXPECT_JSON_VALID(configFile);
362 }
363
364 // Invalid: no FRU property.
365 {
366 json configFile = comparePresenceFile;
367 configFile["rules"][0]["actions"][1]["compare_presence"].erase("fru");
368 EXPECT_JSON_INVALID(configFile, "Validation failed.",
369 "u'fru' is a required property");
370 }
371
372 // Invalid: FRU property length is string less than 1.
373 {
374 json configFile = comparePresenceFile;
375 configFile["rules"][0]["actions"][1]["compare_presence"]["fru"] = "";
376 EXPECT_JSON_INVALID(configFile, "Validation failed.",
377 "u'' is too short");
378 }
379
380 // Invalid: no value property.
381 {
382 json configFile = comparePresenceFile;
383 configFile["rules"][0]["actions"][1]["compare_presence"].erase("value");
384 EXPECT_JSON_INVALID(configFile, "Validation failed.",
385 "u'value' is a required property");
386 }
387
388 // Invalid: value property type is not boolean.
389 {
390 json configFile = comparePresenceFile;
391 configFile["rules"][0]["actions"][1]["compare_presence"]["value"] = "1";
392 EXPECT_JSON_INVALID(configFile, "Validation failed.",
393 "u'1' is not of type u'boolean'");
394 }
395
396 // Invalid: FRU property type is not string.
397 {
398 json configFile = comparePresenceFile;
399 configFile["rules"][0]["actions"][1]["compare_presence"]["fru"] = 1;
400 EXPECT_JSON_INVALID(configFile, "Validation failed.",
401 "1 is not of type u'string'");
402 }
403}