1 #include "config_parser.hpp" 2 3 #include <phosphor-logging/elog-errors.hpp> 4 #include <stdplus/fd/atomic.hpp> 5 #include <stdplus/fd/fmt.hpp> 6 #include <stdplus/gtest/tmp.hpp> 7 #include <stdplus/print.hpp> 8 #include <xyz/openbmc_project/Common/error.hpp> 9 10 #include <exception> 11 #include <format> 12 #include <fstream> 13 #include <stdexcept> 14 15 #include <gmock/gmock.h> 16 #include <gtest/gtest.h> 17 18 namespace phosphor 19 { 20 namespace network 21 { 22 namespace config 23 { 24 25 using testing::ElementsAre; 26 using std::literals::string_view_literals::operator""sv; 27 28 TEST(TestConvert, iCaseEq) 29 { 30 EXPECT_TRUE(icaseeq("VaL", "val")); 31 EXPECT_TRUE(icaseeq("[ab1", "[ab1")); 32 } 33 34 TEST(TestConvert, ParseBool) 35 { 36 EXPECT_TRUE(parseBool("tRue").value()); 37 EXPECT_FALSE(parseBool("tru").has_value()); 38 EXPECT_TRUE(parseBool("t").value()); 39 EXPECT_TRUE(parseBool("Yes").value()); 40 EXPECT_FALSE(parseBool("ye").has_value()); 41 EXPECT_TRUE(parseBool("y").value()); 42 EXPECT_TRUE(parseBool("oN").value()); 43 44 EXPECT_FALSE(parseBool("fAlse").value()); 45 EXPECT_FALSE(parseBool("fal").has_value()); 46 EXPECT_FALSE(parseBool("f").value()); 47 EXPECT_FALSE(parseBool("No").value()); 48 EXPECT_FALSE(parseBool("n").value()); 49 EXPECT_FALSE(parseBool("oFf").value()); 50 } 51 52 TEST(TestTypeChecking, Section) 53 { 54 Section(""); 55 Section("fds#1!'\""); 56 EXPECT_THROW(Section("fds]sf"), std::invalid_argument); 57 EXPECT_THROW(Section("g\ng"), std::invalid_argument); 58 } 59 60 TEST(TestTypeChecking, Value) 61 { 62 Value(""); 63 Value("=fds1!'\"#="); 64 Value("fds]sf'' #"); 65 EXPECT_THROW(Value("g\ng"), std::invalid_argument); 66 } 67 68 TEST(TestTypeChecking, Key) 69 { 70 Key(""); 71 Key("fds1!'\"#"); 72 Key("fds]sf'' #"); 73 EXPECT_THROW(Key("fds]sf'='"), std::invalid_argument); 74 EXPECT_THROW(Key("g\ng"), std::invalid_argument); 75 } 76 77 class TestConfigParser : public stdplus::gtest::TestWithTmp 78 { 79 public: 80 std::string filename = std::format("{}/eth0.network", CaseTmpDir()); 81 Parser parser; 82 83 void WriteSampleFile() 84 { 85 std::ofstream filestream(filename); 86 filestream << "\n\n\n\nBad=key\n[Match]\n # K=v \nName =eth0\n" 87 << "[Network\nDHCP=true\n[DHCP]\nClientIdentifier= mac\n" 88 << "[Network] a\nDHCP=false #hi\n\n\nDHCP = yes \n" 89 << " [ SEC ] \n'DHCP#'=\"#hi\"\nDHCP#=ho\n[Network]\n" 90 << "Key=val\nAddress=::/0\n[]\n=\nKey"; 91 filestream.close(); 92 } 93 94 void ValidateSectionMap() 95 { 96 EXPECT_THAT( 97 parser.map, 98 testing::ContainerEq(SectionMap(SectionMapInt{ 99 {"Match", {{{"Name", {"eth0"}}}}}, 100 {"Network", 101 { 102 {{"DHCP", {"true"}}}, 103 {{"DHCP", {"false #hi", "yes"}}}, 104 {{"Key", {"val"}}, {"Address", {"::/0"}}}, 105 }}, 106 {"DHCP", {{{"ClientIdentifier", {"mac"}}}}}, 107 {" SEC ", {{{"'DHCP#'", {"\"#hi\""}}, {"DHCP#", {"ho"}}}}}, 108 {"", {{{"", {""}}}}}, 109 }))); 110 } 111 }; 112 113 TEST_F(TestConfigParser, EmptyObject) 114 { 115 EXPECT_FALSE(parser.getFileExists()); 116 EXPECT_TRUE(parser.getFilename().empty()); 117 EXPECT_EQ(0, parser.getWarnings().size()); 118 EXPECT_EQ(SectionMap(), parser.map); 119 } 120 121 TEST_F(TestConfigParser, ReadDirectory) 122 { 123 parser.setFile("/"); 124 EXPECT_FALSE(parser.getFileExists()); 125 EXPECT_EQ("/", parser.getFilename()); 126 EXPECT_EQ(1, parser.getWarnings().size()); 127 EXPECT_EQ(SectionMap(), parser.map); 128 } 129 130 TEST_F(TestConfigParser, ReadConfigDataMissingFile) 131 { 132 parser.setFile("/no-such-path"); 133 EXPECT_FALSE(parser.getFileExists()); 134 EXPECT_EQ("/no-such-path", parser.getFilename()); 135 EXPECT_EQ(1, parser.getWarnings().size()); 136 EXPECT_EQ(SectionMap(), parser.map); 137 } 138 139 TEST_F(TestConfigParser, ReadConfigDataFromFile) 140 { 141 WriteSampleFile(); 142 parser.setFile(filename); 143 EXPECT_TRUE(parser.getFileExists()); 144 EXPECT_EQ(filename, parser.getFilename()); 145 EXPECT_EQ(4, parser.getWarnings().size()); 146 ValidateSectionMap(); 147 148 const auto& map = parser.map; 149 150 EXPECT_EQ("eth0", *map.getLastValueString("Match", "Name")); 151 EXPECT_EQ("yes", *map.getLastValueString("Network", "DHCP")); 152 EXPECT_EQ(nullptr, map.getLastValueString("Match", "BadKey")); 153 EXPECT_EQ(nullptr, map.getLastValueString("BadSec", "Name")); 154 EXPECT_EQ(nullptr, map.getLastValueString("BadSec", "Name")); 155 156 EXPECT_THAT(map.getValueStrings("Match", "Name"), ElementsAre("eth0")); 157 EXPECT_THAT(map.getValueStrings("DHCP", "ClientIdentifier"), 158 ElementsAre("mac")); 159 EXPECT_THAT(map.getValueStrings("Network", "DHCP"), 160 ElementsAre("true", "false #hi", "yes")); 161 EXPECT_THAT(map.getValueStrings(" SEC ", "'DHCP#'"), 162 ElementsAre("\"#hi\"")); 163 EXPECT_THAT(map.getValueStrings("Blah", "nil"), ElementsAre()); 164 EXPECT_THAT(map.getValueStrings("Network", "nil"), ElementsAre()); 165 } 166 167 TEST_F(TestConfigParser, WriteConfigFile) 168 { 169 WriteSampleFile(); 170 parser.setFile(filename); 171 EXPECT_EQ(4, parser.getWarnings().size()); 172 ValidateSectionMap(); 173 174 parser.writeFile(); 175 176 parser.setFile(filename); 177 EXPECT_EQ(0, parser.getWarnings().size()); 178 ValidateSectionMap(); 179 } 180 181 TEST_F(TestConfigParser, Perf) 182 { 183 GTEST_SKIP(); 184 stdplus::fd::AtomicWriter file(std::format("{}/tmp.XXXXXX", CaseTmpDir()), 185 0600); 186 stdplus::fd::FormatBuffer out(file); 187 std::string obj(500, 'a'); 188 std::string kv(500 * 70, 'b'); 189 for (size_t i = 0; i < 500; ++i) 190 { 191 out.appends("["sv, std::string_view{obj}.substr(0, i + 1), "[\n"sv); 192 for (size_t j = 0; j < 70; j++) 193 { 194 auto sv = std::string_view(kv).substr(0, i * 70 + j + 1); 195 out.appends(sv, "="sv, sv, "\n"sv); 196 } 197 } 198 out.flush(); 199 file.commit(); 200 201 auto start = std::chrono::steady_clock::now(); 202 parser.setFile(filename); 203 stdplus::print("Duration: {}\n", std::chrono::steady_clock::now() - start); 204 // Make sure this test isn't enabled 205 EXPECT_FALSE(true); 206 } 207 208 } // namespace config 209 } // namespace network 210 } // namespace phosphor 211