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 writes4
// a tiny .purr that calls a single os (or os.path) function, compiles it with5
// 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 the7
// per-module system-level test (StdlibE2E.Os).8
//9
// Programs must be DETERMINISTIC. Purely-lexical os.path helpers print their10
// result directly. Filesystem functions operate under a UNIQUE /tmp directory11
// created inside the .purr and print a deterministic property (e.g. mkdir then12
// os.path.isdir -> True). Non-deterministic functions (getpid, getcwd,13
// cpu_count, system exit status, getenv of a process-set var) assert a14
// deterministic property instead of the raw value.15
//16
// SKIPPED: none — every os/os.path function below is covered. The only thing17
// that cannot be printed directly is os.path.splitext (returns a std::pair, not18
// 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) -------23
TEST(OsCompileRun, PathJoin) {24
e2e::expect_e2e("os_path_join", R"PURR(import io25
import os26
io.print(os.path.join("a", "b", "c"))27
)PURR", "a/b/c\n");28
}30
TEST(OsCompileRun, PathBasename) {31
e2e::expect_e2e("os_path_basename", R"PURR(import io32
import os33
io.print(os.path.basename("/a/b/c.txt"))34
)PURR", "c.txt\n");35
}37
TEST(OsCompileRun, PathDirname) {38
e2e::expect_e2e("os_path_dirname", R"PURR(import io39
import os40
io.print(os.path.dirname("/a/b/c.txt"))41
)PURR", "/a/b\n");42
}44
TEST(OsCompileRun, PathNormpath) {45
e2e::expect_e2e("os_path_normpath", R"PURR(import io46
import os47
io.print(os.path.normpath("a/./b/../c"))48
)PURR", "a/c\n");49
}51
TEST(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 io54
import os55
io.print(os.path.splitext("dir/file.purr").first, os.path.splitext("dir/file.purr").second)56
)PURR", "dir/file .purr\n");57
}59
TEST(OsCompileRun, PathAbspath) {60
// An already-absolute path is returned unchanged: deterministic.61
e2e::expect_e2e("os_path_abspath", R"PURR(import io62
import os63
io.print(os.path.abspath("/already/abs"))64
)PURR", "/already/abs\n");65
}67
// ---- os.path: filesystem queries (deterministic against fixed paths) --------69
TEST(OsCompileRun, PathExists) {70
e2e::expect_e2e("os_path_exists", R"PURR(import io71
import os72
io.print(os.path.exists("/tmp"))73
)PURR", "True\n");74
}76
TEST(OsCompileRun, PathIsdir) {77
e2e::expect_e2e("os_path_isdir", R"PURR(import io78
import os79
io.print(os.path.isdir("/tmp"))80
)PURR", "True\n");81
}83
TEST(OsCompileRun, PathIsfile) {84
// /tmp is a directory, not a regular file.85
e2e::expect_e2e("os_path_isfile", R"PURR(import io86
import os87
io.print(os.path.isfile("/tmp"))88
)PURR", "False\n");89
}91
TEST(OsCompileRun, PathGetsize) {92
// Create a 5-byte file, then query its size.93
e2e::expect_e2e("os_path_getsize", R"PURR(import io94
import os95
os.mkdir("/tmp/cr_os_getsize_dir")96
os.system("printf 12345 > /tmp/cr_os_getsize_dir/f.txt")97
io.print(os.path.getsize("/tmp/cr_os_getsize_dir/f.txt"))98
os.remove("/tmp/cr_os_getsize_dir/f.txt")99
os.rmdir("/tmp/cr_os_getsize_dir")100
)PURR", "5\n");101
}103
// ---- os: directory creation / removal ---------------------------------------105
TEST(OsCompileRun, Mkdir) {106
e2e::expect_e2e("os_mkdir", R"PURR(import io107
import os108
os.mkdir("/tmp/cr_os_mkdir_dir")109
io.print(os.path.isdir("/tmp/cr_os_mkdir_dir"))110
os.rmdir("/tmp/cr_os_mkdir_dir")111
)PURR", "True\n");112
}114
TEST(OsCompileRun, Makedirs) {115
e2e::expect_e2e("os_makedirs", R"PURR(import io116
import os117
os.makedirs("/tmp/cr_os_makedirs_dir/a/b/c")118
io.print(os.path.isdir("/tmp/cr_os_makedirs_dir/a/b/c"))119
os.rmdir("/tmp/cr_os_makedirs_dir/a/b/c")120
os.rmdir("/tmp/cr_os_makedirs_dir/a/b")121
os.rmdir("/tmp/cr_os_makedirs_dir/a")122
os.rmdir("/tmp/cr_os_makedirs_dir")123
)PURR", "True\n");124
}126
TEST(OsCompileRun, Rmdir) {127
e2e::expect_e2e("os_rmdir", R"PURR(import io128
import os129
os.mkdir("/tmp/cr_os_rmdir_dir")130
os.rmdir("/tmp/cr_os_rmdir_dir")131
io.print(os.path.exists("/tmp/cr_os_rmdir_dir"))132
)PURR", "False\n");133
}135
// ---- os: file operations ----------------------------------------------------137
TEST(OsCompileRun, Remove) {138
// remove() returns true when something was deleted.139
e2e::expect_e2e("os_remove", R"PURR(import io140
import os141
os.mkdir("/tmp/cr_os_remove_dir")142
os.system("printf x > /tmp/cr_os_remove_dir/f.txt")143
io.print(os.remove("/tmp/cr_os_remove_dir/f.txt"))144
os.rmdir("/tmp/cr_os_remove_dir")145
)PURR", "True\n");146
}148
TEST(OsCompileRun, Rename) {149
e2e::expect_e2e("os_rename", R"PURR(import io150
import os151
os.mkdir("/tmp/cr_os_rename_dir")152
os.system("printf x > /tmp/cr_os_rename_dir/one.txt")153
os.rename("/tmp/cr_os_rename_dir/one.txt", "/tmp/cr_os_rename_dir/two.txt")154
io.print(os.path.isfile("/tmp/cr_os_rename_dir/two.txt"))155
os.remove("/tmp/cr_os_rename_dir/two.txt")156
os.rmdir("/tmp/cr_os_rename_dir")157
)PURR", "True\n");158
}160
TEST(OsCompileRun, Listdir) {161
// A directory with a single known entry: listdir returns basenames.162
e2e::expect_e2e("os_listdir", R"PURR(import io163
import os164
os.mkdir("/tmp/cr_os_listdir_dir")165
os.system("printf x > /tmp/cr_os_listdir_dir/only.txt")166
io.print(os.listdir("/tmp/cr_os_listdir_dir")[0])167
os.remove("/tmp/cr_os_listdir_dir/only.txt")168
os.rmdir("/tmp/cr_os_listdir_dir")169
)PURR", "only.txt\n");170
}172
// ---- os: working directory --------------------------------------------------174
TEST(OsCompileRun, Getcwd) {175
// The cwd is non-deterministic; assert it is a non-empty path.176
e2e::expect_e2e("os_getcwd", R"PURR(import io177
import os178
io.print(len(os.getcwd()) > 0)179
)PURR", "True\n");180
}182
TEST(OsCompileRun, Chdir) {183
// chdir into a unique temp dir, then confirm getcwd's basename matches.184
e2e::expect_e2e("os_chdir", R"PURR(import io185
import os186
os.makedirs("/tmp/cr_os_chdir_dir")187
os.chdir("/tmp/cr_os_chdir_dir")188
io.print(os.path.basename(os.getcwd()))189
os.chdir("/tmp")190
os.rmdir("/tmp/cr_os_chdir_dir")191
)PURR", "cr_os_chdir_dir\n");192
}194
// ---- os: environment --------------------------------------------------------196
TEST(OsCompileRun, Setenv) {197
// setenv then getenv round-trips within the process.198
e2e::expect_e2e("os_setenv", R"PURR(import io199
import os200
os.setenv("CHEATAH_CR_OS_VAR", "meow")201
io.print(os.getenv("CHEATAH_CR_OS_VAR"))202
)PURR", "meow\n");203
}205
TEST(OsCompileRun, Getenv) {206
// An unset variable returns the supplied fallback.207
e2e::expect_e2e("os_getenv", R"PURR(import io208
import os209
io.print(os.getenv("CHEATAH_CR_OS_NONEXISTENT_zzz", "fallback"))210
)PURR", "fallback\n");211
}213
// ---- os: process / system ---------------------------------------------------215
TEST(OsCompileRun, Getpid) {216
// The pid is non-deterministic; assert it is positive.217
e2e::expect_e2e("os_getpid", R"PURR(import io218
import os219
io.print(os.getpid() > 0)220
)PURR", "True\n");221
}223
TEST(OsCompileRun, CpuCount) {224
// The exact count is host-dependent; assert at least one CPU.225
e2e::expect_e2e("os_cpu_count", R"PURR(import io226
import os227
io.print(os.cpu_count() >= 1)228
)PURR", "True\n");229
}231
TEST(OsCompileRun, System) {232
// `true` exits 0; the exact value of `false` is shell-dependent, so assert233
// only that it is non-zero.234
e2e::expect_e2e("os_system", R"PURR(import io235
import os236
io.print(os.system("true"))237
io.print(os.system("false") != 0)238
)PURR", "0\nTrue\n");239
}241
TEST(OsCompileRun, Urandom) {242
// urandom returns real entropy (non-deterministic), so assert its PROPERTIES: the243
// requested length, and that two independent draws don't match.244
e2e::expect_e2e("os_urandom", R"PURR(import io245
import os246
io.print(len(os.urandom(32)))247
io.print(os.urandom(16) != os.urandom(16))248
)PURR", "32\nTrue\n");249
}251
TEST(OsCompileRun, ModuleExt) {252
// The loadable-module extension is platform-fixed; assert the exact value for253
// 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
#else259
const char* expected = ".so\n";260
#endif261
e2e::expect_e2e("os_module_ext", R"PURR(import io262
import os263
io.print(os.module_ext())264
)PURR", expected);265
}