[PATCH v3 1/4] libstdc++: Add tests for format_to with ostreambuf_iterator
Tomasz Kamiński <[email protected]>
| Newsgroups | gmane.comp.gcc.patches,gmane.comp.gcc.libstdc++.devel |
|---|---|
| Message-ID | <[email protected]> |
From: Anlai Lu <[email protected]> Cover format_to, format_to_n truncation, write failure, and large writes that exceed the internal buffer. These paths were previously untested. Additionally includes examples of formatters that observe if the output is buffered (string is created by call to format), before inserting to stream. libstdc++-v3/ChangeLog: * estsuite/27_io/basic_ostream/print/buffered.cc: New test. * testsuite/std/format/functions/format_to_ostreambuf.cc: New test. Co-authored-by: Tomasz Kamiński <[email protected]> Signed-off-by: Anlai Lu <[email protected]> Signed-off-by: Tomasz Kamiński <[email protected]> --- v3 adds test suggested by Anlai Lu in his review. .../27_io/basic_ostream/print/buffered.cc | 90 +++++ .../format/functions/format_to_ostreambuf.cc | 340 ++++++++++++++++++ 2 files changed, 430 insertions(+) create mode 100644 libstdc++-v3/testsuite/27_io/basic_ostream/print/buffered.cc create mode 100644 libstdc++-v3/testsuite/std/format/functions/format_to_ostreambuf.cc diff --git a/libstdc++-v3/testsuite/27_io/basic_ostream/print/buffered.cc b/libstdc++-v3/testsuite/27_io/basic_ostream/print/buffered.cc new file mode 100644 index 00000000000..04ef159db39 --- /dev/null +++ b/libstdc++-v3/testsuite/27_io/basic_ostream/print/buffered.cc @@ -0,0 +1,90 @@ +// { dg-additional-options "-lstdc++exp" { target { *-*-mingw* } } } +// { dg-do run { target c++23 } } + +#include <sstream> +#include <algorithm> +#include <testsuite_hooks.h> + +struct ThrowingFormat +{ + std::string val; + bool throw_from_format = false; +}; + +template<> +struct std::formatter<ThrowingFormat, char> +{ + constexpr + std::format_parse_context::iterator + parse(std::format_parse_context& ctx) const + { return ctx.begin(); } + + template<typename Out> + Out + format(const ThrowingFormat& t, std::basic_format_context<Out, char>& fc) const + { + if (t.throw_from_format) + throw std::logic_error("Formatting stopped"); + return std::ranges::copy(t.val, fc.out()).out; + } +}; + +void +test_throwing() +{ + std::string s; + s.reserve(100); + std::ostringstream out(std::move(s)); + ThrowingFormat tf1{std::string(500, 'a'), false}, tf2{std::string(500, 'b'), true}; + + try + { + std::print(out, "{} {}", tf1, tf2); + VERIFY(false); + } catch (...) { + VERIFY(true); + } + VERIFY( out.view().empty() ); +} + +struct MixedFormat +{ + std::ostream* out; + std::string val; +}; + +template<> +struct std::formatter<MixedFormat, char> +{ + constexpr + std::format_parse_context::iterator + parse(std::format_parse_context& ctx) const + { return ctx.begin(); } + + template<typename Out> + Out + format(const MixedFormat& t, std::basic_format_context<Out, char>& fc) const + { + if (t.out) + *t.out << "<<[" << t.val << "]"; + return std::ranges::copy(t.val, fc.out()).out; + } +}; + +void +test_interleaved() +{ + std::string s; + s.reserve(100); + std::ostringstream out(std::move(s)); + MixedFormat mf1{&out, "abc"}, mf2{&out, "DEF"}; + + std::print(out, "f[{}]f[{}]", mf1, mf2); + VERIFY( out.view() == "<<[abc]<<[DEF]f[abc]f[DEF]" ); +} + +int main() +{ + test_throwing(); + test_interleaved(); +} diff --git a/libstdc++-v3/testsuite/std/format/functions/format_to_ostreambuf.cc b/libstdc++-v3/testsuite/std/format/functions/format_to_ostreambuf.cc new file mode 100644 index 00000000000..1292057ff5c --- /dev/null +++ b/libstdc++-v3/testsuite/std/format/functions/format_to_ostreambuf.cc @@ -0,0 +1,340 @@ +// { dg-do run { target c++20 } } + +#include <format> +#include <sstream> +#include <string> +#include <testsuite_hooks.h> + +template<std::streamsize BufSize> +struct ShortWriteStreambuf : std::streambuf +{ + std::string data; + + std::streamsize + xsputn(const char_type* s, std::streamsize n) override + { + std::streamsize limit = std::max<std::streamsize>(0, BufSize - data.size()); + std::streamsize to_write = std::min(n, limit); + if (to_write > 0) + data.append(s, to_write); + return to_write; + } + + int_type + overflow(int_type c) override + { + if (data.size() < BufSize) + { + data.push_back(traits_type::to_char_type(c)); + return c; + } + return traits_type::eof(); + } +}; + +bool +check_suffix(std::string_view s, size_t n, char c) +{ + if (s.size() < n) + return false; + + s.remove_prefix(s.size() - n); + for (char v : s) + if (v != c) + return !n; + else if (!n) + return false; + else + --n; + return !n; +} + +void +test_format_to() +{ + { + std::ostringstream os; + std::format_to(std::ostreambuf_iterator<char>(os), "{}", 42); + VERIFY( os.view() == "42" ); + } + + { + std::ostringstream os; + std::string big(800, 'y'); + std::format_to(std::ostreambuf_iterator<char>(os), "{}", big); + VERIFY( check_suffix(os.view(), 800, 'y') ); + } + + { + std::ostringstream os; + std::string part(700, 'P'); + std::format_to(std::ostreambuf_iterator<char>(os), + "{}{}{}", part, part, part); + VERIFY( check_suffix(os.view(), 2100, 'P') ); + } + + { + std::ostringstream os; + std::string body(400, 'B'); + std::format_to(std::ostreambuf_iterator<char>(os), + "header-{}-footer", body); + + std::string_view sv = os.view(); + VERIFY( sv.starts_with("header-") ); + sv.remove_prefix(7); + VERIFY( sv.ends_with("-footer") ); + sv.remove_suffix(7); + VERIFY( check_suffix(sv, 400, 'B') ); + } +} + +void +test_format_to_n() +{ + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 1, "{}", "A"); + VERIFY( res.size == 1 ); + VERIFY( os.view() == "A" ); + } + + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 1, "{}", "AAA"); + VERIFY( res.size == 3 ); + VERIFY( os.view() == "A" ); + } + + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 3, "{}", "hello"); + VERIFY( res.size == 5 ); + VERIFY( os.view() == "hel" ); + } + + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 10, "{}", std::string(100, 'x')); + VERIFY( res.size == 100 ); + VERIFY( check_suffix(os.view(), 10, 'x') ); + } + + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 10, "{}", std::string(1000, 'x')); + VERIFY( res.size == 1000 ); + VERIFY( check_suffix(os.view(), 10, 'x') ); + } + + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 256, "{}", std::string(1000, 'x')); + VERIFY( res.size == 1000 ); + VERIFY( check_suffix(os.view(), 256, 'x') ); + } + + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 257, "{}", std::string(1000, 'x')); + VERIFY( res.size == 1000 ); + VERIFY( check_suffix(os.view(), 257, 'x') ); + } + + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 8, "{}{}{}", "AAAA", "BBBB", "CCCC"); + VERIFY( res.size == 12 ); + VERIFY( os.view() == "AAAABBBB" ); + } + + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 2, "{}{}", "AAAA", "BBBB"); + VERIFY( res.size == 8 ); + VERIFY( os.view() == "AA" ); + } + + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 8, "{}{}", "AAAA", "BBBB"); + VERIFY( res.size == 8 ); + VERIFY( os.view() == "AAAABBBB" ); + } + + { + std::string big(257, 'A'); + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 257, "{}", big); + VERIFY( res.size == 257 ); + std::string_view sv = os.view(); + VERIFY( os.view() == big ); + } + + { + std::string big(300, 'A'); + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 257, "{}", big); + VERIFY( res.size == 300 ); + VERIFY( check_suffix(os.view(), 257, 'A') ); + } +} + +void +test_format_to_n_padding() +{ + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 3, "{:>10}", "hello"); + VERIFY( res.size == 10 ); + VERIFY( os.view().size() == 3 ); + } + + { + std::ostringstream os; + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 3, "{:.5}", std::string(100, 'z')); + VERIFY( res.size == 5 ); + VERIFY( check_suffix(os.view(), 3, 'z') ); + } +} + +void +test_format_to_n_prefilled() +{ + // Pre-filled ostringstream: after seekp(0) the streambuf has a put + // area pointing into existing data. This exercises the + // _M_use_put_area code path combined with format_to_n truncation. + { + std::string init(100, 'Z'); + std::ostringstream os(init); + os.seekp(0); + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 5, "{}", "1234567890"); + VERIFY( res.size == 10 ); + VERIFY( os.view().substr(0, 10) == "12345ZZZZZ" ); + VERIFY( check_suffix(os.view(), 90, 'Z') ); + } + { + std::string init(100, 'Z'); + std::ostringstream os(init); + os.seekp(0); + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 8, "{:=>15}", "1234567890"); + VERIFY( res.size == 15 ); + VERIFY( os.view().substr(0, 10) == "=====123ZZ" ); + VERIFY( check_suffix(os.view(), 90, 'Z') ); + } + { + std::string init(100, 'Z'); + std::ostringstream os(init); + os.seekp(0); + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 3, "{}", 12345); + VERIFY( res.size == 5 ); + VERIFY( os.view().substr(0, 10) == "123ZZZZZZZ" ); + VERIFY( check_suffix(os.view(), 90, 'Z') ); + } + { + std::string init(100, 'Z'); + std::ostringstream os(init); + os.seekp(0); + auto res = std::format_to_n(std::ostreambuf_iterator<char>(os), + 7, "{:=>10}", 12345); + VERIFY( res.size == 10 ); + VERIFY( os.view().substr(0, 10) == "=====12ZZZ" ); + VERIFY( check_suffix(os.view(), 90, 'Z') ); + } +} + +void +test_write_failure() +{ + const std::string payload(20, 'Z'); + + { + ShortWriteStreambuf<20> buf; + auto it = std::ostreambuf_iterator<char>(&buf); + auto res = std::format_to(it, "{}", payload); + VERIFY( !res.failed() ); + VERIFY( check_suffix(buf.data, 20, 'Z') ); + } + + { + ShortWriteStreambuf<19> buf; + auto it = std::ostreambuf_iterator<char>(&buf); + auto res = std::format_to(it, "{}", payload); + VERIFY( res.failed() ); + VERIFY( check_suffix(buf.data, 19, 'Z') ); + } + + { + ShortWriteStreambuf<10> buf; + auto it = std::ostreambuf_iterator<char>(&buf); + auto res = std::format_to_n(it, 10, "{}", payload); + VERIFY( !res.out.failed() ); + VERIFY( res.size == 20 ); + VERIFY( check_suffix(buf.data, 10, 'Z') ); + } + + { + ShortWriteStreambuf<10> buf; + auto it = std::ostreambuf_iterator<char>(&buf); + auto res = std::format_to_n(it, 11, "{}", payload); + VERIFY( res.out.failed() ); + VERIFY( res.size == 20 ); + VERIFY( check_suffix(buf.data, 10, 'Z') ); + } + + { + ShortWriteStreambuf<100> buf; + auto it = std::ostreambuf_iterator<char>(&buf); + auto res = std::format_to(it, "{}", std::string(300, 'Y')); + VERIFY( res.failed() ); + VERIFY( buf.data.size() == 100 ); + VERIFY( check_suffix(buf.data, 100, 'Y') ); + } + // format_to_n after write failure: size must still be the total + // number of characters that would have been produced. + { + ShortWriteStreambuf<50> buf; + auto it = std::ostreambuf_iterator<char>(&buf); + auto res = std::format_to_n(it, 100, + "{}{}{}", + std::string(20, 'A'), + std::string(30, 'B'), + std::string(40, 'C')); + // The first two arguments fill the buffer (20 + 30 == 50). + // The third argument (40 C's) triggers write failure and the + // remaining chars must still be counted in res.size. + VERIFY( res.out.failed() ); + VERIFY( res.size == 90 ); + + std::string_view sv = buf.data; + VERIFY( buf.data.size() == 50 ); + VERIFY( check_suffix(sv, 30, 'B') ); + sv.remove_suffix(30); + VERIFY( check_suffix(sv, 20, 'A') ); + } +} + +int main() +{ + test_format_to(); + test_format_to_n(); + test_format_to_n_padding(); + test_format_to_n_prefilled(); + test_write_failure(); +} -- 2.55.0