cheatah
Source

tests/purrc/os_cr_test.cpp

1// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).
2// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.
3// Compile-run unit tests for the `os` module: one test per function. Each writes
4// a tiny .purr that calls a single os (or os.path) function, compiles it with
5// purrc, runs it under the cheatah runtime, and asserts the exact stdout.
6// Complements the in-process unit tests (stdlib/tests/os_test.cpp) and the
7// per-module system-level test (StdlibE2E.Os).
8//
9// Programs must be DETERMINISTIC. Purely-lexical os.path helpers print their
10// result directly. Filesystem functions operate under a UNIQUE /tmp directory
11// created inside the .purr and print a deterministic property (e.g. mkdir then
12// os.path.isdir -> True). Non-deterministic functions (getpid, getcwd,
13// cpu_count, system exit status, getenv of a process-set var) assert a
14// deterministic property instead of the raw value.
15//
16// SKIPPED: none — every os/os.path function below is covered. The only thing
17// that cannot be printed directly is os.path.splitext (returns a std::pair, not
18// io.print-Streamable); it is covered via the pair's `.first`/`.second` members.
19#include "e2e_harness.hpp"
21// ---- os.path: purely-lexical helpers (deterministic from the literal) -------
23TEST(OsCompileRun, PathJoin) {
24 e2e::expect_e2e("os_path_join", R"PURR(import io
25import os
26io.print(os.path.join("a", "b", "c"))
27)PURR", "a/b/c\n");
30TEST(OsCompileRun, PathBasename) {
31 e2e::expect_e2e("os_path_basename", R"PURR(import io
32import os
33io.print(os.path.basename("/a/b/c.txt"))
34)PURR", "c.txt\n");
37TEST(OsCompileRun, PathDirname) {
38 e2e::expect_e2e("os_path_dirname", R"PURR(import io
39import os
40io.print(os.path.dirname("/a/b/c.txt"))
41)PURR", "/a/b\n");
44TEST(OsCompileRun, PathNormpath) {
45 e2e::expect_e2e("os_path_normpath", R"PURR(import io
46import os
47io.print(os.path.normpath("a/./b/../c"))
48)PURR", "a/c\n");
51TEST(OsCompileRun, PathSplitext) {
52 // splitext returns a std::pair (not io.print-Streamable); read its members.
53 e2e::expect_e2e("os_path_splitext", R"PURR(import io
54import os
55io.print(os.path.splitext("dir/file.purr").first, os.path.splitext("dir/file.purr").second)
56)PURR", "dir/file .purr\n");
59TEST(OsCompileRun, PathAbspath) {
60 // An already-absolute path is returned unchanged: deterministic.
61 e2e::expect_e2e("os_path_abspath", R"PURR(import io
62import os
63io.print(os.path.abspath("/already/abs"))
64)PURR", "/already/abs\n");
67// ---- os.path: filesystem queries (deterministic against fixed paths) --------
69TEST(OsCompileRun, PathExists) {
70 e2e::expect_e2e("os_path_exists", R"PURR(import io
71import os
72io.print(os.path.exists("/tmp"))
73)PURR", "True\n");
76TEST(OsCompileRun, PathIsdir) {
77 e2e::expect_e2e("os_path_isdir", R"PURR(import io
78import os
79io.print(os.path.isdir("/tmp"))
80)PURR", "True\n");
83TEST(OsCompileRun, PathIsfile) {
84 // /tmp is a directory, not a regular file.
85 e2e::expect_e2e("os_path_isfile", R"PURR(import io
86import os
87io.print(os.path.isfile("/tmp"))
88)PURR", "False\n");
91TEST(OsCompileRun, PathGetsize) {
92 // Create a 5-byte file, then query its size.
93 e2e::expect_e2e("os_path_getsize", R"PURR(import io
94import os
95os.mkdir("/tmp/cr_os_getsize_dir")
96os.system("printf 12345 > /tmp/cr_os_getsize_dir/f.txt")
97io.print(os.path.getsize("/tmp/cr_os_getsize_dir/f.txt"))
98os.remove("/tmp/cr_os_getsize_dir/f.txt")
99os.rmdir("/tmp/cr_os_getsize_dir")
100)PURR", "5\n");
103// ---- os: directory creation / removal ---------------------------------------
105TEST(OsCompileRun, Mkdir) {
106 e2e::expect_e2e("os_mkdir", R"PURR(import io
107import os
108os.mkdir("/tmp/cr_os_mkdir_dir")
109io.print(os.path.isdir("/tmp/cr_os_mkdir_dir"))
110os.rmdir("/tmp/cr_os_mkdir_dir")
111)PURR", "True\n");
114TEST(OsCompileRun, Makedirs) {
115 e2e::expect_e2e("os_makedirs", R"PURR(import io
116import os
117os.makedirs("/tmp/cr_os_makedirs_dir/a/b/c")
118io.print(os.path.isdir("/tmp/cr_os_makedirs_dir/a/b/c"))
119os.rmdir("/tmp/cr_os_makedirs_dir/a/b/c")
120os.rmdir("/tmp/cr_os_makedirs_dir/a/b")
121os.rmdir("/tmp/cr_os_makedirs_dir/a")
122os.rmdir("/tmp/cr_os_makedirs_dir")
123)PURR", "True\n");
126TEST(OsCompileRun, Rmdir) {
127 e2e::expect_e2e("os_rmdir", R"PURR(import io
128import os
129os.mkdir("/tmp/cr_os_rmdir_dir")
130os.rmdir("/tmp/cr_os_rmdir_dir")
131io.print(os.path.exists("/tmp/cr_os_rmdir_dir"))
132)PURR", "False\n");
135// ---- os: file operations ----------------------------------------------------
137TEST(OsCompileRun, Remove) {
138 // remove() returns true when something was deleted.
139 e2e::expect_e2e("os_remove", R"PURR(import io
140import os
141os.mkdir("/tmp/cr_os_remove_dir")
142os.system("printf x > /tmp/cr_os_remove_dir/f.txt")
143io.print(os.remove("/tmp/cr_os_remove_dir/f.txt"))
144os.rmdir("/tmp/cr_os_remove_dir")
145)PURR", "True\n");
148TEST(OsCompileRun, Rename) {
149 e2e::expect_e2e("os_rename", R"PURR(import io
150import os
151os.mkdir("/tmp/cr_os_rename_dir")
152os.system("printf x > /tmp/cr_os_rename_dir/one.txt")
153os.rename("/tmp/cr_os_rename_dir/one.txt", "/tmp/cr_os_rename_dir/two.txt")
154io.print(os.path.isfile("/tmp/cr_os_rename_dir/two.txt"))
155os.remove("/tmp/cr_os_rename_dir/two.txt")
156os.rmdir("/tmp/cr_os_rename_dir")
157)PURR", "True\n");
160TEST(OsCompileRun, Listdir) {
161 // A directory with a single known entry: listdir returns basenames.
162 e2e::expect_e2e("os_listdir", R"PURR(import io
163import os
164os.mkdir("/tmp/cr_os_listdir_dir")
165os.system("printf x > /tmp/cr_os_listdir_dir/only.txt")
166io.print(os.listdir("/tmp/cr_os_listdir_dir")[0])
167os.remove("/tmp/cr_os_listdir_dir/only.txt")
168os.rmdir("/tmp/cr_os_listdir_dir")
169)PURR", "only.txt\n");
172// ---- os: working directory --------------------------------------------------
174TEST(OsCompileRun, Getcwd) {
175 // The cwd is non-deterministic; assert it is a non-empty path.
176 e2e::expect_e2e("os_getcwd", R"PURR(import io
177import os
178io.print(len(os.getcwd()) > 0)
179)PURR", "True\n");
182TEST(OsCompileRun, Chdir) {
183 // chdir into a unique temp dir, then confirm getcwd's basename matches.
184 e2e::expect_e2e("os_chdir", R"PURR(import io
185import os
186os.makedirs("/tmp/cr_os_chdir_dir")
187os.chdir("/tmp/cr_os_chdir_dir")
188io.print(os.path.basename(os.getcwd()))
189os.chdir("/tmp")
190os.rmdir("/tmp/cr_os_chdir_dir")
191)PURR", "cr_os_chdir_dir\n");
194// ---- os: environment --------------------------------------------------------
196TEST(OsCompileRun, Setenv) {
197 // setenv then getenv round-trips within the process.
198 e2e::expect_e2e("os_setenv", R"PURR(import io
199import os
200os.setenv("CHEATAH_CR_OS_VAR", "meow")
201io.print(os.getenv("CHEATAH_CR_OS_VAR"))
202)PURR", "meow\n");
205TEST(OsCompileRun, Getenv) {
206 // An unset variable returns the supplied fallback.
207 e2e::expect_e2e("os_getenv", R"PURR(import io
208import os
209io.print(os.getenv("CHEATAH_CR_OS_NONEXISTENT_zzz", "fallback"))
210)PURR", "fallback\n");
213// ---- os: process / system ---------------------------------------------------
215TEST(OsCompileRun, Getpid) {
216 // The pid is non-deterministic; assert it is positive.
217 e2e::expect_e2e("os_getpid", R"PURR(import io
218import os
219io.print(os.getpid() > 0)
220)PURR", "True\n");
223TEST(OsCompileRun, CpuCount) {
224 // The exact count is host-dependent; assert at least one CPU.
225 e2e::expect_e2e("os_cpu_count", R"PURR(import io
226import os
227io.print(os.cpu_count() >= 1)
228)PURR", "True\n");
231TEST(OsCompileRun, System) {
232 // `true` exits 0; the exact value of `false` is shell-dependent, so assert
233 // only that it is non-zero.
234 e2e::expect_e2e("os_system", R"PURR(import io
235import os
236io.print(os.system("true"))
237io.print(os.system("false") != 0)
238)PURR", "0\nTrue\n");
241TEST(OsCompileRun, Urandom) {
242 // urandom returns real entropy (non-deterministic), so assert its PROPERTIES: the
243 // requested length, and that two independent draws don't match.
244 e2e::expect_e2e("os_urandom", R"PURR(import io
245import os
246io.print(len(os.urandom(32)))
247io.print(os.urandom(16) != os.urandom(16))
248)PURR", "32\nTrue\n");
251TEST(OsCompileRun, ModuleExt) {
252 // The loadable-module extension is platform-fixed; assert the exact value for
253 // the platform this test is built on (matches os::module_ext()).
254#if defined(__APPLE__)
255 const char* expected = ".dylib\n";
256#elif defined(_WIN32)
257 const char* expected = ".dll\n";
258#else
259 const char* expected = ".so\n";
260#endif
261 e2e::expect_e2e("os_module_ext", R"PURR(import io
262import os
263io.print(os.module_ext())
264)PURR", expected);