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