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