test.hpp 7.43 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
119
120
#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
121
122
};

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

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

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

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

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

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

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

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

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

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

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

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

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

Paul's avatar
Paul committed
288
#endif