Skip to content

DOCTEST_CHECK_THROWS_WITH_AS fails to work with dependant exception type #447

@YarikTH

Description

@YarikTH

Description

I tried to write a templated test case. In this test case, I need to check the thrown exception. But compilation failed because my exception type depends on the tested type. Unfortunately deep inside doctest's macroses (namely in DOCTEST_ASSERT_THROWS_AS) code is not ready to dependant exception types.

Steps to reproduce

https://godbolt.org/z/f5M4on

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <https://raw.githubusercontent.com/onqtam/doctest/2.4.1/doctest/doctest.h>
#include <exception>

template<class T>
struct Json
{
    using out_of_range = std::out_of_range;

    T get()
    {
        throw out_of_range( "Out of range" );
        return T();
    }
};

TEST_CASE_TEMPLATE("My testcase", T,
                   Json<int>)
{
    T json;
    DOCTEST_CHECK_THROWS_WITH_AS(json.get(), "Out of range", typename T::out_of_range);
}

Example output

x86-64 gcc 7.3 (Editor #1, Compiler #1) C++
x86-64 gcc 7.3
-std=c++11 -O0
1
<Compilation failed>
x86-64 gcc 7.3 - 1737ms
#1 with x86-64 gcc 7.3
<source>: In function 'void _DOCTEST_ANON_TMP_8()':
<doctest/doctest.h>:2055:73: error: type/value mismatch at argument 1 in template parameter list for 'template<class T> struct doctest::detail::remove_const'
<doctest/doctest.h>:2099:58: note: in expansion of macro 'DOCTEST_ASSERT_THROWS_AS'
<source>:21:5: note: in expansion of macro 'DOCTEST_CHECK_THROWS_WITH_AS'
<doctest/doctest.h>:2055:73: note:   expected a type, got 'doctest::detail::remove_reference<typename T::out_of_range>::type'
<doctest/doctest.h>:2099:58: note: in expansion of macro 'DOCTEST_ASSERT_THROWS_AS'
<source>:21:5: note: in expansion of macro 'DOCTEST_CHECK_THROWS_WITH_AS'
<doctest/doctest.h>:2055:80: error: expected ')' before '&' token
<doctest/doctest.h>:2099:58: note: in expansion of macro 'DOCTEST_ASSERT_THROWS_AS'
<source>:21:5: note: in expansion of macro 'DOCTEST_CHECK_THROWS_WITH_AS'
<doctest/doctest.h>:2055:80: error: expected '{' before '&' token
<doctest/doctest.h>:2099:58: note: in expansion of macro 'DOCTEST_ASSERT_THROWS_AS'
<source>:21:5: note: in expansion of macro 'DOCTEST_CHECK_THROWS_WITH_AS'
<doctest/doctest.h>:2055:81: error: expected primary-expression before ')' token
<doctest/doctest.h>:2099:58: note: in expansion of macro 'DOCTEST_ASSERT_THROWS_AS'
<source>:21:5: note: in expansion of macro 'DOCTEST_CHECK_THROWS_WITH_AS'
<doctest/doctest.h>:2058:15: error: expected primary-expression before 'catch'
<doctest/doctest.h>:2099:58: note: in expansion of macro 'DOCTEST_ASSERT_THROWS_AS'
<source>:21:5: note: in expansion of macro 'DOCTEST_CHECK_THROWS_WITH_AS'

The workaround for this is quite straightforward:
https://godbolt.org/z/qWqxsr

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <https://raw.githubusercontent.com/onqtam/doctest/2.4.1/doctest/doctest.h>
#include <exception>

template<class T>
struct Json
{
    using out_of_range = std::out_of_range;

    T get()
    {
        throw out_of_range( "Out of range" );
        return T();
    }
};

#define DOCTEST_ASSERT_THROWS_AS_PATCHED(expr, assert_type, message, ...)                          \
    do {                                                                                           \
        if(!doctest::getContextOptions()->no_throw) {                                              \
            doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
                                                       __LINE__, #expr, #__VA_ARGS__, message);    \
            try {                                                                                  \
                DOCTEST_CAST_TO_VOID(expr)                                                         \
            } catch(const typename doctest::detail::remove_const<                                  \
                    typename doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) {       \
                _DOCTEST_RB.translateException();                                                  \
                _DOCTEST_RB.m_threw_as = true;                                                     \
            } catch(...) { _DOCTEST_RB.translateException(); }                                     \
            DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB);                                             \
        }                                                                                          \
    } while(false)

#define DOCTEST_CHECK_THROWS_WITH_AS_PATCHED(expr, message, ...) DOCTEST_ASSERT_THROWS_AS_PATCHED(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__)


TEST_CASE_TEMPLATE("My testcase2", T,
                   Json<int>)
{
    T json;
    DOCTEST_CHECK_THROWS_WITH_AS_PATCHED(json.get(), "Out of range", typename T::out_of_range);
}

Need to add "typename" before both "doctest::detail::remove_const" and "doctest::detail::remove_reference". It seems not to harm even if the exception type is not dependant. But I'm not sure is it true for most of the compilers and c++ standards. At least such macro group could be added separately. Like DOCTEST_CHECK_THROWS_WITH_AS_DEPENDANT.

Extra information

  • doctest version: v2.4.1
  • Operating System: Godbolt
  • Compiler+version: any

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions