cheatah
Source

tests/purrc/socket_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 `socket` module: one test per function. Each
4// writes a tiny .purr that calls a single socket 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/socket_test.cpp) and the
7// per-module system-level test (StdlibE2E.Socket).
8//
9// Real networking has no fixed output, so each program asserts DETERMINISTIC
10// PROPERTIES as booleans (io.print emits "True"/"False"): bind to 127.0.0.1
11// port 0 (OS-assigned) so nothing depends on a fixed port or external network,
12// connect to port 1 (nothing listens there) for the refusal paths, and use a
13// bad fd (-1) for the error paths. Functions that block waiting on a peer are
14// exercised only through their non-blocking bad-fd error path.
15#include "e2e_harness.hpp"
17// tcp_listen: binding a listener on 127.0.0.1:0 yields a valid (>= 0) fd.
18TEST(SocketCompileRun, TcpListen) {
19 e2e::expect_e2e("socket_tcp_listen", R"PURR(import io
20import socket
21let fd = socket.tcp_listen("127.0.0.1", 0, 1)
22io.print(fd >= 0)
23socket.close(fd)
24)PURR", "True\n");
27// local_port: after binding to port 0 the OS assigns a real (> 0) port.
28TEST(SocketCompileRun, LocalPort) {
29 e2e::expect_e2e("socket_local_port", R"PURR(import io
30import socket
31let fd = socket.tcp_listen("127.0.0.1", 0, 1)
32io.print(socket.local_port(fd) > 0)
33socket.close(fd)
34)PURR", "True\n");
37// tcp_connect: connecting to port 1 (nothing listening) is refused -> < 0.
38TEST(SocketCompileRun, TcpConnect) {
39 e2e::expect_e2e("socket_tcp_connect", R"PURR(import io
40import socket
41io.print(socket.tcp_connect("127.0.0.1", 1) < 0)
42)PURR", "True\n");
45// close: closing a live fd returns 0; closing a bad fd returns < 0.
46TEST(SocketCompileRun, Close) {
47 e2e::expect_e2e("socket_close", R"PURR(import io
48import socket
49let fd = socket.tcp_listen("127.0.0.1", 0, 1)
50io.print(socket.close(fd) == 0, socket.close(-1) < 0)
51)PURR", "True True\n");
54// socket: creating a raw IPv4 TCP socket yields a valid (>= 0) fd.
55TEST(SocketCompileRun, Socket) {
56 e2e::expect_e2e("socket_socket", R"PURR(import io
57import socket
58let fd = socket.socket()
59io.print(fd >= 0)
60socket.close(fd)
61)PURR", "True\n");
64// set_reuseaddr: enabling SO_REUSEADDR on a fresh socket succeeds (returns 0).
65// udp_socket / sendto / recvfrom: a datagram round trip from purr, the sender's address reported.
66TEST(SocketCompileRun, UdpLoopback) {
67 e2e::expect_e2e("socket_udp_loopback", R"PURR(import io
68import socket
69let rx = socket.udp_socket()
70socket.bind(rx, "127.0.0.1", 0)
71socket.set_timeout(rx, 500)
72let port = socket.local_port(rx)
73let tx = socket.udp_socket()
74let sent = socket.sendto(tx, "127.0.0.1", port, "pose 7")
75let host = ""
76let from_port = 0
77let got = socket.recvfrom(rx, 64, host, from_port)
78io.print(sent == 6 and got == "pose 7" and host == "127.0.0.1" and from_port > 0)
79socket.close(tx)
80socket.close(rx)
81)PURR", "True\n");
84TEST(SocketCompileRun, SetReuseaddr) {
85 e2e::expect_e2e("socket_set_reuseaddr", R"PURR(import io
86import socket
87let fd = socket.socket()
88io.print(socket.set_reuseaddr(fd) == 0)
89socket.close(fd)
90)PURR", "True\n");
93// bind: binding a socket to 127.0.0.1:0 succeeds (returns 0).
94TEST(SocketCompileRun, Bind) {
95 e2e::expect_e2e("socket_bind", R"PURR(import io
96import socket
97let fd = socket.socket()
98io.print(socket.bind(fd, "127.0.0.1", 0) == 0)
99socket.close(fd)
100)PURR", "True\n");
103// listen: marking a bound socket passive returns 0; listen on a bad fd < 0.
104TEST(SocketCompileRun, Listen) {
105 e2e::expect_e2e("socket_listen", R"PURR(import io
106import socket
107let fd = socket.socket()
108socket.bind(fd, "127.0.0.1", 0)
109io.print(socket.listen(fd, 1) == 0, socket.listen(-1, 1) < 0)
110socket.close(fd)
111)PURR", "True True\n");
114// connect: connecting to port 1 (nothing listening) is refused -> < 0.
115TEST(SocketCompileRun, Connect) {
116 e2e::expect_e2e("socket_connect", R"PURR(import io
117import socket
118let fd = socket.socket()
119io.print(socket.connect(fd, "127.0.0.1", 1) < 0)
120socket.close(fd)
121)PURR", "True\n");
124// last_error: after a failed call, last_error() reports a non-empty message.
125TEST(SocketCompileRun, LastError) {
126 e2e::expect_e2e("socket_last_error", R"PURR(import io
127import socket
128let r = socket.connect(-1, "127.0.0.1", 1)
129io.print(r < 0, socket.last_error() != "")
130)PURR", "True True\n");
133// send: a single send() on a bad fd returns < 0 (error path).
134TEST(SocketCompileRun, Send) {
135 e2e::expect_e2e("socket_send", R"PURR(import io
136import socket
137io.print(socket.send(-1, "x") < 0)
138)PURR", "True\n");
141// sendall: looping send on a bad fd returns < 0 (error path).
142TEST(SocketCompileRun, Sendall) {
143 e2e::expect_e2e("socket_sendall", R"PURR(import io
144import socket
145io.print(socket.sendall(-1, "x") < 0)
146)PURR", "True\n");
149// recv: a real recv() blocks until a peer sends, so it can't be made
150// deterministic standalone. Exercise only the non-blocking error paths: a bad
151// fd and a non-positive bufsize both return "" without touching the socket.
152TEST(SocketCompileRun, Recv) {
153 e2e::expect_e2e("socket_recv", R"PURR(import io
154import socket
155io.print(socket.recv(-1, 64) == "", socket.recv(5, 0) == "")
156)PURR", "True True\n");
159// accept: a real accept() blocks until a client connects, so it can't be made
160// deterministic standalone. Exercise only the non-blocking error path: accept
161// on a bad fd returns < 0 without blocking.
162TEST(SocketCompileRun, Accept) {
163 e2e::expect_e2e("socket_accept", R"PURR(import io
164import socket
165io.print(socket.accept(-1) < 0)
166)PURR", "True\n");