Can I know which exception was thrown inside a gtest EXPECT_NO_THROW (or ASSERT_NO_THROW)?
Asked Answered
C

3

5

To test my C++ project I am using GoogleTest framework. Normally I can use the following syntax to easily debug a failure:

EXPECT_TRUE(*statement*) << *debugMessage*;

When I use the macro EXPECT_NO_THROW (or ASSERT_NO_THROW) I might of course do the same, but I do not have access to the exception object that was thrown (and caught) inside the macro itself and so the debugMessage cannot tell me anything about it.

Is it possible to show information about this exception in any way?

EDIT

Is not possible without any custom function/macro.

Cham answered 22/3, 2017 at 15:46 Comment(1)
My solution is not to use EXPECT_NO_THROW at all. If/when it throws, then you'll see the exception message as the gtest harness will display it. However, this only works for the first exception, because the test ends right away.Kalvn
R
5

Here's one way:

#include <exception>
#include <stdexcept>
#include <ostream>
#include <iostream> // for the test
#include <gtest/gtest.h>
namespace detail {

    struct unwrapper
    {
        unwrapper(std::exception_ptr pe) : pe_(pe) {}

        operator bool() const {
            return bool(pe_);
        }

        friend auto operator<<(std::ostream& os, unwrapper const& u) -> std::ostream&
        {
            try {
                std::rethrow_exception(u.pe_);
                return os << "no exception";
            }
            catch(std::runtime_error const& e)
            {
                return os << "runtime_error: " << e.what();
            }
            catch(std::logic_error const& e)
            {
                return os << "logic_error: " << e.what();
            }
            catch(std::exception const& e)
            {
                return os << "exception: " << e.what();
            }
            catch(...)
            {
                return os << "non-standard exception";
            }

        }
        std::exception_ptr pe_;
    };

}

auto unwrap(std::exception_ptr pe)
{
    return detail::unwrapper(pe);
}


template<class F>
::testing::AssertionResult does_not_throw(F&& f)
         {
             try {
                 f();
                 return ::testing::AssertionSuccess();
             }
             catch(...) {
                 return ::testing::AssertionFailure() << unwrap(std::current_exception());
             }
         };


TEST(a, b)
{
    ASSERT_TRUE(does_not_throw([] { throw std::runtime_error("i threw"); }));
}

example output:

Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from a
[ RUN      ] a.b
/Users/rhodges/play/project/nod.cpp:66: Failure
Value of: does_not_throw([] { throw std::runtime_error("i threw"); })
  Actual: false (runtime_error: i threw)
Expected: true
[  FAILED  ] a.b (1 ms)
[----------] 1 test from a (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] a.b

 1 FAILED TEST
Rink answered 22/3, 2017 at 16:6 Comment(0)
D
4

An alternative to the answer by Richard Hodges, is to use a try-catch structure inside the test-body. This solution comes from the very good book Modern C++ Programming with Test-Driven Development written by Jeff Langr.

A complete working example could look like the following:

#include <stdexcept>
#include "gtest/gtest.h"

struct foo
{
  void bar() {
    throw std::runtime_error("unexpected error");
  }
};

TEST(foo_test, does_not_throw)
{
  foo f;
  try {
    f.bar();
    SUCCEED();
  }
  catch (std::exception const & err) {
    FAIL() << err.what();
  }
}

int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

And the ouput:

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from foo_test
[ RUN      ] foo_test.does_not_throw
    /Users/Soeren/Documents/cmakeProject/src/applications/modelTest/main.cpp(26): error: Failed
unexpected error messages
[  FAILED  ] foo_test.does_not_throw (1 ms)
[----------] 1 test from foo_test (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (3 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] foo_test.does_not_throw

 1 FAILED TEST
Delinquent answered 24/3, 2017 at 10:1 Comment(1)
I agree. This can also easily defined in an helper function or, even simplier, a macro in order to keep the one-line structureCham
S
0

I am a little bit late to the party, but here's one approach which works:

/**
 * @brief Wrap a code block with try-catch, handle exceptions thrown, print them
 * into EXCEPT_STREAM and rethrow.
 */
#define PRINT_AND_RETHROW(CODE_BLOCK, EXCEPT_STREAM) try{do{ CODE_BLOCK }while(0);}catch(const std::exception& ex){ EXCEPT_STREAM << "std::exception thrown: " << ex.what() << std::endl; throw;  }catch(...){ EXCEPT_STREAM << "unknown structure thrown" << std::endl; throw;}


/**
 * @brief Wrap a code block with try-catch, handle exceptions thrown, print them
 * into std::cerr and rethrow.
 */
#define PRINT_STDERR_AND_RETHROW(CODE_BLOCK) PRINT_AND_RETHROW(CODE_BLOCK, std::cerr)

#define EXPECT_NO_THROW_PRINT(CODE_BLOCK) EXPECT_NO_THROW(SPECTRE_PRINT_STDERR_AND_RETHROW(CODE_BLOCK))

#define ASSERT_NO_THROW_PRINT(CODE_BLOCK) ASSERT_NO_THROW(SPECTRE_PRINT_STDERR_AND_RETHROW(CODE_BLOCK))

Later in code, replace *_NO_THROW's with *_NO_THROW_PRINT and voila.


void f(){
    throw std::runtime_error{"this should be printed"};
}

TEST(test, test){
    EXPECT_NO_THROW_PRINT( f(); );
}

Expected output of test case above:

Running main() from /build/googletest-qXr8Ln/googletest-1.10.0/googletest/src/gtest_main.cc
Note: Randomizing tests' orders with a seed of 60645 .
[==========] Running 2 tests from 2 test suites.
[----------] Global test environment set-up.
[----------] 1 test from test
[ RUN      ] test.test
std::exception thrown: this should be printed
/workspace/libasync/test/ut_executor_factory.cpp:56: Failure
Expected: try{do{ f(); }while(0);}catch(const std::exception& ex){ std::cerr << "std::exception thrown: " << ex.what() << std::endl; throw; }catch(...){ std::cerr << "unknown structure thrown" << std::endl; throw;} doesn't throw an exception.
  Actual: it throws.
[  FAILED  ] test.test (0 ms)
[----------] 1 test from test (0 ms total)

Sister answered 31/5, 2020 at 10:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.