diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c3a88f..69c58dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,3 +22,18 @@ jobs: working-directory: build run: | ctest -C Debug --verbose + + windows: + timeout-minutes: 15 + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + - name: Build tests + run: | + cmake -DNODEC_BUILD_TESTS=ON -B ./build + cmake --build ./build --config Debug -j 4 + - name: Run tests + working-directory: build + run: | + ctest -C Debug --verbose diff --git a/include/nodec/formatter.hpp b/include/nodec/formatter.hpp index 1c74590..58c1143 100644 --- a/include/nodec/formatter.hpp +++ b/include/nodec/formatter.hpp @@ -8,19 +8,6 @@ namespace nodec { -/* - * The Text Formatter - * - * if you use C++20, consider to use std::format. - * * - * - * It is designed for easy use of text format even though under C++20. - * - * Implementation refs: - * * - * - */ - class Formatter { public: Formatter() noexcept {} @@ -57,7 +44,8 @@ class ErrorFormatter { } operator std::string() noexcept { - stream_ << "\n" << std::dec + stream_ << "\n" + << std::dec << "[File] " << file_ << "\n" << "[Line] " << line_; return stream_.str(); diff --git a/include/nodec/logging/formatters/simple_formatter.hpp b/include/nodec/logging/formatters/simple_formatter.hpp index 1497029..374d1d0 100644 --- a/include/nodec/logging/formatters/simple_formatter.hpp +++ b/include/nodec/logging/formatters/simple_formatter.hpp @@ -3,6 +3,7 @@ #include +#include "../../string_builder.hpp" #include "../log_record.hpp" namespace nodec { @@ -11,40 +12,44 @@ namespace formatters { struct SimpleFormatter { std::string operator()(const LogRecord &record) { - std::ostringstream oss; + std::string text; + StringBuilder builder(text); + + // std::ostringstream builder; switch (record.level) { case nodec::logging::Level::Unset: - oss << "[UNSET]"; + builder << "[UNSET]"; break; case nodec::logging::Level::Debug: - oss << "[DEBUG]"; + builder << "[DEBUG]"; break; case nodec::logging::Level::Info: - oss << "[INFO] "; + builder << "[INFO] "; break; case nodec::logging::Level::Warn: - oss << "[WARN] "; + builder << "[WARN] "; break; case nodec::logging::Level::Error: - oss << "[ERROR]"; + builder << "[ERROR]"; break; case nodec::logging::Level::Fatal: - oss << "[FATAL]"; + builder << "[FATAL]"; break; default: - oss << "[???] "; + builder << "[???] "; break; } if (!record.name.empty()) { - oss << " [" << record.name << "]"; + builder << " [" << record.name << "]"; } - oss << " - " << record.message << "\n"; - oss << "(" << record.file << " line " << record.line << ")\n"; + builder << " - " << record.message << "\n"; + builder << "(" << record.file << " line " << record.line << ")\n"; - return oss.str(); + return std::move(text); + // return builder.str(); } }; diff --git a/include/nodec/logging/log_record.hpp b/include/nodec/logging/log_record.hpp index d0875e2..8f05d35 100644 --- a/include/nodec/logging/log_record.hpp +++ b/include/nodec/logging/log_record.hpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include "level.hpp" @@ -12,14 +12,18 @@ namespace logging { struct LogRecord { public: - LogRecord(std::chrono::system_clock::time_point time, const std::string &name, Level level, const std::string &message, const char *file, std::size_t line) + LogRecord(std::chrono::system_clock::time_point time, + std::string_view name, + Level level, + std::string_view message, + std::string_view file, std::size_t line) : time(time), name(name), level(level), message(message), file(file), line(line) {} const std::chrono::system_clock::time_point time; - const std::string &name; + std::string_view name; const Level level; - const std::string &message; - const char *file; + std::string_view message; + std::string_view file; const std::size_t line; }; diff --git a/include/nodec/logging/logger.hpp b/include/nodec/logging/logger.hpp index 4be8d66..dfff5a4 100644 --- a/include/nodec/logging/logger.hpp +++ b/include/nodec/logging/logger.hpp @@ -10,6 +10,7 @@ #include "../macros.hpp" #include "../optional.hpp" #include "../signals/signal.hpp" +#include "../string_builder.hpp" #include "log_record.hpp" namespace nodec { @@ -67,15 +68,15 @@ class Logger { class LogStream { public: LogStream(Logger &logger, Level level, const char *file, std::size_t line) - : logger_(logger), level_(level), file_(file), line_(line) {} + : logger_(logger), level_(level), file_(file), line_(line), builder_(message) {} ~LogStream() { - logger_.log(level_, stream_.str(), file_, line_); + logger_.log(level_, message, file_, line_); } template LogStream &operator<<(const T &value) { - stream_ << value; + builder_ << value; return *this; } @@ -84,7 +85,8 @@ class Logger { Level level_; const char *file_; std::size_t line_; - std::ostringstream stream_; + std::string message; + StringBuilder builder_; }; public: diff --git a/include/nodec/string_builder.hpp b/include/nodec/string_builder.hpp new file mode 100644 index 0000000..17631fc --- /dev/null +++ b/include/nodec/string_builder.hpp @@ -0,0 +1,160 @@ +#ifndef NODEC__STRING_BUILDER_HPP_ +#define NODEC__STRING_BUILDER_HPP_ + +#include + +namespace nodec { + +template> +class BasicStringBuilder { +private: + class BasicStringBuilderStreamBuf : public std::basic_streambuf { + public: + using base_type = std::basic_streambuf; + using string_type = std::basic_string; + using string_view_type = std::basic_string_view; + using size_type = typename string_type::size_type; + using int_type = typename Traits::int_type; + using char_type = CharT; + using traits_type = Traits; + using pos_type = typename Traits::pos_type; + using off_type = typename Traits::off_type; + + BasicStringBuilderStreamBuf(string_type &dest): dest_(dest) {} + + protected: + /** + * @note This function is called when sputc() + */ + int_type overflow(int_type c = Traits::eof()) override { + if (Traits::eq_int_type(c, Traits::eof())) { + return Traits::not_eof(c); + } + dest_.push_back(Traits::to_char_type(c)); + return c; + } + + std::streamsize xsputn(const char_type *s, std::streamsize count) override { + if (count <= 0) { + return 0; + } + + dest_.append(s, static_cast(count)); + return count; + } + + private: + string_type &dest_; + }; + +public: + using string_type = std::basic_string; + using string_view_type = std::basic_string_view; + using size_type = typename string_type::size_type; + using stringbuf_type = BasicStringBuilderStreamBuf; + + BasicStringBuilder(string_type &dest): base_stream_(&buffer_), buffer_(dest) {} + + string_type str() const { + return buffer_.str(); + } + + BasicStringBuilder &operator<<(const string_view_type str) { + using size_type = typename string_view_type::size_type; + + const size_type str_size = str.size(); + size_type pad; + + if (base_stream_.width() <= 0 || static_cast(base_stream_.width()) <= str_size) { + pad = 0; + } else { + pad = static_cast(base_stream_.width()) - str_size; + } + + // 左詰め以外の場合は左側にパディング + if ((base_stream_.flags() & std::ios_base::adjustfield) != std::ios_base::left) { + for (; 0 < pad; --pad) { + buffer_.sputc(base_stream_.fill()); + } + } + + // 文字列本体を出力 + buffer_.sputn(str.data(), static_cast(str_size)); + + // 右側にパディング(左詰めの場合) + for (; 0 < pad; --pad) { + buffer_.sputc(base_stream_.fill()); + } + + // width をリセット + base_stream_.width(0); + + return *this; + } + + /** + * @brief Inserts a null-terminated character string into the stream + * + * This operator performs formatted output of a C-style string with support for + * width, fill character, and alignment manipulators (setw, setfill, left, right, internal). + * The behavior is consistent with std::ostream::operator<<(const char*). + * + * @param str Pointer to a null-terminated character string + * @return Reference to this BasicStringBuilder object for method chaining + * + * @pre str must be a valid pointer to a null-terminated string (str != nullptr) + * @warning Passing nullptr results in undefined behavior + * + * @note The width setting is automatically reset to 0 after the operation, + * following standard stream behavior + * + * @example + * @code + * std::string buffer; + * StringBuilder builder(buffer); + * builder << std::setw(10) << std::setfill('*') << "Hello"; + * // buffer contains "*****Hello" + * @endcode + */ + BasicStringBuilder &operator<<(const char *str) { + std::streamsize count = static_cast(Traits::length(str)); + + std::streamsize pad = base_stream_.width() <= 0 || base_stream_.width() <= count ? 0 : base_stream_.width() - count; + + // 左詰め以外の場合は左側にパディング + if ((base_stream_.flags() & std::ios_base::adjustfield) != std::ios_base::left) { + for (; 0 < pad; --pad) { + buffer_.sputc(base_stream_.fill()); + } + } + + // 文字列本体を出力 + buffer_.sputn(str, count); + + // 右側にパディング(左詰めの場合) + for (; 0 < pad; --pad) { + buffer_.sputc(base_stream_.fill()); + } + + // width をリセット + base_stream_.width(0); + + return *this; + } + + template + BasicStringBuilder &operator<<(const T &value) { + base_stream_ << value; + return *this; + } + +private: + stringbuf_type buffer_; + std::basic_ostream base_stream_; +}; + +using StringBuilder = BasicStringBuilder; + +} // namespace nodec + +#endif \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 46ae4f4..1424378 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,10 +15,17 @@ add_basic_test("nodec__entitites__storage" entities/storage.cpp) add_basic_test("nodec__asyncio__event_loop" asyncio/event_loop.cpp) add_basic_test("nodec__asyncio__event_promise" asyncio/event_promise.cpp) add_basic_test("nodec__flags" flags/flags.cpp) -add_basic_test("nodec__formatter" formatter/formatter.cpp) + +add_basic_test("nodec__string_builder__basic_test" string_builder/basic_test.cpp) +add_basic_test("nodec__string_builder__benchmark_test" string_builder/benchmark_test.cpp) +add_basic_test("nodec__string_builder__oss_compat_test" string_builder/oss_compat_test.cpp) + add_basic_test("nodec__gfx" gfx/gfx.cpp) -add_basic_test("nodec__logging__logging" logging/logging.cpp) -add_basic_test("nodec__logging_bench" logging/bench.cpp) + +add_basic_test("nodec__logging__logger_test" logging/logger_test.cpp) +add_basic_test("nodec__logging__bench_test" logging/bench_test.cpp) +add_basic_test("nodec__logging__simple_formatter_test" logging/simple_formatter_test.cpp) + add_basic_test("nodec__math" math/math.cpp) add_basic_test("nodec__matrix4x4" matrix4x4/matrix4x4.cpp) add_basic_test("nodec__observers" observers/observers.cpp) diff --git a/tests/formatter/formatter.cpp b/tests/formatter/formatter.cpp deleted file mode 100644 index b120a6e..0000000 --- a/tests/formatter/formatter.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -#include - -#include - -TEST_CASE("Testing Formatter") { - std::string str = nodec::Formatter() << "A" - << "B"; - CHECK(str == "AB"); -} \ No newline at end of file diff --git a/tests/logging/bench.cpp b/tests/logging/bench_test.cpp similarity index 93% rename from tests/logging/bench.cpp rename to tests/logging/bench_test.cpp index 4e81a1b..594009f 100644 --- a/tests/logging/bench.cpp +++ b/tests/logging/bench_test.cpp @@ -9,7 +9,7 @@ #include TEST_CASE("Single thread, 100,000 iterations") { - int iters = 100000; + int iters = 10'000; using namespace nodec::logging; auto logger = get_logger(); @@ -33,11 +33,12 @@ TEST_CASE("Single thread, 100,000 iterations") { // NOTE: '<<' operator is very slow. // About 50% of total cpu consumption is occupied... - // Should use std::format() instead if c++17 is available. + // Should use std::format() instead if c++20 is available. + CHECK(true); } -TEST_CASE("10 threads, competing over the same logger object, 100,000 iterations") { - int iters = 100000; +TEST_CASE("10 threads, competing over the same logger object, 10,000 iterations") { + int iters = 10'000; int thread_count = 10; using namespace nodec::logging; @@ -69,4 +70,5 @@ TEST_CASE("10 threads, competing over the same logger object, 100,000 iterations auto delta_d = duration_cast>(delta).count(); MESSAGE("Elapsed: ", delta_d, " secs ", std::size_t(iters / delta_d), "/sec"); + CHECK(true); } \ No newline at end of file diff --git a/tests/logging/logging.cpp b/tests/logging/logger_test.cpp similarity index 100% rename from tests/logging/logging.cpp rename to tests/logging/logger_test.cpp diff --git a/tests/logging/simple_formatter_test.cpp b/tests/logging/simple_formatter_test.cpp new file mode 100644 index 0000000..7ddbd06 --- /dev/null +++ b/tests/logging/simple_formatter_test.cpp @@ -0,0 +1,80 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +// ベンチマーク測定用のヘルパークラス +class BenchmarkTimer { +public: + BenchmarkTimer(const std::string &name, int iterations = 1): name_(name), iterations_(iterations) { + start_time_ = std::chrono::high_resolution_clock::now(); + } + + ~BenchmarkTimer() { + auto end_time = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time_); + double avg_time = static_cast(duration.count()) / iterations_; + MESSAGE(name_, ": ", duration.count(), " microseconds total, ", avg_time, " microseconds per operation"); + } + +private: + std::string name_; + int iterations_; + std::chrono::high_resolution_clock::time_point start_time_; +}; + +TEST_CASE("SimpleFormatter - Output Format Structure") { + using namespace std::chrono; + using namespace nodec::logging; + using namespace std::string_view_literals; + + formatters::SimpleFormatter formatter; + LogRecord record{system_clock::time_point(), "TestLogger"sv, Level::Info, "Test message"sv, "test.cpp"sv, 100}; + std::string result = formatter(record); + + // MESSAGE("Formatted output: ", result); + CHECK(result == "[INFO] [TestLogger] - Test message\n(test.cpp line 100)\n"); +} + +TEST_CASE("SimpleFormatter - 10'000 Format Benchmark") { + const int iterations = 10'000; + + using namespace std::chrono; + using namespace nodec::logging; + using namespace std::string_view_literals; + + MESSAGE("--- SimpleFormatter 10'000 Format Benchmark ---"); + + formatters::SimpleFormatter formatter; + + { + BenchmarkTimer timer("SimpleFormatter - 10'000 format operations", iterations); + + for (int i = 0; i < iterations; ++i) { + LogRecord record{ + system_clock::now(), + "BenchLogger"sv, + Level::Info, + "Benchmark message " + std::to_string(i), + __FILE__, + __LINE__}; + + std::string result = formatter(record); + + // 結果を使用して最適化を防ぐ + volatile auto len = result.length(); + (void)len; + } + } + + // テストが正常に実行されたことを確認 + CHECK(true); +} diff --git a/tests/string_builder/basic_test.cpp b/tests/string_builder/basic_test.cpp new file mode 100644 index 0000000..f656f8a --- /dev/null +++ b/tests/string_builder/basic_test.cpp @@ -0,0 +1,129 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + +#include +#include + +TEST_CASE("Testing StringBuilder - Basic Types") { + // 文字列連結 + { + std::string result; + nodec::StringBuilder(result) << "Hello" << " " << "World"; + CHECK(result == "Hello World"); + } + + // 整数 + { + std::string result; + nodec::StringBuilder(result) << "Number: " << 42; + CHECK(result == "Number: 42"); + } + + // 浮動小数点 + { + std::string result; + nodec::StringBuilder(result) << "Float: " << 3.14; + CHECK(result == "Float: 3.14"); + } + + // 文字 + { + std::string result; + nodec::StringBuilder(result) << 'A' << 'B' << 'C'; + CHECK(result == "ABC"); + } +} + +// TEST_CASE("Testing Formatter - Numeric Types") { +// // 様々な整数型 +// std::string result1 = nodec::Formatter() << static_cast(123); +// CHECK(result1 == "123"); + +// std::string result2 = nodec::Formatter() << static_cast(456L); +// CHECK(result2 == "456"); + +// std::string result3 = nodec::Formatter() << static_cast(789LL); +// CHECK(result3 == "789"); + +// // 負の数 +// std::string result4 = nodec::Formatter() << -42; +// CHECK(result4 == "-42"); + +// // ゼロ +// std::string result5 = nodec::Formatter() << 0; +// CHECK(result5 == "0"); +// } + +// TEST_CASE("Testing Formatter - Hex Mode") { +// // 16進数モード +// std::string result1 = nodec::Formatter() << "Hex: " << std::hex << 255; +// CHECK(result1 == "Hex: ff"); + +// // 10進数に戻す +// std::string result2 = nodec::Formatter() << std::hex << 16 << ", " << std::dec << 16; +// CHECK(result2 == "10, 16"); + +// // 複数の16進数 +// std::string result3 = nodec::Formatter() << std::hex << 10 << " " << 15 << " " << 255; +// CHECK(result3 == "a f ff"); +// } + +// // TEST_CASE("Testing Formatter - Reserve Functionality") { +// // // 事前リザーブ +// // nodec::Formatter formatter(100); +// // formatter << "This is a test string that might be long"; +// // std::string result = formatter.str(); +// // CHECK(result == "This is a test string that might be long"); + +// // // クリア機能 +// // formatter.clear(); +// // formatter << "New content"; +// // CHECK(formatter.str() == "New content"); +// // } + +// // TEST_CASE("Testing Formatter - Utility Methods") { +// // nodec::Formatter formatter; +// // formatter << "Hello"; + +// // // サイズチェック +// // CHECK(formatter.size() == 5); + +// // // 追加リザーブ +// // formatter.reserve(100); +// // formatter << " World"; +// // CHECK(formatter.str() == "Hello World"); + +// // // クリア +// // formatter.clear(); +// // CHECK(formatter.size() == 0); +// // CHECK(formatter.str() == ""); +// // } + +// TEST_CASE("Testing Formatter - Vector3 Fallback") { +// // Vector3の文字列化テスト(ostream演算子フォールバック) +// nodec::Vector3f vec3f(1.5f, 2.7f, 3.9f); +// std::string result1 = nodec::Formatter() << "Vector3f: " << vec3f; +// CHECK(result1 == "Vector3f: ( 1.5, 2.7, 3.9 )"); + +// // Vector3iの文字列化テスト +// nodec::Vector3i vec3i(10, 20, 30); +// std::string result2 = nodec::Formatter() << "Position: " << vec3i << ", End"; +// CHECK(result2 == "Position: ( 10, 20, 30 ), End"); + +// // Vector3dの文字列化テスト +// nodec::Vector3d vec3d(1.23456, 7.89012, 3.45678); +// std::string result3 = nodec::Formatter() << vec3d; +// CHECK(result3 == "( 1.23456, 7.89012, 3.45678 )"); + +// // 複数のVector3を連結 +// nodec::Vector3f pos(1.0f, 2.0f, 3.0f); +// nodec::Vector3f dir(0.0f, 1.0f, 0.0f); +// std::string result4 = nodec::Formatter() << "Pos: " << pos << ", Dir: " << dir; +// CHECK(result4 == "Pos: ( 1, 2, 3 ), Dir: ( 0, 1, 0 )"); + +// // ostringstream との比較確認 +// std::ostringstream oss; +// oss << "Vector3f: " << vec3f; +// std::string oss_result = oss.str(); +// CHECK(result1 == oss_result); // 同じ結果であることを確認 +// } \ No newline at end of file diff --git a/tests/string_builder/benchmark_test.cpp b/tests/string_builder/benchmark_test.cpp new file mode 100644 index 0000000..114d20f --- /dev/null +++ b/tests/string_builder/benchmark_test.cpp @@ -0,0 +1,211 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// ベンチマーク測定用のヘルパークラス +class BenchmarkTimer { +public: + BenchmarkTimer(const std::string &name): name_(name) { + start_time_ = std::chrono::high_resolution_clock::now(); + } + + ~BenchmarkTimer() { + auto end_time = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end_time - start_time_); + std::cout << name_ << ": " << duration.count() << " microseconds" << std::endl; + } + +private: + std::string name_; + std::chrono::high_resolution_clock::time_point start_time_; +}; + +TEST_CASE("Benchmark - Simple Concatenation") { + const int iterations = 100000; + + std::cout << "\n--- Simple Concatenation Test ---" << std::endl; + + // nodec::Formatterのベンチマーク + { + BenchmarkTimer timer("nodec::Formatter - Simple concatenation"); + for (int i = 0; i < iterations; ++i) { + std::string result = nodec::Formatter() << "Hello" << " " << "World" << i; + // 結果を使用して最適化を防ぐ + volatile auto len = result.length(); + (void)len; + } + } + + // std::ostringstreamのベンチマーク(比較用) + { + BenchmarkTimer timer("std::ostringstream - Simple concatenation"); + for (int i = 0; i < iterations; ++i) { + std::ostringstream oss; + oss << "Hello" << " " << "World" << i; + std::string result = oss.str(); + // 結果を使用して最適化を防ぐ + volatile auto len = result.length(); + (void)len; + } + } + + // nodec::StringBuilderStreamのベンチマーク + { + BenchmarkTimer timer("StringBuilder - Simple concatenation"); + for (int i = 0; i < iterations; ++i) { + std::string result; + nodec::StringBuilder builder(result); + + builder << "Hello" << " " << "World" << i; + // 結果を使用して最適化を防ぐ + volatile auto len = result.length(); + (void)len; + } + } + + // std::string + 演算子のベンチマーク(比較用) + { + BenchmarkTimer timer("std::string + operator - Simple concatenation"); + for (int i = 0; i < iterations; ++i) { + std::string result = std::string("Hello") + " " + "World" + std::to_string(i); + // 結果を使用して最適化を防ぐ + volatile auto len = result.length(); + (void)len; + } + } + + // テストが正常に実行されたことを確認 + CHECK(true); +} + +TEST_CASE("Benchmark - Numeric Formatting") { + const int iterations = 100000; + + std::cout << "\n--- Numeric Formatting Test ---" << std::endl; + + // nodec::Formatterのベンチマーク + { + BenchmarkTimer timer("nodec::Formatter - Numeric formatting"); + for (int i = 0; i < iterations; ++i) { + std::string result = nodec::Formatter() << "Number: " << i + << ", Float: " << (i * 3.14) + << ", Hex: " << std::hex << i; + volatile auto len = result.length(); + (void)len; + } + } + + // nodec::FormatterStreamのベンチマーク + { + BenchmarkTimer timer("StringBuilder - Numeric formatting"); + for (int i = 0; i < iterations; ++i) { + std::string result; + nodec::StringBuilder builder(result); + builder << "Number: " << i << ", Float: " << (i * 3.14) << ", Hex: " << std::hex << i; + volatile auto len = result.length(); + (void)len; + } + } + + // std::ostringstreamのベンチマーク(比較用) + { + BenchmarkTimer timer("std::ostringstream - Numeric formatting"); + for (int i = 0; i < iterations; ++i) { + std::ostringstream oss; + oss << "Number: " << i << ", Float: " << (i * 3.14) << ", Hex: " << std::hex << i; + std::string result = oss.str(); + volatile auto len = result.length(); + (void)len; + } + } + + // テストが正常に実行されたことを確認 + CHECK(true); +} + +TEST_CASE("Benchmark - Long Chain") { + const int iterations = 10000; + + std::cout << "\n--- Long Chain Test ---" << std::endl; + + // nodec::Formatterのベンチマーク - 長いチェーン + { + BenchmarkTimer timer("nodec::Formatter - Long chain"); + for (int i = 0; i < iterations; ++i) { + std::string result = nodec::Formatter() << "Start: " << i + << ", Value1: " << (i * 2) + << ", Value2: " << (i * 3.5) + << ", Value3: " << (i % 100) + << ", Value4: " << std::hex << i + << ", Value5: " << std::dec << (i + 1000) + << ", End"; + volatile auto len = result.length(); + (void)len; + } + } + + // StringBuilderStreamのベンチマーク + { + BenchmarkTimer timer("StringBuilder - Long chain"); + for (int i = 0; i < iterations; ++i) { + std::string result; + nodec::StringBuilder builder(result); + builder << "Start: " << i + << ", Value1: " << (i * 2) + << ", Value2: " << (i * 3.5) + << ", Value3: " << (i % 100) + << ", Value4: " << std::hex << i + << ", Value5: " << std::dec << (i + 1000) + << ", End"; + volatile auto len = result.length(); + (void)len; + } + } + + // std::ostringstreamのベンチマーク(比較用) + { + BenchmarkTimer timer("std::ostringstream - Long chain"); + for (int i = 0; i < iterations; ++i) { + std::ostringstream oss; + oss << "Start: " << i + << ", Value1: " << (i * 2) + << ", Value2: " << (i * 3.5) + << ", Value3: " << (i % 100) + << ", Value4: " << std::hex << i + << ", Value5: " << std::dec << (i + 1000) + << ", End"; + std::string result = oss.str(); + volatile auto len = result.length(); + (void)len; + } + } + + // std::string + 演算子のベンチマーク(比較用) + { + BenchmarkTimer timer("std::string + operator - Long chain"); + for (int i = 0; i < iterations; ++i) { + std::string result = std::string("Start: ") + std::to_string(i) + + ", Value1: " + std::to_string(i * 2) + + ", Value2: " + std::to_string(i * 3.5) + + ", Value3: " + std::to_string(i % 100) + + ", Value4: " + std::to_string(i) // 16進数は複雑なので10進数で代用 + + ", Value5: " + std::to_string(i + 1000) + + ", End"; + volatile auto len = result.length(); + (void)len; + } + } + + // テストが正常に実行されたことを確認 + CHECK(true); +} diff --git a/tests/string_builder/oss_compat_test.cpp b/tests/string_builder/oss_compat_test.cpp new file mode 100644 index 0000000..8f380df --- /dev/null +++ b/tests/string_builder/oss_compat_test.cpp @@ -0,0 +1,181 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + +#include +#include + +#include + +TEST_CASE("StringBuilder - Enhanced Manipulator Support") { + // setw テスト + std::string buffer1; + nodec::StringBuilder stream1(buffer1); + stream1 << std::setw(10) << 42; + CHECK(buffer1 == " 42"); // 右揃え、幅10 + + // setfill テスト + std::string buffer2; + nodec::StringBuilder stream2(buffer2); + stream2 << std::setfill('0') << std::setw(5) << 123; + CHECK(buffer2 == "00123"); + + // setprecision テスト + std::string buffer3; + nodec::StringBuilder stream3(buffer3); + stream3 << std::fixed << std::setprecision(2) << 3.14159; + CHECK(buffer3 == "3.14"); + + // 組み合わせテスト + std::string buffer4; + nodec::StringBuilder stream4(buffer4); + stream4 << std::setfill('*') << std::setw(8) << "Hi"; + CHECK(buffer4 == "******Hi"); +} + +TEST_CASE("StringBuilder - Number Base Support") { + // 16進数テスト(小文字) + std::string buffer1; + nodec::StringBuilder stream1(buffer1); + stream1 << std::hex << 255; + CHECK(buffer1 == "ff"); + + // 16進数テスト(大文字) + std::string buffer2; + nodec::StringBuilder stream2(buffer2); + stream2 << std::hex << std::uppercase << 255; + CHECK(buffer2 == "FF"); + + // 16進数 with showbase + std::string buffer3; + nodec::StringBuilder stream3(buffer3); + stream3 << std::hex << std::showbase << 255; + CHECK(buffer3 == "0xff"); + + // 8進数テスト + std::string buffer4; + nodec::StringBuilder stream4(buffer4); + stream4 << std::oct << 64; + CHECK(buffer4 == "100"); + + // 8進数 with showbase + std::string buffer5; + nodec::StringBuilder stream5(buffer5); + stream5 << std::oct << std::showbase << 64; + CHECK(buffer5 == "0100"); + + // 10進数に戻す + std::string buffer6; + nodec::StringBuilder stream6(buffer6); + stream6 << std::hex << 16 << ", " << std::dec << 16; + CHECK(buffer6 == "10, 16"); +} + +TEST_CASE("StringBuilder - Alignment Support") { + // 左揃え + std::string buffer1; + nodec::StringBuilder stream1(buffer1); + stream1 << std::left << std::setw(8) << "Hi"; + CHECK(buffer1 == "Hi "); + + // 右揃え(デフォルト) + std::string buffer2; + nodec::StringBuilder stream2(buffer2); + stream2 << std::right << std::setw(8) << "Hi"; + CHECK(buffer2 == " Hi"); + + // 内部揃え(符号付き数値) + std::string buffer3; + nodec::StringBuilder stream3(buffer3); + stream3 << std::internal << std::setfill('0') << std::setw(8) << -42; + CHECK(buffer3 == "-0000042"); + + // 内部揃え(16進数) + std::string buffer4; + nodec::StringBuilder stream4(buffer4); + stream4 << std::hex << std::showbase << std::internal + << std::setfill('0') << std::setw(8) << 255; + CHECK(buffer4 == "0x0000ff"); +} + +TEST_CASE("StringBuilder - Bool Support") { + // 数値としてのbool + std::string buffer1; + nodec::StringBuilder stream1(buffer1); + stream1 << true << ", " << false; + CHECK(buffer1 == "1, 0"); + + // boolalpha + std::string buffer2; + nodec::StringBuilder stream2(buffer2); + stream2 << std::boolalpha << true << ", " << false; + CHECK(buffer2 == "true, false"); + + // noboolalpha + std::string buffer3; + nodec::StringBuilder stream3(buffer3); + stream3 << std::boolalpha << true << ", " + << std::noboolalpha << false; + CHECK(buffer3 == "true, 0"); +} + +TEST_CASE("StringBuilder - Float Formatting") { + // fixed フォーマット + std::string buffer1; + nodec::StringBuilder stream1(buffer1); + stream1 << std::fixed << std::setprecision(3) << 3.14159; + CHECK(buffer1 == "3.142"); + + // scientific フォーマット + std::string buffer2; + nodec::StringBuilder stream2(buffer2); + stream2 << std::scientific << std::setprecision(2) << 1234.5; + CHECK(buffer2 == "1.23e+03"); + + // uppercase scientific + std::string buffer3; + nodec::StringBuilder stream3(buffer3); + stream3 << std::scientific << std::uppercase + << std::setprecision(2) << 1234.5; + CHECK(buffer3 == "1.23E+03"); +} + +TEST_CASE("StringBuilder - ostringstream Compatibility Check") { + // 同じフォーマットでostringreamと結果比較 + int value = 42; + + // StringBuilder + std::string buffer1; + nodec::StringBuilder stream1(buffer1); + stream1 << std::setfill('0') << std::setw(6) << value; + + // ostringstream + std::ostringstream oss; + oss << std::setfill('0') << std::setw(6) << value; + std::string oss_result = oss.str(); + + CHECK(buffer1 == oss_result); + + // 16進数比較 + std::string buffer2; + nodec::StringBuilder stream2(buffer2); + stream2 << std::hex << std::uppercase << 255; + + std::ostringstream oss_hex; + oss_hex << std::hex << std::uppercase << 255; + std::string oss_hex_result = oss_hex.str(); + + CHECK(buffer2 == oss_hex_result); +} + +TEST_CASE("StringBuilder - Complex Format Chains") { + // 複雑なフォーマットチェーン + std::string buffer; + nodec::StringBuilder stream(buffer); + stream << "Value: " << std::hex << std::uppercase << std::showbase + << std::setfill('0') << std::setw(8) << 255 + << ", Float: " << std::dec << std::fixed << std::setprecision(2) << 3.14159 + << ", Bool: " << std::boolalpha << true; + + std::string expected = "Value: 00000XFF, Float: 3.14, Bool: true"; + CHECK(buffer == expected); +}