In Catch Unit Test v1.8.1, with gcc 6.2.0, I'm trying to conveniently output the contents of a vector when a test fails by passing the vector to INFO(...)
or CAPTURE(...)
. To do so I'm overloading the stream insertion operator:
#include <Catch/single_include/catch.hpp>
#include <vector>
#include <iostream>
#define THIS_WORKS_BUT_EXTENDING_NAMESPACE_STD_IS_ILLEGAL
#ifdef THIS_WORKS_BUT_EXTENDING_NAMESPACE_STD_IS_ILLEGAL
namespace std {
#endif
std::ostream& operator<<( std::ostream& os, const std::vector<int>& v ) {
for ( const auto& e : v ) {
os << e << " ";
}
return os;
}
#ifdef THIS_WORKS_BUT_EXTENDING_NAMESPACE_STD_IS_ILLEGAL
} //namespace std
#endif
int some_operation_on_vector( const std::vector<int>& v ) {
return 1;
}
SCENARIO( "some scenario" )
{
GIVEN( "a vector" )
{
const auto the_vector = std::vector<int>{ 1, 2, 3, 4, 5 };
WHEN( "some result is calculated from the vector" )
{
const auto actual_result = some_operation_on_vector( the_vector );
THEN( "the result should be correct. If not, print out the vector." )
{
const auto expected_result = 0;
CAPTURE( the_vector ); // <--------
//^^^^
//How do I legally make this work?
REQUIRE( expected_result == actual_result );
}
}
}
}
If I (illegally) extend the std
namespace as above, then it works, and I see the desired output:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
catchtestexample is a Catch v1.8.1 host application.
Run with -? for options
-------------------------------------------------------------------------------
Scenario: some scenario
Given: a vector
When: some result is calculated from the vector
Then: the result should be correct. If not, print out the vector.
-------------------------------------------------------------------------------
ExampleTest.cpp:91
...............................................................................
ExampleTest.cpp:95: FAILED:
REQUIRE( expected_result == actual_result )
with expansion:
0 == 1
with message:
the_vector := 1 2 3 4 5
===============================================================================
test cases: 1 | 1 failed
assertions: 1 | 1 failed
But to try to be legal, when I try to move the operator<<
overload out of the std
namespace and into the global namespace (by commenting out #define THIS_WORKS_BUT_EXTENDING_NAMESPACE_STD_IS_ILLEGAL
), the code doesn't compile due to passing a vector to the CAPTURE()
macro.
Per the Catch docs, I tried replacing the operator <<
overload with a Catch::toString
overload:
#include <string>
#include <sstream>
namespace Catch {
std::string toString( const std::vector<int>& v ) {
std::ostringstream ss;
for ( const auto& e : v ) {
ss << e << " ";
}
return ss.str();
}
}
or with a Catch::StringMaker
specialisation:
#include <string>
#include <sstream>
namespace Catch {
template<> struct StringMaker<std::vector<int>> {
static std::string convert( const std::vector<int>& v ) {
std::ostringstream ss;
for ( const auto& e : v ) {
ss << e << " ";
}
return ss.str();
}
};
}
but in either case the test still doesn't compile, due to passing a vector to the CAPTURE()
macro.
The Catch docs say to put the operator<<
overload into the same namespace as your type, but std::vector
is not my type, and putting that overload into namespace std
is illegal.
But the only way I've been able to find to get CAPTURE()
(or INFO()
, or WARN()
, etc.) to accept a std::vector
argument is to illegally put the operator<<
overload into namespace std
.
Is there a proper, legal way to do this?
I think I found some solutions better than the one I gave previously:
Update Catch to v1.8.2 or newer. From some quick tests, it looks like v1.8.2 added support for std::vector
in CAPTURE macros, without any extra effort on your part. Overloading operator<<
for std::vector
isn't needed in this case.
If you can't update to Catch v1.8.2 or newer for whatever reason, this solution is similar to the proposed solution in my original question, but with improvements based on this answer from C++ committee member Jonathan Wakely (Thank you!).
He gives the following advice:
Don't overload operators for types you don't control.
...
Instead create a tiny adaptor class and define the operator for that...
So with that in mind:
#include <Catch/single_include/catch.hpp>
#include <vector>
#include <iostream>
template <typename T> struct PrintableVector {
const std::vector<T>& vec;
};
template <typename T>
PrintableVector<T> makePrintable( const std::vector<T>& vec ) {
return PrintableVector<T>{ vec };
}
template <typename T>
std::ostream& operator<<( std::ostream& os, const PrintableVector<T>& printableVec ) {
for ( const auto& e : printableVec.vec ) {
os << e << " ";
}
return os;
}
int some_operation_on_vector( const std::vector<int>& v ) {
return 1;
}
SCENARIO( "some scenario" )
{
GIVEN( "a vector" )
{
const auto the_vector = std::vector<int>{ 1, 2, 3, 4, 5 };
WHEN( "some result is calculated from the vector" )
{
const auto actual_result = some_operation_on_vector( the_vector );
THEN( "the result should be correct. If not, print out the vector." )
{
const auto expected_result = 0;
CAPTURE( makePrintable( the_vector ) );
REQUIRE( expected_result == actual_result );
}
}
}
}
This compiles and runs on Catch v1.8.1, and gives the following output:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
catchtestexample is a Catch v1.8.1 host application.
Run with -? for options
-------------------------------------------------------------------------------
Scenario: some scenario
Given: a vector
When: some result is calculated from the vector
Then: the result should be correct. If not, print out the vector.
-------------------------------------------------------------------------------
main.cpp:43
...............................................................................
main.cpp:47: FAILED:
REQUIRE( expected_result == actual_result )
with expansion:
0 == 1
with message:
makePrintable( the_vector ) := 1 2 3 4 5
===============================================================================
test cases: 1 | 1 failed
assertions: 1 | 1 failed