Overte C++ Documentation
QTestExtensions.h
1 //
2 // QTestExtensions.h
3 // tests/
4 //
5 // Created by Seiji Emery on 6/20/15.
6 // Copyright 2015 High Fidelity, Inc.
7 //
8 // Distributed under the Apache License, Version 2.0.
9 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
10 //
11 
12 #ifndef hifi_QTestExtensions_hpp
13 #define hifi_QTestExtensions_hpp
14 
15 #include <QtTest/QtTest>
16 #include <QtCore/QFileInfo>
17 #include <functional>
18 #include <NumericalConstants.h>
19 #include <SharedUtil.h>
20 #include "GLMTestUtils.h"
21 
22 // Implements several extensions to QtTest.
23 //
24 // Problems with QtTest:
25 // - QCOMPARE can compare float values (using a fuzzy compare), but uses an internal threshold
26 // that cannot be set explicitely (and we need explicit, adjustable error thresholds for our physics
27 // and math test code).
28 // - QFAIL takes a const char * failure message, and writing custom messages to it is complicated.
29 //
30 // To solve this, we have:
31 // - QCOMPARE_WITH_ABS_ERROR (compares floats, or *any other type* using explicitely defined error thresholds.
32 // To use it, you need to have a compareWithAbsError function ((T, T) -> V), and operator << for QTextStream).
33 // - QFAIL_WITH_MESSAGE("some " << streamed << " message"), which builds, writes to, and stringifies
34 // a QTextStream using black magic.
35 // - QCOMPARE_WITH_LAMBDA / QCOMPARE_WITH_FUNCTION, which implements QCOMPARE, but with a user-defined
36 // test function ((T, T) -> bool).
37 // - A simple framework to write additional custom test macros as needed (QCOMPARE is reimplemented
38 // from scratch using QTest::qFail, for example).
39 //
40 
41 float getErrorDifference(const float& a, const float& b) {
42  return fabsf(a - b);
43 }
44 
45 // Generates a QCOMPARE-style failure message that can be passed to QTest::qFail.
46 //
47 // Formatting looks like this:
48 // <qFail message> <failMessage....>
49 // Actual: (<stringified actual expr>) : <actual value>
50 // Expected: (<stringified expected expr>): <expected value>
51 // < additional messages (should be separated by "\n\t" for indent formatting)>
52 // Loc: [<file path....>(<linenum>)]
53 //
54 // Additional messages (after actual/expected) can be written using the std::function callback.
55 // If these messages span more than one line, wrap them with "\n\t" to get proper indentation / formatting)
56 //
57 template <typename T> inline
58 QString QTest_generateCompareFailureMessage (
59  const char* failMessage,
60  const T& actual, const T& expected,
61  const char* actual_expr, const char* expected_expr,
62  std::function<QTextStream& (QTextStream&)> writeAdditionalMessages
63 ) {
64  QString s1 = actual_expr, s2 = expected_expr;
65  int pad1_ = qMax(s2.length() - s1.length(), 0);
66  int pad2_ = qMax(s1.length() - s2.length(), 0);
67 
68  QString pad1 = QString(")").rightJustified(pad1_, ' ');
69  QString pad2 = QString(")").rightJustified(pad2_, ' ');
70 
71  QString msg;
72  QTextStream stream(&msg);
73  stream << failMessage << "\n\t"
74  "Actual: (" << actual_expr << pad1 << ": " << actual << "\n\t"
75  "Expected: (" << expected_expr << pad2 << ": " << expected << "\n\t";
76  writeAdditionalMessages(stream);
77  return msg;
78 }
79 
80 // Generates a QCOMPARE-style failure message that can be passed to QTest::qFail.
81 //
82 // Formatting looks like this:
83 // <qFail message> <failMessage....>
84 // Actual: (<stringified actual expr>) : <actual value>
85 // Expected: (<stringified expected expr>): <expected value>
86 // Loc: [<file path....>(<linenum>)]
87 // (no message callback)
88 //
89 template <typename T> inline
90 QString QTest_generateCompareFailureMessage (
91  const char* failMessage,
92  const T& actual, const T& expected,
93  const char* actual_expr, const char* expected_expr
94 ) {
95  QString s1 = actual_expr, s2 = expected_expr;
96  int pad1_ = qMax(s2.length() - s1.length(), 0);
97  int pad2_ = qMax(s1.length() - s2.length(), 0);
98 
99  QString pad1 = QString("): ").rightJustified(pad1_, ' ');
100  QString pad2 = QString("): ").rightJustified(pad2_, ' ');
101 
102  QString msg;
103  QTextStream stream(&msg);
104  stream << failMessage << "\n\t"
105  "Actual: (" << actual_expr << pad1 << actual << "\n\t"
106  "Expected: (" << expected_expr << pad2 << expected;
107  return msg;
108 }
109 
110 // Hacky function that can assemble a QString from a QTextStream via a callback
111 // (ie. stream operations w/out qDebug())
112 inline
113 QString makeMessageFromStream (std::function<void(QTextStream&)> writeMessage) {
114  QString msg;
115  QTextStream stream(&msg);
116  writeMessage(stream);
117  return msg;
118 }
119 
120 inline
121 void QTest_failWithCustomMessage (
122  std::function<void(QTextStream&)> writeMessage, int line, const char* file
123 ) {
124  QTest::qFail(qPrintable(makeMessageFromStream(writeMessage)), file, line);
125 }
126 
127 // Equivalent to QFAIL, but takes a message that can be formatted using stream operators.
128 // Writes to a QTextStream internally, and calls QTest::qFail (the internal impl of QFAIL,
129 // with the current file and line number)
130 //
131 // example:
132 // inline void foo () {
133 // int thing = 2;
134 // QFAIL_WITH_MESSAGE("Message " << thing << ";");
135 // }
136 //
137 #define QFAIL_WITH_MESSAGE(...) \
138 do { \
139  QTest_failWithCustomMessage([&](QTextStream& stream) { stream << __VA_ARGS__; }, __LINE__, __FILE__); \
140  return; \
141 } while(0)
142 
143 // Calls qFail using QTest_generateCompareFailureMessage.
144 // This is (usually) wrapped in macros, but if you call this directly you should return immediately to get QFAIL semantics.
145 template <typename T> inline
146 void QTest_failWithMessage(
147  const char* failMessage,
148  const T& actual, const T& expected,
149  const char* actualExpr, const char* expectedExpr,
150  int line, const char* file
151 ) {
152  QTest::qFail(qPrintable(QTest_generateCompareFailureMessage(
153  failMessage, actual, expected, actualExpr, expectedExpr)), file, line);
154 }
155 
156 // Calls qFail using QTest_generateCompareFailureMessage.
157 // This is (usually) wrapped in macros, but if you call this directly you should return immediately to get QFAIL semantics.
158 template <typename T> inline
159 void QTest_failWithMessage(
160  const char* failMessage,
161  const T& actual, const T& expected,
162  const char* actualExpr, const char* expectedExpr,
163  int line, const char* file,
164  std::function<QTextStream& (QTextStream&)> writeAdditionalMessageLines
165 ) {
166  QTest::qFail(qPrintable(QTest_generateCompareFailureMessage(
167  failMessage, actual, expected, actualExpr, expectedExpr, writeAdditionalMessageLines)), file, line);
168 }
169 
170 // Implements QCOMPARE_WITH_ABS_ERROR
171 template <typename T, typename V> inline
172 bool QTest_compareWithAbsError(
173  const T& actual, const T& expected,
174  const char* actual_expr, const char* expected_expr,
175  int line, const char* file,
176  const V& epsilon
177 ) {
178  if (fabsf(getErrorDifference(actual, expected)) > fabsf(epsilon)) {
179  QTest_failWithMessage(
180  "Compared values are not the same (fuzzy compare)",
181  actual, expected, actual_expr, expected_expr, line, file,
182  [&] (QTextStream& stream) -> QTextStream& {
183  return stream << "Err tolerance: " << getErrorDifference((actual), (expected)) << " > " << epsilon;
184  });
185  return false;
186  }
187  return true;
188 }
189 
190 // Implements a fuzzy QCOMPARE using an explicit epsilon error value.
191 // If you use this, you must have the following functions defined for the types you're using:
192 // <T, V> V compareWithAbsError (const T& a, const T& b) (should return the absolute, max difference between a and b)
193 // <T> QTextStream & operator << (QTextStream& stream, const T& value)
194 //
195 // Here's an implementation for glm::vec3:
196 // inline float compareWithAbsError (const glm::vec3 & a, const glm::vec3 & b) { // returns
197 // return glm::distance(a, b);
198 // }
199 // inline QTextStream & operator << (QTextStream & stream, const T & v) {
200 // return stream << "glm::vec3 { " << v.x << ", " << v.y << ", " << v.z << " }"
201 // }
202 //
203 #define QCOMPARE_WITH_ABS_ERROR(actual, expected, epsilon) \
204 do { \
205  if (!QTest_compareWithAbsError((actual), (expected), #actual, #expected, __LINE__, __FILE__, epsilon)) \
206  return; \
207 } while(0)
208 
209 // Implements QCOMPARE using an explicit, externally defined test function.
210 // The advantage of this (over a manual check or what have you) is that the values of actual and
211 // expected are printed in the event that the test fails.
212 //
213 // testFunc(const T & actual, const T & expected) -> bool: true (test succeeds) | false (test fails)
214 //
215 #define QCOMPARE_WITH_FUNCTION(actual, expected, testFunc) \
216 do { \
217  if (!(testFunc((actual), (expected)))) { \
218  QTest_failWithMessage("Compared values are not the same", (actual), (expected), #actual, #expected, __LINE__, __FILE__); \
219  return; \
220  } \
221 } while (0)
222 
223 // Implements QCOMPARE using an explicit, externally defined test function.
224 // Unlike QCOMPARE_WITH_FUNCTION, this func / closure takes no arguments (which is much more convenient
225 // if you're using a c++11 closure / lambda).
226 //
227 // usage:
228 // QCOMPARE_WITH_LAMBDA(foo, expectedFoo, [&foo, &expectedFoo] () {
229 // return foo->isFooish() && foo->fooishness() >= expectedFoo->fooishness();
230 // });
231 // (fails if foo is not as fooish as expectedFoo)
232 //
233 #define QCOMPARE_WITH_LAMBDA(actual, expected, testClosure) \
234 do { \
235  if (!(testClosure())) { \
236  QTest_failWithMessage("Compared values are not the same", (actual), (expected), #actual, #expected, __LINE__, __FILE__); \
237  return; \
238  } \
239 } while (0)
240 
241 // Same as QCOMPARE_WITH_FUNCTION, but with a custom fail message
242 #define QCOMPARE_WITH_FUNCTION_AND_MESSAGE(actual, expected, testfunc, failMessage) \
243 do { \
244  if (!(testFunc((actual), (expected)))) { \
245  QTest_failWithMessage((failMessage), (actual), (expected), #actual, #expected, __LINE__, __FILE__); \
246  return; \
247  } \
248 } while (0)
249 
250 // Same as QCOMPARE_WITH_FUNCTION, but with a custom fail message
251 #define QCOMPARE_WITH_LAMBDA_AND_MESSAGE(actual, expected, testClosure, failMessage) \
252 do { \
253  if (!(testClosure())) { \
254  QTest_failWithMessage((failMessage), (actual), (expected), #actual, #expected, __LINE__, __FILE__); \
255  return; \
256  } \
257 } while (0)
258 
259 #endif
260 
261 #define QCOMPARE_WITH_EXPR(actual, expected, testExpr) \
262  do { \
263  if (!(testExpr)) { \
264  QTest_failWithMessage("Compared values are not the same", (actual), (expected), #actual, #expected, __LINE__, __FILE__); \
265  } \
266  } while (0)
267 
268 struct ByteData {
269  ByteData (const char* data, size_t length)
270  : data(data), length(length) {}
271  const char* data;
272  size_t length;
273 };
274 
275 QTextStream& operator<<(QTextStream& stream, const ByteData& wrapper) {
276  // Print bytes as hex
277  stream << QByteArray::fromRawData(wrapper.data, (int)wrapper.length).toHex();
278 
279  return stream;
280 }
281 
282 bool compareData(const char* data, const char* expectedData, size_t length) {
283  return memcmp(data, expectedData, length) == 0;
284 }
285 
286 #define COMPARE_DATA(actual, expected, length) \
287  QCOMPARE_WITH_EXPR((ByteData(actual, length)), (ByteData(expected, length)), compareData(actual, expected, length))
288 
289 // Produces a relative error test for float usable QCOMPARE_WITH_LAMBDA.
290 inline auto errorTest (float actual, float expected, float acceptableRelativeError)
291 -> std::function<bool ()> {
292  return [actual, expected, acceptableRelativeError]() {
293  if (fabsf(expected) <= acceptableRelativeError) {
294  return fabsf(actual - expected) < fabsf(acceptableRelativeError);
295  }
296  return fabsf((actual - expected) / expected) < fabsf(acceptableRelativeError);
297  };
298 }
299 
300 #define QCOMPARE_WITH_RELATIVE_ERROR(actual, expected, relativeError) \
301  QCOMPARE_WITH_LAMBDA(actual, expected, errorTest(actual, expected, relativeError))
302 
303 inline QString getTestResource(const QString& relativePath) {
304  static QDir dir;
305  static std::once_flag once;
306  std::call_once(once, [] {
307  QFileInfo fileInfo(__FILE__);
308  auto parentDir = fileInfo.absoluteDir();
309  auto rootDir = parentDir.absoluteFilePath("..");
310  dir.setPath(QDir::cleanPath(rootDir));
311  });
312 
313  return QDir::cleanPath(dir.absoluteFilePath(relativePath));
314 }
315 
316 inline void failAfter(quint64 startUsecs, quint64 secs, const char* message) {
317  if (afterSecs(startUsecs, secs)) {
318  QFAIL(message);
319  }
320 }