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

417 lines, 266 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 <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
15TEST for_test_coverage() {
16 pyos::FlushStdout();
17
18 PASS();
19}
20
21GLOBAL_STR(v1, "v1");
22GLOBAL_STR(v2, "v2");
23
24TextFile gTmp[] = {
25 {.rel_path = "k1", .contents = v1},
26 {.rel_path = "k2", .contents = v2},
27 {.rel_path = nullptr, .contents = nullptr},
28};
29
30TextFile* gEmbeddedFiles = gTmp; // turn array into pointer
31
32TEST 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
54TEST 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
68TEST 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
77TEST 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
93TEST 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
104TEST 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
137TEST 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
169TEST 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
195TEST 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
224TEST 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
237TEST 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
299TEST 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
323TEST 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
349TEST 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.
370struct Node {
371 Node* next;
372};
373Node* gNode;
374
375TEST 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
387GREATEST_MAIN_DEFS();
388
389int 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}