Skip to content

Commit 6ef29db

Browse files
committed
feat: cpp tests
1 parent d2d1329 commit 6ef29db

1 file changed

Lines changed: 246 additions & 0 deletions

File tree

tests/cpp/test.cpp

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/**
2+
* Test file for the C++ StreamingJsonParser
3+
*
4+
* Compiles with:
5+
* g++ -std=c++17 test_json_parser.cpp -o test_json_parser -I/path/to/pybind11/include jsonparser.cpp
6+
*/
7+
8+
#include "jsonparser.h"
9+
10+
#include <iostream>
11+
#include <string>
12+
#include <cassert>
13+
#include <functional>
14+
#include <map>
15+
16+
class TestSuite {
17+
public:
18+
void add_test(const std::string& name, std::function<void()> test) {
19+
tests[name] = test;
20+
}
21+
22+
void run_all() {
23+
int passed = 0;
24+
int failed = 0;
25+
26+
for (const auto& [name, test] : tests) {
27+
try {
28+
test();
29+
std::cout << "[PASS] " << name << std::endl;
30+
passed++;
31+
} catch (const std::exception& e) {
32+
std::cout << "[FAIL] " << name << ": " << e.what() << std::endl;
33+
failed++;
34+
}
35+
}
36+
37+
std::cout << "\nResults: " << passed << " passed, " << failed << " failed" << std::endl;
38+
}
39+
40+
private:
41+
std::map<std::string, std::function<void()>> tests;
42+
};
43+
44+
// Check if two py::dict objects are equal
45+
bool dicts_equal(const py::dict& a, const py::dict& b) {
46+
if (a.size() != b.size()) return false;
47+
48+
for (auto item : a) {
49+
std::string key = py::str(item.first);
50+
if (!b.contains(item.first)) return false;
51+
52+
py::object a_val = item.second;
53+
py::object b_val = b[item.first];
54+
55+
// Check if the values are dictionaries
56+
if (py::isinstance<py::dict>(a_val) && py::isinstance<py::dict>(b_val)) {
57+
if (!dicts_equal(a_val.cast<py::dict>(), b_val.cast<py::dict>())) return false;
58+
}
59+
// Check if the values are strings
60+
else if (py::isinstance<py::str>(a_val) && py::isinstance<py::str>(b_val)) {
61+
if (std::string(py::str(a_val)) != std::string(py::str(b_val))) return false;
62+
}
63+
else {
64+
return false;
65+
}
66+
}
67+
return true;
68+
}
69+
70+
// Create expected dictionaries using pybind11
71+
py::dict create_expected_dict(const std::map<std::string, py::object>& items) {
72+
py::dict result;
73+
for (const auto& [key, value] : items) {
74+
result[py::str(key)] = value;
75+
}
76+
return result;
77+
}
78+
79+
py::dict create_nested_dict(const std::map<std::string, py::object>& items) {
80+
return create_expected_dict(items);
81+
}
82+
83+
int main() {
84+
TestSuite suite;
85+
86+
// Basic parsing test
87+
suite.add_test("test_basic_json", []() {
88+
StreamingJsonParser parser;
89+
parser.consume("{\"foo\": \"bar\"}");
90+
91+
py::dict expected;
92+
expected["foo"] = py::str("bar");
93+
94+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected));
95+
});
96+
97+
// Test parsing in chunks
98+
suite.add_test("test_chunked_parsing", []() {
99+
StreamingJsonParser parser;
100+
parser.consume("{\"foo\":");
101+
parser.consume("\"bar\"}");
102+
103+
py::dict expected;
104+
expected["foo"] = py::str("bar");
105+
106+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected));
107+
});
108+
109+
// Test character-by-character parsing
110+
suite.add_test("test_char_by_char_parsing", []() {
111+
StreamingJsonParser parser;
112+
std::string json = "{\"foo\": \"bar\"}";
113+
for (char c : json) {
114+
parser.consume(std::string(1, c));
115+
}
116+
117+
py::dict expected;
118+
expected["foo"] = py::str("bar");
119+
120+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected));
121+
});
122+
123+
// Test empty object
124+
suite.add_test("test_empty_object", []() {
125+
StreamingJsonParser parser;
126+
parser.consume("{}");
127+
128+
py::dict expected;
129+
130+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected));
131+
});
132+
133+
// Test partial string value
134+
suite.add_test("test_partial_string", []() {
135+
StreamingJsonParser parser;
136+
parser.consume("{\"foo\": \"partial");
137+
138+
py::dict expected;
139+
expected["foo"] = py::str("partial");
140+
141+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected));
142+
});
143+
144+
// Test nested objects
145+
suite.add_test("test_nested_objects", []() {
146+
StreamingJsonParser parser;
147+
parser.consume("{\"foo\": {\"bar\":\"value\"}}");
148+
149+
py::dict inner;
150+
inner["bar"] = py::str("value");
151+
152+
py::dict expected;
153+
expected["foo"] = inner;
154+
155+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected));
156+
});
157+
158+
// Test partial nested objects
159+
suite.add_test("test_partial_nested", []() {
160+
StreamingJsonParser parser;
161+
parser.consume("{\"foo\": {\"bar\":\"");
162+
163+
py::dict inner;
164+
inner["bar"] = py::str("");
165+
166+
py::dict expected;
167+
expected["foo"] = inner;
168+
169+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected));
170+
});
171+
172+
// Test multiple key-value pairs
173+
suite.add_test("test_multiple_keys", []() {
174+
StreamingJsonParser parser;
175+
parser.consume("{\"key1\": \"value1\", \"key2\": \"value2\"}");
176+
177+
py::dict expected;
178+
expected["key1"] = py::str("value1");
179+
expected["key2"] = py::str("value2");
180+
181+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected));
182+
});
183+
184+
// Test deep nesting
185+
suite.add_test("test_deep_nesting", []() {
186+
StreamingJsonParser parser;
187+
parser.consume("{\"level1\": {\"level2\": {\"level3\": \"deep value\"}}}");
188+
189+
py::dict level3;
190+
level3["level3"] = py::str("deep value");
191+
192+
py::dict level2;
193+
level2["level2"] = level3;
194+
195+
py::dict expected;
196+
expected["level1"] = level2;
197+
198+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected));
199+
});
200+
201+
// Test complex incremental parsing
202+
suite.add_test("test_complex_incremental", []() {
203+
StreamingJsonParser parser;
204+
205+
// Start with empty object
206+
parser.consume("{");
207+
py::dict expected1;
208+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected1));
209+
210+
// Add first key and start nested object
211+
parser.consume("\"outer1\": {");
212+
py::dict inner1;
213+
py::dict expected2;
214+
expected2["outer1"] = inner1;
215+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected2));
216+
217+
// Add key-value inside first nested object
218+
parser.consume("\"inner1\": \"value1\"");
219+
py::dict inner1_updated;
220+
inner1_updated["inner1"] = py::str("value1");
221+
py::dict expected3;
222+
expected3["outer1"] = inner1_updated;
223+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected3));
224+
225+
// Close first nested object, start second key and nested object
226+
parser.consume("}, \"outer2\": {");
227+
py::dict inner2;
228+
py::dict expected4;
229+
expected4["outer1"] = inner1_updated;
230+
expected4["outer2"] = inner2;
231+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected4));
232+
233+
// Complete all objects
234+
parser.consume("\"inner2\": \"value2\"}}");
235+
py::dict inner2_updated;
236+
inner2_updated["inner2"] = py::str("value2");
237+
py::dict expected5;
238+
expected5["outer1"] = inner1_updated;
239+
expected5["outer2"] = inner2_updated;
240+
assert(dicts_equal(parser.getPython().cast<py::dict>(), expected5));
241+
});
242+
243+
suite.run_all();
244+
245+
return 0;
246+
}

0 commit comments

Comments
 (0)