OILS / cpp / core_test.cc View on Github | oilshell.org

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