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
template <class T>
Paul's avatar
Paul committed
40
41
42
inline std::ostream& operator<<(std::ostream& s, const std::vector<T>& v)
{
    char delim = '{';
Paul's avatar
Paul committed
43
    for(auto&& x : v)
Paul's avatar
Paul committed
44
45
46
47
48
49
50
51
    {
        s << delim << " " << x;
        delim = ',';
    }
    s << " }";
    return s;
}

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

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

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

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

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

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

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

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

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

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