| 1 | #include "cpp/core.h"
 | 
| 2 | 
 | 
| 3 | #include <errno.h>        // errno
 | 
| 4 | #include <fcntl.h>        // O_RDWR
 | 
| 5 | #include <signal.h>       // SIG*, kill()
 | 
| 6 | #include <sys/stat.h>     // stat
 | 
| 7 | #include <sys/utsname.h>  // uname
 | 
| 8 | #include <unistd.h>       // getpid(), getuid(), environ
 | 
| 9 | 
 | 
| 10 | #include "cpp/embedded_file.h"
 | 
| 11 | #include "cpp/stdlib.h"         // posix::getcwd
 | 
| 12 | #include "mycpp/gc_builtins.h"  // IOError_OSError
 | 
| 13 | #include "vendor/greatest.h"
 | 
| 14 | 
 | 
| 15 | TEST for_test_coverage() {
 | 
| 16 |   pyos::FlushStdout();
 | 
| 17 | 
 | 
| 18 |   PASS();
 | 
| 19 | }
 | 
| 20 | 
 | 
| 21 | GLOBAL_STR(v1, "v1");
 | 
| 22 | GLOBAL_STR(v2, "v2");
 | 
| 23 | 
 | 
| 24 | TextFile gTmp[] = {
 | 
| 25 |     {.rel_path = "k1", .contents = v1},
 | 
| 26 |     {.rel_path = "k2", .contents = v2},
 | 
| 27 |     {.rel_path = nullptr, .contents = nullptr},
 | 
| 28 | };
 | 
| 29 | 
 | 
| 30 | TextFile* gEmbeddedFiles = gTmp;  // turn array into pointer
 | 
| 31 | 
 | 
| 32 | TEST loader_test() {
 | 
| 33 |   auto loader = pyutil::GetResourceLoader();
 | 
| 34 | 
 | 
| 35 |   BigStr* version = pyutil::GetVersion(loader);
 | 
| 36 |   ASSERT(len(version) > 3);
 | 
| 37 | 
 | 
| 38 |   pyutil::PrintVersionDetails(loader);
 | 
| 39 | 
 | 
| 40 |   ASSERT_EQ(v1, loader->Get(StrFromC("k1")));
 | 
| 41 |   ASSERT_EQ(v2, loader->Get(StrFromC("k2")));
 | 
| 42 | 
 | 
| 43 |   bool caught = false;
 | 
| 44 |   try {
 | 
| 45 |     loader->Get(kEmptyString);
 | 
| 46 |   } catch (IOError*) {
 | 
| 47 |     caught = true;
 | 
| 48 |   }
 | 
| 49 |   ASSERT(caught);
 | 
| 50 | 
 | 
| 51 |   PASS();
 | 
| 52 | }
 | 
| 53 | 
 | 
| 54 | TEST exceptions_test() {
 | 
| 55 |   bool caught = false;
 | 
| 56 |   try {
 | 
| 57 |     throw Alloc<pyos::ReadError>(0);
 | 
| 58 |   } catch (pyos::ReadError* e) {
 | 
| 59 |     log("e %p", e);
 | 
| 60 |     caught = true;
 | 
| 61 |   }
 | 
| 62 | 
 | 
| 63 |   ASSERT(caught);
 | 
| 64 | 
 | 
| 65 |   PASS();
 | 
| 66 | }
 | 
| 67 | 
 | 
| 68 | TEST environ_test() {
 | 
| 69 |   Dict<BigStr*, BigStr*>* env = pyos::Environ();
 | 
| 70 |   BigStr* p = env->get(StrFromC("PATH"));
 | 
| 71 |   ASSERT(p != nullptr);
 | 
| 72 |   log("PATH = %s", p->data_);
 | 
| 73 | 
 | 
| 74 |   PASS();
 | 
| 75 | }
 | 
| 76 | 
 | 
| 77 | TEST user_home_dir_test() {
 | 
| 78 |   uid_t uid = getuid();
 | 
| 79 |   BigStr* username = pyos::GetUserName(uid);
 | 
| 80 |   ASSERT(username != nullptr);
 | 
| 81 | 
 | 
| 82 |   BigStr* dir0 = pyos::GetMyHomeDir();
 | 
| 83 |   ASSERT(dir0 != nullptr);
 | 
| 84 | 
 | 
| 85 |   BigStr* dir1 = pyos::GetHomeDir(username);
 | 
| 86 |   ASSERT(dir1 != nullptr);
 | 
| 87 | 
 | 
| 88 |   ASSERT(str_equals(dir0, dir1));
 | 
| 89 | 
 | 
| 90 |   PASS();
 | 
| 91 | }
 | 
| 92 | 
 | 
| 93 | TEST uname_test() {
 | 
| 94 |   BigStr* os_type = pyos::OsType();
 | 
| 95 |   ASSERT(os_type != nullptr);
 | 
| 96 | 
 | 
| 97 |   utsname un = {};
 | 
| 98 |   ASSERT(uname(&un) == 0);
 | 
| 99 |   ASSERT(str_equals(StrFromC(un.sysname), os_type));
 | 
| 100 | 
 | 
| 101 |   PASS();
 | 
| 102 | }
 | 
| 103 | 
 | 
| 104 | TEST pyos_readbyte_test() {
 | 
| 105 |   // Write 2 bytes to this file
 | 
| 106 |   const char* tmp_name = "pyos_ReadByte";
 | 
| 107 |   int fd = ::open(tmp_name, O_CREAT | O_RDWR, 0644);
 | 
| 108 |   if (fd < 0) {
 | 
| 109 |     printf("1. ERROR %s\n", strerror(errno));
 | 
| 110 |   }
 | 
| 111 |   ASSERT(fd > 0);
 | 
| 112 |   write(fd, "SH", 2);
 | 
| 113 |   close(fd);
 | 
| 114 | 
 | 
| 115 |   fd = ::open(tmp_name, O_CREAT | O_RDWR, 0644);
 | 
| 116 |   if (fd < 0) {
 | 
| 117 |     printf("2. ERROR %s\n", strerror(errno));
 | 
| 118 |   }
 | 
| 119 | 
 | 
| 120 |   Tuple2<int, int> tup = pyos::ReadByte(fd);
 | 
| 121 |   ASSERT_EQ_FMT(0, tup.at1(), "%d");  // error code
 | 
| 122 |   ASSERT_EQ_FMT('S', tup.at0(), "%d");
 | 
| 123 | 
 | 
| 124 |   tup = pyos::ReadByte(fd);
 | 
| 125 |   ASSERT_EQ_FMT(0, tup.at1(), "%d");  // error code
 | 
| 126 |   ASSERT_EQ_FMT('H', tup.at0(), "%d");
 | 
| 127 | 
 | 
| 128 |   tup = pyos::ReadByte(fd);
 | 
| 129 |   ASSERT_EQ_FMT(0, tup.at1(), "%d");  // error code
 | 
| 130 |   ASSERT_EQ_FMT(pyos::EOF_SENTINEL, tup.at0(), "%d");
 | 
| 131 | 
 | 
| 132 |   close(fd);
 | 
| 133 | 
 | 
| 134 |   PASS();
 | 
| 135 | }
 | 
| 136 | 
 | 
| 137 | TEST pyos_read_test() {
 | 
| 138 |   const char* tmp_name = "pyos_Read";
 | 
| 139 |   int fd = ::open(tmp_name, O_CREAT | O_RDWR, 0644);
 | 
| 140 |   if (fd < 0) {
 | 
| 141 |     printf("3. ERROR %s\n", strerror(errno));
 | 
| 142 |   }
 | 
| 143 |   ASSERT(fd > 0);
 | 
| 144 |   write(fd, "SH", 2);
 | 
| 145 |   close(fd);
 | 
| 146 | 
 | 
| 147 |   // open needs an absolute path for some reason?  _tmp/pyos doesn't work
 | 
| 148 |   fd = ::open(tmp_name, O_CREAT | O_RDWR, 0644);
 | 
| 149 |   if (fd < 0) {
 | 
| 150 |     printf("4. ERROR %s\n", strerror(errno));
 | 
| 151 |   }
 | 
| 152 | 
 | 
| 153 |   List<BigStr*>* chunks = NewList<BigStr*>();
 | 
| 154 |   Tuple2<int, int> tup = pyos::Read(fd, 4096, chunks);
 | 
| 155 |   ASSERT_EQ_FMT(2, tup.at0(), "%d");  // error code
 | 
| 156 |   ASSERT_EQ_FMT(0, tup.at1(), "%d");
 | 
| 157 |   ASSERT_EQ_FMT(1, len(chunks), "%d");
 | 
| 158 | 
 | 
| 159 |   tup = pyos::Read(fd, 4096, chunks);
 | 
| 160 |   ASSERT_EQ_FMT(0, tup.at0(), "%d");  // error code
 | 
| 161 |   ASSERT_EQ_FMT(0, tup.at1(), "%d");
 | 
| 162 |   ASSERT_EQ_FMT(1, len(chunks), "%d");
 | 
| 163 | 
 | 
| 164 |   close(fd);
 | 
| 165 | 
 | 
| 166 |   PASS();
 | 
| 167 | }
 | 
| 168 | 
 | 
| 169 | TEST pyos_test() {
 | 
| 170 |   Tuple3<double, double, double> t = pyos::Time();
 | 
| 171 |   ASSERT(t.at0() > 0.0);
 | 
| 172 |   ASSERT(t.at1() >= 0.0);
 | 
| 173 |   ASSERT(t.at2() >= 0.0);
 | 
| 174 | 
 | 
| 175 |   Tuple2<int, int> result = pyos::WaitPid(0);
 | 
| 176 |   ASSERT_EQ(-1, result.at0());  // no children to wait on
 | 
| 177 | 
 | 
| 178 |   // This test isn't hermetic but it should work in most places, including in a
 | 
| 179 |   // container
 | 
| 180 | 
 | 
| 181 |   BigStr* current = posix::getcwd();
 | 
| 182 | 
 | 
| 183 |   int err_num = pyos::Chdir(StrFromC("/"));
 | 
| 184 |   ASSERT(err_num == 0);
 | 
| 185 | 
 | 
| 186 |   err_num = pyos::Chdir(StrFromC("/nonexistent__"));
 | 
| 187 |   ASSERT(err_num != 0);
 | 
| 188 | 
 | 
| 189 |   err_num = pyos::Chdir(current);
 | 
| 190 |   ASSERT(err_num == 0);
 | 
| 191 | 
 | 
| 192 |   PASS();
 | 
| 193 | }
 | 
| 194 | 
 | 
| 195 | TEST pyutil_test() {
 | 
| 196 |   ASSERT_EQ(true, pyutil::IsValidCharEscape(StrFromC("#")));
 | 
| 197 |   ASSERT_EQ(false, pyutil::IsValidCharEscape(StrFromC("a")));
 | 
| 198 | 
 | 
| 199 |   // OK this seems to work
 | 
| 200 |   BigStr* escaped =
 | 
| 201 |       pyutil::BackslashEscape(StrFromC("'foo bar'"), StrFromC(" '"));
 | 
| 202 |   ASSERT(str_equals(escaped, StrFromC("\\'foo\\ bar\\'")));
 | 
| 203 | 
 | 
| 204 |   BigStr* escaped2 = pyutil::BackslashEscape(StrFromC(""), StrFromC(" '"));
 | 
| 205 |   ASSERT(str_equals(escaped2, StrFromC("")));
 | 
| 206 | 
 | 
| 207 |   BigStr* s = pyutil::ChArrayToString(NewList<int>({65}));
 | 
| 208 |   ASSERT(str_equals(s, StrFromC("A")));
 | 
| 209 |   ASSERT_EQ_FMT(1, len(s), "%d");
 | 
| 210 | 
 | 
| 211 |   BigStr* s2 = pyutil::ChArrayToString(NewList<int>({102, 111, 111}));
 | 
| 212 |   ASSERT(str_equals(s2, StrFromC("foo")));
 | 
| 213 |   ASSERT_EQ_FMT(3, len(s2), "%d");
 | 
| 214 | 
 | 
| 215 |   BigStr* s3 = pyutil::ChArrayToString(NewList<int>({45, 206, 188, 45}));
 | 
| 216 |   ASSERT(str_equals(s3, StrFromC("-\xce\xbc-")));  // mu char
 | 
| 217 |   ASSERT_EQ_FMT(4, len(s3), "%d");
 | 
| 218 | 
 | 
| 219 |   pyos::PrintTimes();
 | 
| 220 | 
 | 
| 221 |   PASS();
 | 
| 222 | }
 | 
| 223 | 
 | 
| 224 | TEST strerror_test() {
 | 
| 225 |   IOError_OSError err(EINVAL);
 | 
| 226 |   BigStr* s1 = pyutil::strerror(&err);
 | 
| 227 |   ASSERT(s1 != nullptr);
 | 
| 228 | 
 | 
| 229 |   BigStr* s2 = StrFromC(strerror(EINVAL));
 | 
| 230 |   ASSERT(s2 != nullptr);
 | 
| 231 | 
 | 
| 232 |   ASSERT(str_equals(s1, s2));
 | 
| 233 | 
 | 
| 234 |   PASS();
 | 
| 235 | }
 | 
| 236 | 
 | 
| 237 | TEST signal_test() {
 | 
| 238 |   pyos::SignalSafe* signal_safe = pyos::InitSignalSafe();
 | 
| 239 | 
 | 
| 240 |   {
 | 
| 241 |     List<int>* q = signal_safe->TakePendingSignals();
 | 
| 242 |     ASSERT(q != nullptr);
 | 
| 243 |     ASSERT_EQ(0, len(q));
 | 
| 244 |     signal_safe->ReuseEmptyList(q);
 | 
| 245 |   }
 | 
| 246 | 
 | 
| 247 |   pid_t mypid = getpid();
 | 
| 248 | 
 | 
| 249 |   pyos::RegisterSignalInterest(SIGUSR1);
 | 
| 250 |   pyos::RegisterSignalInterest(SIGUSR2);
 | 
| 251 | 
 | 
| 252 |   kill(mypid, SIGUSR1);
 | 
| 253 |   ASSERT_EQ(SIGUSR1, signal_safe->LastSignal());
 | 
| 254 | 
 | 
| 255 |   kill(mypid, SIGUSR2);
 | 
| 256 |   ASSERT_EQ(SIGUSR2, signal_safe->LastSignal());
 | 
| 257 | 
 | 
| 258 |   {
 | 
| 259 |     List<int>* q = signal_safe->TakePendingSignals();
 | 
| 260 |     ASSERT(q != nullptr);
 | 
| 261 |     ASSERT_EQ(2, len(q));
 | 
| 262 |     ASSERT_EQ(SIGUSR1, q->at(0));
 | 
| 263 |     ASSERT_EQ(SIGUSR2, q->at(1));
 | 
| 264 | 
 | 
| 265 |     q->clear();
 | 
| 266 |     signal_safe->ReuseEmptyList(q);
 | 
| 267 |   }
 | 
| 268 | 
 | 
| 269 |   pyos::Sigaction(SIGUSR1, SIG_IGN);
 | 
| 270 |   kill(mypid, SIGUSR1);
 | 
| 271 |   {
 | 
| 272 |     List<int>* q = signal_safe->TakePendingSignals();
 | 
| 273 |     ASSERT(q != nullptr);
 | 
| 274 |     ASSERT(len(q) == 0);
 | 
| 275 |     signal_safe->ReuseEmptyList(q);
 | 
| 276 |   }
 | 
| 277 |   pyos::Sigaction(SIGUSR2, SIG_IGN);
 | 
| 278 | 
 | 
| 279 |   pyos::RegisterSignalInterest(SIGWINCH);
 | 
| 280 | 
 | 
| 281 |   kill(mypid, SIGWINCH);
 | 
| 282 |   ASSERT_EQ(pyos::UNTRAPPED_SIGWINCH, signal_safe->LastSignal());
 | 
| 283 | 
 | 
| 284 |   signal_safe->SetSigWinchCode(SIGWINCH);
 | 
| 285 | 
 | 
| 286 |   kill(mypid, SIGWINCH);
 | 
| 287 |   ASSERT_EQ(SIGWINCH, signal_safe->LastSignal());
 | 
| 288 |   {
 | 
| 289 |     List<int>* q = signal_safe->TakePendingSignals();
 | 
| 290 |     ASSERT(q != nullptr);
 | 
| 291 |     ASSERT_EQ(2, len(q));
 | 
| 292 |     ASSERT_EQ(SIGWINCH, q->at(0));
 | 
| 293 |     ASSERT_EQ(SIGWINCH, q->at(1));
 | 
| 294 |   }
 | 
| 295 | 
 | 
| 296 |   PASS();
 | 
| 297 | }
 | 
| 298 | 
 | 
| 299 | TEST signal_safe_test() {
 | 
| 300 |   pyos::SignalSafe signal_safe;
 | 
| 301 | 
 | 
| 302 |   List<int>* received = signal_safe.TakePendingSignals();
 | 
| 303 | 
 | 
| 304 |   // We got now signals
 | 
| 305 |   ASSERT_EQ_FMT(0, len(received), "%d");
 | 
| 306 | 
 | 
| 307 |   // The existing queue is of length 0
 | 
| 308 |   ASSERT_EQ_FMT(0, len(signal_safe.pending_signals_), "%d");
 | 
| 309 | 
 | 
| 310 |   // Capacity is a ROUND NUMBER from the allocator's POV
 | 
| 311 |   // There's no convenient way to test the obj_len we pass to gHeap.Allocate,
 | 
| 312 |   // but it should be (1022 + 2) * 4.
 | 
| 313 |   ASSERT_EQ_FMT(1022, signal_safe.pending_signals_->capacity_, "%d");
 | 
| 314 | 
 | 
| 315 |   // Register too many signals
 | 
| 316 |   for (int i = 0; i < pyos::kMaxPendingSignals + 10; ++i) {
 | 
| 317 |     signal_safe.UpdateFromSignalHandler(SIGINT);
 | 
| 318 |   }
 | 
| 319 | 
 | 
| 320 |   PASS();
 | 
| 321 | }
 | 
| 322 | 
 | 
| 323 | TEST passwd_test() {
 | 
| 324 |   uid_t my_uid = getuid();
 | 
| 325 |   BigStr* username = pyos::GetUserName(my_uid);
 | 
| 326 |   ASSERT(username != nullptr);
 | 
| 327 | 
 | 
| 328 |   List<pyos::PasswdEntry*>* entries = pyos::GetAllUsers();
 | 
| 329 |   if (len(entries) == 0) {
 | 
| 330 |     fprintf(stderr, "No *pwent() functions, skipping tests\n");
 | 
| 331 |     PASS();
 | 
| 332 |   }
 | 
| 333 | 
 | 
| 334 |   pyos::PasswdEntry* me = nullptr;
 | 
| 335 |   for (ListIter<pyos::PasswdEntry*> it(entries); !it.Done(); it.Next()) {
 | 
| 336 |     pyos::PasswdEntry* entry = it.Value();
 | 
| 337 |     if (entry->pw_uid == static_cast<int>(my_uid)) {
 | 
| 338 |       me = entry;
 | 
| 339 |       break;
 | 
| 340 |     }
 | 
| 341 |   }
 | 
| 342 |   ASSERT(me != nullptr);
 | 
| 343 |   ASSERT(me->pw_name != nullptr);
 | 
| 344 |   ASSERT(str_equals(username, me->pw_name));
 | 
| 345 | 
 | 
| 346 |   PASS();
 | 
| 347 | }
 | 
| 348 | 
 | 
| 349 | TEST dir_cache_key_test() {
 | 
| 350 |   struct stat st;
 | 
| 351 |   ASSERT(::stat("/", &st) == 0);
 | 
| 352 | 
 | 
| 353 |   Tuple2<BigStr*, int>* key = pyos::MakeDirCacheKey(StrFromC("/"));
 | 
| 354 |   ASSERT(str_equals(key->at0(), StrFromC("/")));
 | 
| 355 |   ASSERT(key->at1() == st.st_mtime);
 | 
| 356 | 
 | 
| 357 |   int ec = -1;
 | 
| 358 |   try {
 | 
| 359 |     pyos::MakeDirCacheKey(StrFromC("nonexistent_ZZ"));
 | 
| 360 |   } catch (IOError_OSError* e) {
 | 
| 361 |     ec = e->errno_;
 | 
| 362 |   }
 | 
| 363 |   ASSERT(ec == ENOENT);
 | 
| 364 | 
 | 
| 365 |   PASS();
 | 
| 366 | }
 | 
| 367 | 
 | 
| 368 | // Test the theory that LeakSanitizer tests for reachability from global
 | 
| 369 | // variables.
 | 
| 370 | struct Node {
 | 
| 371 |   Node* next;
 | 
| 372 | };
 | 
| 373 | Node* gNode;
 | 
| 374 | 
 | 
| 375 | TEST asan_global_leak_test() {
 | 
| 376 |   // NOT reported as a leak
 | 
| 377 |   gNode = static_cast<Node*>(malloc(sizeof(Node)));
 | 
| 378 |   gNode->next = static_cast<Node*>(malloc(sizeof(Node)));
 | 
| 379 | 
 | 
| 380 |   // Turn this on and ASAN will report a leak!
 | 
| 381 |   if (0) {
 | 
| 382 |     free(gNode);
 | 
| 383 |   }
 | 
| 384 |   PASS();
 | 
| 385 | }
 | 
| 386 | 
 | 
| 387 | GREATEST_MAIN_DEFS();
 | 
| 388 | 
 | 
| 389 | int main(int argc, char** argv) {
 | 
| 390 |   gHeap.Init();
 | 
| 391 | 
 | 
| 392 |   GREATEST_MAIN_BEGIN();
 | 
| 393 | 
 | 
| 394 |   RUN_TEST(for_test_coverage);
 | 
| 395 |   RUN_TEST(loader_test);
 | 
| 396 |   RUN_TEST(exceptions_test);
 | 
| 397 |   RUN_TEST(environ_test);
 | 
| 398 |   RUN_TEST(user_home_dir_test);
 | 
| 399 |   RUN_TEST(uname_test);
 | 
| 400 |   RUN_TEST(pyos_readbyte_test);
 | 
| 401 |   RUN_TEST(pyos_read_test);
 | 
| 402 |   RUN_TEST(pyos_test);  // non-hermetic
 | 
| 403 |   RUN_TEST(pyutil_test);
 | 
| 404 |   RUN_TEST(strerror_test);
 | 
| 405 | 
 | 
| 406 |   RUN_TEST(signal_test);
 | 
| 407 |   RUN_TEST(signal_safe_test);
 | 
| 408 | 
 | 
| 409 |   RUN_TEST(passwd_test);
 | 
| 410 |   RUN_TEST(dir_cache_key_test);
 | 
| 411 |   RUN_TEST(asan_global_leak_test);
 | 
| 412 | 
 | 
| 413 |   gHeap.CleanProcessExit();
 | 
| 414 | 
 | 
| 415 |   GREATEST_MAIN_END(); /* display results */
 | 
| 416 |   return 0;
 | 
| 417 | }
 |