blob: 08e798ddbc707c19fffb94ff68e96dbe135291dc [file] [log] [blame]
Bob King386d33f2019-12-26 17:28:56 +08001/**
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
36using json = nlohmann::json;
37
38const 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
90std::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
103std::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
111int 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
152void 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
162void 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
174void 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
187void 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
202TEST(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}