cheatah
Source

tests/purrc/compile_run_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// End-to-end pipeline test: compile a .purr with purrc into a loadable module,
4// then run it with the cheatah runtime and check what it prints.
6#include <array>
7#include <cstdio>
8#include <cstdlib>
9#include <fstream>
10#include <string>
12#include <sys/wait.h>
14#include <gtest/gtest.h>
16#ifndef PURRC_PATH
17#define PURRC_PATH ""
18#endif
19#ifndef CHEATAH_RUNTIME_PATH
20#define CHEATAH_RUNTIME_PATH ""
21#endif
22#ifndef PURR_TEST_TMP
23#define PURR_TEST_TMP "."
24#endif
26namespace {
28std::string run_capture(const std::string& cmd, int& exit_code) {
29 std::string out;
30 FILE* pipe = popen(cmd.c_str(), "r");
31 if (pipe == nullptr) {
32 exit_code = -1;
33 return out;
34 }
35 std::array<char, 256> buf{};
36 while (std::fgets(buf.data(), static_cast<int>(buf.size()), pipe) != nullptr) {
37 out += buf.data();
38 }
39 const int status = pclose(pipe);
40 exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
41 return out;
44} // namespace
46TEST(PurrcPipeline, CompilesAndRunsHelloMeow) {
47 const std::string tmp = PURR_TEST_TMP;
48 const std::string purr = tmp + "/hellomeow_it.purr";
49 const std::string mod = tmp + "/hellomeow_it.so";
51 {
52 std::ofstream f(purr);
53 f << "import io\n";
54 f << "io.print(\"meow\")\n";
55 }
57 // 1) Compile with purrc.
58 const std::string compile =
59 std::string(PURRC_PATH) + " \"" + purr + "\" -o \"" + mod + "\"";
60 ASSERT_EQ(std::system(compile.c_str()), 0) << "purrc failed to compile the program";
62 // 2) Run it with the cheatah runtime and capture stdout.
63 int rc = -1;
64 const std::string out = run_capture(std::string(CHEATAH_RUNTIME_PATH) + " \"" + mod + "\"", rc);
65 EXPECT_EQ(rc, 0);
66 EXPECT_EQ(out, "meow\n");
69TEST(PurrcPipeline, CompilesAndRunsStructsFunctionsAndLoops) {
70 const std::string tmp = PURR_TEST_TMP;
71 const std::string purr = tmp + "/lang_it.purr";
72 const std::string mod = tmp + "/lang_it.so";
74 {
75 std::ofstream f(purr);
76 f << "import io\n";
77 f << "struct P { x: int\n y: int }\n";
78 f << "fn add(a, b) { return a + b }\n";
79 f << "let total = 0\n";
80 f << "for i in range(1, 5) { total = total + i }\n"; // 1+2+3+4 = 10
81 f << "let p = P(2, 3)\n";
82 f << "io.print(total, add(p.x, p.y))\n"; // "10 5"
83 }
85 const std::string compile =
86 std::string(PURRC_PATH) + " \"" + purr + "\" -o \"" + mod + "\"";
87 ASSERT_EQ(std::system(compile.c_str()), 0) << "purrc failed to compile the program";
89 int rc = -1;
90 const std::string out = run_capture(std::string(CHEATAH_RUNTIME_PATH) + " \"" + mod + "\"", rc);
91 EXPECT_EQ(rc, 0);
92 EXPECT_EQ(out, "10 5\n");
95TEST(PurrcPipeline, CompilesAndRunsContainersAndStringConcat) {
96 const std::string tmp = PURR_TEST_TMP;
97 const std::string purr = tmp + "/cont_it.purr";
98 const std::string mod = tmp + "/cont_it.so";
100 {
101 std::ofstream f(purr);
102 f << "import io\n";
103 f << "let xs = [2, 3, 5]\n";
104 f << "let sum = 0\n";
105 f << "for x in xs { sum = sum + x }\n"; // 2+3+5 = 10
106 f << "let m = {\"k\": 7}\n";
107 f << "let name = \"hello\" + \"-world\"\n"; // string concat
108 f << "io.print(len(xs), sum, xs[0], m[\"k\"], name)\n";
109 }
111 const std::string compile =
112 std::string(PURRC_PATH) + " \"" + purr + "\" -o \"" + mod + "\"";
113 ASSERT_EQ(std::system(compile.c_str()), 0) << "purrc failed to compile the program";
115 int rc = -1;
116 const std::string out = run_capture(std::string(CHEATAH_RUNTIME_PATH) + " \"" + mod + "\"", rc);
117 EXPECT_EQ(rc, 0);
118 EXPECT_EQ(out, "3 10 2 7 hello-world\n");
121TEST(PurrcPipeline, CompilesAndRunsTryExceptRaise) {
122 const std::string tmp = PURR_TEST_TMP;
123 const std::string purr = tmp + "/exc_it.purr";
124 const std::string mod = tmp + "/exc_it.so";
126 {
127 std::ofstream f(purr);
128 f << "import io\n";
129 f << "try {\n";
130 f << " raise \"boom\"\n";
131 f << " io.print(\"unreachable\")\n";
132 f << "} except e {\n";
133 f << " io.print(\"caught:\", e)\n";
134 f << "}\n";
135 f << "io.print(\"after\")\n";
136 }
138 const std::string compile =
139 std::string(PURRC_PATH) + " \"" + purr + "\" -o \"" + mod + "\"";
140 ASSERT_EQ(std::system(compile.c_str()), 0) << "purrc failed to compile the program";
142 int rc = -1;
143 const std::string out = run_capture(std::string(CHEATAH_RUNTIME_PATH) + " \"" + mod + "\"", rc);
144 EXPECT_EQ(rc, 0);
145 EXPECT_EQ(out, "caught: boom\nafter\n");
148TEST(PurrcPipeline, CompilesAndRunsSemicolons) {
149 const std::string tmp = PURR_TEST_TMP;
150 const std::string purr = tmp + "/semi_it.purr";
151 const std::string mod = tmp + "/semi_it.so";
153 {
154 std::ofstream f(purr);
155 f << "import io\n";
156 f << "let a = 1; let b = 2;\n"; // separator + terminator on one line
157 f << "fn add(x, y) { return x + y; }\n"; // ; inside a function body
158 f << "struct P { x: int; y: int }\n"; // ; as struct field separator
159 f << "let p = P(3, 4)\n";
160 f << "io.print(a, b, add(p.x, p.y));\n"; // 1 2 7
161 }
163 const std::string compile =
164 std::string(PURRC_PATH) + " \"" + purr + "\" -o \"" + mod + "\"";
165 ASSERT_EQ(std::system(compile.c_str()), 0) << "purrc failed to compile the program";
167 int rc = -1;
168 const std::string out = run_capture(std::string(CHEATAH_RUNTIME_PATH) + " \"" + mod + "\"", rc);
169 EXPECT_EQ(rc, 0);
170 EXPECT_EQ(out, "1 2 7\n");
173TEST(PurrcPipeline, CompilesAndRunsCppEscapeHatch) {
174 const std::string tmp = PURR_TEST_TMP;
175 const std::string purr = tmp + "/cpp_it.purr";
176 const std::string mod = tmp + "/cpp_it.so";
178 {
179 std::ofstream f(purr);
180 f << "import io\n";
181 f << "cpp {\n"; // top-level -> file scope
182 f << "static long long triple(long long n) { return n * 3; }\n";
183 f << "}\n";
184 f << "fn demo() {\n";
185 f << " let acc = 0\n";
186 f << " cpp {\n"; // nested -> inline
187 f << " for (int i = 1; i <= 4; ++i) { acc += i; }\n"; // 1+2+3+4 = 10
188 f << " }\n";
189 f << " return acc + triple(2)\n"; // 10 + 6 = 16
190 f << "}\n";
191 f << "io.print(\"cpp escape hatch:\", demo())\n";
192 }
194 const std::string compile =
195 std::string(PURRC_PATH) + " \"" + purr + "\" -o \"" + mod + "\"";
196 ASSERT_EQ(std::system(compile.c_str()), 0) << "purrc failed to compile the program";
198 int rc = -1;
199 const std::string out = run_capture(std::string(CHEATAH_RUNTIME_PATH) + " \"" + mod + "\"", rc);
200 EXPECT_EQ(rc, 0);
201 EXPECT_EQ(out, "cpp escape hatch: 16\n");