forked from yozlet/interface
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathQTestExtensions.h
256 lines (230 loc) · 9.72 KB
/
QTestExtensions.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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
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
252
//
// QTestExtensions.h
// tests/
//
// Created by Seiji Emery on 6/20/15.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_QTestExtensions_hpp
#define hifi_QTestExtensions_hpp
#include <QtTest/QtTest>
#include <functional>
// Implements several extensions to QtTest.
//
// Problems with QtTest:
// - QCOMPARE can compare float values (using a fuzzy compare), but uses an internal threshold
// that cannot be set explicitely (and we need explicit, adjustable error thresholds for our physics
// and math test code).
// - QFAIL takes a const char * failure message, and writing custom messages to it is complicated.
//
// To solve this, we have:
// - QCOMPARE_WITH_ABS_ERROR (compares floats, or *any other type* using explicitely defined error thresholds.
// To use it, you need to have a compareWithAbsError function ((T, T) -> V), and operator << for QTextStream).
// - QFAIL_WITH_MESSAGE("some " << streamed << " message"), which builds, writes to, and stringifies
// a QTextStream using black magic.
// - QCOMPARE_WITH_LAMBDA / QCOMPARE_WITH_FUNCTION, which implements QCOMPARE, but with a user-defined
// test function ((T, T) -> bool).
// - A simple framework to write additional custom test macros as needed (QCOMPARE is reimplemented
// from scratch using QTest::qFail, for example).
//
// Generates a QCOMPARE-style failure message that can be passed to QTest::qFail.
//
// Formatting looks like this:
// <qFail message> <failMessage....>
// Actual: (<stringified actual expr>) : <actual value>
// Expected: (<stringified expected expr>): <expected value>
// < additional messages (should be separated by "\n\t" for indent formatting)>
// Loc: [<file path....>(<linenum>)]
//
// Additional messages (after actual/expected) can be written using the std::function callback.
// If these messages span more than one line, wrap them with "\n\t" to get proper indentation / formatting)
//
template <typename T> inline
QString QTest_generateCompareFailureMessage (
const char* failMessage,
const T& actual, const T& expected,
const char* actual_expr, const char* expected_expr,
std::function<QTextStream& (QTextStream&)> writeAdditionalMessages
) {
QString s1 = actual_expr, s2 = expected_expr;
int pad1_ = qMax(s2.length() - s1.length(), 0);
int pad2_ = qMax(s1.length() - s2.length(), 0);
QString pad1 = QString(")").rightJustified(pad1_, ' ');
QString pad2 = QString(")").rightJustified(pad2_, ' ');
QString msg;
QTextStream stream (&msg);
stream << failMessage << "\n\t"
"Actual: (" << actual_expr << pad1 << ": " << actual << "\n\t"
"Expected: (" << expected_expr << pad2 << ": " << expected << "\n\t";
writeAdditionalMessages(stream);
return msg;
}
// Generates a QCOMPARE-style failure message that can be passed to QTest::qFail.
//
// Formatting looks like this:
// <qFail message> <failMessage....>
// Actual: (<stringified actual expr>) : <actual value>
// Expected: (<stringified expected expr>): <expected value>
// Loc: [<file path....>(<linenum>)]
// (no message callback)
//
template <typename T> inline
QString QTest_generateCompareFailureMessage (
const char* failMessage,
const T& actual, const T& expected,
const char* actual_expr, const char* expected_expr
) {
QString s1 = actual_expr, s2 = expected_expr;
int pad1_ = qMax(s2.length() - s1.length(), 0);
int pad2_ = qMax(s1.length() - s2.length(), 0);
QString pad1 = QString("): ").rightJustified(pad1_, ' ');
QString pad2 = QString("): ").rightJustified(pad2_, ' ');
QString msg;
QTextStream stream (&msg);
stream << failMessage << "\n\t"
"Actual: (" << actual_expr << pad1 << actual << "\n\t"
"Expected: (" << expected_expr << pad2 << expected;
return msg;
}
// Hacky function that can assemble a QString from a QTextStream via a callback
// (ie. stream operations w/out qDebug())
inline
QString makeMessageFromStream (std::function<void(QTextStream&)> writeMessage) {
QString msg;
QTextStream stream(&msg);
writeMessage(stream);
return msg;
}
inline
void QTest_failWithCustomMessage (
std::function<void(QTextStream&)> writeMessage, int line, const char* file
) {
QTest::qFail(qPrintable(makeMessageFromStream(writeMessage)), file, line);
}
// Equivalent to QFAIL, but takes a message that can be formatted using stream operators.
// Writes to a QTextStream internally, and calls QTest::qFail (the internal impl of QFAIL,
// with the current file and line number)
//
// example:
// inline void foo () {
// int thing = 2;
// QFAIL_WITH_MESSAGE("Message " << thing << ";");
// }
//
#define QFAIL_WITH_MESSAGE(...) \
do { \
QTest_failWithCustomMessage([&](QTextStream& stream) { stream << __VA_ARGS__; }, __LINE__, __FILE__); \
return; \
} while(0)
// Calls qFail using QTest_generateCompareFailureMessage.
// This is (usually) wrapped in macros, but if you call this directly you should return immediately to get QFAIL semantics.
template <typename T> inline
void QTest_failWithMessage(
const char* failMessage,
const T& actual, const T& expected,
const char* actualExpr, const char* expectedExpr,
int line, const char* file
) {
QTest::qFail(qPrintable(QTest_generateCompareFailureMessage(
failMessage, actual, expected, actualExpr, expectedExpr)), file, line);
}
// Calls qFail using QTest_generateCompareFailureMessage.
// This is (usually) wrapped in macros, but if you call this directly you should return immediately to get QFAIL semantics.
template <typename T> inline
void QTest_failWithMessage(
const char* failMessage,
const T& actual, const T& expected,
const char* actualExpr, const char* expectedExpr,
int line, const char* file,
std::function<QTextStream& (QTextStream&)> writeAdditionalMessageLines
) {
QTest::qFail(qPrintable(QTest_generateCompareFailureMessage(
failMessage, actual, expected, actualExpr, expectedExpr, writeAdditionalMessageLines)), file, line);
}
// Implements QCOMPARE_WITH_ABS_ERROR
template <typename T, typename V> inline
bool QTest_compareWithAbsError(
const T& actual, const T& expected,
const char* actual_expr, const char* expected_expr,
int line, const char* file,
const V& epsilon
) {
if (abs(getErrorDifference(actual, expected)) > abs(epsilon)) {
QTest_failWithMessage(
"Compared values are not the same (fuzzy compare)",
actual, expected, actual_expr, expected_expr, line, file,
[&] (QTextStream& stream) -> QTextStream& {
return stream << "Err tolerance: " << getErrorDifference((actual), (expected)) << " > " << epsilon;
});
return false;
}
return true;
}
// Implements a fuzzy QCOMPARE using an explicit epsilon error value.
// If you use this, you must have the following functions defined for the types you're using:
// <T, V> V compareWithAbsError (const T& a, const T& b) (should return the absolute, max difference between a and b)
// <T> QTextStream & operator << (QTextStream& stream, const T& value)
//
// Here's an implementation for glm::vec3:
// inline float compareWithAbsError (const glm::vec3 & a, const glm::vec3 & b) { // returns
// return glm::distance(a, b);
// }
// inline QTextStream & operator << (QTextStream & stream, const T & v) {
// return stream << "glm::vec3 { " << v.x << ", " << v.y << ", " << v.z << " }"
// }
//
#define QCOMPARE_WITH_ABS_ERROR(actual, expected, epsilon) \
do { \
if (!QTest_compareWithAbsError((actual), (expected), #actual, #expected, __LINE__, __FILE__, epsilon)) \
return; \
} while(0)
// Implements QCOMPARE using an explicit, externally defined test function.
// The advantage of this (over a manual check or what have you) is that the values of actual and
// expected are printed in the event that the test fails.
//
// testFunc(const T & actual, const T & expected) -> bool: true (test succeeds) | false (test fails)
//
#define QCOMPARE_WITH_FUNCTION(actual, expected, testFunc) \
do { \
if (!(testFunc((actual), (expected)))) { \
QTest_failWithMessage("Compared values are not the same", (actual), (expected), #actual, #expected, __LINE__, __FILE__); \
return; \
} \
} while (0)
// Implements QCOMPARE using an explicit, externally defined test function.
// Unlike QCOMPARE_WITH_FUNCTION, this func / closure takes no arguments (which is much more convenient
// if you're using a c++11 closure / lambda).
//
// usage:
// QCOMPARE_WITH_LAMBDA(foo, expectedFoo, [&foo, &expectedFoo] () {
// return foo->isFooish() && foo->fooishness() >= expectedFoo->fooishness();
// });
// (fails if foo is not as fooish as expectedFoo)
//
#define QCOMPARE_WITH_LAMBDA(actual, expected, testClosure) \
do { \
if (!(testClosure())) { \
QTest_failWithMessage("Compared values are not the same", (actual), (expected), #actual, #expected, __LINE__, __FILE__); \
return; \
} \
} while (0)
// Same as QCOMPARE_WITH_FUNCTION, but with a custom fail message
#define QCOMPARE_WITH_FUNCTION_AND_MESSAGE(actual, expected, testfunc, failMessage) \
do { \
if (!(testFunc((actual), (expected)))) { \
QTest_failWithMessage((failMessage), (actual), (expected), #actual, #expected, __LINE__, __FILE__); \
return; \
} \
} while (0)
// Same as QCOMPARE_WITH_FUNCTION, but with a custom fail message
#define QCOMPARE_WITH_LAMBDA_AND_MESSAGE(actual, expected, testClosure, failMessage) \
do { \
if (!(testClosure())) { \
QTest_failWithMessage((failMessage), (actual), (expected), #actual, #expected, __LINE__, __FILE__); \
return; \
} \
} while (0)
#endif