Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
utils_test.cc
Go to the documentation of this file.
1 /**
2  * \file
3  */
4 
5 #include <gtest/gtest.h>
6 
7 #include <sys/stat.h>
8 #include <fstream>
9 #include <map>
10 #include <random>
11 #include <set>
12 
13 #include <boost/algorithm/hex.hpp>
14 #include <boost/archive/iterators/dataflow_exception.hpp>
15 
16 #include "utilities/utils.h"
17 
18 bool CharOk(char c) {
19  if (('a' <= c) && (c <= 'z')) {
20  return true;
21  } else if (('0' <= c) && (c <= '9')) {
22  return true;
23  } else if (c == '-') {
24  return true;
25  } else {
26  return false;
27  }
28 }
29 
30 bool PrettyNameOk(const std::string &name) {
31  if (name.size() < 5) {
32  return false;
33  }
34  for (std::string::const_iterator c = name.begin(); c != name.end(); ++c) {
35  if (!CharOk(*c)) {
36  return false;
37  }
38  }
39  return true;
40 }
41 
42 TEST(Utils, PrettyNameOk) {
43  EXPECT_TRUE(PrettyNameOk("foo-bar-123"));
44  EXPECT_FALSE(PrettyNameOk("NoCapitals"));
45  EXPECT_FALSE(PrettyNameOk(""));
46  EXPECT_FALSE(PrettyNameOk("foo-bar-123&"));
47 }
48 
49 TEST(Utils, parseJSON) {
50  // this should not cause valgrind warnings
51  Utils::parseJSON("");
52 }
53 
54 TEST(Utils, jsonToCanonicalStr) {
55  const std::string sample = " { \"b\": 0, \"a\": [1, 2, {}], \"0\": \"x\"}";
56  Json::Value parsed;
57 
58  parsed = Utils::parseJSON(sample);
59  EXPECT_EQ(Utils::jsonToCanonicalStr(parsed), "{\"0\":\"x\",\"a\":[1,2,{}],\"b\":0}");
60 
61  const std::string sample2 = "0";
62  parsed = Utils::parseJSON(sample2);
63  EXPECT_EQ(Utils::jsonToCanonicalStr(parsed), "0");
64 }
65 
66 /* Read hardware info from the system. */
67 TEST(Utils, getHardwareInfo) {
68  Json::Value hwinfo = Utils::getHardwareInfo();
69  EXPECT_NE(hwinfo, Json::Value());
70  EXPECT_FALSE(hwinfo.isArray());
71 }
72 
73 /* Read networking info from the system. */
74 TEST(Utils, getNetworkInfo) {
75  Json::Value netinfo = Utils::getNetworkInfo();
76  EXPECT_NE(netinfo["local_ipv4"].asString(), "");
77  EXPECT_NE(netinfo["mac"].asString(), "");
78  EXPECT_NE(netinfo["hostname"].asString(), "");
79 }
80 
81 /* Read the hostname from the system. */
82 TEST(Utils, getHostname) { EXPECT_NE(Utils::getHostname(), ""); }
83 
84 /**
85  * Check that aktualizr can generate a pet name.
86  *
87  * Try 100 times and check that no duplicate names are produced. Tolerate 1
88  * duplicate, since we have actually seen it before and it is statistically
89  * not unreasonable with our current inputs.
90  */
91 TEST(Utils, GenPrettyNameSane) {
92  RecordProperty("zephyr_key", "OTA-986,TST-144");
93  std::set<std::string> names;
94  for (int i = 0; i < 100; i++) {
95  const std::string name = Utils::genPrettyName();
96  names.insert(name);
97  const auto count = names.count(name);
98  if (count > 2) {
99  FAIL() << "Something wrong with randomness: " << name;
100  } else if (count == 2) {
101  std::cerr << "Lucky draw: " << name;
102  }
103  }
104 }
105 
106 /* Generate a random UUID. */
107 TEST(Utils, RandomUuidSane) {
108  std::set<std::string> uuids;
109  for (int i = 0; i < 1000; i++) {
110  std::string uuid = Utils::randomUuid();
111  EXPECT_EQ(0, uuids.count(uuid));
112  uuids.insert(uuid);
113  EXPECT_EQ(1, uuids.count(uuid));
114  }
115 }
116 
117 TEST(Utils, ToBase64) {
118  // Generated using python's base64.b64encode
119  EXPECT_EQ("aGVsbG8=", Utils::toBase64("hello"));
120  EXPECT_EQ("", Utils::toBase64(""));
121  EXPECT_EQ("CQ==", Utils::toBase64("\t"));
122  EXPECT_EQ("YWI=", Utils::toBase64("ab"));
123  EXPECT_EQ("YWJj", Utils::toBase64("abc"));
124 }
125 
126 TEST(Utils, FromBase64) {
127  EXPECT_EQ(Utils::fromBase64("aGVsbG8="), "hello");
128  EXPECT_EQ(Utils::fromBase64(""), "");
129  EXPECT_EQ(Utils::fromBase64("YWI="), "ab");
130  EXPECT_EQ(Utils::fromBase64("YWJj"), "abc");
131 }
132 
133 TEST(Utils, FromBase64Wrong) {
134  EXPECT_THROW(Utils::fromBase64("Привіт"), boost::archive::iterators::dataflow_exception);
135  EXPECT_THROW(Utils::fromBase64("aGVsbG8=="), boost::archive::iterators::dataflow_exception);
136  EXPECT_THROW(Utils::fromBase64("CQ==="), boost::archive::iterators::dataflow_exception);
137 }
138 
139 TEST(Utils, Base64RoundTrip) {
140  std::mt19937 gen;
141  std::uniform_int_distribution<char> chars(std::numeric_limits<char>::min(), std::numeric_limits<char>::max());
142 
143  std::uniform_int_distribution<int> length(0, 20);
144 
145  for (int test = 0; test < 100; test++) {
146  int len = length(gen);
147  std::string original;
148  for (int i = 0; i < len; i++) {
149  original += chars(gen);
150  }
151  std::string b64 = Utils::toBase64(original);
152  std::string output = Utils::fromBase64(b64);
153  EXPECT_EQ(original, output);
154  }
155 }
156 
157 /*
158  * Extract credentials from a provided archive.
159  */
160 TEST(Utils, ArchiveRead) {
161  const std::string archive_path = "tests/test_data/credentials.zip";
162 
163  {
164  std::ifstream as(archive_path, std::ios::binary | std::ios::in);
165  EXPECT_FALSE(as.fail());
166  EXPECT_THROW(Utils::readFileFromArchive(as, "bogus_filename"), std::runtime_error);
167  }
168 
169  {
170  std::ifstream as(archive_path, std::ios::binary | std::ios::in);
171  EXPECT_FALSE(as.fail());
172 
173  std::string url = Utils::readFileFromArchive(as, "autoprov.url");
174  EXPECT_EQ(url.rfind("https://", 0), 0);
175  }
176 }
177 
178 TEST(Utils, ArchiveWrite) {
179  std::string archive_bytes;
180  {
181  std::map<std::string, std::string> fm{{"test", "A"}};
182  std::stringstream as;
183  Utils::writeArchive(fm, as);
184  archive_bytes = as.str();
185  }
186 
187  {
188  std::stringstream as(archive_bytes);
189  EXPECT_EQ(Utils::readFileFromArchive(as, "test"), "A");
190  }
191 }
192 
193 /* Create a temporary directory. */
195  boost::filesystem::path p;
196  {
197  TemporaryDirectory f("ahint");
198  p = f.Path();
199  EXPECT_TRUE(boost::filesystem::exists(p)); // The dir should exist
200  EXPECT_NE(p.string().find("ahint"), std::string::npos); // The hint is included in the filename
201 
202  struct stat statbuf {};
203  EXPECT_GE(stat(p.parent_path().c_str(), &statbuf), 0);
204  EXPECT_EQ(statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO), S_IRWXU);
205  }
206  EXPECT_FALSE(boost::filesystem::exists(p));
207 }
208 
209 /* Create a temporary file. */
211  boost::filesystem::path p;
212  {
213  TemporaryFile f("ahint");
214  p = f.Path();
215  EXPECT_FALSE(boost::filesystem::exists(p)); // The file shouldn't already exist
216  std::ofstream file(p.c_str());
217  file << "test";
218  file.close();
219  EXPECT_TRUE(file); // The write succeeded
220  EXPECT_TRUE(boost::filesystem::exists(p)); // The file should exist here
221  EXPECT_NE(p.string().find("ahint"), std::string::npos); // The hint is included in the filename
222 
223  struct stat statbuf {};
224  EXPECT_GE(stat(p.parent_path().c_str(), &statbuf), 0);
225  EXPECT_EQ(statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO), S_IRWXU);
226  }
227  EXPECT_FALSE(boost::filesystem::exists(p)); // The file gets deleted by the RAII dtor
228 }
229 
230 /* Write to a temporary file. */
231 TEST(Utils, TemporaryFilePutContents) {
232  TemporaryFile f("ahint");
233  f.PutContents("thecontents");
234  EXPECT_TRUE(boost::filesystem::exists(f.Path()));
235  std::ifstream a(f.Path().c_str());
236  std::string b;
237  a >> b;
238  EXPECT_EQ(b, "thecontents");
239 }
240 
241 TEST(Utils, copyDir) {
242  TemporaryDirectory temp_dir;
243 
244  Utils::writeFile(temp_dir.Path() / "from/1/foo", std::string("foo"));
245  Utils::writeFile(temp_dir.Path() / "from/1/2/bar", std::string("bar"));
246  Utils::writeFile(temp_dir.Path() / "from/1/2/baz", std::string("baz"));
247 
248  Utils::copyDir(temp_dir.Path() / "from", temp_dir.Path() / "to");
249  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to"));
250  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to/1"));
251  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to/1/foo"));
252  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to/1/2"));
253  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to/1/2/bar"));
254  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to/1/2/baz"));
255  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "to/1/foo"), "foo");
256  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "to/1/2/bar"), "bar");
257  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "to/1/2/baz"), "baz");
258 }
259 
260 TEST(Utils, writeFileWithoutDirAutoCreation) {
261  TemporaryDirectory temp_dir;
262 
263  boost::filesystem::create_directories(temp_dir.Path() / "1/2");
264  Utils::writeFile(temp_dir.Path() / "1/foo", std::string("foo"), false);
265  Utils::writeFile(temp_dir.Path() / "1/2/bar", std::string("bar"), false);
266 
267  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/foo"), "foo");
268  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/2/bar"), "bar");
269 }
270 
271 TEST(Utils, writeFileWithDirAutoCreation) {
272  TemporaryDirectory temp_dir;
273 
274  Utils::writeFile(temp_dir.Path() / "1/foo", std::string("foo"), true);
275  Utils::writeFile(temp_dir.Path() / "1/2/bar", std::string("bar"), true);
276 
277  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/foo"), "foo");
278  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/2/bar"), "bar");
279 }
280 
281 TEST(Utils, writeFileWithDirAutoCreationDefault) {
282  TemporaryDirectory temp_dir;
283 
284  Utils::writeFile(temp_dir.Path() / "1/foo", std::string("foo"));
285  Utils::writeFile(temp_dir.Path() / "1/2/bar", std::string("bar"));
286 
287  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/foo"), "foo");
288  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/2/bar"), "bar");
289 }
290 
291 TEST(Utils, writeFileWithoutDirAutoCreationException) {
292  TemporaryDirectory temp_dir;
293 
294  try {
295  Utils::writeFile(temp_dir.Path() / "1/foo", std::string("foo"), false);
296  EXPECT_TRUE(false);
297  } catch (...) {
298  }
299 }
300 
301 TEST(Utils, writeFileJson) {
302  TemporaryDirectory temp_dir;
303 
304  Json::Value val;
305  val["key"] = "val";
306 
307  Utils::writeFile(temp_dir.Path() / "1/foo", val);
308  Json::Value result_json = Utils::parseJSONFile(temp_dir.Path() / "1/foo");
309  EXPECT_EQ(result_json["key"].asString(), val["key"].asString());
310 }
311 
312 TEST(Utils, shell) {
313  std::string out;
314  int statuscode = Utils::shell("ls /", &out);
315  EXPECT_EQ(statuscode, 0);
316 
317  statuscode = Utils::shell("ls /nonexistentdir123", &out);
318  EXPECT_NE(statuscode, 0);
319 }
320 
321 TEST(Utils, createSecureDirectory) {
322  TemporaryDirectory temp_dir;
323 
324  EXPECT_TRUE(Utils::createSecureDirectory(temp_dir / "sec"));
325  EXPECT_TRUE(boost::filesystem::is_directory(temp_dir / "sec"));
326  // check that it succeeds the second time
327  EXPECT_TRUE(Utils::createSecureDirectory(temp_dir / "sec"));
328 
329  // regular directory is insecure
330  boost::filesystem::create_directory(temp_dir / "unsec");
331  EXPECT_FALSE(Utils::createSecureDirectory(temp_dir / "unsec"));
332 }
333 
334 TEST(Utils, urlencode) { EXPECT_EQ(Utils::urlEncode("test! test@ test#"), "test%21%20test%40%20test%23"); }
335 
336 TEST(Utils, getDirEntriesByExt) {
337  TemporaryDirectory temp_dir;
338 
339  Utils::writeFile(temp_dir / "02-test.toml", std::string(""));
340  Utils::writeFile(temp_dir / "0x-test.noml", std::string(""));
341  Utils::writeFile(temp_dir / "03-test.toml", std::string(""));
342  Utils::writeFile(temp_dir / "01-test.toml", std::string(""));
343 
344  auto files = Utils::getDirEntriesByExt(temp_dir.Path(), ".toml");
345  std::vector<boost::filesystem::path> expected{temp_dir / "01-test.toml", temp_dir / "02-test.toml",
346  temp_dir / "03-test.toml"};
347  EXPECT_EQ(files, expected);
348 }
349 
350 TEST(Utils, BasedPath) {
351  BasedPath bp("a/test.xml");
352 
353  EXPECT_EQ(BasedPath(bp.get("")), bp);
354  EXPECT_EQ(bp.get("/"), "/a/test.xml");
355  EXPECT_EQ(bp.get("/x"), "/x/a/test.xml");
356 
357  BasedPath abp("/a/test.xml");
358 
359  EXPECT_EQ(abp.get(""), "/a/test.xml");
360  EXPECT_EQ(abp.get("/root/var"), "/a/test.xml");
361 }
362 
363 TEST(Utils, TrimNewline) {
364  TemporaryFile f("newline");
365  std::string input = "test with newline";
366  Utils::writeFile(f.Path(), input + "\n");
367  std::string output = Utils::readFile(f.Path(), false);
368  EXPECT_EQ(output, input + "\n");
369  output = Utils::readFile(f.Path(), true);
370  EXPECT_EQ(output, input);
371 }
372 
373 #ifndef __NO_MAIN__
374 int main(int argc, char **argv) {
375  ::testing::InitGoogleTest(&argc, argv);
376  return RUN_ALL_TESTS();
377 }
378 #endif
TEST(Utils, GenPrettyNameSane)
Check that aktualizr can generate a pet name.
Definition: utils_test.cc:91
Definition: utils.h:13
RAII Temporary file creation.
Definition: utils.h:68