xref: /openbmc/phosphor-power/phosphor-regulators/test/validate-regulators-config_tests.cpp (revision bf1cbeaa3fc1c20c7bd97a16db233c7bcf05f7a9)
1  /**
2   * Copyright c 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 <nlohmann/json.hpp>
22  
23  #include <fstream>
24  
25  #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  
37  using json = nlohmann::json;
38  
39  const 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  
91  std::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  
104  std::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  
112  int 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  
153  void 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  
163  void 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  
175  void 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  
188  void 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  
203  TEST(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      }
290  }
291  TEST(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  }
351  TEST(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  }
404