test.hpp 7.46 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
14
namespace test {
// NOLINTNEXTLINE
Paul's avatar
Paul committed
15
16
17
#define TEST_FOREACH_OPERATOR(m)                                                                   \
    m(==, equal) m(!=, not_equal) m(<=, less_than_equal) m(>=, greater_than_equal) m(<, less_than) \
        m(>, greater_than)
Paul's avatar
Paul committed
18
19

// NOLINTNEXTLINE
Paul's avatar
Paul committed
20
21
22
23
24
25
26
27
28
29
#define TEST_EACH_OPERATOR_OBJECT(op, name)            \
    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
30

Paul's avatar
Paul committed
31
32
TEST_FOREACH_OPERATOR(TEST_EACH_OPERATOR_OBJECT)

Paul's avatar
Paul committed
33
34
35
36
37
38
inline std::ostream& operator<<(std::ostream& s, std::nullptr_t)
{
    s << "nullptr";
    return s;
}

Paul's avatar
Paul committed
39
40
41
42
43
44
45
46
47
48
49
50
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;
}

Paul's avatar
Paul committed
51
template <class T, class U, class Operator>
Paul's avatar
Paul committed
52
struct expression
Paul's avatar
Paul committed
53
{
Paul's avatar
Paul committed
54
55
56
57
    T lhs;
    U rhs;

    friend std::ostream& operator<<(std::ostream& s, const expression& self)
Paul's avatar
Paul committed
58
    {
Paul's avatar
Paul committed
59
60
        s << " [ " << self.lhs << " " << Operator::as_string() << " " << self.rhs << " ]";
        return s;
Paul's avatar
Paul committed
61
    }
Paul's avatar
Paul committed
62

Paul's avatar
Paul committed
63
64
65
    decltype(auto) value() const { return Operator::call(lhs, rhs); };
};

Paul's avatar
Paul committed
66
// TODO: Remove rvalue references
Paul's avatar
Paul committed
67
template <class T, class U, class Operator>
Paul's avatar
Paul committed
68
expression<T, U, Operator> make_expression(T&& rhs, U&& lhs, Operator)
Paul's avatar
Paul committed
69
{
Paul's avatar
Paul committed
70
    return {std::forward<T>(rhs), std::forward<U>(lhs)};
Paul's avatar
Paul committed
71
}
Paul's avatar
Paul committed
72

Paul's avatar
Paul committed
73
template <class T>
Paul's avatar
Paul committed
74
75
struct lhs_expression;

Paul's avatar
Paul committed
76
// TODO: Remove rvalue reference
Paul's avatar
Paul committed
77
template <class T>
Paul's avatar
Paul committed
78
lhs_expression<T> make_lhs_expression(T&& lhs)
Paul's avatar
Paul committed
79
{
Paul's avatar
Paul committed
80
    return lhs_expression<T>{std::forward<T>(lhs)};
Paul's avatar
Paul committed
81
82
}

Paul's avatar
Paul committed
83
template <class T>
Paul's avatar
Paul committed
84
85
86
struct lhs_expression
{
    T lhs;
Paul's avatar
Paul committed
87
    explicit lhs_expression(T e) : lhs(e) {}
Paul's avatar
Paul committed
88
89
90
91
92
93
94

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

Paul's avatar
Paul committed
95
    T value() const { return lhs; }
Paul's avatar
Paul committed
96
// NOLINTNEXTLINE
Paul's avatar
Paul committed
97
98
99
100
#define TEST_LHS_OPERATOR(op, name)                            \
    template <class U>                                         \
    auto operator op(const U& rhs) const                       \
    {                                                          \
Paul's avatar
Paul committed
101
        return make_expression(lhs, rhs, name{}); /* NOLINT */ \
Paul's avatar
Paul committed
102
    }
Paul's avatar
Paul committed
103
104

    TEST_FOREACH_OPERATOR(TEST_LHS_OPERATOR)
Paul's avatar
Paul committed
105
// NOLINTNEXTLINE
Paul's avatar
Paul committed
106
107
108
109
110
111
112
113
114
115
116
117
118
#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(|)
119
    TEST_LHS_REOPERATOR (^)
Paul's avatar
Paul committed
120
121
    TEST_LHS_REOPERATOR(&&)
    TEST_LHS_REOPERATOR(||)
Paul's avatar
Paul committed
122
123
};

Paul's avatar
Paul committed
124
struct capture
Paul's avatar
Paul committed
125
{
Paul's avatar
Paul committed
126
    template <class T>
Paul's avatar
Paul committed
127
    auto operator->*(const T& x) const
Paul's avatar
Paul committed
128
129
130
    {
        return make_lhs_expression(x);
    }
Paul's avatar
Paul committed
131
132
};

Paul's avatar
Paul committed
133
template <class T, class F>
Paul's avatar
Paul committed
134
void failed(T x, const char* msg, const char* func, const char* file, int line, F f)
Paul's avatar
Paul committed
135
{
Paul's avatar
Paul committed
136
    if(!x.value())
Paul's avatar
Paul committed
137
    {
Paul's avatar
Paul committed
138
        std::cout << func << std::endl;
Paul's avatar
Paul committed
139
140
141
142
143
        std::cout << file << ":" << line << ":" << std::endl;
        std::cout << "    FAILED: " << msg << " " << x << std::endl;
        f();
    }
}
Paul's avatar
Paul committed
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158

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

Khalique's avatar
Khalique committed
159
template <class Exception, class F>
Paul's avatar
Paul committed
160
bool throws(F f, const std::string& msg = "")
Paul's avatar
Paul committed
161
162
163
164
165
166
167
168
169
170
171
172
{
    try
    {
        f();
        return false;
    }
    catch(const Exception& ex)
    {
        return std::string(ex.what()).find(msg) != std::string::npos;
    }
}

Paul's avatar
Paul committed
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
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
205
    get_test_cases().emplace_back(std::move(name), std::move(f));
Paul's avatar
Paul committed
206
207
}

Paul's avatar
Paul committed
208
struct auto_register_test_case
Paul's avatar
Paul committed
209
{
Paul's avatar
Paul committed
210
    template <class F>
Paul's avatar
Paul committed
211
    auto_register_test_case(const char* name, F f) noexcept
Paul's avatar
Paul committed
212
213
214
    {
        add_test_case(name, f);
    }
Paul's avatar
Paul committed
215
216
};

Paul's avatar
Paul committed
217
inline void run_test_case(const std::string& name, const std::function<void()>& f)
Paul's avatar
Paul committed
218
{
Paul's avatar
Paul committed
219
220
221
222
223
224
225
226
227
    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
228
    auto args  = parse(as, [](auto &&) -> std::vector<std::string> { return {}; });
Paul's avatar
Paul committed
229
230
231
    auto cases = args[""];
    if(cases.empty())
    {
Paul's avatar
Paul committed
232
        for(auto&& tc : get_test_cases())
Paul's avatar
Paul committed
233
234
235
236
            run_test_case(tc.first, tc.second);
    }
    else
    {
Paul's avatar
Paul committed
237
238
239
        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
240
241
        {
            auto f = m.find(name);
Paul's avatar
Paul committed
242
            if(f == m.end())
Paul's avatar
Paul committed
243
244
245
246
                std::cout << "[  ERROR   ] Test case '" << name << "' not found." << std::endl;
            else
                run_test_case(name, f->second);
        }
Paul's avatar
Paul committed
247
    }
Paul's avatar
Paul committed
248
249
}

Paul's avatar
Paul committed
250
251
252
} // namespace test

// NOLINTNEXTLINE
Paul's avatar
Paul committed
253
254
255
256
#define CHECK(...)                                                                                 \
    test::failed(                                                                                  \
        test::capture{}->*__VA_ARGS__, #__VA_ARGS__, __PRETTY_FUNCTION__, __FILE__, __LINE__, [] { \
        })
Paul's avatar
Paul committed
257
// NOLINTNEXTLINE
Paul's avatar
Paul committed
258
259
260
261
262
263
264
#define EXPECT(...)                             \
    test::failed(test::capture{}->*__VA_ARGS__, \
                 #__VA_ARGS__,                  \
                 __PRETTY_FUNCTION__,           \
                 __FILE__,                      \
                 __LINE__,                      \
                 &std::abort)
Paul's avatar
Paul committed
265
266
267
// NOLINTNEXTLINE
#define STATUS(...) EXPECT((__VA_ARGS__) == 0)

Paul's avatar
Paul committed
268
269
// NOLINTNEXTLINE
#define TEST_CAT(x, ...) TEST_PRIMITIVE_CAT(x, __VA_ARGS__)
Paul's avatar
Paul committed
270
// NOLINTNEXTLINE
Paul's avatar
Paul committed
271
#define TEST_PRIMITIVE_CAT(x, ...) x##__VA_ARGS__
Paul's avatar
Paul committed
272
273

// NOLINTNEXTLINE
Paul's avatar
Paul committed
274
#define TEST_CASE_REGISTER(...)                                                    \
Paul's avatar
Paul committed
275
276
    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
277

Paul's avatar
Paul committed
278
// NOLINTNEXTLINE
Paul's avatar
Paul committed
279
280
#define TEST_CASE(...)              \
    void __VA_ARGS__();             \
Paul's avatar
Paul committed
281
    TEST_CASE_REGISTER(__VA_ARGS__) \
Paul's avatar
Paul committed
282
    void __VA_ARGS__()
Paul's avatar
Paul committed
283
284
285
286
287
288

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

Paul's avatar
Paul committed
289
#endif