Catch2 icon indicating copy to clipboard operation
Catch2 copied to clipboard

Issue with printing std::vector content

Open neusdan opened this issue 10 months ago • 5 comments

Describe the bug There seems to be a bug when printing the content of some std::vectors.

Reproduction steps

TEST_CASE( "DebugTest", "[DebugTest]" )
{
    std::vector<uint8_t> vec = { 0x4e, 0xf2, 0xde, 0x6a, 0xa2, 0x9b, 0x66, 0x3d, 0x7b, 0x3e, 0x80, 0x2e, 0x22, 0x66, 0x48, 0x01, 0xeb, 0x10, 0x2d,
                                 0xc3, 0x5a, 0xfd, 0x61, 0xd5, 0xcb, 0x21, 0x23, 0xea, 0x94, 0x05, 0xc2, 0x92, 0xe4, 0xab, 0x05, 0xf9, 0xce, 0x65,
                                 0x54, 0xd8, 0x19, 0xd7, 0x0c, 0x04, 0x0d, 0x80, 0x7a, 0x70, 0x9e, 0xc5, 0x61, 0x61, 0xac, 0x09, 0x78, 0x12, 0xf8,
                                 0xaf, 0xf1, 0x16, 0xb0, 0x7f, 0x31, 0x97, 0x6c, 0x0e, 0xa0, 0xaa, 0x05, 0xfd, 0xd7, 0xaf, 0x5f, 0xfc, 0x39, 0xcf,
                                 0x91, 0x6d, 0x96, 0x4d, 0xc6, 0x46, 0xda, 0xc5, 0xae, 0x30, 0x5c, 0x3b, 0x6c, 0x83, 0x34, 0x63, 0x0c, 0x37, 0x05,
                                 0xed, 0x05, 0xf4, 0x01, 0xd5, 0xf6, 0x8e, 0xb3, 0x73, 0x6f, 0x79, 0x5e, 0x56, 0x35, 0x4d, 0x80, 0x69, 0x3a, 0x7d,
                                 0x77, 0x3d, 0xd4, 0x85, 0x54, 0x57, 0x62, 0x54, 0xd0, 0xca, 0x34, 0x0f, 0x6f, 0xe3, 0x88, 0x54, 0x2f, 0xb3, 0xcd,
                                 0xac, 0xfa, 0x09, 0xb3, 0xcc, 0x58, 0xf2, 0xe3, 0x98, 0xd7, 0x8e, 0x7d, 0x11, 0x15, 0x6b, 0xca, 0x67, 0x87, 0x21,
                                 0xb2, 0xa8, 0xea, 0x26, 0x42, 0x5f, 0x7b, 0x96, 0x5c, 0xd0, 0x9f, 0x26, 0xf4, 0x39, 0x61, 0xd0, 0x4a, 0x4b, 0xff };

    REQUIRE( vec == vec );
}

Execute the test binary with "DebugTest" --success

Result:

Image

Platform information:

  • OS: Windows 11 23H2
  • Compiler+version: Visual Studio 2022 v17.13.2 / 19.43.34808
  • Catch version: v3.8.0

neusdan avatar Mar 05 '25 16:03 neusdan

uint8_t is unsigned char which is a character type therefore it is printed as a char

Use std::byte to avoid this.

Let's be more clear.

From cppreference

There is this non-member function.

template< class Traits >

basic_ostream<char, Traits>&
    operator<<( basic_ostream<char, Traits>& os, const unsigned char* s );

Therefore const unsigned char* will satisfy IsStreamInsertible and Catch2 uses it.

    template <typename T, typename = void>
    struct StringMaker {
        template <typename Fake = T>
        static
        std::enable_if_t<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>
            convert(const Fake& value) {
                ReusableStringStream rss;
                // NB: call using the function-like syntax to avoid ambiguity with
                // user-defined templated operator<< under clang.
                rss.operator<<(value);
                return rss.str();
        }

    };

ZXShady avatar Apr 03 '25 00:04 ZXShady

uint8_t is unsigned char which is a character type therefore it is printed as a char

You're basically printing an array of characters, therefore they printed as characters. I'm not sure what the library can do about this. It's just a corner of C++'s type system where you sometimes get undesirable results. I encourage you to try the above suggestion to use a vector of std::byte.

ChrisThrasher avatar Jun 14 '25 16:06 ChrisThrasher

I'd like to request this issue be reopened.

The problem is not the format of the printing; the problem is that Catch breaks when trying to wrap the formatted expression. This is due to https://github.com/catchorg/Catch2/pull/2849 which uses 0xFF as a sentinel value. If that is present in the stringified value, you will get an incorrect value displayed ('m'), and if it wraps, you will hit an assertion failure in debug mode or an exception in std::string in release:

TEST_CASE("Test") {
    CHECK('\xff' == -1);
    std::string v(256, 0xFF);
    REQUIRE(v == v);
}
-------------------------------------------------------------------------------
Test
-------------------------------------------------------------------------------
test.cpp:44
...............................................................................

test.cpp:46: PASSED:
  CHECK( '\xff' == -1 )
with expansion:
  'm' == -1

test.cpp:47: PASSED:
  REQUIRE( v == v )
with expansion:
test: src/catch2/internal/catch_textflow.cpp:127: void Catch::TextFlow::AnsiSkippingString::const_iterator::unadvance(): Assertion `m_it != m_string->begin()' failed.
test.cpp:47: FAILED:
  REQUIRE( v == v )
due to a fatal error condition:
  SIGABRT - Abort (abnormal termination) signal

===============================================================================
test cases: 1 | 1 failed
assertions: 3 | 2 passed | 1 failed

Aborted (core dumped)

This bug was introduced in 3.6.0.

dmccabe-jt avatar Jul 01 '25 15:07 dmccabe-jt

@ChrisThrasher Is it possible to reopen this issue?

neusdan avatar Aug 20 '25 15:08 neusdan

We're blocked on updating Catch2 (stuck on v3.5.3 ATM), as a pretty large chunk of our testing is about byte arrays.

Following the excellent reply by @dmccabe-jt, a minimal repro relevant for us is

TEST_CASE("Test", "[debug]")
{
    std::vector<uint8_t> v(31, 0xff); // 31 is about where line wrapping begins
    REQUIRE(v == v);
}

Using std::vector<uint16_t> v(31, 0xff); works fine, of course, but changing all our container types is not really feasible...

jtunhag avatar Oct 24 '25 08:10 jtunhag