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