blob: d97288d445966b0a20c990d50c5353d00913d9e6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Test file to verify that template_streamop generated code compiles and works correctly.
* This tests the templated operator<< and printTo with various stream types.
*/
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <cassert>
#include <chrono>
#include <cstring>
#include <cstdio>
#include <cstdint>
// Include generated thrift types with template_streamop option
#include "ThriftTest_types.h"
#include <thrift/TToString.h>
using namespace thrift::test;
// Custom minimal stream implementation for testing and performance comparison
class MinimalStream {
private:
static constexpr size_t STACK_BUFFER_SIZE = 2048;
char stack_buffer_[STACK_BUFFER_SIZE];
char* buffer_;
size_t size_;
size_t capacity_;
bool on_heap_;
void ensure_capacity(size_t additional) {
size_t needed = size_ + additional;
if (needed <= capacity_) return;
size_t new_capacity = capacity_;
while (new_capacity < needed) {
new_capacity *= 2;
}
char* new_buffer = new char[new_capacity];
if (size_ > 0) {
std::memcpy(new_buffer, buffer_, size_);
}
if (on_heap_) {
delete[] buffer_;
}
buffer_ = new_buffer;
capacity_ = new_capacity;
on_heap_ = true;
}
void append(const char* s, size_t len) {
ensure_capacity(len);
std::memcpy(buffer_ + size_, s, len);
size_ += len;
}
// Helper to print integer directly to buffer
template<typename T>
void print_integer(T value) {
char temp[32]; // Enough for any 64-bit integer
char* p = temp + sizeof(temp);
bool negative = value < 0;
if (negative) {
value = -value;
}
do {
*--p = '0' + (value % 10);
value /= 10;
} while (value > 0);
if (negative) {
*--p = '-';
}
append(p, temp + sizeof(temp) - p);
}
// Helper to print unsigned integer directly to buffer
template<typename T>
void print_unsigned(T value) {
char temp[32];
char* p = temp + sizeof(temp);
do {
*--p = '0' + (value % 10);
value /= 10;
} while (value > 0);
append(p, temp + sizeof(temp) - p);
}
public:
MinimalStream()
: buffer_(stack_buffer_), size_(0), capacity_(STACK_BUFFER_SIZE), on_heap_(false) {}
~MinimalStream() {
if (on_heap_) {
delete[] buffer_;
}
}
MinimalStream& operator<<(const std::string& s) {
append(s.c_str(), s.size());
return *this;
}
MinimalStream& operator<<(const char* s) {
append(s, std::strlen(s));
return *this;
}
MinimalStream& operator<<(char c) {
ensure_capacity(1);
buffer_[size_++] = c;
return *this;
}
MinimalStream& operator<<(int32_t i) {
print_integer(i);
return *this;
}
MinimalStream& operator<<(int64_t i) {
print_integer(i);
return *this;
}
MinimalStream& operator<<(uint32_t i) {
print_unsigned(i);
return *this;
}
MinimalStream& operator<<(uint64_t i) {
print_unsigned(i);
return *this;
}
MinimalStream& operator<<(double d) {
// For doubles, we still need sprintf for proper formatting
char temp[64];
int len = std::snprintf(temp, sizeof(temp), "%g", d);
if (len > 0) {
append(temp, len);
}
return *this;
}
MinimalStream& operator<<(bool b) {
if (b) {
append("true", 4);
} else {
append("false", 5);
}
return *this;
}
std::string str() const {
return std::string(buffer_, size_);
}
void clear() {
if (on_heap_) {
delete[] buffer_;
buffer_ = stack_buffer_;
capacity_ = STACK_BUFFER_SIZE;
on_heap_ = false;
}
size_ = 0;
}
};
int main() {
std::cout << "Testing template_streamop with ThriftTest types..." << std::endl;
// Test 1: Test with std::ostringstream
{
Xtruct x;
x.__set_string_thing("test string");
x.__set_byte_thing(42);
x.__set_i32_thing(12345);
x.__set_i64_thing(9876543210LL);
std::ostringstream oss;
oss << x;
std::string result = oss.str();
std::cout << " Generated output: " << result << std::endl;
assert(!result.empty());
assert(result.find("test string") != std::string::npos);
assert(result.find("42") != std::string::npos);
assert(result.find("12345") != std::string::npos);
std::cout << " ✓ std::ostringstream works: " << result << std::endl;
}
// Test 2: Test with custom MinimalStream
{
Xtruct x;
x.__set_string_thing("custom stream");
x.__set_byte_thing(7);
x.__set_i32_thing(999);
x.__set_i64_thing(1234567890LL);
MinimalStream ms;
ms << x;
std::string result = ms.str();
assert(!result.empty());
assert(result.find("custom stream") != std::string::npos);
assert(result.find("7") != std::string::npos);
assert(result.find("999") != std::string::npos);
std::cout << " ✓ MinimalStream works: " << result << std::endl;
}
// Test 3: Test nested structures
{
Xtruct x;
x.__set_string_thing("inner");
x.__set_i32_thing(100);
Xtruct2 x2;
x2.__set_byte_thing(5);
x2.__set_struct_thing(x);
x2.__set_i32_thing(200);
std::ostringstream oss;
oss << x2;
std::string result = oss.str();
assert(!result.empty());
assert(result.find("inner") != std::string::npos);
assert(result.find("100") != std::string::npos);
assert(result.find("200") != std::string::npos);
std::cout << " ✓ Nested structures work" << std::endl;
}
// Test 4: Test optional fields
{
Bonk bonk;
bonk.__set_message("test message");
bonk.__set_type(42);
std::ostringstream oss;
oss << bonk;
std::string result = oss.str();
assert(!result.empty());
assert(result.find("test message") != std::string::npos);
assert(result.find("42") != std::string::npos);
std::cout << " ✓ Optional fields work" << std::endl;
}
// Test 5: Test structs with map/set/list/vector
{
std::cout << "\n Testing collection types..." << std::endl;
// Create an Insanity struct with map and list
Insanity insanity;
// Add items to the map
std::map<Numberz::type, UserId> userMap;
userMap[Numberz::ONE] = 1;
userMap[Numberz::FIVE] = 5;
insanity.__set_userMap(userMap);
// Add items to the list
std::vector<Xtruct> xtructs;
Xtruct x1;
x1.__set_string_thing("first");
x1.__set_i32_thing(111);
xtructs.push_back(x1);
Xtruct x2;
x2.__set_string_thing("second");
x2.__set_i32_thing(222);
xtructs.push_back(x2);
insanity.__set_xtructs(xtructs);
// Test with std::ostringstream
std::ostringstream oss;
oss << insanity;
std::string result = oss.str();
std::cout << " std::ostringstream output: " << result << std::endl;
assert(!result.empty());
assert(result.find("Insanity") != std::string::npos);
assert(result.find("userMap") != std::string::npos);
assert(result.find("xtructs") != std::string::npos);
// Test with MinimalStream
MinimalStream ms;
ms << insanity;
std::string ms_result = ms.str();
std::cout << " MinimalStream output: " << ms_result << std::endl;
assert(!ms_result.empty());
assert(ms_result.find("Insanity") != std::string::npos);
std::cout << " ✓ Map/List collections work with both streams" << std::endl;
}
// Test 6: Test to_string compatibility with collection structs
{
std::cout << "\n Testing to_string with collection structs..." << std::endl;
Insanity insanity;
std::map<Numberz::type, UserId> userMap;
userMap[Numberz::TWO] = 2;
insanity.__set_userMap(userMap);
std::vector<Xtruct> xtructs;
Xtruct x;
x.__set_string_thing("test");
x.__set_i32_thing(42);
xtructs.push_back(x);
insanity.__set_xtructs(xtructs);
// to_string should work with the generated types
std::string str_result = apache::thrift::to_string(insanity);
std::cout << " to_string output: " << str_result << std::endl;
assert(!str_result.empty());
assert(str_result.find("Insanity") != std::string::npos);
std::cout << " ✓ to_string works with collection structs" << std::endl;
}
// Test 7: Test enum output - should print by name
{
std::cout << "\n Testing enum output..." << std::endl;
// Create a struct with an enum field
Insanity insanity;
std::map<Numberz::type, UserId> userMap;
userMap[Numberz::ONE] = 1;
userMap[Numberz::FIVE] = 5;
userMap[Numberz::TWO] = 2;
insanity.__set_userMap(userMap);
// Test with std::ostringstream
std::ostringstream oss;
oss << insanity;
std::string result = oss.str();
std::cout << " std::ostringstream output: " << result << std::endl;
assert(result.find("ONE") != std::string::npos || result.find("1") != std::string::npos);
// Test with MinimalStream
MinimalStream ms;
ms << insanity;
std::string ms_result = ms.str();
std::cout << " MinimalStream output: " << ms_result << std::endl;
assert(!ms_result.empty());
std::cout << " ✓ Enum fields output correctly" << std::endl;
}
// Test 8: Test floating point types
{
std::cout << "\n Testing floating point types..." << std::endl;
// Note: ThriftTest doesn't have a struct with float/double fields
// So we test directly with printTo
float f = 3.14159f;
double d = 2.71828;
// Test with std::ostringstream
std::ostringstream oss_f, oss_d;
apache::thrift::printTo(oss_f, f);
apache::thrift::printTo(oss_d, d);
std::string f_result = oss_f.str();
std::string d_result = oss_d.str();
std::cout << " float printTo: " << f_result << std::endl;
std::cout << " double printTo: " << d_result << std::endl;
assert(!f_result.empty());
assert(!d_result.empty());
assert(f_result.find("3.14") != std::string::npos || f_result.find("3,14") != std::string::npos);
assert(d_result.find("2.71") != std::string::npos || d_result.find("2,71") != std::string::npos);
// Test with MinimalStream
MinimalStream ms_f, ms_d;
apache::thrift::printTo(ms_f, f);
apache::thrift::printTo(ms_d, d);
std::cout << " MinimalStream float: " << ms_f.str() << std::endl;
std::cout << " MinimalStream double: " << ms_d.str() << std::endl;
assert(!ms_f.str().empty());
assert(!ms_d.str().empty());
std::cout << " ✓ Floating point types work correctly" << std::endl;
}
// Performance Test: Compare std::ostringstream vs MinimalStream
{
const int iterations = 10000;
Xtruct x;
x.__set_string_thing("performance test string");
x.__set_byte_thing(123);
x.__set_i32_thing(456789);
x.__set_i64_thing(9876543210LL);
// Test std::ostringstream performance
auto start_oss = std::chrono::high_resolution_clock::now();
std::string accumulated_result; // Prevent optimization by accumulating results
for (int i = 0; i < iterations; ++i) {
std::ostringstream oss;
oss << x;
accumulated_result += oss.str(); // Use result to prevent optimization
}
auto end_oss = std::chrono::high_resolution_clock::now();
auto duration_oss = std::chrono::duration_cast<std::chrono::microseconds>(end_oss - start_oss).count();
// Test MinimalStream performance
auto start_ms = std::chrono::high_resolution_clock::now();
accumulated_result.clear(); // Reuse for MinimalStream test
for (int i = 0; i < iterations; ++i) {
MinimalStream ms;
ms << x;
accumulated_result += ms.str(); // Use result to prevent optimization
}
auto end_ms = std::chrono::high_resolution_clock::now();
auto duration_ms = std::chrono::duration_cast<std::chrono::microseconds>(end_ms - start_ms).count();
std::cout << "\n Performance comparison (" << iterations << " iterations):" << std::endl;
std::cout << " std::ostringstream: " << duration_oss << " μs" << std::endl;
std::cout << " MinimalStream: " << duration_ms << " μs" << std::endl;
if (duration_ms < duration_oss) {
double improvement = ((double)(duration_oss - duration_ms) / duration_oss) * 100.0;
std::cout << " MinimalStream is " << improvement << "% faster" << std::endl;
} else {
double difference = ((double)(duration_ms - duration_oss) / duration_oss) * 100.0;
std::cout << " std::ostringstream is " << difference << "% faster" << std::endl;
}
std::cout << " ✓ Performance test completed" << std::endl;
}
std::cout << "\n✅ All template_streamop tests passed!" << std::endl;
return 0;
}