blob: d97288d445966b0a20c990d50c5353d00913d9e6 [file] [log] [blame]
copilot-swe-agent[bot]c3cdacf2026-02-09 21:30:16 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20/**
21 * Test file to verify that template_streamop generated code compiles and works correctly.
22 * This tests the templated operator<< and printTo with various stream types.
23 */
24
25#include <iostream>
26#include <sstream>
27#include <string>
28#include <vector>
29#include <map>
30#include <cassert>
31#include <chrono>
32#include <cstring>
33#include <cstdio>
34#include <cstdint>
35
36// Include generated thrift types with template_streamop option
37#include "ThriftTest_types.h"
38#include <thrift/TToString.h>
39
40using namespace thrift::test;
41
42// Custom minimal stream implementation for testing and performance comparison
43class MinimalStream {
44private:
45 static constexpr size_t STACK_BUFFER_SIZE = 2048;
46 char stack_buffer_[STACK_BUFFER_SIZE];
47 char* buffer_;
48 size_t size_;
49 size_t capacity_;
50 bool on_heap_;
51
52 void ensure_capacity(size_t additional) {
53 size_t needed = size_ + additional;
54 if (needed <= capacity_) return;
55
56 size_t new_capacity = capacity_;
57 while (new_capacity < needed) {
58 new_capacity *= 2;
59 }
60
61 char* new_buffer = new char[new_capacity];
62 if (size_ > 0) {
63 std::memcpy(new_buffer, buffer_, size_);
64 }
65
66 if (on_heap_) {
67 delete[] buffer_;
68 }
69
70 buffer_ = new_buffer;
71 capacity_ = new_capacity;
72 on_heap_ = true;
73 }
74
75 void append(const char* s, size_t len) {
76 ensure_capacity(len);
77 std::memcpy(buffer_ + size_, s, len);
78 size_ += len;
79 }
80
81 // Helper to print integer directly to buffer
82 template<typename T>
83 void print_integer(T value) {
84 char temp[32]; // Enough for any 64-bit integer
85 char* p = temp + sizeof(temp);
86 bool negative = value < 0;
87
88 if (negative) {
89 value = -value;
90 }
91
92 do {
93 *--p = '0' + (value % 10);
94 value /= 10;
95 } while (value > 0);
96
97 if (negative) {
98 *--p = '-';
99 }
100
101 append(p, temp + sizeof(temp) - p);
102 }
103
104 // Helper to print unsigned integer directly to buffer
105 template<typename T>
106 void print_unsigned(T value) {
107 char temp[32];
108 char* p = temp + sizeof(temp);
109
110 do {
111 *--p = '0' + (value % 10);
112 value /= 10;
113 } while (value > 0);
114
115 append(p, temp + sizeof(temp) - p);
116 }
117
118public:
119 MinimalStream()
120 : buffer_(stack_buffer_), size_(0), capacity_(STACK_BUFFER_SIZE), on_heap_(false) {}
121
122 ~MinimalStream() {
123 if (on_heap_) {
124 delete[] buffer_;
125 }
126 }
127
128 MinimalStream& operator<<(const std::string& s) {
129 append(s.c_str(), s.size());
130 return *this;
131 }
132
133 MinimalStream& operator<<(const char* s) {
134 append(s, std::strlen(s));
135 return *this;
136 }
137
138 MinimalStream& operator<<(char c) {
139 ensure_capacity(1);
140 buffer_[size_++] = c;
141 return *this;
142 }
143
144 MinimalStream& operator<<(int32_t i) {
145 print_integer(i);
146 return *this;
147 }
148
149 MinimalStream& operator<<(int64_t i) {
150 print_integer(i);
151 return *this;
152 }
153
154 MinimalStream& operator<<(uint32_t i) {
155 print_unsigned(i);
156 return *this;
157 }
158
159 MinimalStream& operator<<(uint64_t i) {
160 print_unsigned(i);
161 return *this;
162 }
163
164 MinimalStream& operator<<(double d) {
165 // For doubles, we still need sprintf for proper formatting
166 char temp[64];
167 int len = std::snprintf(temp, sizeof(temp), "%g", d);
168 if (len > 0) {
169 append(temp, len);
170 }
171 return *this;
172 }
173
174 MinimalStream& operator<<(bool b) {
175 if (b) {
176 append("true", 4);
177 } else {
178 append("false", 5);
179 }
180 return *this;
181 }
182
183 std::string str() const {
184 return std::string(buffer_, size_);
185 }
186
187 void clear() {
188 if (on_heap_) {
189 delete[] buffer_;
190 buffer_ = stack_buffer_;
191 capacity_ = STACK_BUFFER_SIZE;
192 on_heap_ = false;
193 }
194 size_ = 0;
195 }
196};
197
198int main() {
199 std::cout << "Testing template_streamop with ThriftTest types..." << std::endl;
200
201 // Test 1: Test with std::ostringstream
202 {
203 Xtruct x;
204 x.__set_string_thing("test string");
205 x.__set_byte_thing(42);
206 x.__set_i32_thing(12345);
207 x.__set_i64_thing(9876543210LL);
208
209 std::ostringstream oss;
210 oss << x;
211 std::string result = oss.str();
212
213 std::cout << " Generated output: " << result << std::endl;
214
215 assert(!result.empty());
216 assert(result.find("test string") != std::string::npos);
217 assert(result.find("42") != std::string::npos);
218 assert(result.find("12345") != std::string::npos);
219 std::cout << " ✓ std::ostringstream works: " << result << std::endl;
220 }
221
222 // Test 2: Test with custom MinimalStream
223 {
224 Xtruct x;
225 x.__set_string_thing("custom stream");
226 x.__set_byte_thing(7);
227 x.__set_i32_thing(999);
228 x.__set_i64_thing(1234567890LL);
229
230 MinimalStream ms;
231 ms << x;
232 std::string result = ms.str();
233
234 assert(!result.empty());
235 assert(result.find("custom stream") != std::string::npos);
236 assert(result.find("7") != std::string::npos);
237 assert(result.find("999") != std::string::npos);
238 std::cout << " ✓ MinimalStream works: " << result << std::endl;
239 }
240
241 // Test 3: Test nested structures
242 {
243 Xtruct x;
244 x.__set_string_thing("inner");
245 x.__set_i32_thing(100);
246
247 Xtruct2 x2;
248 x2.__set_byte_thing(5);
249 x2.__set_struct_thing(x);
250 x2.__set_i32_thing(200);
251
252 std::ostringstream oss;
253 oss << x2;
254 std::string result = oss.str();
255
256 assert(!result.empty());
257 assert(result.find("inner") != std::string::npos);
258 assert(result.find("100") != std::string::npos);
259 assert(result.find("200") != std::string::npos);
260 std::cout << " ✓ Nested structures work" << std::endl;
261 }
262
263 // Test 4: Test optional fields
264 {
265 Bonk bonk;
266 bonk.__set_message("test message");
267 bonk.__set_type(42);
268
269 std::ostringstream oss;
270 oss << bonk;
271 std::string result = oss.str();
272
273 assert(!result.empty());
274 assert(result.find("test message") != std::string::npos);
275 assert(result.find("42") != std::string::npos);
276 std::cout << " ✓ Optional fields work" << std::endl;
277 }
278
279 // Test 5: Test structs with map/set/list/vector
280 {
281 std::cout << "\n Testing collection types..." << std::endl;
282
283 // Create an Insanity struct with map and list
284 Insanity insanity;
285
286 // Add items to the map
287 std::map<Numberz::type, UserId> userMap;
288 userMap[Numberz::ONE] = 1;
289 userMap[Numberz::FIVE] = 5;
290 insanity.__set_userMap(userMap);
291
292 // Add items to the list
293 std::vector<Xtruct> xtructs;
294 Xtruct x1;
295 x1.__set_string_thing("first");
296 x1.__set_i32_thing(111);
297 xtructs.push_back(x1);
298
299 Xtruct x2;
300 x2.__set_string_thing("second");
301 x2.__set_i32_thing(222);
302 xtructs.push_back(x2);
303 insanity.__set_xtructs(xtructs);
304
305 // Test with std::ostringstream
306 std::ostringstream oss;
307 oss << insanity;
308 std::string result = oss.str();
309
310 std::cout << " std::ostringstream output: " << result << std::endl;
311 assert(!result.empty());
312 assert(result.find("Insanity") != std::string::npos);
313 assert(result.find("userMap") != std::string::npos);
314 assert(result.find("xtructs") != std::string::npos);
315
316 // Test with MinimalStream
317 MinimalStream ms;
318 ms << insanity;
319 std::string ms_result = ms.str();
320
321 std::cout << " MinimalStream output: " << ms_result << std::endl;
322 assert(!ms_result.empty());
323 assert(ms_result.find("Insanity") != std::string::npos);
324
325 std::cout << " ✓ Map/List collections work with both streams" << std::endl;
326 }
327
328 // Test 6: Test to_string compatibility with collection structs
329 {
330 std::cout << "\n Testing to_string with collection structs..." << std::endl;
331
332 Insanity insanity;
333 std::map<Numberz::type, UserId> userMap;
334 userMap[Numberz::TWO] = 2;
335 insanity.__set_userMap(userMap);
336
337 std::vector<Xtruct> xtructs;
338 Xtruct x;
339 x.__set_string_thing("test");
340 x.__set_i32_thing(42);
341 xtructs.push_back(x);
342 insanity.__set_xtructs(xtructs);
343
344 // to_string should work with the generated types
345 std::string str_result = apache::thrift::to_string(insanity);
346
347 std::cout << " to_string output: " << str_result << std::endl;
348 assert(!str_result.empty());
349 assert(str_result.find("Insanity") != std::string::npos);
350
351 std::cout << " ✓ to_string works with collection structs" << std::endl;
352 }
353
354 // Test 7: Test enum output - should print by name
355 {
356 std::cout << "\n Testing enum output..." << std::endl;
357
358 // Create a struct with an enum field
359 Insanity insanity;
360 std::map<Numberz::type, UserId> userMap;
361 userMap[Numberz::ONE] = 1;
362 userMap[Numberz::FIVE] = 5;
363 userMap[Numberz::TWO] = 2;
364 insanity.__set_userMap(userMap);
365
366 // Test with std::ostringstream
367 std::ostringstream oss;
368 oss << insanity;
369 std::string result = oss.str();
370
371 std::cout << " std::ostringstream output: " << result << std::endl;
372 assert(result.find("ONE") != std::string::npos || result.find("1") != std::string::npos);
373
374 // Test with MinimalStream
375 MinimalStream ms;
376 ms << insanity;
377 std::string ms_result = ms.str();
378
379 std::cout << " MinimalStream output: " << ms_result << std::endl;
380 assert(!ms_result.empty());
381
382 std::cout << " ✓ Enum fields output correctly" << std::endl;
383 }
384
385 // Test 8: Test floating point types
386 {
387 std::cout << "\n Testing floating point types..." << std::endl;
388
389 // Note: ThriftTest doesn't have a struct with float/double fields
390 // So we test directly with printTo
391 float f = 3.14159f;
392 double d = 2.71828;
393
394 // Test with std::ostringstream
395 std::ostringstream oss_f, oss_d;
396 apache::thrift::printTo(oss_f, f);
397 apache::thrift::printTo(oss_d, d);
398
399 std::string f_result = oss_f.str();
400 std::string d_result = oss_d.str();
401
402 std::cout << " float printTo: " << f_result << std::endl;
403 std::cout << " double printTo: " << d_result << std::endl;
404
405 assert(!f_result.empty());
406 assert(!d_result.empty());
407 assert(f_result.find("3.14") != std::string::npos || f_result.find("3,14") != std::string::npos);
408 assert(d_result.find("2.71") != std::string::npos || d_result.find("2,71") != std::string::npos);
409
410 // Test with MinimalStream
411 MinimalStream ms_f, ms_d;
412 apache::thrift::printTo(ms_f, f);
413 apache::thrift::printTo(ms_d, d);
414
415 std::cout << " MinimalStream float: " << ms_f.str() << std::endl;
416 std::cout << " MinimalStream double: " << ms_d.str() << std::endl;
417
418 assert(!ms_f.str().empty());
419 assert(!ms_d.str().empty());
420
421 std::cout << " ✓ Floating point types work correctly" << std::endl;
422 }
423
424 // Performance Test: Compare std::ostringstream vs MinimalStream
425 {
426 const int iterations = 10000;
427 Xtruct x;
428 x.__set_string_thing("performance test string");
429 x.__set_byte_thing(123);
430 x.__set_i32_thing(456789);
431 x.__set_i64_thing(9876543210LL);
432
433 // Test std::ostringstream performance
434 auto start_oss = std::chrono::high_resolution_clock::now();
435 std::string accumulated_result; // Prevent optimization by accumulating results
436 for (int i = 0; i < iterations; ++i) {
437 std::ostringstream oss;
438 oss << x;
439 accumulated_result += oss.str(); // Use result to prevent optimization
440 }
441 auto end_oss = std::chrono::high_resolution_clock::now();
442 auto duration_oss = std::chrono::duration_cast<std::chrono::microseconds>(end_oss - start_oss).count();
443
444 // Test MinimalStream performance
445 auto start_ms = std::chrono::high_resolution_clock::now();
446 accumulated_result.clear(); // Reuse for MinimalStream test
447 for (int i = 0; i < iterations; ++i) {
448 MinimalStream ms;
449 ms << x;
450 accumulated_result += ms.str(); // Use result to prevent optimization
451 }
452 auto end_ms = std::chrono::high_resolution_clock::now();
453 auto duration_ms = std::chrono::duration_cast<std::chrono::microseconds>(end_ms - start_ms).count();
454
455 std::cout << "\n Performance comparison (" << iterations << " iterations):" << std::endl;
456 std::cout << " std::ostringstream: " << duration_oss << " μs" << std::endl;
457 std::cout << " MinimalStream: " << duration_ms << " μs" << std::endl;
458
459 if (duration_ms < duration_oss) {
460 double improvement = ((double)(duration_oss - duration_ms) / duration_oss) * 100.0;
461 std::cout << " MinimalStream is " << improvement << "% faster" << std::endl;
462 } else {
463 double difference = ((double)(duration_ms - duration_oss) / duration_oss) * 100.0;
464 std::cout << " std::ostringstream is " << difference << "% faster" << std::endl;
465 }
466
467 std::cout << " ✓ Performance test completed" << std::endl;
468 }
469
470 std::cout << "\n✅ All template_streamop tests passed!" << std::endl;
471 return 0;
472}