test.hpp 9.13 KB
Newer Older
Paul's avatar
Paul committed
1
2
3
4

#include <cassert>
#include <cstdio>
#include <cstdlib>
Paul's avatar
Paul committed
5
#include <functional>
Paul's avatar
Paul committed
6
#include <iostream>
Paul's avatar
Paul committed
7
8
#include <unordered_map>
#include <vector>
Paul's avatar
Paul committed
9

Paul's avatar
Paul committed
10
11
#ifndef MIGRAPHX_GUARD_TEST_TEST_HPP
#define MIGRAPHX_GUARD_TEST_TEST_HPP
Paul's avatar
Paul committed
12

Paul's avatar
Paul committed
13
namespace test {
14
// clang-format off
Paul's avatar
Paul committed
15
// NOLINTNEXTLINE
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#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
32
33

// NOLINTNEXTLINE
34
#define TEST_EACH_BINARY_OPERATOR_OBJECT(op, name)     \
Paul's avatar
Paul committed
35
36
37
38
39
40
41
42
43
    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
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
// 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
69

Paul's avatar
Paul committed
70
71
72
73
74
75
inline std::ostream& operator<<(std::ostream& s, std::nullptr_t)
{
    s << "nullptr";
    return s;
}

Paul's avatar
Paul committed
76
77
78
79
80
81
82
83
84
85
86
87
template <class T>
inline std::ostream& operator<<(std::ostream& s, const std::vector<T>& v)
{
    s << "{ ";
    for(auto&& x : v)
    {
        s << x << ", ";
    }
    s << "}";
    return s;
}

88
89
90
91
92
93
94
95
96
97
98
inline std::ostream& operator<<(std::ostream& s, const std::vector<bool>& v)
{
    s << "{ ";
    for(auto x : v)
    {
        s << x << ", ";
    }
    s << "}";
    return s;
}

Paul's avatar
Paul committed
99
template <class T, class U, class Operator>
Paul's avatar
Paul committed
100
struct expression
Paul's avatar
Paul committed
101
{
Paul's avatar
Paul committed
102
103
104
105
    T lhs;
    U rhs;

    friend std::ostream& operator<<(std::ostream& s, const expression& self)
Paul's avatar
Paul committed
106
    {
107
        s << self.lhs << " " << Operator::as_string() << " " << self.rhs;
Paul's avatar
Paul committed
108
        return s;
Paul's avatar
Paul committed
109
    }
Paul's avatar
Paul committed
110

Paul's avatar
Paul committed
111
112
113
    decltype(auto) value() const { return Operator::call(lhs, rhs); };
};

Paul's avatar
Paul committed
114
// TODO: Remove rvalue references
Paul's avatar
Paul committed
115
template <class T, class U, class Operator>
Paul's avatar
Paul committed
116
expression<T, U, Operator> make_expression(T&& rhs, U&& lhs, Operator)
Paul's avatar
Paul committed
117
{
Paul's avatar
Paul committed
118
    return {std::forward<T>(rhs), std::forward<U>(lhs)};
Paul's avatar
Paul committed
119
}
Paul's avatar
Paul committed
120

121
template <class T, class Operator = nop>
Paul's avatar
Paul committed
122
123
struct lhs_expression;

Paul's avatar
Paul committed
124
// TODO: Remove rvalue reference
Paul's avatar
Paul committed
125
template <class T>
Paul's avatar
Paul committed
126
lhs_expression<T> make_lhs_expression(T&& lhs)
Paul's avatar
Paul committed
127
{
Paul's avatar
Paul committed
128
    return lhs_expression<T>{std::forward<T>(lhs)};
Paul's avatar
Paul committed
129
130
}

131
132
133
134
135
136
137
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
138
139
140
struct lhs_expression
{
    T lhs;
Paul's avatar
Paul committed
141
    explicit lhs_expression(T e) : lhs(e) {}
Paul's avatar
Paul committed
142
143
144

    friend std::ostream& operator<<(std::ostream& s, const lhs_expression& self)
    {
145
        s << Operator::as_string() << " " << self.lhs;
Paul's avatar
Paul committed
146
147
148
        return s;
    }

149
    decltype(auto) value() const { return Operator::call(lhs); }
Paul's avatar
Paul committed
150
// NOLINTNEXTLINE
151
#define TEST_LHS_BINARY_OPERATOR(op, name)                     \
Paul's avatar
Paul committed
152
153
154
    template <class U>                                         \
    auto operator op(const U& rhs) const                       \
    {                                                          \
Paul's avatar
Paul committed
155
        return make_expression(lhs, rhs, name{}); /* NOLINT */ \
Paul's avatar
Paul committed
156
    }
Paul's avatar
Paul committed
157

158
159
160
161
162
163
164
165
    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
166
// NOLINTNEXTLINE
Paul's avatar
Paul committed
167
168
169
170
171
172
173
174
175
176
177
178
179
#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(|)
180
    TEST_LHS_REOPERATOR (^)
Paul's avatar
Paul committed
181
182
};

Paul's avatar
Paul committed
183
struct capture
Paul's avatar
Paul committed
184
{
Paul's avatar
Paul committed
185
    template <class T>
Paul's avatar
Paul committed
186
    auto operator->*(const T& x) const
Paul's avatar
Paul committed
187
188
189
    {
        return make_lhs_expression(x);
    }
190
191
192
193
194
195

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

Paul's avatar
Paul committed
198
template <class T, class F>
Paul's avatar
Paul committed
199
void failed(T x, const char* msg, const char* func, const char* file, int line, F f)
Paul's avatar
Paul committed
200
{
201
    if(!bool(x.value()))
Paul's avatar
Paul committed
202
    {
Paul's avatar
Paul committed
203
        std::cout << func << std::endl;
Paul's avatar
Paul committed
204
        std::cout << file << ":" << line << ":" << std::endl;
205
206
        std::cout << "    FAILED: " << msg << " "
                  << "[ " << x << " ]" << std::endl;
Paul's avatar
Paul committed
207
208
209
        f();
    }
}
Paul's avatar
Paul committed
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224

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

Khalique's avatar
Khalique committed
225
template <class Exception, class F>
Paul's avatar
Paul committed
226
bool throws(F f, const std::string& msg = "")
Paul's avatar
Paul committed
227
228
229
230
231
232
233
234
235
236
237
238
{
    try
    {
        f();
        return false;
    }
    catch(const Exception& ex)
    {
        return std::string(ex.what()).find(msg) != std::string::npos;
    }
}

Paul's avatar
Paul committed
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
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()
{
    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
271
    get_test_cases().emplace_back(std::move(name), std::move(f));
Paul's avatar
Paul committed
272
273
}

Paul's avatar
Paul committed
274
struct auto_register_test_case
Paul's avatar
Paul committed
275
{
Paul's avatar
Paul committed
276
    template <class F>
Paul's avatar
Paul committed
277
    auto_register_test_case(const char* name, F f) noexcept
Paul's avatar
Paul committed
278
279
280
    {
        add_test_case(name, f);
    }
Paul's avatar
Paul committed
281
282
};

Paul's avatar
Paul committed
283
inline void run_test_case(const std::string& name, const std::function<void()>& f)
Paul's avatar
Paul committed
284
{
Paul's avatar
Paul committed
285
286
287
288
289
290
291
292
293
    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
294
    auto args  = parse(as, [](auto &&) -> std::vector<std::string> { return {}; });
Paul's avatar
Paul committed
295
296
297
    auto cases = args[""];
    if(cases.empty())
    {
Paul's avatar
Paul committed
298
        for(auto&& tc : get_test_cases())
Paul's avatar
Paul committed
299
300
301
302
            run_test_case(tc.first, tc.second);
    }
    else
    {
Paul's avatar
Paul committed
303
304
305
        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
306
307
        {
            auto f = m.find(name);
Paul's avatar
Paul committed
308
            if(f == m.end())
Paul's avatar
Paul committed
309
310
311
312
                std::cout << "[  ERROR   ] Test case '" << name << "' not found." << std::endl;
            else
                run_test_case(name, f->second);
        }
Paul's avatar
Paul committed
313
    }
Paul's avatar
Paul committed
314
315
}

Paul's avatar
Paul committed
316
317
318
} // namespace test

// NOLINTNEXTLINE
Paul's avatar
Paul committed
319
320
321
322
#define CHECK(...)                                                                                 \
    test::failed(                                                                                  \
        test::capture{}->*__VA_ARGS__, #__VA_ARGS__, __PRETTY_FUNCTION__, __FILE__, __LINE__, [] { \
        })
Paul's avatar
Paul committed
323
// NOLINTNEXTLINE
Paul's avatar
Paul committed
324
325
326
327
328
329
330
#define EXPECT(...)                             \
    test::failed(test::capture{}->*__VA_ARGS__, \
                 #__VA_ARGS__,                  \
                 __PRETTY_FUNCTION__,           \
                 __FILE__,                      \
                 __LINE__,                      \
                 &std::abort)
Paul's avatar
Paul committed
331
332
333
// NOLINTNEXTLINE
#define STATUS(...) EXPECT((__VA_ARGS__) == 0)

Paul's avatar
Paul committed
334
335
// NOLINTNEXTLINE
#define TEST_CAT(x, ...) TEST_PRIMITIVE_CAT(x, __VA_ARGS__)
Paul's avatar
Paul committed
336
// NOLINTNEXTLINE
Paul's avatar
Paul committed
337
#define TEST_PRIMITIVE_CAT(x, ...) x##__VA_ARGS__
Paul's avatar
Paul committed
338
339

// NOLINTNEXTLINE
Paul's avatar
Paul committed
340
#define TEST_CASE_REGISTER(...)                                                    \
Paul's avatar
Paul committed
341
342
    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
343

Paul's avatar
Paul committed
344
// NOLINTNEXTLINE
Paul's avatar
Paul committed
345
346
#define TEST_CASE(...)              \
    void __VA_ARGS__();             \
Paul's avatar
Paul committed
347
    TEST_CASE_REGISTER(__VA_ARGS__) \
Paul's avatar
Paul committed
348
    void __VA_ARGS__()
Paul's avatar
Paul committed
349
350
351
352
353
354

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

Paul's avatar
Paul committed
355
#endif