Issue with printing std::vector content
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:
Platform information:
- OS: Windows 11 23H2
- Compiler+version: Visual Studio 2022 v17.13.2 / 19.43.34808
- Catch version: v3.8.0
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();
}
};
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.
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.
@ChrisThrasher Is it possible to reopen this issue?
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...