E3SM-Project / EKAT

Tools and libraries for writing Kokkos-enabled HPC C++ in E3SM ecosystem
Other
16 stars 7 forks source link

Improve EKAT_REQUIRE macro to allow passing exception type #354

Open bartgol opened 21 hours ago

bartgol commented 21 hours ago

Motivation

When doing unit tests, I often check that exceptions are raised when appropriate, via something like

  REQUIRE_THROWS (some_call_which_throws());

But if the function can throw at several points in its execution, this check may not verify the desired behavior. E.g., consdider this

void func (int i, int j) {
  EKAT_REQUIRE_MSG (i>0, "wrong i");
  EKAT_REQUIRE_MSG (j>0, "wrong j");
}
TEST_CASE ("my-test")
{
  int i=2, j=3;
  func(i,j); // This should work

  i = -1;
  REQUIRE_THROWS (func(i,j)); // should fail b/c i<=0
  j = -1;
  REQUIRE_THROWS (func(i,j)); // should fail b/c j<=0
}

The second REQUIRE_THROWS will throw, but NOT b/c j<0, bur rather b/c i<0, since we did not change its value back to 2.

With this PR, I can do

class ExceptionA : public std::runtime_error {};
class ExceptionB : public std::logic_error {};
void func (int i, int j) {
  EKAT_REQUIRE_MSG (i>0, "wrong i",ExceptionA);
  EKAT_REQUIRE_MSG (j>0, "wrong j",ExceptionB);
}
TEST_CASE ("my-test")
{
  int i=2, j=3;
  func(i,j); // This should work

  i = -1;
  REQUIRE_THROWS_AS (func(i,j),ExceptionA); // should fail b/c i<=0
  j = -1;
  REQUIRE_THROWS_AS (func(i,j),ExceptionB); // should fail b/c j<=0
}

Now, the test will fail, since the 2nd REQUIRE_THROWS will still throw ExceptionA.

Note: this change is backward compatible.

Testing

I added a unit test that verifies we can now throw a user-defined exception type.

jgfouca commented 20 hours ago

I see now what you're getting at. The unit test itself had mistakes.

bartgol commented 19 hours ago

I see now what you're getting at. The unit test itself had mistakes.

Yeah, the macro was fine. But always throwing the same exception does not allow us to differentiate in case we need to throw multiple exceptions in the same call. Besides, it is helpful to be able to throw a specific type of exception, in case the calling code is able to handle SOME type of exceptions but nothers (e.g., a bad cast can be handled, but a bad alloc can't)

bartgol commented 18 hours ago

Tests pass on mappy.