1 #include "config_parser.hpp"
2 
3 #include <fmt/chrono.h>
4 #include <fmt/compile.h>
5 #include <fmt/format.h>
6 
7 #include <exception>
8 #include <fstream>
9 #include <phosphor-logging/elog-errors.hpp>
10 #include <stdexcept>
11 #include <stdplus/fd/atomic.hpp>
12 #include <stdplus/fd/fmt.hpp>
13 #include <stdplus/gtest/tmp.hpp>
14 #include <xyz/openbmc_project/Common/error.hpp>
15 
16 #include <gmock/gmock.h>
17 #include <gtest/gtest.h>
18 
19 namespace phosphor
20 {
21 namespace network
22 {
23 namespace config
24 {
25 
26 using testing::ElementsAre;
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 = fmt::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_TRUE(parser.getFilename().empty());
116     EXPECT_EQ(0, parser.getWarnings().size());
117     EXPECT_EQ(SectionMap(), parser.map);
118 }
119 
120 TEST_F(TestConfigParser, ReadDirectory)
121 {
122     parser.setFile("/");
123     EXPECT_EQ("/", parser.getFilename());
124     EXPECT_EQ(1, parser.getWarnings().size());
125     EXPECT_EQ(SectionMap(), parser.map);
126 }
127 
128 TEST_F(TestConfigParser, ReadConfigDataMissingFile)
129 {
130     parser.setFile("/no-such-path");
131     EXPECT_EQ("/no-such-path", parser.getFilename());
132     EXPECT_EQ(1, parser.getWarnings().size());
133     EXPECT_EQ(SectionMap(), parser.map);
134 }
135 
136 TEST_F(TestConfigParser, ReadConfigDataFromFile)
137 {
138     WriteSampleFile();
139     parser.setFile(filename);
140     EXPECT_EQ(filename, parser.getFilename());
141     EXPECT_EQ(4, parser.getWarnings().size());
142     ValidateSectionMap();
143 
144     const auto& map = parser.map;
145 
146     EXPECT_EQ("eth0", *map.getLastValueString("Match", "Name"));
147     EXPECT_EQ("yes", *map.getLastValueString("Network", "DHCP"));
148     EXPECT_EQ(nullptr, map.getLastValueString("Match", "BadKey"));
149     EXPECT_EQ(nullptr, map.getLastValueString("BadSec", "Name"));
150     EXPECT_EQ(nullptr, map.getLastValueString("BadSec", "Name"));
151 
152     EXPECT_THAT(map.getValueStrings("Match", "Name"), ElementsAre("eth0"));
153     EXPECT_THAT(map.getValueStrings("DHCP", "ClientIdentifier"),
154                 ElementsAre("mac"));
155     EXPECT_THAT(map.getValueStrings("Network", "DHCP"),
156                 ElementsAre("true", "false #hi", "yes"));
157     EXPECT_THAT(map.getValueStrings(" SEC ", "'DHCP#'"),
158                 ElementsAre("\"#hi\""));
159     EXPECT_THAT(map.getValueStrings("Blah", "nil"), ElementsAre());
160     EXPECT_THAT(map.getValueStrings("Network", "nil"), ElementsAre());
161 }
162 
163 TEST_F(TestConfigParser, WriteConfigFile)
164 {
165     WriteSampleFile();
166     parser.setFile(filename);
167     EXPECT_EQ(4, parser.getWarnings().size());
168     ValidateSectionMap();
169 
170     parser.writeFile();
171 
172     parser.setFile(filename);
173     EXPECT_EQ(0, parser.getWarnings().size());
174     ValidateSectionMap();
175 }
176 
177 TEST_F(TestConfigParser, Perf)
178 {
179     GTEST_SKIP();
180     stdplus::fd::AtomicWriter file(fmt::format("{}/tmp.XXXXXX", CaseTmpDir()),
181                                    0600);
182     stdplus::fd::FormatBuffer out(file);
183     for (size_t i = 0; i < 500; ++i)
184     {
185         out.append(FMT_COMPILE("[{:a>{}}]\n"), "", i + 1);
186         for (size_t j = 0; j < 70; j++)
187         {
188             const size_t es = i * 70 + j + 1;
189             out.append(FMT_COMPILE("{:b>{}}={:c>{}}\n"), "", es, "", es);
190         }
191     }
192     out.flush();
193     file.commit();
194 
195     auto start = std::chrono::steady_clock::now();
196     parser.setFile(filename);
197     fmt::print("Duration: {}\n", std::chrono::steady_clock::now() - start);
198     // Make sure this test isn't enabled
199     EXPECT_FALSE(true);
200 }
201 
202 } // namespace config
203 } // namespace network
204 } // namespace phosphor
205