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 << " \n Results: " << 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