mirror of
https://github.com/overte-org/overte.git
synced 2025-04-10 07:42:43 +02:00
304 lines
11 KiB
C++
304 lines
11 KiB
C++
//
|
|
// 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>
|
|
|
|
#include "GLMTestUtils.h"
|
|
|
|
// 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).
|
|
//
|
|
|
|
|
|
float getErrorDifference(const float& a, const float& b) {
|
|
return fabsf(a - b);
|
|
}
|
|
|
|
// 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 (fabsf(getErrorDifference(actual, expected)) > fabsf(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
|
|
|
|
#define QCOMPARE_WITH_EXPR(actual, expected, testExpr) \
|
|
do { \
|
|
if (!(testExpr)) { \
|
|
QTest_failWithMessage("Compared values are not the same", (actual), (expected), #actual, #expected, __LINE__, __FILE__); \
|
|
} \
|
|
} while(0)
|
|
|
|
|
|
struct ByteData {
|
|
ByteData (const char* data, size_t length)
|
|
: data(data), length(length) {}
|
|
const char* data;
|
|
size_t length;
|
|
};
|
|
|
|
QTextStream & operator << (QTextStream& stream, const ByteData & wrapper) {
|
|
// Print bytes as hex
|
|
stream << QByteArray::fromRawData(wrapper.data, (int)wrapper.length).toHex();
|
|
|
|
return stream;
|
|
}
|
|
|
|
bool compareData (const char* data, const char* expectedData, size_t length) {
|
|
return memcmp(data, expectedData, length) == 0;
|
|
}
|
|
|
|
#define COMPARE_DATA(actual, expected, length) \
|
|
QCOMPARE_WITH_EXPR((ByteData ( actual, length )), (ByteData ( expected, length )), compareData(actual, expected, length))
|
|
|
|
|
|
// Produces a relative error test for float usable QCOMPARE_WITH_LAMBDA.
|
|
inline auto errorTest (float actual, float expected, float acceptableRelativeError)
|
|
-> std::function<bool ()> {
|
|
return [actual, expected, acceptableRelativeError] () {
|
|
if (fabsf(expected) <= acceptableRelativeError) {
|
|
return fabsf(actual - expected) < fabsf(acceptableRelativeError);
|
|
}
|
|
return fabsf((actual - expected) / expected) < fabsf(acceptableRelativeError);
|
|
};
|
|
}
|
|
|
|
#define QCOMPARE_WITH_RELATIVE_ERROR(actual, expected, relativeError) \
|
|
QCOMPARE_WITH_LAMBDA(actual, expected, errorTest(actual, expected, relativeError))
|
|
|
|
|