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 "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 /* Read hardware info from the system. */
50 TEST(Utils, getHardwareInfo) {
51  Json::Value hwinfo = Utils::getHardwareInfo();
52  EXPECT_NE(hwinfo, Json::Value());
53  EXPECT_FALSE(hwinfo.isArray());
54 }
55 
56 /* Read networking info from the system. */
57 TEST(Utils, getNetworkInfo) {
58  Json::Value netinfo = Utils::getNetworkInfo();
59  EXPECT_NE(netinfo["local_ipv4"].asString(), "");
60  EXPECT_NE(netinfo["mac"].asString(), "");
61  EXPECT_NE(netinfo["hostname"].asString(), "");
62 }
63 
64 /* Read the hostname from the system. */
65 TEST(Utils, getHostname) { EXPECT_NE(Utils::getHostname(), ""); }
66 
67 /**
68  * Check that aktualizr can generate a pet name.
69  *
70  * Try 100 times and check that no duplicate names are produced. Tolerate 1
71  * duplicate, since we have actually seen it before and it is statistically
72  * not unreasonable with our current inputs.
73  */
74 TEST(Utils, GenPrettyNameSane) {
75  RecordProperty("zephyr_key", "OTA-986,TST-144");
76  std::set<std::string> names;
77  for (int i = 0; i < 100; i++) {
78  const std::string name = Utils::genPrettyName();
79  names.insert(name);
80  const auto count = names.count(name);
81  if (count > 2) {
82  FAIL() << "Something wrong with randomness: " << name;
83  } else if (count == 2) {
84  std::cerr << "Lucky draw: " << name;
85  }
86  }
87 }
88 
89 /* Generate a random UUID. */
90 TEST(Utils, RandomUuidSane) {
91  std::set<std::string> uuids;
92  for (int i = 0; i < 1000; i++) {
93  std::string uuid = Utils::randomUuid();
94  EXPECT_EQ(0, uuids.count(uuid));
95  uuids.insert(uuid);
96  EXPECT_EQ(1, uuids.count(uuid));
97  }
98 }
99 
100 TEST(Utils, ToBase64) {
101  // Generated using python's base64.b64encode
102  EXPECT_EQ("aGVsbG8=", Utils::toBase64("hello"));
103  EXPECT_EQ("", Utils::toBase64(""));
104  EXPECT_EQ("CQ==", Utils::toBase64("\t"));
105  EXPECT_EQ("YWI=", Utils::toBase64("ab"));
106  EXPECT_EQ("YWJj", Utils::toBase64("abc"));
107 }
108 
109 TEST(Utils, FromBase64) {
110  EXPECT_EQ(Utils::fromBase64("aGVsbG8="), "hello");
111  EXPECT_EQ(Utils::fromBase64(""), "");
112  EXPECT_EQ(Utils::fromBase64("YWI="), "ab");
113  EXPECT_EQ(Utils::fromBase64("YWJj"), "abc");
114 }
115 
116 TEST(Utils, FromBase64Wrong) {
117  EXPECT_THROW(Utils::fromBase64("Привіт"), boost::archive::iterators::dataflow_exception);
118  EXPECT_THROW(Utils::fromBase64("aGVsbG8=="), boost::archive::iterators::dataflow_exception);
119  EXPECT_THROW(Utils::fromBase64("CQ==="), boost::archive::iterators::dataflow_exception);
120 }
121 
122 TEST(Utils, Base64RoundTrip) {
123  std::mt19937 gen;
124  std::uniform_int_distribution<char> chars(std::numeric_limits<char>::min(), std::numeric_limits<char>::max());
125 
126  std::uniform_int_distribution<int> length(0, 20);
127 
128  for (int test = 0; test < 100; test++) {
129  int len = length(gen);
130  std::string original;
131  for (int i = 0; i < len; i++) {
132  original += chars(gen);
133  }
134  std::string b64 = Utils::toBase64(original);
135  std::string output = Utils::fromBase64(b64);
136  EXPECT_EQ(original, output);
137  }
138 }
139 
140 /*
141  * Extract credentials from a provided archive.
142  */
143 TEST(Utils, ArchiveRead) {
144  const std::string archive_path = "tests/test_data/credentials.zip";
145 
146  {
147  std::ifstream as(archive_path, std::ios::binary | std::ios::in);
148  EXPECT_FALSE(as.fail());
149  EXPECT_THROW(Utils::readFileFromArchive(as, "bogus_filename"), std::runtime_error);
150  }
151 
152  {
153  std::ifstream as(archive_path, std::ios::binary | std::ios::in);
154  EXPECT_FALSE(as.fail());
155 
156  std::string url = Utils::readFileFromArchive(as, "autoprov.url");
157  EXPECT_EQ(url.rfind("https://", 0), 0);
158  }
159 }
160 
161 TEST(Utils, ArchiveWrite) {
162  std::string archive_bytes;
163  {
164  std::map<std::string, std::string> fm{{"test", "A"}};
165  std::stringstream as;
166  Utils::writeArchive(fm, as);
167  archive_bytes = as.str();
168  }
169 
170  {
171  std::stringstream as(archive_bytes);
172  EXPECT_EQ(Utils::readFileFromArchive(as, "test"), "A");
173  }
174 }
175 
176 /* Create a temporary directory. */
178  boost::filesystem::path p;
179  {
180  TemporaryDirectory f("ahint");
181  p = f.Path();
182  EXPECT_TRUE(boost::filesystem::exists(p)); // The dir should exist
183  EXPECT_NE(p.string().find("ahint"), std::string::npos); // The hint is included in the filename
184 
185  struct stat statbuf;
186  EXPECT_GE(stat(p.parent_path().c_str(), &statbuf), 0);
187  EXPECT_EQ(statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO), S_IRWXU);
188  }
189  EXPECT_FALSE(boost::filesystem::exists(p));
190 }
191 
192 /* Create a temporary file. */
194  boost::filesystem::path p;
195  {
196  TemporaryFile f("ahint");
197  p = f.Path();
198  EXPECT_FALSE(boost::filesystem::exists(p)); // The file shouldn't already exist
199  std::ofstream file(p.c_str());
200  file << "test";
201  file.close();
202  EXPECT_TRUE(file); // The write succeeded
203  EXPECT_TRUE(boost::filesystem::exists(p)); // The file should exist here
204  EXPECT_NE(p.string().find("ahint"), std::string::npos); // The hint is included in the filename
205 
206  struct stat statbuf;
207  EXPECT_GE(stat(p.parent_path().c_str(), &statbuf), 0);
208  EXPECT_EQ(statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO), S_IRWXU);
209  }
210  EXPECT_FALSE(boost::filesystem::exists(p)); // The file gets deleted by the RAII dtor
211 }
212 
213 /* Write to a temporary file. */
214 TEST(Utils, TemporaryFilePutContents) {
215  TemporaryFile f("ahint");
216  f.PutContents("thecontents");
217  EXPECT_TRUE(boost::filesystem::exists(f.Path()));
218  std::ifstream a(f.Path().c_str());
219  std::string b;
220  a >> b;
221  EXPECT_EQ(b, "thecontents");
222 }
223 
224 TEST(Utils, copyDir) {
225  TemporaryDirectory temp_dir;
226 
227  Utils::writeFile(temp_dir.Path() / "from/1/foo", std::string("foo"));
228  Utils::writeFile(temp_dir.Path() / "from/1/2/bar", std::string("bar"));
229  Utils::writeFile(temp_dir.Path() / "from/1/2/baz", std::string("baz"));
230 
231  Utils::copyDir(temp_dir.Path() / "from", temp_dir.Path() / "to");
232  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to"));
233  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to/1"));
234  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to/1/foo"));
235  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to/1/2"));
236  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to/1/2/bar"));
237  EXPECT_TRUE(boost::filesystem::exists(temp_dir.Path() / "to/1/2/baz"));
238  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "to/1/foo"), "foo");
239  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "to/1/2/bar"), "bar");
240  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "to/1/2/baz"), "baz");
241 }
242 
243 TEST(Utils, writeFileWithoutDirAutoCreation) {
244  TemporaryDirectory temp_dir;
245 
246  boost::filesystem::create_directories(temp_dir.Path() / "1/2");
247  Utils::writeFile(temp_dir.Path() / "1/foo", std::string("foo"), false);
248  Utils::writeFile(temp_dir.Path() / "1/2/bar", std::string("bar"), false);
249 
250  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/foo"), "foo");
251  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/2/bar"), "bar");
252 }
253 
254 TEST(Utils, writeFileWithDirAutoCreation) {
255  TemporaryDirectory temp_dir;
256 
257  Utils::writeFile(temp_dir.Path() / "1/foo", std::string("foo"), true);
258  Utils::writeFile(temp_dir.Path() / "1/2/bar", std::string("bar"), true);
259 
260  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/foo"), "foo");
261  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/2/bar"), "bar");
262 }
263 
264 TEST(Utils, writeFileWithDirAutoCreationDefault) {
265  TemporaryDirectory temp_dir;
266 
267  Utils::writeFile(temp_dir.Path() / "1/foo", std::string("foo"));
268  Utils::writeFile(temp_dir.Path() / "1/2/bar", std::string("bar"));
269 
270  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/foo"), "foo");
271  EXPECT_EQ(Utils::readFile(temp_dir.Path() / "1/2/bar"), "bar");
272 }
273 
274 TEST(Utils, writeFileWithoutDirAutoCreationException) {
275  TemporaryDirectory temp_dir;
276 
277  try {
278  Utils::writeFile(temp_dir.Path() / "1/foo", std::string("foo"), false);
279  EXPECT_TRUE(false);
280  } catch (...) {
281  }
282 }
283 
284 TEST(Utils, writeFileJson) {
285  TemporaryDirectory temp_dir;
286 
287  Json::Value val;
288  val["key"] = "val";
289 
290  Utils::writeFile(temp_dir.Path() / "1/foo", val);
291  Json::Value result_json = Utils::parseJSONFile(temp_dir.Path() / "1/foo");
292  EXPECT_EQ(result_json["key"].asString(), val["key"].asString());
293 }
294 
295 TEST(Utils, ipUtils) {
296  int fd = socket(AF_INET6, SOCK_STREAM, 0);
297 
298  EXPECT_NE(fd, -1);
299  SocketHandle hdl(new int(fd));
300 
301  sockaddr_in6 sa;
302 
303  memset(&sa, 0, sizeof(sa));
304  sa.sin6_family = AF_INET6;
305  sa.sin6_port = htons(0);
306  sa.sin6_addr = IN6ADDR_ANY_INIT;
307 
308  int reuseaddr = 1;
309  if (setsockopt(*hdl, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) < 0) {
310  throw std::runtime_error("setsockopt(SO_REUSEADDR) failed");
311  }
312 
313  EXPECT_NE(bind(*hdl, reinterpret_cast<const sockaddr *>(&sa), sizeof(sa)), -1);
314 
315  sockaddr_storage ss;
316  EXPECT_NO_THROW(ss = Utils::ipGetSockaddr(*hdl));
317 
318  EXPECT_NE(Utils::ipDisplayName(ss), "unknown");
319  EXPECT_NE(Utils::ipPort(ss), -1);
320 }
321 
322 TEST(Utils, shell) {
323  std::string out;
324  int statuscode = Utils::shell("ls /", &out);
325  EXPECT_EQ(statuscode, 0);
326 
327  statuscode = Utils::shell("ls /nonexistentdir123", &out);
328  EXPECT_NE(statuscode, 0);
329 }
330 
331 TEST(Utils, urlencode) { EXPECT_EQ(Utils::urlEncode("test! test@ test#"), "test%21%20test%40%20test%23"); }
332 
333 TEST(Utils, BasedPath) {
334  BasedPath bp("a/test.xml");
335 
336  EXPECT_EQ(BasedPath(bp.get("")), bp);
337  EXPECT_EQ(bp.get("/"), "/a/test.xml");
338  EXPECT_EQ(bp.get("/x"), "/x/a/test.xml");
339 
340  BasedPath abp("/a/test.xml");
341 
342  EXPECT_EQ(abp.get(""), "/a/test.xml");
343  EXPECT_EQ(abp.get("/root/var"), "/a/test.xml");
344 }
345 
346 TEST(Utils, TrimNewline) {
347  TemporaryFile f("newline");
348  std::string input = "test with newline";
349  Utils::writeFile(f.Path(), input + "\n");
350  std::string output = Utils::readFile(f.Path(), false);
351  EXPECT_EQ(output, input + "\n");
352  output = Utils::readFile(f.Path(), true);
353  EXPECT_EQ(output, input);
354 }
355 
356 #ifndef __NO_MAIN__
357 int main(int argc, char **argv) {
358  ::testing::InitGoogleTest(&argc, argv);
359  return RUN_ALL_TESTS();
360 }
361 #endif
TEST(Utils, GenPrettyNameSane)
Check that aktualizr can generate a pet name.
Definition: utils_test.cc:74
Definition: utils.h:14
RAII Temporary file creation.
Definition: utils.h:56