// // QTestExtensions.hpp // 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 #include // 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: // - QFUZZY_COMPARE (compares floats, or *any other type* using explicitely defined error thresholds. // To use it, you need to have a fuzzyCompare 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: // // Actual: () : // Expected: (): // < additional messages (should be separated by "\n\t" for indent formatting)> // Loc: [()] // // 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 inline QString QTest_generateCompareFailureMessage ( const char * failMessage, const T & actual, const T & expected, const char * actual_expr, const char * expected_expr, std::function 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: // // Actual: () : // Expected: (): // Loc: [()] // (no message callback) // template 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 writeMessage) { QString msg; QTextStream stream(&msg); writeMessage(stream); return msg; } inline void QTest_failWithCustomMessage ( std::function 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 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 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 writeAdditionalMessageLines ) { QTest::qFail(qPrintable(QTest_generateCompareFailureMessage(failMessage, actual, expected, actualExpr, expectedExpr, writeAdditionalMessageLines)), file, line); } // Implements QFUZZY_COMPARE template inline auto QTest_fuzzyCompare(const T & actual, const T & expected, const char * actual_expr, const char * expected_expr, int line, const char * file, const V & epsilon) -> decltype(fuzzyCompare(actual, expected)) { if (fuzzyCompare(actual, expected) > 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: " << fuzzyCompare((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: // V fuzzyCompare (const T& a, const T& b) (should return the absolute, max difference between a and b) // QTextStream & operator << (QTextStream& stream, const T& value) // // Here's an implementation for glm::vec3: // inline float fuzzyCompare (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 QFUZZY_COMPARE(actual, expected, epsilon) \ do { \ if (!QTest_fuzzyCompare(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