Merge topic 'better-test-macros'

899b93ec2 Allow variable arguments to VTKM_TEST_ASSERT

Acked-by: Kitware Robot <kwrobot@kitware.com>
Acked-by: Robert Maynard <robert.maynard@kitware.com>
Merge-request: !1429
This commit is contained in:
Kenneth Moreland 2018-10-08 16:11:07 +00:00 committed by Kitware Robot
commit 6543942568
3 changed files with 186 additions and 27 deletions

@ -0,0 +1,69 @@
# Allow variable arguments to `VTKM_TEST_ASSERT`
The `VTKM_TEST_ASSERT` macro is a very useful tool for performing checks
in tests. However, it is rather annoying to have to always specify a
message for the assert. Often the failure is self evident from the
condition (which is already printed out), and specifying a message is
both repetative and annoying.
Also, it is often equally annoying to print out additional information
in the case of an assertion failure. In that case, you have to either
attach a debugger or add a printf, see the problem, and remove the
printf.
This change solves both of these problems. `VTKM_TEST_ASSERT` now takes a
condition and a variable number of message arguments. If no message
arguments are given, then a default message (along with the condition)
are output. If multiple message arguments are given, they are appended
together in the result. The messages do not have to be strings. Any
object that can be sent to a stream will be printed correctly. This
allows you to print out the values that caused the issue.
So the old behavior of `VTKM_TEST_ASSERT` still works. So you can have a
statement like
```cpp
VTKM_TEST_ASSERT(array.GetNumberOfValues() != 0, "Array is empty");
```
As before, if this assertion failed, you would get the following error
message.
```
Array is empty (array.GetNumberOfValues() != 0)
```
However, in the statement above, you may feel that it is self evident that
`array.GetNumberOfValues() == 0` means the array is empty and you have to
type this into your test, like, 20 times. You can save yourself some work
by dropping the message.
```cpp
VTKM_TEST_ASSERT(array.GetNumberOfValues() != 0);
```
In this case if the assertion fails, you will get a message like this.
```
Test assertion failed (array.GetNumberOfValues() != 0)
```
But perhaps you have the opposite problem. Perhaps you need to output more
information. Let's say that you expected a particular operation to half the
length of an array. If the operation fails, it could be helpful to know how
big the array actually is. You can now actually output that on failure by
adding more message arguments.
```cpp
VTKM_TEST_ARRAY(outarray.GetNumberOfValues() == inarrayGetNumberOfValues()/2,
"Expected array size ",
inarrayGetNumberOfValues()/2,
" but got ",
outarray.GetNumberOfValues());
```
In this case, if the test failed, you might get an error like this.
```
Expected array size 5 but got 6 (outarray.GetNumberOfValues() == inarrayGetNumberOfValues()/2)
```

@ -51,21 +51,33 @@
#endif
#endif
/// \def VTKM_TEST_ASSERT(condition, message)
/// \def VTKM_STRINGIFY_FIRST(...)
///
/// A utility macro that takes 1 or more arguments and converts it into the C string version
/// of the first argument.
#define VTKM_STRINGIFY_FIRST(...) VTKM_EXPAND(VTK_M_STRINGIFY_FIRST_IMPL(__VA_ARGS__, dummy))
#define VTK_M_STRINGIFY_FIRST_IMPL(first, ...) #first
#define VTKM_EXPAND(x) x
/// \def VTKM_TEST_ASSERT(condition, messages..)
///
/// Asserts a condition for a test to pass. A passing condition is when \a
/// condition resolves to true. If \a condition is false, then the test is
/// aborted and failure is returned.
/// aborted and failure is returned. If one or more message arguments are
/// given, they are printed out by concatinating them. If no messages are
/// given, a generic message is given. In any case, the condition that failed
/// is written out.
#define VTKM_TEST_ASSERT(condition, message) \
::vtkm::testing::Testing::Assert(condition, __FILE__, __LINE__, message, #condition)
#define VTKM_TEST_ASSERT(...) \
::vtkm::testing::Testing::Assert( \
VTKM_STRINGIFY_FIRST(__VA_ARGS__), __FILE__, __LINE__, __VA_ARGS__)
/// \def VTKM_TEST_FAIL(message)
/// \def VTKM_TEST_FAIL(messages..)
///
/// Causes a test to fail with the given \a message.
/// Causes a test to fail with the given \a messages. At least one argument must be given.
#define VTKM_TEST_FAIL(message) \
throw ::vtkm::testing::Testing::TestFailure(__FILE__, __LINE__, message)
#define VTKM_TEST_FAIL(...) ::vtkm::testing::Testing::TestFail(__FILE__, __LINE__, __VA_ARGS__)
namespace vtkm
{
@ -169,40 +181,67 @@ public:
class TestFailure
{
public:
VTKM_CONT TestFailure(const std::string& file, vtkm::Id line, const std::string& message)
: File(file)
, Line(line)
, Message(message)
{
}
VTKM_CONT TestFailure(const std::string& file,
vtkm::Id line,
const std::string& message,
const std::string& condition)
template <typename... Ts>
VTKM_CONT TestFailure(const std::string& file, vtkm::Id line, Ts&&... messages)
: File(file)
, Line(line)
{
this->Message.append(message);
this->Message.append(" (");
this->Message.append(condition);
this->Message.append(")");
std::stringstream messageStream;
this->AppendMessages(messageStream, std::forward<Ts>(messages)...);
this->Message = messageStream.str();
}
VTKM_CONT const std::string& GetFile() const { return this->File; }
VTKM_CONT vtkm::Id GetLine() const { return this->Line; }
VTKM_CONT const std::string& GetMessage() const { return this->Message; }
private:
template <typename T1>
VTKM_CONT void AppendMessages(std::stringstream& messageStream, T1&& m1)
{
messageStream << m1;
}
template <typename T1, typename T2>
VTKM_CONT void AppendMessages(std::stringstream& messageStream, T1&& m1, T2&& m2)
{
messageStream << m1 << m2;
}
template <typename T1, typename T2, typename T3>
VTKM_CONT void AppendMessages(std::stringstream& messageStream, T1&& m1, T2&& m2, T3&& m3)
{
messageStream << m1 << m2 << m3;
}
template <typename T1, typename T2, typename T3, typename T4>
VTKM_CONT void AppendMessages(std::stringstream& messageStream,
T1&& m1,
T2&& m2,
T3&& m3,
T4&& m4)
{
messageStream << m1 << m2 << m3 << m4;
}
template <typename T1, typename T2, typename T3, typename T4, typename... Ts>
VTKM_CONT void AppendMessages(std::stringstream& messageStream,
T1&& m1,
T2&& m2,
T3&& m3,
T4&& m4,
Ts&&... ms)
{
messageStream << m1 << m2 << m3 << m4;
this->AppendMessages(messageStream, std::forward<Ts>(ms)...);
}
std::string File;
vtkm::Id Line;
std::string Message;
};
static VTKM_CONT void Assert(bool condition,
template <typename... Ts>
static VTKM_CONT void Assert(const std::string& conditionString,
const std::string& file,
vtkm::Id line,
const std::string& message,
const std::string& conditionString)
bool condition,
Ts&&... messages)
{
if (condition)
{
@ -210,10 +249,24 @@ public:
}
else
{
throw TestFailure(file, line, message, conditionString);
throw TestFailure(file, line, std::forward<Ts>(messages)..., " (", conditionString, ")");
}
}
static VTKM_CONT void Assert(const std::string& conditionString,
const std::string& file,
vtkm::Id line,
bool condition)
{
Assert(conditionString, file, line, condition, "Test assertion failed");
}
template <typename... Ts>
static VTKM_CONT void TestFail(const std::string& file, vtkm::Id line, Ts&&... messages)
{
throw TestFailure(file, line, std::forward<Ts>(messages)...);
}
#ifndef VTKM_TESTING_IN_CONT
/// Calls the test function \a function with no arguments. Catches any errors
/// generated by VTKM_TEST_ASSERT or VTKM_TEST_FAIL, reports the error, and

@ -31,14 +31,33 @@ void Fail()
VTKM_TEST_FAIL("I expect this error.");
}
void Fail2()
{
vtkm::Id num = 5;
VTKM_TEST_FAIL("I can provide a number: ", num);
}
void BadAssert()
{
VTKM_TEST_ASSERT(0 == 1, "I expect this error.");
}
void BadAssert2()
{
vtkm::Id num1 = 0;
vtkm::Id num2 = 1;
VTKM_TEST_ASSERT(num1 == num2, "num 1 is ", num1, "; num 2 is ", num2);
}
void BadAssert3()
{
VTKM_TEST_ASSERT(0 == 1);
}
void GoodAssert()
{
VTKM_TEST_ASSERT(1 == 1, "Always true.");
VTKM_TEST_ASSERT(1 == 1);
}
void TestTestEqual()
@ -65,11 +84,29 @@ int UnitTestTesting(int, char* [])
return 1;
}
std::cout << "This call should fail." << std::endl;
if (vtkm::testing::Testing::Run(Fail2) == 0)
{
std::cout << "Did not get expected fail!" << std::endl;
return 1;
}
std::cout << "This call should fail." << std::endl;
if (vtkm::testing::Testing::Run(BadAssert) == 0)
{
std::cout << "Did not get expected fail!" << std::endl;
return 1;
}
std::cout << "This call should fail." << std::endl;
if (vtkm::testing::Testing::Run(BadAssert2) == 0)
{
std::cout << "Did not get expected fail!" << std::endl;
return 1;
}
std::cout << "This call should fail." << std::endl;
if (vtkm::testing::Testing::Run(BadAssert3) == 0)
{
std::cout << "Did not get expected fail!" << std::endl;
return 1;
}
std::cout << "This call should pass." << std::endl;
// This is what your main function typically looks like.