cheatah
Source

stdlib/tests/io_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#include "io.hpp"
5#include <complex>
6#include <filesystem>
7#include <iostream>
8#include <sstream>
9#include <string>
10#include <unordered_map>
11#include <vector>
13#include <gtest/gtest.h>
15namespace io = cheatah::io;
17TEST(CheatahIo, StrRendersComplex) {
18 using C = std::complex<double>;
19 EXPECT_EQ(io::str(C(8, 3)), "8+3j"); // positive imaginary
20 EXPECT_EQ(io::str(C(1, -2)), "1-2j"); // negative imaginary
21 EXPECT_EQ(io::str(C(1, 0)), "1+0j"); // +0 (not "-0") after a conjugate
22 EXPECT_EQ(io::str(std::conj(C(1, 0))), "1+0j");
23 // repr matches str (numbers are not quoted), and a list of complex prints readably.
24 EXPECT_EQ(io::repr(C(2, -5)), "2-5j");
25 EXPECT_EQ(io::str(std::vector<C>{C(0, 1), C(2, 0)}), "[0+1j, 2+0j]");
28TEST(CheatahIo, StrByteWidthIntsAreNumbers) {
29 // i8/u8 (signed char / unsigned char) print as NUMBERS, not characters — mirrors
30 // builtins::str, so print/repr of a narrow-width value shows digits. See io.hpp str(signed char).
31 EXPECT_EQ(io::str(static_cast<signed char>(65)), "65");
32 EXPECT_EQ(io::str(static_cast<signed char>(-5)), "-5");
33 EXPECT_EQ(io::str(static_cast<unsigned char>(200)), "200");
34 EXPECT_EQ(io::str(static_cast<unsigned char>(0)), "0");
37// The io concept must accept exactly what the templates already take (streamable
38// types) and reject the rest, so a bad call gives a clear diagnostic.
39static_assert(io::Streamable<int>);
40static_assert(io::Streamable<std::string>);
41static_assert(io::Streamable<const char*>);
42static_assert(!io::Streamable<std::vector<int>>);
44// print() requires Printable, NOT Streamable: a value is printable if it streams
45// directly, has a `str()`, or is a list/dict of printable elements.
46namespace {
47struct WithStr { std::string str() const { return "W"; } };
48struct NotPrintable { int z; }; // not streamable, no str()
49} // namespace
50static_assert(io::Printable<int>);
51static_assert(io::Printable<std::vector<int>>);
52static_assert(io::Printable<std::vector<std::vector<std::string>>>);
53static_assert(io::Printable<std::unordered_map<std::string, int>>);
54static_assert(io::Printable<WithStr>);
55static_assert(!io::Printable<NotPrintable>); // no stream op, no str()
56static_assert(!io::Printable<std::vector<NotPrintable>>); // element not printable
58TEST(CheatahIo, PrintWritesSpaceSeparatedLine) {
59 std::ostringstream cap;
60 std::streambuf* old = std::cout.rdbuf(cap.rdbuf());
61 io::print("meow", 42, "purr");
62 std::cout.rdbuf(old);
63 EXPECT_EQ(cap.str(), "meow 42 purr\n");
66TEST(CheatahIo, PrintNoArgsIsJustNewline) {
67 std::ostringstream cap;
68 std::streambuf* old = std::cout.rdbuf(cap.rdbuf());
69 io::print();
70 std::cout.rdbuf(old);
71 EXPECT_EQ(cap.str(), "\n");
74namespace {
75// A stand-in for a cheatah struct: it both streams (operator<<, the compact form rprint/str
76// use) and exposes a cheatah_pretty_print member (the pretty form print uses).
77struct PrettyStub {
78 friend std::ostream& operator<<(std::ostream& os, const PrettyStub&) { return os << "P(compact)"; }
79 void cheatah_pretty_print(std::ostream& os, long long indent) const {
80 os << std::string(static_cast<std::size_t>(indent), ' ') << "P(\n x = 1\n)";
81 }
82};
83} // namespace
85TEST(CheatahIo, PrintPrettyPrintsStructs) {
86 std::ostringstream cap;
87 std::streambuf* old = std::cout.rdbuf(cap.rdbuf());
88 io::print(PrettyStub{}); // has cheatah_pretty_print -> pretty branch
89 std::cout.rdbuf(old);
90 EXPECT_EQ(cap.str(), "P(\n x = 1\n)\n");
93TEST(CheatahIo, RprintIsCompact) {
94 std::ostringstream cap;
95 std::streambuf* old = std::cout.rdbuf(cap.rdbuf());
96 io::rprint(PrettyStub{}, 7); // compact (operator<<) + a scalar, space-separated
97 std::cout.rdbuf(old);
98 EXPECT_EQ(cap.str(), "P(compact) 7\n");
101TEST(CheatahIo, StrRendersContainersAndObjects) {
102 EXPECT_EQ(io::str(std::vector<long long>{1, 2, 3}), "[1, 2, 3]");
103 EXPECT_EQ(io::str(std::vector<std::string>{"a", "b"}), "['a', 'b']"); // nested strings quoted
104 EXPECT_EQ(io::str(std::vector<std::vector<long long>>{{1, 2}, {3, 4}}), "[[1, 2], [3, 4]]");
105 std::unordered_map<std::string, long long> m{{"a", 1}};
106 EXPECT_EQ(io::str(m), "{'a': 1}");
107 EXPECT_EQ(io::str(WithStr{}), "W"); // a type with a str() method
108 EXPECT_EQ(io::str(std::vector<WithStr>{{}, {}}), "[W, W]");
111TEST(CheatahIo, StrFormatsPythonStyle) {
112 EXPECT_EQ(io::str(42), "42");
113 EXPECT_EQ(io::str(true), "True");
114 EXPECT_EQ(io::str(false), "False");
115 EXPECT_EQ(io::str(std::string("meow")), "meow");
118TEST(CheatahIo, FixedFormatsAndRounds) {
119 EXPECT_EQ(io::fixed(3.14159, 2), "3.14");
120 EXPECT_EQ(io::fixed(2.675, 2), "2.67"); // binary 2.675 is 2.67499…: correct rounding
121 EXPECT_EQ(io::fixed(12.0, 1), "12.0");
122 EXPECT_EQ(io::fixed(0.5, 0), "0"); // half-to-even at the integer boundary
123 EXPECT_EQ(io::fixed(1.5, 0), "2");
124 EXPECT_EQ(io::fixed(-1.25, 1), "-1.2");
125 EXPECT_EQ(io::fixed(7.0, -3), "7"); // negative places clamps to 0
126 EXPECT_EQ(io::fixed(0.1, 17), "0.10000000000000001"); // places capped at 17
129TEST(CheatahIo, ReprQuotesStrings) {
130 EXPECT_EQ(io::repr(std::string("meow")), "'meow'");
131 EXPECT_EQ(io::repr("purr"), "'purr'");
132 EXPECT_EQ(io::repr(42), "42");
135TEST(CheatahIo, FormatSubstitutesBraces) {
136 EXPECT_EQ(io::format("{} ate {} fish", "cat", 3), "cat ate 3 fish");
137 EXPECT_EQ(io::format("no slots here", 1, 2), "no slots here");
140TEST(CheatahIo, FileWriteThenReadWhole) {
141 const std::string path = "purr_io_whole_tmp.txt";
142 {
143 io::File f = io::open(path, "w");
144 f.write("meow\npurr\n");
145 }
146 {
147 io::File f = io::open(path, "r");
148 EXPECT_EQ(f.read(), "meow\npurr\n");
149 }
150 std::filesystem::remove(path);
153TEST(CheatahIo, FileReadlineThenReadlines) {
154 const std::string path = "purr_io_lines_tmp.txt";
155 {
156 io::File f = io::open(path, "w");
157 f.write("meow\npurr\nnap\n");
158 }
159 {
160 io::File f = io::open(path, "r");
161 EXPECT_EQ(f.readline(), "meow");
162 const std::vector<std::string> rest = f.readlines();
163 ASSERT_EQ(rest.size(), 2u);
164 EXPECT_EQ(rest[0], "purr");
165 EXPECT_EQ(rest[1], "nap");
166 }
167 std::filesystem::remove(path);
170TEST(CheatahIo, FileAppendMode) {
171 const std::string path = "purr_io_append_tmp.txt";
172 {
173 io::File f = io::open(path, "w");
174 f.write("meow\n");
175 }
176 {
177 io::File f = io::open(path, "a");
178 f.write("purr\n");
179 }
180 {
181 io::File f = io::open(path, "r");
182 EXPECT_EQ(f.read(), "meow\npurr\n");
183 }
184 std::filesystem::remove(path);
187TEST(CheatahIo, FileIsOpenAndClose) {
188 const std::string path = "purr_io_isopen_tmp.txt";
189 io::File f = io::open(path, "w");
190 EXPECT_TRUE(f.is_open());
191 f.write("x");
192 f.close();
193 EXPECT_FALSE(f.is_open());
194 std::filesystem::remove(path);
197TEST(CheatahIo, InputReadsALine) {
198 std::istringstream fake("hello world\nsecond\n");
199 std::streambuf* saved = std::cin.rdbuf(fake.rdbuf()); // feed stdin
200 EXPECT_EQ(io::input("prompt> "), "hello world");
201 EXPECT_EQ(io::input(), "second");
202 std::cin.rdbuf(saved); // restore
205TEST(CheatahIo, FormatMultiArgAndExtraArgs) {
206 EXPECT_EQ(io::format("{}-{}", 1, 2), "1-2"); // recursion across placeholders
207 EXPECT_EQ(io::format("{}", 1, 2, 3), "1"); // more args than placeholders → dropped
208 EXPECT_EQ(io::format("none", 7), "none"); // no placeholders at all
211TEST(CheatahIo, ReadFileWholeAndBinary) {
212 const std::string path = "purr_io_readfile_tmp.bin";
213 std::string payload = "meow\npurr\n";
214 payload.push_back('\0'); // embedded NUL — read_file must preserve it
215 payload += "after-nul";
216 {
217 io::File f = io::open(path, "wb");
218 f.write(payload);
219 }
220 EXPECT_EQ(io::read_file(path), payload); // whole file, byte-exact
221 EXPECT_EQ(io::read_file("no_such_file_qzx.bin"), ""); // missing → ""
222 std::filesystem::remove(path);