Perhaps we could have a method similar to Calling that returns the
exception thrown.
That way we could use our existing constraints on it.
Assert::That(ExceptionThrownBy(a, &A::foo).what(), Contains("ouch!"));
/J
I think one difficulty is that the return type of ExceptionThrownBy()
is essentially unknown, as we don't know what A::foo might throw. We'd
have to explicitly specialize it;
Assert::That(ExceptionThrownBy<std::exception>(a, &A::foo).what(),
Contains("ouch!"));
Maybe Thrown() is a simpler name for such a helper function, á la
Assert::That(Thrown<std::exception>(a, &A::foo).what(), Contains("ouch!"));
There's also the complication of what happens if the method doesn't
throw or if it throws an unrelated exception. Not sure about that.
Interesting take!
- Kim
I agree. Looks better.
> There's also the complication of what happens if the method doesn't
> throw or if it throws an unrelated exception. Not sure about that.
>
I was thinking that Thrown<>() would look something like the following
psuedo-code:
template<...>
ExceptionType Thrown(InstanceType i, MethodType m)
{
try
{
(i.*m)();
}
catch(const ExceptionType& e)
{
return e;
}
catch(...)
{
Assert::Failure("wrong exception");
}
Assert::Failure("no exception"!);
}
I can' t really think of a valid syntax at the moment, but to me it
feels important to keep the imperative call syntax;
CPPUNIT_ASSERT_THROWS(objectUnderTest.Method(1, 2, 3), std::logic_error);
rather than the awkward method pointer + args.
This speaks in favor of a preprocessor-based solution, that can
capture a full expression for delayed execution inside an exception
handler, but I can't find a nice way to make that interact with
Assert::That.
- Kim
Yeah, I know. I just had to get that out of my system.
> I can' t really think of a valid syntax at the moment, but to me it
> feels important to keep the imperative call syntax;
>
> CPPUNIT_ASSERT_THROWS(objectUnderTest.Method(1, 2, 3), std::logic_error);
I think we should go with macros.
We could have a macro like this:
Throws(std::exception, objectUnderTest.Method(1, 2, 3))
that fails if an exception of the correct type is not thrown.
We could have that macro return the exception if the test succeeds,
and make assertions later:
std::exception e = Throws(std::exception, objectUnderTest.Method(1, 2, 3));
Assert::That(e.what(), Contains("error!"));
/J
I had a similar idea, though I don't see how to implement it. I know
you have mad skills in the thinking-outside-the-box department,
though, so I look forward to see your next prototype ;-)
I'll try and build something along these lines as well, and see if I
can overcome my preconceptions.
- Kim
Thanks! Getting my previously proposed syntax to compile might prove
to be too hard though.
While brainstorming, I got the following to work at least:
Spec(CanAssertOnLastException)
{
AssertThrows(std::logic_error, objectUnderTest.LogicError());
Assert::That(LastException<std::logic_error>().what(),
Contains("not logical!"));
}
If the AssertThrows macro succeeds, it stores the exception in a
static auto_ptr.
I think it's quite readable, but there is a risk of cross-test
contamination because of the static storage. And the implementation
with an auto_ptr isn't as pretty as I would have wanted.
/J
But I wonder if we could keep a stack of caught exceptions? Or
multiple stacks, one per type, or something?
- Kim
Thanks Danny!
Your ShouldThrowWhatError macro is what started me on trying to come
up with a solution for asserting on any characteristic of a thrown
exception.
/J
Yep. I'm giving up on that one for now.
> But I wonder if we could keep a stack of caught exceptions? Or
> multiple stacks, one per type, or something?
>
I pushed my prototype [1]. I haven't been able to clean up after each
test method properly, so there is a risk of cross-test contamination.
Jonas Blunck suggested using __thread for storage, and call each test
on its own thread. MIght be worth a try. We'll have to check if there
are any restrictions on what platforms and compilers support TLS.
[1] http://github.com/joakimkarlsson/igloo/blob/master/tests/exceptions_tests.cpp
Maybe you could use some simple RAII to clean up the exception state
at the end of the scope?
Something like;
template< class ExceptionType >
struct LastExceptionResetter {
~LastExceptionResetter() {
ExceptionStorage<ExceptionType>::last_exception = NULL;
}
};
... and then instantiate LastExceptionResetter<EXCEPTION_TYPE>
somewhere in the scope of AssertThrows.
If running tests in parallel is something we want to allow then
last_exception needs to be thread-local, of course, though I'm not
sure the rest of the framework is going to play nicely.
Now that we're degenerating into macros, maybe we should reconsider my
previous (nasty!) attempt that allowed Assert::Throws(...)?
- Kim
Oh, and I was sure I'd read somewhere about a new feature in C++0x
that allowed direct access to the latest exception, but I couldn't
find a reference to it. Then I ran into this in my blog reader:
http://www.mr-edd.co.uk/blog/in_the_absence_of_exception_ptr
It looks like the library (or its techniques) might be useful to us,
though I haven't read it thoroughly yet.
- Kim
Brilliant!
>
> If running tests in parallel is something we want to allow then
> last_exception needs to be thread-local, of course, though I'm not
> sure the rest of the framework is going to play nicely.
>
With your RAII solution I don't think we need to go that route for
now. I'm not sure how well TLS plays with different platforms and
compilers.
> Now that we're degenerating into macros, maybe we should reconsider my
> previous (nasty!) attempt that allowed Assert::Throws(...)?
>
I think that adds too much complexity. And it might confuse users if
the Throws "method" is nowhere in the intellisense list.
Since we will need macros to add __FILE__ and __LINE_ to get nicer
output we should perhaps go with macros a' la AssertThat or similar.
/J
Please take a look and see if it looks good.
Tests:
http://github.com/joakimkarlsson/igloo/blob/master/tests/exceptions_tests.cpp
Code:
http://github.com/joakimkarlsson/igloo/blob/master/igloo/core/exceptions.h
/J
The only nit I can think of is it would be safer to decorate the name
the exception storage variable even more; I've tried to add an IGLOO_
prefix to all names that are magically injected into the user's
namespace. E.g.:
ExceptionStorage<EXCEPTION_TYPE>
IGLOO_CONCAT(IGLOO_exception_storage_, __LINE__);
IGLOO_CONCAT(IGLOO_exception_storage_,
__LINE__).compiler_thinks_i_am_unused(); \
The nastier the name is, the less risk of collisions :-)
- Kim
> The nastier the name is, the less risk of collisions :-)
>
Great. I'll nastify it a bit.
/J