11

I wrote this really trivial class so that it's clear what my problem is:

class A
{
public:
    int x;
    A(int y) {x=y;}
    bool operator==(const A &other) const {return x==other.x;}
};

Now, if I define A first(1) and A second(1), it would seem natural to me that BOOST_CHECK_EQUAL(first, second) should pass. However, I got 50 errors when trying to do this, the first one sounds like: no math for operator << in ostr << t which is somewhere in the boost code... Other tests work just fine, comparing known types or even pointers, but there is something different that appears to happen with class objects.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
Ioana
  • 343
  • 5
  • 10
  • 2
    What were the errors? – Dennis Jul 10 '13 at 14:01
  • It doesn't compile... And I'm working in Codeblocks so I can't copy-paste the errors. And all of them are in the file test_tools.hpp and I don't really understand what they are about (I mentioned the first one in the post). Should I give more examples? – Ioana Jul 10 '13 at 14:05
  • I compiled with gcc and I would like to paste the errors but it is 11400 characters too long for a comment. – Ioana Jul 10 '13 at 14:08
  • According to [this reference](http://www.boost.org/doc/libs/1_34_1/libs/test/doc/components/test_tools/reference/BOOST_CHECK_EQUAL.html) `BOOST_CHECK_EQUAL` will print the values of non-equal arguments, so the macro expands to code which probably contains something like `cout << first << " != " << second`. Thus you have to define an output operator for your class (a non-member function `std::ostream& operator<<(std::ostream&, const A&)`). – gx_ Jul 10 '13 at 14:13
  • 1
    That sounds weird, does everybody do this when using unit tests? Isn't there some other way of checking? – Ioana Jul 10 '13 at 14:17
  • Just give us the errors generated by a single call to the macro and that should help a lot. – Dennis Jul 10 '13 at 14:22
  • Accomplishing this is trickier than it might seem, and it's not well-documented. – John Dibling Jul 10 '13 at 14:32

4 Answers4

27

There are three ways I have identified to solve the problem with operator<<.

The first way is to provide an operator<< for your type. This is needed because when boost_check_equal fails, it also logs the failure by calling operator<< with the objects. See the verbose addendum after the break to see how this is actually accomplished. It's harder than it might seem.

The second way is to not do the logging I just mentioned. You can do this by #definineing BOOST_TEST_DONT_PRINT_LOG_VALUE. To disable logging for just one test, you might surround the test in question with this #define, then immediately #undef it:

#define BOOST_TEST_DONT_PRINT_LOG_VALUE
BOOST_CHECK_EQUAL (first, second);
#undef BOOST_TEST_DONT_PRINT_LOG_VALUE

The third way is to sidestep the need for an operator<< that works with your type by not comparing one item to another, but just checking a bool:

BOOST_CHECK (first == second);

Select your preferred method.


My preference is the first, but implementing that is suprisingly challenging. If you simply define an operator<< in global scope, it won't work. I think the reason for this is because of a problem with name resolution. One popular suggestion to fix this is to put the operator<< in the std namespace. This works, at least in practice on some compilers, but I don't like it because the Standard prohibits adding anything to the std namespace.

A better method I found is to implement a custom print_log_value class template specialization for your type. print_log_value is a class template useb by the Boost.Test internals to actually call the correct operator<< for the specified type. It delegates to an operator<< to do the heavy lifting. Specializing print_log_value for your custom types is officially supported by Boost [citation needed], and is accomplished thusly.

Assuming your type is called Timestamp (it is in my code), first define a global free operator<< for Timestamp:

static inline std::ostream& operator<< (std::ostream& os, const Mdi::Timestamp& ts)
{
    os << "Timestamp";
    return os;
}   

...and then provide the print_log_value specialization for it, delegating to the operator<< you just defined:

namespace boost { namespace test_tools {
template<>           
struct print_log_value<Mdi::Timestamp > {
void operator()( std::ostream& os,
    Mdi::Timestamp const& ts)
{
    ::operator<<(os,ts);
}
};                                                          
}}
John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • I understand, thank you, I'll do a simple boost_check () for now, since I really need to test some things pretty urgently, but I'll consider the other methods for later. – Ioana Jul 10 '13 at 14:28
  • 6
    At least as of boost_1.61.00 you have to specify the `print_log_value` template inside `boost::test_tools::tt_detail`. – Ace7k3 Sep 28 '16 at 12:08
  • Thank you for this clear explanation of the problem and solution. Yet another variation is that you may simply add an ``operator<<`` to the ``boost::test_tools::tt_detail`` namespace rather than specializing ``print_log_value``. See [my answer](http://stackoverflow.com/a/40747110/3154588) below for more details with the proper formatting. – Phil Nov 22 '16 at 16:39
  • Maybe it's my version of Boost, but I find that `BOOST_CHECK(a == b);` still requires `operator<<`, but you can trick it into cooperating with `BOOST_CHECK((a == b))` – codegardener Mar 07 '22 at 16:41
5

This is a supplement to the excellent answer from John Dibling. The problem seems to be that there needs to be an output operator in the right namespace. So if you have a global output operator<< defined, then you may avoid this error (at least with Visual Studio 2015, aka vc14, and boost 1.60) by defining another one in the boost::test_tools::tt_detail namespace that forwards to the global operator. This minor tweak enables one to avoid a strange and more verbose specialization of the print_log_value class. Here is what I did:

namespace boost {
namespace test_tools {
namespace tt_detail {
std::ostream& operator<<(std::ostream& os, Mdi::Timestamp const& ts)
{
    return ::operator<<(os, ts);
}
}  // namespace tt_detail
}  // namespace test_tools
}  // namespace boost

While it has been three years since this question and answer have been posted, I have not seen this discussed clearly in the Boost.Test documentation.

Phil
  • 5,822
  • 2
  • 31
  • 60
4

Based on John Dibling's answer I was looking for a way to dump integer values in HEX rather than decimal I came up with this approach:

// test_macros.h in my project
namespace myproject
{
namespace test
{
namespace macros
{
    extern bool hex;

    // context manager
    struct use_hex
    {
        use_hex()  { hex = true; }
        ~use_hex() { hex = false; }
    };

 }; // namespace
 }; // namespace
 }; // namespace

namespace boost
{
namespace test_tools
{

    // boost 1.56+ uses these methods

    template<>
    inline                                               
    void                                                 
    print_log_value<uint64>::                       
    operator()(std::ostream & out, const uint64 & t) 
    {                                                    
        if(myproject::test::macros::hex)                    
            out << ::boost::format("0x%016X") % t;           
        else 
            out << t;                                        
    }                                                    

namespace tt_detail
{

    // Boost < 1.56 uses these methods

    template <>
    inline
    std::ostream &
    operator<<(std::ostream & ostr, print_helper_t<uint64> const & ph )
    {
        if(myproject::test::macros::hex)
            return ostr << ::boost::format("0x%016X") % ph.m_t;

        return ostr << ph.m_t;
    }

}; // namespace
}; // namespace
}; // namespace

Now in my unit test case, I can turn on/off hex by setting the global static bool value, for example:

for(uint64 i = 1; i <= 256/64; ++i)
{
    if(i % 2 == 0) test::macros::hex = true;
    else           test::macros::hex = false;
    BOOST_CHECK_EQUAL(i+1, dst.pop());
}

And I get the behavior I was looking for:

test_foobar.cc(106): error in "test_foobar_51": check i+1 == dst.pop() failed [2 != 257]
test_foobar.cc(106): error in "test_foobar_51": check i+1 == dst.pop() failed [0x0000000000000003 != 0x0000000000000102]
test_foobar.cc(106): error in "test_foobar_51": check i+1 == dst.pop() failed [4 != 259]
test_foobar.cc(106): error in "test_foobar_51": check i+1 == dst.pop() failed [0x0000000000000005 != 0x0000000000000104]

Alternatively, I can use the context manager:

{
    test::macros::use_hex context;

    for(uint64 i = 1; i <= 4; ++i)
    {
        BOOST_CHECK_EQUAL(i + 0x200, i + 0x100);
    }
}

for(uint64 i = 1; i <= 4; ++i)
{
    BOOST_CHECK_EQUAL(i + 0x200, i + 0x100);
}

And hex output will only be used in that block:

test_foobar.cc(94): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [0x0000000000000201 != 0x0000000000000101]
test_foobar.cc(94): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [0x0000000000000202 != 0x0000000000000102]
test_foobar.cc(94): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [0x0000000000000203 != 0x0000000000000103]
test_foobar.cc(94): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [0x0000000000000204 != 0x0000000000000104]
test_foobar.cc(100): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [513 != 257]
test_foobar.cc(100): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [514 != 258]
test_foobar.cc(100): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [515 != 259]
test_foobar.cc(100): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [516 != 260]
Community
  • 1
  • 1
Nick
  • 2,342
  • 28
  • 25
  • +1: Just saw this now, and I'm a fan. Something like this is very handy when trying to dump things like UDP packets. – John Dibling Aug 23 '14 at 16:49
3

There is a clean way starting Boost 1.64 to log user defined types via the customization points. The full documentation of this feature can be found here.

An example from the documentation is given below. The idea is to define the function boost_test_print_type for the type you want to print, and to bring this function into the test case (found via ADL):

#define BOOST_TEST_MODULE logger-customization-point
#include <boost/test/included/unit_test.hpp>

namespace user_defined_namespace {
  struct user_defined_type {
      int value;

      user_defined_type(int value_) : value(value_)
      {}

      bool operator==(int right) const {
          return right == value;
      }
  };
}

namespace user_defined_namespace {
  std::ostream& boost_test_print_type(std::ostream& ostr, user_defined_type const& right) {
      ostr << "** value of user_defined_type is " << right.value << " **";
      return ostr;
  }
}

BOOST_AUTO_TEST_CASE(test1)
{
    user_defined_namespace::user_defined_type t(10);
    BOOST_TEST(t == 11);

    using namespace user_defined_namespace;
    user_defined_type t2(11);
    BOOST_TEST(t2 == 11);
}
Raffi
  • 3,068
  • 31
  • 33
  • This worked for me, but I didn't have to define the BOOST_TEST_MODULE variable. I also had my operator<< already defined on the global scope, so I modified my code to look like this in the test file: namespace foobar { std::ostream& boost_test_print_type(std::ostream& os, const mytype& foo { return ::operator<<(os, foo); } } I might end up putting these common things into a test helper file since I have several compilation units and test files. – alex Aug 05 '20 at 16:06