[igloo] Exceptions (Was: Igloo - meet CppSpec..)

47 views
Skip to first unread message

Joakim Karlsson

unread,
Oct 13, 2010, 4:23:30 AM10/13/10
to igloo-...@googlegroups.com
On Tue, Oct 12, 2010 at 10:21 PM, Kim Gräsman <kim.g...@gmail.com> wrote:
>
>   A a;
>   Assert::That(Calling(a, &A::foo), Throws<...>());
>

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

Kim Gräsman

unread,
Oct 13, 2010, 6:38:28 AM10/13/10
to igloo-...@googlegroups.com

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

Joakim Karlsson

unread,
Oct 13, 2010, 6:57:43 AM10/13/10
to igloo-...@googlegroups.com
On Wed, Oct 13, 2010 at 12:38 PM, Kim Gräsman <kim.g...@gmail.com> wrote:
> Maybe Thrown() is a simpler name for such a helper function, á la
>
>   Assert::That(Thrown<std::exception>(a, &A::foo).what(), Contains("ouch!"));
>

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"!);
}

Joakim Karlsson

unread,
Oct 13, 2010, 3:10:23 PM10/13/10
to igloo-...@googlegroups.com
I got the following working. Is it readable? Useful?

class ClassWithExceptions
{
public:
  int Method()
  {
    throw std::logic_error("ouch!");
  }
};

Context(MethodsWithExceptions)
{
  ClassWithExceptions objectUnderTest; 
 
  Spec(CanDetectExceptions)
  { 
    Assert::That(Thrown<std::logic_error>(objectUnderTest, &ClassWithExceptions::Method).what(), Is().EqualTo("ouch!"));
  } 
};


/J

Kim Gräsman

unread,
Oct 13, 2010, 3:42:53 PM10/13/10
to igloo-...@googlegroups.com
Nice! But it's definitely not readable... It's a shame, because it
_is_ pretty cool.

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

Joakim Karlsson

unread,
Oct 14, 2010, 2:56:38 AM10/14/10
to igloo-...@googlegroups.com
On Wed, Oct 13, 2010 at 9:42 PM, Kim Gräsman <kim.g...@gmail.com> wrote:
> Nice! But it's definitely not readable... It's a shame, because it
> _is_ pretty cool.
>

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

Kim Gräsman

unread,
Oct 14, 2010, 11:09:56 AM10/14/10
to igloo-...@googlegroups.com
On Thu, Oct 14, 2010 at 08:56, Joakim Karlsson <joa...@jkarlsson.com> wrote:
>
> 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!"));

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

Joakim Karlsson

unread,
Oct 14, 2010, 2:54:10 PM10/14/10
to igloo-...@googlegroups.com
On Thu, Oct 14, 2010 at 5:09 PM, Kim Gräsman <kim.g...@gmail.com> wrote:
>
> 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 ;-)
>

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

Joakim Karlsson

unread,
Oct 15, 2010, 4:41:42 AM10/15/10
to igloo-...@googlegroups.com
Perhaps if we macrofy the following? 

That would make it possible to have the syntax

auto_ptr<exception> last_exception  = Throws(exception, objectUnderTest.foo(5));

It demands a proper copy constructor on the exception and the igloo_last_exception variable would stop us from using it twice in a method.

/J

std::auto_ptr<std::logic_error> igloo_last_exception;
{
  bool igloo_wrong_exception = false;
  try
  {
  objectUnderTest.foo(5);
    std::cout << "No exception!" << std::endl;
  }
  catch(const std::logic_error& e)
  {
    igloo_last_exception = std::auto_ptr<std::logic_error>(new std::logic_error(e));
  }
  catch(...)
  {
  igloo_wrong_exception = true;
  }
  std::cout << "wrong: " << igloo_wrong_exception << std::endl;
}

Kim Gräsman

unread,
Oct 15, 2010, 4:50:57 AM10/15/10
to igloo-...@googlegroups.com
I think it will prove difficult to combine the generation of try/catch
+ code and the assignment of a variable in the same expression...

But I wonder if we could keep a stack of caught exceptions? Or
multiple stacks, one per type, or something?

- Kim

dannystaple

unread,
Oct 15, 2010, 10:25:26 AM10/15/10
to Igloo-testing
Hmm - I've made some exceptionally crude methods to help me do this:

http://www.squidoo.com/cplusplus-behaviour-driven-development-tools#module125224741

They do not match up with the templated ones that you guys will come
up with, but they have been good enough to set me off.

Danny

On Oct 14, 7:54 pm, Joakim Karlsson <joa...@jkarlsson.com> wrote:

Joakim Karlsson

unread,
Oct 15, 2010, 10:32:20 AM10/15/10
to igloo-...@googlegroups.com
On Fri, Oct 15, 2010 at 4:25 PM, dannystaple <orion...@gmail.com> wrote:
> Hmm - I've made some exceptionally crude methods to help me do this:
>
> http://www.squidoo.com/cplusplus-behaviour-driven-development-tools#module125224741

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

Joakim Karlsson

unread,
Oct 16, 2010, 4:59:42 AM10/16/10
to igloo-...@googlegroups.com
On Fri, Oct 15, 2010 at 10:50 AM, Kim Gräsman <kim.g...@gmail.com> wrote:
> I think it will prove difficult to combine the generation of try/catch
> + code and the assignment of a variable in the same expression...
>

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

Kim Gräsman

unread,
Oct 17, 2010, 8:57:33 AM10/17/10
to igloo-...@googlegroups.com
On Sat, Oct 16, 2010 at 10:59, Joakim Karlsson <joa...@jkarlsson.com> wrote:
>
> 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.

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

Kim Gräsman

unread,
Oct 17, 2010, 9:02:58 AM10/17/10
to igloo-...@googlegroups.com
On Sat, Oct 16, 2010 at 10:59, Joakim Karlsson <joa...@jkarlsson.com> wrote:
>
> 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.

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

Joakim Karlsson

unread,
Oct 17, 2010, 12:10:24 PM10/17/10
to igloo-...@googlegroups.com
On Sun, Oct 17, 2010 at 2:57 PM, Kim Gräsman <kim.g...@gmail.com> wrote:
>
> Maybe you could use some simple RAII to clean up the exception state
> at the end of the scope?

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

Joakim Karlsson

unread,
Oct 17, 2010, 12:15:46 PM10/17/10
to igloo-...@googlegroups.com
Fungo looks cool. Will check it out more properly later.

Joakim Karlsson

unread,
Oct 17, 2010, 2:38:53 PM10/17/10
to igloo-...@googlegroups.com
I've pushed the changes with RIIA-ish cleanup of last stored exception.

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

Kim Gräsman

unread,
Oct 18, 2010, 5:05:21 AM10/18/10
to igloo-...@googlegroups.com
I think it looks good.

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

Joakim Karlsson

unread,
Oct 18, 2010, 7:44:21 AM10/18/10
to igloo-...@googlegroups.com
On Mon, Oct 18, 2010 at 11:05 AM, Kim Gräsman <kim.g...@gmail.com> wrote:

> The nastier the name is, the less risk of collisions :-)
>

Great. I'll nastify it a bit.

/J

Reply all
Reply to author
Forward
0 new messages