cheatah
Source

tests/purrc/sys_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 `sys` module (suite SysCompileRun) PLUS its
4// per-module system-level test (StdlibE2E.Sys — both live here because sys's
5// whole surface is `sys.argv`). Each test writes a tiny .purr, compiles it with
6// purrc, runs it under the cheatah runtime, and asserts the exact stdout.
7// Complements the in-process unit tests (stdlib/tests/sys_test.cpp).
8//
9// The runtime invocation is what is under test: `cheatah <module> [args…]`
10// forwards `<module>` + the user args through the cheatah_set_argv hook, so
11// sys.argv[0] is the module path and sys.argv[1:] the user arguments (Python's
12// convention). The module path is a build-dir temp path — NON-deterministic —
13// so programs never print argv[0] raw; they assert deterministic PROPERTIES of
14// it (non-empty, ends with ".so") and print only the user arguments, which the
15// tests fully control.
16#include "e2e_harness.hpp"
18namespace {
20// Like e2e::expect_e2e, but appends user arguments to the cheatah invocation —
21// `cheatah <module> <args…>` — so a program can observe sys.argv[1:]. `args` is
22// spliced into the shell command verbatim; callers pass pre-quoted words
23// (e.g. "alpha 'two words' ''").
24void expect_e2e_with_args(const std::string& name, const std::string& src,
25 const std::string& args, const std::string& expected) {
26 const std::string tmp = PURR_TEST_TMP;
27 const std::string purr = tmp + "/" + name + "_e2e.purr";
28 const std::string mod = tmp + "/" + name + "_e2e.so";
29 { std::ofstream f(purr); f << src; }
31 const std::string compile =
32 std::string(PURRC_PATH) + " \"" + purr + "\" -o \"" + mod + "\"";
33 ASSERT_EQ(std::system(compile.c_str()), 0) << name << ": purrc failed to compile the program";
35 int rc = -1;
36 const std::string out = e2e::run_capture(
37 "\"" + std::string(CHEATAH_RUNTIME_PATH) + "\" \"" + mod + "\" " + args, rc);
38 EXPECT_EQ(rc, 0) << name << ": program exited non-zero";
39 EXPECT_EQ(out, expected) << name << ": stdout mismatch";
42} // namespace
44// With no user arguments, sys.argv is exactly [module]: length 1, and argv[0]
45// is the module path the runtime was given (a real path to the loaded .so).
46TEST(SysCompileRun, ArgvNoUserArguments) {
47 e2e::expect_e2e("sys_argv_noargs", R"PURR(import io
48import string
49import sys
50io.print(len(sys.argv))
51io.print(len(sys.argv[0]) > 0, string.endswith(sys.argv[0], ".so"))
52)PURR", "1\nTrue True\n");
55// User arguments land in sys.argv[1:] in order, with argv[0] still the module.
56TEST(SysCompileRun, ArgvForwardsUserArguments) {
57 expect_e2e_with_args("sys_argv_forward", R"PURR(import io
58import sys
59io.print(len(sys.argv))
60io.print(sys.argv[1], sys.argv[2], sys.argv[3])
61)PURR", "alpha beta gamma", "4\nalpha beta gamma\n");
64// Arguments are forwarded VERBATIM: an argument with an embedded space stays one
65// argument, and an empty argument stays present (and empty) rather than dropped.
66TEST(SysCompileRun, ArgvPreservesSpacedAndEmptyArguments) {
67 expect_e2e_with_args("sys_argv_verbatim", R"PURR(import io
68import sys
69io.print(len(sys.argv))
70io.print("[" + sys.argv[1] + "]")
71io.print("[" + sys.argv[2] + "]")
72)PURR", "'two words' ''", "3\n[two words]\n[]\n");
75// sys.argv[1:] is the user-argument list — sliceable and joinable like any list.
76TEST(SysCompileRun, ArgvSliceOfUserArguments) {
77 expect_e2e_with_args("sys_argv_slice", R"PURR(import io
78import string
79import sys
80io.print(string.join("|", sys.argv[1:]))
81)PURR", "a b c", "a|b|c\n");
84// sys.argv is iterable (`for a in sys.argv { … }`) — the loop sees every entry.
85TEST(SysCompileRun, ArgvIsIterable) {
86 expect_e2e_with_args("sys_argv_iter", R"PURR(import io
87import sys
88let n = 0
89for a in sys.argv {
90 n = n + 1
92io.print(n, len(sys.argv))
93io.print(n == len(sys.argv))
94)PURR", "one two", "3 3\nTrue\n");
97// System-level test: one cohesive program exercising the whole sys surface —
98// length, the argv[0] module-path properties, indexing, slicing, and iteration —
99// printing only deterministic values (the user args + boolean properties).
100TEST(StdlibE2E, Sys) {
101 expect_e2e_with_args("sys_sys", R"PURR(import io
102import string
103import sys
105# The runtime forwarded [module, x, y, z].
106io.print(len(sys.argv))
108# argv[0] is the module path: never printed raw (build-dir path), only its properties.
109io.print(len(sys.argv[0]) > 0, string.endswith(sys.argv[0], ".so"))
111# Indexing and slicing follow Python: argv[1:] is exactly the user arguments.
112io.print(sys.argv[1], sys.argv[2], sys.argv[3])
113io.print(string.join(",", sys.argv[1:]))
115# Iteration visits every entry once, in order (skip the path at index 0).
116let seen = 0
117let joined = ""
118for a in sys.argv {
119 if seen > 0 {
120 joined = joined + "/" + a
121 }
122 seen = seen + 1
124io.print(seen, joined)
125)PURR", "x y z",
126 "4\n"
127 "True True\n"
128 "x y z\n"
129 "x,y,z\n"
130 "4 /x/y/z\n");