test.hpp 10.9 KB
Newer Older
Paul's avatar
Paul committed
1

2
#include <algorithm>
Paul's avatar
Paul committed
3
4
5
#include <cassert>
#include <cstdio>
#include <cstdlib>
Paul's avatar
Paul committed
6
#include <functional>
Paul's avatar
Paul committed
7
#include <iostream>
8
#include <sstream>
Paul's avatar
Paul committed
9
10
#include <unordered_map>
#include <vector>
Paul's avatar
Paul committed
11

Paul's avatar
Paul committed
12
13
#ifndef MIGRAPHX_GUARD_TEST_TEST_HPP
#define MIGRAPHX_GUARD_TEST_TEST_HPP
Paul's avatar
Paul committed
14

Paul's avatar
Paul committed
15
namespace test {
16
// clang-format off
Paul's avatar
Paul committed
17
// NOLINTNEXTLINE
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define TEST_FOREACH_BINARY_OPERATORS(m) \
    m(==, equal) \
    m(!=, not_equal) \
    m(<=, less_than_equal) \
    m(>=, greater_than_equal) \
    m(<, less_than) \
    m(>, greater_than) \
    m(and, and_op) \
    m(or, or_op)
// clang-format on

// clang-format off
// NOLINTNEXTLINE
#define TEST_FOREACH_UNARY_OPERATORS(m) \
    m(not, not_op)
// clang-format on
Paul's avatar
Paul committed
34
35

// NOLINTNEXTLINE
36
#define TEST_EACH_BINARY_OPERATOR_OBJECT(op, name)     \
Paul's avatar
Paul committed
37
38
39
40
41
42
43
44
45
    struct name                                        \
    {                                                  \
        static std::string as_string() { return #op; } \
        template <class T, class U>                    \
        static decltype(auto) call(T&& x, U&& y)       \
        {                                              \
            return x op y;                             \
        }                                              \
    };
Paul's avatar
Paul committed
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
// NOLINTNEXTLINE
#define TEST_EACH_UNARY_OPERATOR_OBJECT(op, name)      \
    struct name                                        \
    {                                                  \
        static std::string as_string() { return #op; } \
        template <class T>                             \
        static decltype(auto) call(T&& x)              \
        {                                              \
            return op x;                               \
        }                                              \
    };

TEST_FOREACH_BINARY_OPERATORS(TEST_EACH_BINARY_OPERATOR_OBJECT)
TEST_FOREACH_UNARY_OPERATORS(TEST_EACH_UNARY_OPERATOR_OBJECT)

struct nop
{
    static std::string as_string() { return ""; }
    template <class T>
    static decltype(auto) call(T&& x)
    {
        return x;
    }
};
Paul's avatar
Paul committed
71

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
struct function
{
    static std::string as_string() { return ""; }
    template <class T>
    static decltype(auto) call(T&& x)
    {
        return x();
    }
};

template <class Iterator>
inline std::ostream& stream_range(std::ostream& s, Iterator start, Iterator last)
{
    if(start != last)
    {
        s << *start;
        std::for_each(std::next(start), last, [&](auto&& x) { s << ", " << x; });
    }
    return s;
}

Paul's avatar
Paul committed
93
94
95
96
97
98
inline std::ostream& operator<<(std::ostream& s, std::nullptr_t)
{
    s << "nullptr";
    return s;
}

Paul's avatar
Paul committed
99
100
101
102
template <class T>
inline std::ostream& operator<<(std::ostream& s, const std::vector<T>& v)
{
    s << "{ ";
103
    stream_range(s, v.begin(), v.end());
Paul's avatar
Paul committed
104
105
106
107
    s << "}";
    return s;
}

108
109
110
inline std::ostream& operator<<(std::ostream& s, const std::vector<bool>& v)
{
    s << "{ ";
111
    stream_range(s, v.begin(), v.end());
112
113
114
115
    s << "}";
    return s;
}

Paul's avatar
Paul committed
116
template <class T, class U, class Operator>
Paul's avatar
Paul committed
117
struct expression
Paul's avatar
Paul committed
118
{
Paul's avatar
Paul committed
119
120
121
122
    T lhs;
    U rhs;

    friend std::ostream& operator<<(std::ostream& s, const expression& self)
Paul's avatar
Paul committed
123
    {
124
        s << self.lhs << " " << Operator::as_string() << " " << self.rhs;
Paul's avatar
Paul committed
125
        return s;
Paul's avatar
Paul committed
126
    }
Paul's avatar
Paul committed
127

Paul's avatar
Paul committed
128
129
130
    decltype(auto) value() const { return Operator::call(lhs, rhs); };
};

Paul's avatar
Paul committed
131
// TODO: Remove rvalue references
Paul's avatar
Paul committed
132
template <class T, class U, class Operator>
Paul's avatar
Paul committed
133
expression<T, U, Operator> make_expression(T&& rhs, U&& lhs, Operator)
Paul's avatar
Paul committed
134
{
Paul's avatar
Paul committed
135
    return {std::forward<T>(rhs), std::forward<U>(lhs)};
Paul's avatar
Paul committed
136
}
Paul's avatar
Paul committed
137

138
template <class T, class Operator = nop>
Paul's avatar
Paul committed
139
140
struct lhs_expression;

Paul's avatar
Paul committed
141
// TODO: Remove rvalue reference
Paul's avatar
Paul committed
142
template <class T>
Paul's avatar
Paul committed
143
lhs_expression<T> make_lhs_expression(T&& lhs)
Paul's avatar
Paul committed
144
{
Paul's avatar
Paul committed
145
    return lhs_expression<T>{std::forward<T>(lhs)};
Paul's avatar
Paul committed
146
147
}

148
149
150
151
152
153
154
template <class T, class Operator>
lhs_expression<T, Operator> make_lhs_expression(T&& lhs, Operator)
{
    return lhs_expression<T, Operator>{std::forward<T>(lhs)};
}

template <class T, class Operator>
Paul's avatar
Paul committed
155
156
157
struct lhs_expression
{
    T lhs;
Paul's avatar
Paul committed
158
    explicit lhs_expression(T e) : lhs(e) {}
Paul's avatar
Paul committed
159
160
161

    friend std::ostream& operator<<(std::ostream& s, const lhs_expression& self)
    {
162
163
164
165
        std::string op = Operator::as_string();
        if(not op.empty())
            s << Operator::as_string() << " ";
        s << self.lhs;
Paul's avatar
Paul committed
166
167
168
        return s;
    }

169
    decltype(auto) value() const { return Operator::call(lhs); }
Paul's avatar
Paul committed
170
// NOLINTNEXTLINE
171
#define TEST_LHS_BINARY_OPERATOR(op, name)                     \
Paul's avatar
Paul committed
172
173
174
    template <class U>                                         \
    auto operator op(const U& rhs) const                       \
    {                                                          \
Paul's avatar
Paul committed
175
        return make_expression(lhs, rhs, name{}); /* NOLINT */ \
Paul's avatar
Paul committed
176
    }
Paul's avatar
Paul committed
177

178
179
180
181
182
183
184
185
    TEST_FOREACH_BINARY_OPERATORS(TEST_LHS_BINARY_OPERATOR)

// NOLINTNEXTLINE
#define TEST_LHS_UNARY_OPERATOR(op, name) \
    auto operator op() const { return make_lhs_expression(lhs, name{}); /* NOLINT */ }

    TEST_FOREACH_UNARY_OPERATORS(TEST_LHS_UNARY_OPERATOR)

Paul's avatar
Paul committed
186
// NOLINTNEXTLINE
Paul's avatar
Paul committed
187
188
189
190
191
192
193
194
195
196
197
198
199
#define TEST_LHS_REOPERATOR(op)                 \
    template <class U>                          \
    auto operator op(const U& rhs) const        \
    {                                           \
        return make_lhs_expression(lhs op rhs); \
    }
    TEST_LHS_REOPERATOR(+)
    TEST_LHS_REOPERATOR(-)
    TEST_LHS_REOPERATOR(*)
    TEST_LHS_REOPERATOR(/)
    TEST_LHS_REOPERATOR(%)
    TEST_LHS_REOPERATOR(&)
    TEST_LHS_REOPERATOR(|)
200
    TEST_LHS_REOPERATOR (^)
Paul's avatar
Paul committed
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
template <class F>
struct predicate
{
    std::string msg;
    F f;

    friend std::ostream& operator<<(std::ostream& s, const predicate& self)
    {
        s << self.msg;
        return s;
    }

    decltype(auto) operator()() const { return f(); }

    operator decltype(auto)() const { return f(); }
};

template <class F>
auto make_predicate(const std::string& msg, F f)
{
    return make_lhs_expression(predicate<F>{msg, f}, function{});
}

template <class T>
std::string as_string(const T& x)
{
    std::stringstream ss;
    ss << x;
    return ss.str();
}

template <class Iterator>
std::string as_string(Iterator start, Iterator last)
{
    std::stringstream ss;
    stream_range(ss, start, last);
    return ss.str();
}

template <class F>
auto make_function(const std::string& name, F f)
{
    return [=](auto&&... xs) {
        std::vector<std::string> args = {as_string(xs)...};
        return make_predicate(name + "(" + as_string(args.begin(), args.end()) + ")",
                              [=] { return f(xs...); });
    };
}

Paul's avatar
Paul committed
252
struct capture
Paul's avatar
Paul committed
253
{
Paul's avatar
Paul committed
254
    template <class T>
Paul's avatar
Paul committed
255
    auto operator->*(const T& x) const
Paul's avatar
Paul committed
256
257
258
    {
        return make_lhs_expression(x);
    }
259
260
261
262
263
264

    template <class T, class Operator>
    auto operator->*(const lhs_expression<T, Operator>& x) const
    {
        return x;
    }
Paul's avatar
Paul committed
265
266
};

Paul's avatar
Paul committed
267
template <class T, class F>
Paul's avatar
Paul committed
268
void failed(T x, const char* msg, const char* func, const char* file, int line, F f)
Paul's avatar
Paul committed
269
{
270
    if(!bool(x.value()))
Paul's avatar
Paul committed
271
    {
Paul's avatar
Paul committed
272
        std::cout << func << std::endl;
Paul's avatar
Paul committed
273
        std::cout << file << ":" << line << ":" << std::endl;
274
275
        std::cout << "    FAILED: " << msg << " "
                  << "[ " << x << " ]" << std::endl;
Paul's avatar
Paul committed
276
277
278
        f();
    }
}
Paul's avatar
Paul committed
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293

template <class F>
bool throws(F f)
{
    try
    {
        f();
        return false;
    }
    catch(...)
    {
        return true;
    }
}

Khalique's avatar
Khalique committed
294
template <class Exception, class F>
Paul's avatar
Paul committed
295
bool throws(F f, const std::string& msg = "")
Paul's avatar
Paul committed
296
297
298
299
300
301
302
303
304
305
306
307
{
    try
    {
        f();
        return false;
    }
    catch(const Exception& ex)
    {
        return std::string(ex.what()).find(msg) != std::string::npos;
    }
}

308
309
310
311
312
313
314
template <class T, class U>
auto near(T px, U py, double ptol = 1e-6f)
{
    return make_function("near", [](auto x, auto y, auto tol) { return std::abs(x - y) < tol; })(
        px, py, ptol);
}

Paul's avatar
Paul committed
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
using string_map = std::unordered_map<std::string, std::vector<std::string>>;

template <class Keyword>
string_map parse(std::vector<std::string> as, Keyword keyword)
{
    string_map result;

    std::string flag;
    for(auto&& x : as)
    {
        auto f = keyword(x);
        if(f.empty())
        {
            result[flag].push_back(x);
        }
        else
        {
            flag = f.front();
            result[flag]; // Ensure the flag exists
        }
    }
    return result;
}

inline auto& get_test_cases()
{
341
    // NOLINTNEXTLINE
Paul's avatar
Paul committed
342
343
344
345
346
347
    static std::vector<std::pair<std::string, std::function<void()>>> cases;
    return cases;
}

inline void add_test_case(std::string name, std::function<void()> f)
{
Paul's avatar
Paul committed
348
    get_test_cases().emplace_back(std::move(name), std::move(f));
Paul's avatar
Paul committed
349
350
}

Paul's avatar
Paul committed
351
struct auto_register_test_case
Paul's avatar
Paul committed
352
{
Paul's avatar
Paul committed
353
    template <class F>
Paul's avatar
Paul committed
354
    auto_register_test_case(const char* name, F f) noexcept
Paul's avatar
Paul committed
355
356
357
    {
        add_test_case(name, f);
    }
Paul's avatar
Paul committed
358
359
};

Paul's avatar
Paul committed
360
inline void run_test_case(const std::string& name, const std::function<void()>& f)
Paul's avatar
Paul committed
361
{
Paul's avatar
Paul committed
362
363
364
365
366
367
368
369
370
    std::cout << "[   RUN    ] " << name << std::endl;
    f();
    std::cout << "[ COMPLETE ] " << name << std::endl;
}

inline void run(int argc, const char* argv[])
{
    std::vector<std::string> as(argv + 1, argv + argc);

Paul's avatar
Paul committed
371
    auto args  = parse(as, [](auto &&) -> std::vector<std::string> { return {}; });
Paul's avatar
Paul committed
372
373
374
    auto cases = args[""];
    if(cases.empty())
    {
Paul's avatar
Paul committed
375
        for(auto&& tc : get_test_cases())
Paul's avatar
Paul committed
376
377
378
379
            run_test_case(tc.first, tc.second);
    }
    else
    {
Paul's avatar
Paul committed
380
381
382
        std::unordered_map<std::string, std::function<void()>> m(get_test_cases().begin(),
                                                                 get_test_cases().end());
        for(auto&& name : cases)
Paul's avatar
Paul committed
383
384
        {
            auto f = m.find(name);
Paul's avatar
Paul committed
385
            if(f == m.end())
Paul's avatar
Paul committed
386
387
388
389
                std::cout << "[  ERROR   ] Test case '" << name << "' not found." << std::endl;
            else
                run_test_case(name, f->second);
        }
Paul's avatar
Paul committed
390
    }
Paul's avatar
Paul committed
391
392
}

Paul's avatar
Paul committed
393
394
395
} // namespace test

// NOLINTNEXTLINE
Paul's avatar
Paul committed
396
397
398
399
#define CHECK(...)                                                                                 \
    test::failed(                                                                                  \
        test::capture{}->*__VA_ARGS__, #__VA_ARGS__, __PRETTY_FUNCTION__, __FILE__, __LINE__, [] { \
        })
Paul's avatar
Paul committed
400
// NOLINTNEXTLINE
Paul's avatar
Paul committed
401
402
403
404
405
406
407
#define EXPECT(...)                             \
    test::failed(test::capture{}->*__VA_ARGS__, \
                 #__VA_ARGS__,                  \
                 __PRETTY_FUNCTION__,           \
                 __FILE__,                      \
                 __LINE__,                      \
                 &std::abort)
Paul's avatar
Paul committed
408
409
410
// NOLINTNEXTLINE
#define STATUS(...) EXPECT((__VA_ARGS__) == 0)

Paul's avatar
Paul committed
411
412
// NOLINTNEXTLINE
#define TEST_CAT(x, ...) TEST_PRIMITIVE_CAT(x, __VA_ARGS__)
Paul's avatar
Paul committed
413
// NOLINTNEXTLINE
Paul's avatar
Paul committed
414
#define TEST_PRIMITIVE_CAT(x, ...) x##__VA_ARGS__
Paul's avatar
Paul committed
415
416

// NOLINTNEXTLINE
Paul's avatar
Paul committed
417
#define TEST_CASE_REGISTER(...)                                                    \
Paul's avatar
Paul committed
418
419
    static test::auto_register_test_case TEST_CAT(register_test_case_, __LINE__) = \
        test::auto_register_test_case(#__VA_ARGS__, &__VA_ARGS__);
Paul's avatar
Paul committed
420

Paul's avatar
Paul committed
421
// NOLINTNEXTLINE
Paul's avatar
Paul committed
422
423
#define TEST_CASE(...)              \
    void __VA_ARGS__();             \
Paul's avatar
Paul committed
424
    TEST_CASE_REGISTER(__VA_ARGS__) \
Paul's avatar
Paul committed
425
    void __VA_ARGS__()
Paul's avatar
Paul committed
426
427
428
429
430
431

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#endif

Paul's avatar
Paul committed
432
#endif