blob: f28d827da158edba6a40409aa66399b8cc3c4c31 [file] [log] [blame]
/**
* Copyright © 2024 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "config_file_parser.hpp"
#include "config_file_parser_error.hpp"
#include "rail.hpp"
#include "temporary_file.hpp"
#include <sys/stat.h> // for chmod()
#include <nlohmann/json.hpp>
#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
#include <gtest/gtest.h>
using namespace phosphor::power::sequencer;
using namespace phosphor::power::sequencer::config_file_parser;
using namespace phosphor::power::sequencer::config_file_parser::internal;
using json = nlohmann::json;
using TemporaryFile = phosphor::power::util::TemporaryFile;
void writeConfigFile(const std::filesystem::path& pathName,
const std::string& contents)
{
std::ofstream file{pathName};
file << contents;
}
void writeConfigFile(const std::filesystem::path& pathName,
const json& contents)
{
std::ofstream file{pathName};
file << contents;
}
TEST(ConfigFileParserTests, Parse)
{
// Test where works
{
const json configFileContents = R"(
{
"rails": [
{
"name": "VDD_CPU0",
"page": 11,
"check_status_vout": true
},
{
"name": "VCS_CPU1",
"presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1",
"gpio": { "line": 60 }
}
]
}
)"_json;
TemporaryFile configFile;
std::filesystem::path pathName{configFile.getPath()};
writeConfigFile(pathName, configFileContents);
std::vector<std::unique_ptr<Rail>> rails = parse(pathName);
EXPECT_EQ(rails.size(), 2);
EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
}
// Test where fails: File does not exist
{
std::filesystem::path pathName{"/tmp/non_existent_file"};
EXPECT_THROW(parse(pathName), ConfigFileParserError);
}
// Test where fails: File is not readable
{
const json configFileContents = R"(
{
"rails": [
{
"name": "VDD_CPU0"
}
]
}
)"_json;
TemporaryFile configFile;
std::filesystem::path pathName{configFile.getPath()};
writeConfigFile(pathName, configFileContents);
chmod(pathName.c_str(), 0222);
EXPECT_THROW(parse(pathName), ConfigFileParserError);
}
// Test where fails: File is not valid JSON
{
const std::string configFileContents = "] foo [";
TemporaryFile configFile;
std::filesystem::path pathName{configFile.getPath()};
writeConfigFile(pathName, configFileContents);
EXPECT_THROW(parse(pathName), ConfigFileParserError);
}
// Test where fails: JSON does not conform to config file format
{
const json configFileContents = R"( [ "foo", "bar" ] )"_json;
TemporaryFile configFile;
std::filesystem::path pathName{configFile.getPath()};
writeConfigFile(pathName, configFileContents);
EXPECT_THROW(parse(pathName), ConfigFileParserError);
}
}
TEST(ConfigFileParserTests, GetRequiredProperty)
{
// Test where property exists
{
const json element = R"( { "name": "VDD_CPU0" } )"_json;
const json& propertyElement = getRequiredProperty(element, "name");
EXPECT_EQ(propertyElement.get<std::string>(), "VDD_CPU0");
}
// Test where property does not exist
try
{
const json element = R"( { "foo": 23 } )"_json;
getRequiredProperty(element, "name");
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Required property missing: name");
}
}
TEST(ConfigFileParserTests, ParseBoolean)
{
// Test where works: true
{
const json element = R"( true )"_json;
bool value = parseBoolean(element);
EXPECT_EQ(value, true);
}
// Test where works: false
{
const json element = R"( false )"_json;
bool value = parseBoolean(element);
EXPECT_EQ(value, false);
}
// Test where fails: Element is not a boolean
try
{
const json element = R"( 1 )"_json;
parseBoolean(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not a boolean");
}
}
TEST(ConfigFileParserTests, ParseGPIO)
{
// Test where works: Only required properties specified
{
const json element = R"(
{
"line": 60
}
)"_json;
GPIO gpio = parseGPIO(element);
EXPECT_EQ(gpio.line, 60);
EXPECT_FALSE(gpio.activeLow);
}
// Test where works: All properties specified
{
const json element = R"(
{
"line": 131,
"active_low": true
}
)"_json;
GPIO gpio = parseGPIO(element);
EXPECT_EQ(gpio.line, 131);
EXPECT_TRUE(gpio.activeLow);
}
// Test where fails: Element is not an object
try
{
const json element = R"( [ "vdda", "vddb" ] )"_json;
parseGPIO(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an object");
}
// Test where fails: Required line property not specified
try
{
const json element = R"(
{
"active_low": true
}
)"_json;
parseGPIO(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Required property missing: line");
}
// Test where fails: line value is invalid
try
{
const json element = R"(
{
"line": -131,
"active_low": true
}
)"_json;
parseGPIO(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
}
// Test where fails: active_low value is invalid
try
{
const json element = R"(
{
"line": 131,
"active_low": "true"
}
)"_json;
parseGPIO(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not a boolean");
}
// Test where fails: Invalid property specified
try
{
const json element = R"(
{
"line": 131,
"foo": "bar"
}
)"_json;
parseGPIO(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element contains an invalid property");
}
}
TEST(ConfigFileParserTests, ParseRail)
{
// Test where works: Only required properties specified
{
const json element = R"(
{
"name": "VDD_CPU0"
}
)"_json;
std::unique_ptr<Rail> rail = parseRail(element);
EXPECT_EQ(rail->getName(), "VDD_CPU0");
EXPECT_FALSE(rail->getPresence().has_value());
EXPECT_FALSE(rail->getPage().has_value());
EXPECT_FALSE(rail->isPowerSupplyRail());
EXPECT_FALSE(rail->getCheckStatusVout());
EXPECT_FALSE(rail->getCompareVoltageToLimit());
EXPECT_FALSE(rail->getGPIO().has_value());
}
// Test where works: All properties specified
{
const json element = R"(
{
"name": "12.0VB",
"presence": "/xyz/openbmc_project/inventory/system/chassis/powersupply1",
"page": 11,
"is_power_supply_rail": true,
"check_status_vout": true,
"compare_voltage_to_limit": true,
"gpio": { "line": 60, "active_low": true }
}
)"_json;
std::unique_ptr<Rail> rail = parseRail(element);
EXPECT_EQ(rail->getName(), "12.0VB");
EXPECT_TRUE(rail->getPresence().has_value());
EXPECT_EQ(rail->getPresence().value(),
"/xyz/openbmc_project/inventory/system/chassis/powersupply1");
EXPECT_TRUE(rail->getPage().has_value());
EXPECT_EQ(rail->getPage().value(), 11);
EXPECT_TRUE(rail->isPowerSupplyRail());
EXPECT_TRUE(rail->getCheckStatusVout());
EXPECT_TRUE(rail->getCompareVoltageToLimit());
EXPECT_TRUE(rail->getGPIO().has_value());
EXPECT_EQ(rail->getGPIO().value().line, 60);
EXPECT_TRUE(rail->getGPIO().value().activeLow);
}
// Test where fails: Element is not an object
try
{
const json element = R"( [ "vdda", "vddb" ] )"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an object");
}
// Test where fails: Required name property not specified
try
{
const json element = R"(
{
"page": 11
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Required property missing: name");
}
// Test where fails: name value is invalid
try
{
const json element = R"(
{
"name": 31,
"page": 11
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not a string");
}
// Test where fails: presence value is invalid
try
{
const json element = R"(
{
"name": "VCS_CPU1",
"presence": false
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not a string");
}
// Test where fails: page value is invalid
try
{
const json element = R"(
{
"name": "VCS_CPU1",
"page": 256
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
}
// Test where fails: is_power_supply_rail value is invalid
try
{
const json element = R"(
{
"name": "12.0VA",
"is_power_supply_rail": "true"
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not a boolean");
}
// Test where fails: check_status_vout value is invalid
try
{
const json element = R"(
{
"name": "VCS_CPU1",
"check_status_vout": "false"
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not a boolean");
}
// Test where fails: compare_voltage_to_limit value is invalid
try
{
const json element = R"(
{
"name": "VCS_CPU1",
"compare_voltage_to_limit": 23
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not a boolean");
}
// Test where fails: gpio value is invalid
try
{
const json element = R"(
{
"name": "VCS_CPU1",
"gpio": 131
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an object");
}
// Test where fails: check_status_vout is true and page not specified
try
{
const json element = R"(
{
"name": "VCS_CPU1",
"check_status_vout": true
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Required property missing: page");
}
// Test where fails: compare_voltage_to_limit is true and page not
// specified
try
{
const json element = R"(
{
"name": "VCS_CPU1",
"compare_voltage_to_limit": true
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Required property missing: page");
}
// Test where fails: Invalid property specified
try
{
const json element = R"(
{
"name": "VCS_CPU1",
"foo": "bar"
}
)"_json;
parseRail(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element contains an invalid property");
}
}
TEST(ConfigFileParserTests, ParseRailArray)
{
// Test where works: Array is empty
{
const json element = R"(
[
]
)"_json;
std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element);
EXPECT_EQ(rails.size(), 0);
}
// Test where works: Array is not empty
{
const json element = R"(
[
{ "name": "VDD_CPU0" },
{ "name": "VCS_CPU1" }
]
)"_json;
std::vector<std::unique_ptr<Rail>> rails = parseRailArray(element);
EXPECT_EQ(rails.size(), 2);
EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
}
// Test where fails: Element is not an array
try
{
const json element = R"(
{
"foo": "bar"
}
)"_json;
parseRailArray(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an array");
}
// Test where fails: Element within array is invalid
try
{
const json element = R"(
[
{ "name": "VDD_CPU0" },
23
]
)"_json;
parseRailArray(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an object");
}
}
TEST(ConfigFileParserTests, ParseRoot)
{
// Test where works
{
const json element = R"(
{
"rails": [
{
"name": "VDD_CPU0",
"page": 11,
"check_status_vout": true
},
{
"name": "VCS_CPU1",
"presence": "/xyz/openbmc_project/inventory/system/chassis/motherboard/cpu1",
"gpio": { "line": 60 }
}
]
}
)"_json;
std::vector<std::unique_ptr<Rail>> rails = parseRoot(element);
EXPECT_EQ(rails.size(), 2);
EXPECT_EQ(rails[0]->getName(), "VDD_CPU0");
EXPECT_EQ(rails[1]->getName(), "VCS_CPU1");
}
// Test where fails: Element is not an object
try
{
const json element = R"( [ "VDD_CPU0", "VCS_CPU1" ] )"_json;
parseRoot(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an object");
}
// Test where fails: Required rails property not specified
try
{
const json element = R"(
{
}
)"_json;
parseRoot(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Required property missing: rails");
}
// Test where fails: rails value is invalid
try
{
const json element = R"(
{
"rails": 31
}
)"_json;
parseRoot(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an array");
}
// Test where fails: Invalid property specified
try
{
const json element = R"(
{
"rails": [
{
"name": "VDD_CPU0",
"page": 11,
"check_status_vout": true
}
],
"foo": true
}
)"_json;
parseRoot(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element contains an invalid property");
}
}
TEST(ConfigFileParserTests, ParseString)
{
// Test where works: Empty string
{
const json element = "";
std::string value = parseString(element, true);
EXPECT_EQ(value, "");
}
// Test where works: Non-empty string
{
const json element = "vdd_cpu1";
std::string value = parseString(element, false);
EXPECT_EQ(value, "vdd_cpu1");
}
// Test where fails: Element is not a string
try
{
const json element = R"( { "foo": "bar" } )"_json;
parseString(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not a string");
}
// Test where fails: Empty string
try
{
const json element = "";
parseString(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element contains an empty string");
}
}
TEST(ConfigFileParserTests, ParseUint8)
{
// Test where works: 0
{
const json element = R"( 0 )"_json;
uint8_t value = parseUint8(element);
EXPECT_EQ(value, 0);
}
// Test where works: UINT8_MAX
{
const json element = R"( 255 )"_json;
uint8_t value = parseUint8(element);
EXPECT_EQ(value, 255);
}
// Test where fails: Element is not an integer
try
{
const json element = R"( 1.03 )"_json;
parseUint8(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an integer");
}
// Test where fails: Value < 0
try
{
const json element = R"( -1 )"_json;
parseUint8(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
}
// Test where fails: Value > UINT8_MAX
try
{
const json element = R"( 256 )"_json;
parseUint8(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
}
}
TEST(ConfigFileParserTests, ParseUnsignedInteger)
{
// Test where works: 1
{
const json element = R"( 1 )"_json;
unsigned int value = parseUnsignedInteger(element);
EXPECT_EQ(value, 1);
}
// Test where fails: Element is not an integer
try
{
const json element = R"( 1.5 )"_json;
parseUnsignedInteger(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
}
// Test where fails: Value < 0
try
{
const json element = R"( -1 )"_json;
parseUnsignedInteger(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
}
}
TEST(ConfigFileParserTests, VerifyIsArray)
{
// Test where element is an array
{
const json element = R"( [ "foo", "bar" ] )"_json;
verifyIsArray(element);
}
// Test where element is not an array
try
{
const json element = R"( { "foo": "bar" } )"_json;
verifyIsArray(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an array");
}
}
TEST(ConfigFileParserTests, VerifyIsObject)
{
// Test where element is an object
{
const json element = R"( { "foo": "bar" } )"_json;
verifyIsObject(element);
}
// Test where element is not an object
try
{
const json element = R"( [ "foo", "bar" ] )"_json;
verifyIsObject(element);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element is not an object");
}
}
TEST(ConfigFileParserTests, VerifyPropertyCount)
{
// Test where element has expected number of properties
{
const json element = R"(
{
"line": 131,
"active_low": true
}
)"_json;
verifyPropertyCount(element, 2);
}
// Test where element has unexpected number of properties
try
{
const json element = R"(
{
"line": 131,
"active_low": true,
"foo": 1.3
}
)"_json;
verifyPropertyCount(element, 2);
ADD_FAILURE() << "Should not have reached this line.";
}
catch (const std::invalid_argument& e)
{
EXPECT_STREQ(e.what(), "Element contains an invalid property");
}
}