-
Notifications
You must be signed in to change notification settings - Fork 83
/
Copy pathtest_common.h
337 lines (315 loc) · 13.3 KB
/
test_common.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
// This file is part of KWIVER, and is distributed under the
// OSI-approved BSD 3-Clause License. See top-level LICENSE file or
// https://github.com/Kitware/kwiver/blob/master/LICENSE for details.
/**
* \file
*
* \brief Macro definitions for creating and running test cases
*
* These integrate with the paired CTest infrastucture managed by KWIVER's
* CMake.
*/
#ifndef KWIVER_TEST_TEST_COMMON_H_
#define KWIVER_TEST_TEST_COMMON_H_
#include <functional>
#include <exception>
#include <iostream>
#include <map>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <cmath>
typedef std::string testname_t;
// ------------------------------------------------------------------
/// Report an error to stderr.
/**
* @param msg The message to report.
*/
#define TEST_ERROR(msg) \
do \
{ \
std::cerr << "Error: " << msg << std::endl; \
} while (false)
// ------------------------------------------------------------------
/// Attempt a code block that should throw some exception
/**
* @param ex Exception class or type to expect.
* @param code Block of code to execute in which we expect the given
* exception.
* @param action Message describing the action that should have caused
* the expected exception.
*/
#define EXPECT_EXCEPTION(ex, code, action) \
do \
{ \
bool got_exception = false; \
\
try \
{ \
code; \
} \
catch (ex const& e) \
{ \
got_exception = true; \
\
std::cerr << "Expected exception: " \
<< e.what() \
<< std::endl; \
} \
catch (std::exception const& e) \
{ \
TEST_ERROR("Unexpected exception: " \
<< e.what()); \
\
got_exception = true; \
} \
catch (...) \
{ \
TEST_ERROR("Non-standard exception"); \
\
got_exception = true; \
} \
\
if (!got_exception) \
{ \
TEST_ERROR("Did not get " \
"expected exception when " \
<< action); \
} \
} while (false)
// ------------------------------------------------------------------
/// Set-up macro defining the test case function map for the current file
/**
* This *MUST* be declared once at the top of every file containing test case
* functions.
*
* \a TEST_ARGS must be defined before this call, declaring the function
* argument signature for test cases declared in the file, i.e. if cases
* needed to take in a path to a data directory or the like.
* For example:
\code
#define TEST_ARGS () // for a function that takes no args
#define TEST_ARGS (sprokit::path_t const& pipe_file)
\endcode
*/
#define DECLARE_TEST_MAP() \
namespace \
{ \
typedef std::function<void TEST_ARGS> test_function_t; \
typedef std::map<testname_t, test_function_t> test_map_t; \
} \
test_map_t __all_tests; \
struct __add_test \
{ \
__add_test(testname_t const& name, \
test_function_t const& func) \
{ \
__all_tests[name] = func; \
} \
} \
// ------------------------------------------------------------------
/// Macro for displaying tests available
#define DISPLAY_AVAILABLE_TESTS() \
do \
{ \
std::cerr << "Available tests:" << std::endl; \
for( test_map_t::value_type const& p : __all_tests ) \
{ \
std::cerr << "\t" << p.first << std::endl; \
} \
} while (false)
// ------------------------------------------------------------------
/// Add a CMake property to the next test declared
/**
* This is a hook for the CMake parsing code to set CTest test properties via
* the ``set_tests_properties(...)`` CMake method. Properties declared are set
* on the next test declared. The special property \a ENVIRONMENT can only be
* set once. Subsiquent TEST_PROPERTY calls setting \a ENVIRONMENT overwrite
* previous set attempts.
*
* @param property The CMake test property to set.
* @param value The value to set to the CMake test property.
*
* @sa IMPLEMENT_TEST(testname)
*/
#define TEST_PROPERTY(property, value, ...)
// ------------------------------------------------------------------
/// Define a test case
/**
* @param testname The name of the test case to define.
* @sa TEST_PROPERTY
*/
#define IMPLEMENT_TEST(testname) \
static void \
test_##testname TEST_ARGS; \
static __add_test const \
__add_test_##testname(#testname, test_##testname); \
void \
test_##testname TEST_ARGS
// ------------------------------------------------------------------
/// Check the number of positional arguments given to the top level executable
/**
* @param numargs The number of arguments to expect after the name of the
* executable.
*/
#define CHECK_ARGS(numargs) \
do \
{ \
if (argc != (numargs + 1)) \
{ \
TEST_ERROR("Expected " \
#numargs \
" arguments"); \
\
std::cerr << std::endl; \
DISPLAY_AVAILABLE_TESTS();\
std::cerr << std::endl; \
\
return EXIT_FAILURE; \
} \
} while (false)
// ------------------------------------------------------------------
/// Run the a test case by a given name
/**
* Find an run the test function associated with the given testname.
* Parameters after the test name are the arguments to pass to the function.
*
* @param testname The name of the test to run. This name should match one
* given to an IMPLEMENT_TEST() macro.
*
* @sa DECLARE_TEST_MAP(), IMPLEMENT_TEST(), CHECK_ARGS()
*/
#define RUN_TEST(testname, ...) \
do \
{ \
test_map_t::const_iterator const i = \
__all_tests.find(testname); \
\
if (i == __all_tests.end()) \
{ \
TEST_ERROR("Unknown test: " << testname); \
\
std::cerr << std::endl; \
DISPLAY_AVAILABLE_TESTS(); \
std::cerr << std::endl; \
\
return EXIT_FAILURE; \
} \
\
test_function_t const& func = i->second; \
\
try \
{ \
func(__VA_ARGS__); \
} \
catch (std::exception const& e) \
{ \
TEST_ERROR("Unexpected exception: " \
<< e.what()); \
\
return EXIT_FAILURE; \
} \
\
return EXIT_SUCCESS; \
} while (false)
// ------------------------------------------------------------------
//
// Testing helper macros/methods
//
namespace kwiver {
namespace testing {
/// Test double approximate equality to given epsilon
/**
* @param value The value subject for comparison.
* @param target The value to compare to.
* @param epsilon The allowed varience.
*/
inline bool is_almost(double const &value,
double const &target,
double const &epsilon)
{
return fabs(value - target) <= epsilon;
}
// ------------------------------------------------------------------
/// \copydoc kwiver::testing::test_equal
#define TEST_EQUAL(name, value, expected) \
do \
{ \
if((value) != (expected)) \
{ \
TEST_ERROR("TEST_EQUAL check '" << name << "' failed:\n" \
<< " Expected: ``" << (expected) << "``\n" \
<< " Got : ``" << (value) << "``"); \
} \
} while(false)
// ------------------------------------------------------------------
/// General equality test with message generation on inequality
/**
* Test equality between values with a '!=' expression. This wraps a standard
* error response message.
*
* @param name A descriptive name for this specific test.
* @param value The experimental value of the equality check.
* @param expected The expected value of the equality check.
*/
template <typename ActualType, typename ExpectedType> inline
bool
test_equal( char const* name, ActualType const& value,
ExpectedType const& expected )
{
if (value != expected)
{
TEST_ERROR("test_equal check '" << name << "' failed:\n"
<< " Expected: ``" << expected << "``\n"
<< " Got : ``" << value << "``");
return false;
}
return true;
}
// ------------------------------------------------------------------
/// General range test with message generation on out of range
/**
* Test that a value is between two bounds with '<' expressions. This wraps a
* standard error response message.
*
* @param name A descriptive name for this specific test.
* @param value The experimental value of the equality check.
* @param expected The expected value of the equality check.
*/
template <typename ValueType, typename BoundsType> inline
bool
test_bound( char const* name, ValueType const& value,
BoundsType const& lower, BoundsType const& upper )
{
if ((value < lower) || (upper < value))
{
TEST_ERROR("test_bound check '" << name << "' failed:\n"
<< " Expected: in [``" << lower << "``, ``"
<< upper << "``]\n"
<< " Got : ``" << value << "``");
return false;
}
return true;
}
// ------------------------------------------------------------------
/// Test double/float approximate equality to a given epsilon
/**
* @param name An identifying name for the test.
* @param value The value subject for comparison.
* @param target The value to compare to.
* @param epsilon The allowed varience.
*/
#define TEST_NEAR(name, value, target, epsilon) \
do \
{ \
if(! kwiver::testing::is_almost(value, target, epsilon)) \
{ \
TEST_ERROR("TEST_NEAR check '" << name \
<< "' failed: (epsilon: " << (epsilon) << ")\n" \
<< " Expected: " << (target) << "\n" \
<< " Got : " << (value)); \
} \
}while(false)
} //end namespace testing
} //end namespace kwiver
#endif