Skip to content

Commit

Permalink
Create a QCOMPARE_3WAY macro to test the C++20 spaceship operator <=>
Browse files Browse the repository at this point in the history
Since the C++20 spaceship operator <=> appeared in the standard,
qtest framework starts using it for tests. To make the way of
usage the spaceship operator convenient and informative,
the QCOMPARE_3WAY macro is added.

- Add QCOMPARE_3WAY macro usage testcases.
- Add threewaycompare test to setftests.

[ChangeLog][QtTest] Added the QCOMPARE_3WAY macro. The macro
tests the C++20 spaceship operator <=>

Fixes: QTBUG-104108
Change-Id: Ia14b26c1d70625ac8c6cf2278d597b2a8cbe31d0
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
  • Loading branch information
qt-tatiana committed Nov 14, 2024
1 parent 1e714bb commit 454f010
Show file tree
Hide file tree
Showing 19 changed files with 2,443 additions and 9 deletions.
48 changes: 48 additions & 0 deletions src/testlib/qtestcase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2836,6 +2836,54 @@ bool QTest::compare_helper(bool success, const char *failureMsg,
file, line, failureMsg);
}


/*! \internal
\since 6.9
This function reports the result of a three-way comparison, when needed.
Aside from logging every check if in verbose mode and reporting an
unexpected pass when failure was expected, if \a success is \c true
this produces no output. Otherwise, a failure is reported. The output
on failure reports the expressions compared, their values, the actual
result of the comparison and the expected result of comparison, along
with the supplied failure message \a failureMsg and the \a file and
\a line number at which the error arose.
The expressions compared are supplied as \a lhsExpression and
\a rhsExpression.
These are combined, with \c{"<=>"}, to obtain the actual comparison
expression. Their actual values are pointed to by \a lhsPtr and
\a rhsPtr, which are formatted by \a lhsFormatter and \a rhsFormatter
as, respectively, \c lhsFormatter(lhsPtr) and \c rhsFormatter(rhsPtr).
The actual comparison expression is contrasted,
in the output, with the expected comparison expression
\a expectedExpression. Their respective values are supplied by
\a actualOrderPtr and \a expectedOrderPtr pointers, which are
formatted by \a orderFormatter.
If \a failureMsg is \nullptr a default is used. If a formatter
function returns \a nullptr, the text \c{"<null>"} is used.
*/
bool QTest::compare_3way_helper(bool success, const char *failureMsg,
const void *lhsPtr, const void *rhsPtr,
const char *(*lhsFormatter)(const void*),
const char *(*rhsFormatter)(const void*),
const char *lhsExpression, const char *rhsExpression,
const char *(*orderFormatter)(const void*),
const void *actualOrderPtr, const void *expectedOrderPtr,
const char *expectedExpression,
const char *file, int line)
{
return QTestResult::report3WayResult(success, failureMsg,
lhsPtr, rhsPtr,
lhsFormatter, rhsFormatter,
lhsExpression, rhsExpression,
orderFormatter,
actualOrderPtr, expectedOrderPtr,
expectedExpression,
file, line);
}

/*! \internal
\since 6.4
This function is called by various specializations of QTest::qCompare
Expand Down
75 changes: 74 additions & 1 deletion src/testlib/qtestcase.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
#include <QtCore/qsharedpointer.h>
#include <QtCore/qtemporarydir.h>
#include <QtCore/qthread.h>

#ifdef __cpp_concepts
#include <concepts>
#endif
#include <QtCore/qxpfunctional.h>
#include <QtCore/qxptype_traits.h>
#include <QtCore/q20utility.h>

#include <string.h>
Expand Down Expand Up @@ -288,6 +293,17 @@ do {\
QTEST_FAIL_ACTION; \
} while (false)

#ifdef __cpp_lib_three_way_comparison
#define QCOMPARE_3WAY(lhs, rhs, order) \
do { \
if (!QTest::qCompare3Way(lhs, rhs, order, #lhs, #rhs, #order, __FILE__, __LINE__)) \
QTEST_FAIL_ACTION; \
} while (false)
#else
#define QCOMPARE_3WAY(...) \
static_assert(false, "QCOMPARE_3WAY test requires C++20 operator<=>()")
#endif // __cpp_lib_three_way_comparison

#ifdef QT_TESTCASE_BUILDDIR

#ifndef QT_TESTCASE_SOURCEDIR
Expand Down Expand Up @@ -499,6 +515,17 @@ namespace QTest
const char *actual, const char *expected,
const char *file, int line);

Q_TESTLIB_EXPORT bool compare_3way_helper(bool success, const char *failureMsg,
const void *lhsPtr, const void *rhsPtr,
const char *(*lhsFormatter)(const void*),
const char *(*rhsFormatter)(const void*),
const char *lhsStr, const char *rhsStr,
const char *(*orderFormatter)(const void *),
const void *actualOrderPtr,
const void *expectedOrderPtr,
const char *expectedExpression,
const char *file, int line);

Q_TESTLIB_EXPORT void addColumnInternal(int id, const char *name);

template <typename T>
Expand Down Expand Up @@ -754,8 +781,54 @@ namespace QTest
bool result = Comparator::compare(std::forward<T1>(lhs), std::forward<T2>(rhs));
return doReport(result, std::forward<T1>(lhs), std::forward<T2>(rhs));
}
}

#if defined(__cpp_lib_three_way_comparison)
template <typename OrderingType, typename LHS, typename RHS = LHS>
inline bool qCompare3Way(LHS &&lhs, RHS &&rhs, OrderingType order,
const char *lhsExpression,
const char *rhsExpression,
const char *resultExpression,
const char *file, int line)
{
#if defined(__cpp_concepts)
static_assert(requires { lhs <=> rhs; },
"The three-way comparison operator (<=>) is not implemented");
#endif // __cpp_concepts
static_assert(QtOrderingPrivate::is_ordering_type_v<OrderingType>,
"Please provide, as the order parameter, a value "
"of one of the Qt::{partial,weak,strong}_ordering or "
"std::{partial,weak,strong}_ordering types.");

const auto comparisonResult = std::forward<LHS>(lhs) <=> std::forward<RHS>(rhs);
static_assert(std::is_same_v<decltype(QtOrderingPrivate::to_Qt(comparisonResult)),
decltype(QtOrderingPrivate::to_Qt(order))>,
"The expected and actual ordering types should be the same "
"strength for proper comparison.");

const OrderingType actualOrder = comparisonResult;
using DLHS = q20::remove_cvref_t<LHS>;
using DRHS = q20::remove_cvref_t<RHS>;
using Internal::genericToString;

return compare_3way_helper(actualOrder == order,
"The result of operator<=>() is not what was expected",
std::addressof(lhs), std::addressof(rhs),
genericToString<DLHS>, genericToString<DRHS>,
lhsExpression, rhsExpression,
genericToString<OrderingType>,
std::addressof(actualOrder), std::addressof(order),
resultExpression, file, line);
}
#else
template <typename OrderingType, typename LHS, typename RHS = LHS>
void qCompare3Way(LHS &&lhs, RHS &&rhs, OrderingType order,
const char *lhsExpression,
const char *rhsExpression,
const char *resultExpression,
const char *file, int line) = delete;
#endif // __cpp_lib_three_way_comparison

}

#define QWARN(msg) QTest::qWarn(static_cast<const char *>(msg), __FILE__, __LINE__)

Expand Down
30 changes: 30 additions & 0 deletions src/testlib/qtestcase.qdoc
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,36 @@
\sa QCOMPARE_EQ(), QCOMPARE_NE(), QCOMPARE_LT(), QCOMPARE_LE(), QCOMPARE_GT()
*/

/*! \macro QCOMPARE_3WAY(lhs, rhs, order)
\since 6.9

\relates QTest

The QCOMPARE_3WAY() macro applies the three-way comparison operator \c {<=>}
to the input expressions \a lhs and \a rhs, and checks if the result
is \a order.
If that is true, execution continues. If not, a
failure is recorded in the test log and the test function returns without
attempting any later checks.
The macro only accepts Qt:: and std:: ordering types as \a order
argument, otherwise it asserts.
\note \a order can be a Qt:: ordering type even if a
\c {decltype(lhs <=> rhs)} is a std one.
The result of \c {decltype(lhs <=> rhs)} operation should have the same
strength as \a order. Otherwise, applying the macro will result in
a compilation error. For example, if the result of \c {decltype(lhs <=> rhs)}
has a weak ordering type, the \a order argument can't have partial
or strong ordering types.
\note The macro only works if the compiler supports the \c{<=>} operator,
and otherwise it statically asserts that the prerequisite feature
isn't available. Before a macro usage always check if
\c __cpp_lib_three_way_comparison is defined, and use QSKIP, if it isn't.

\include qtestcase.qdoc macro-usage-limitation

\include qtestcase.qdoc to-string-overload-desc
*/

/*! \macro QVERIFY_EXCEPTION_THROWN(expression, exceptiontype)
\since 5.3

Expand Down
91 changes: 89 additions & 2 deletions src/testlib/qtestresult.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,38 @@ bool QTestResult::verify(bool statement, const char *statementStr,

static const char *leftArgNameForOp(QTest::ComparisonOperation op)
{
return op == QTest::ComparisonOperation::CustomCompare ? "Actual " : "Computed ";
switch (op) {
case QTest::ComparisonOperation::CustomCompare:
return "Actual ";
case QTest::ComparisonOperation::ThreeWayCompare:
return "Left ";
case QTest::ComparisonOperation::Equal:
case QTest::ComparisonOperation::NotEqual:
case QTest::ComparisonOperation::LessThan:
case QTest::ComparisonOperation::LessThanOrEqual:
case QTest::ComparisonOperation::GreaterThan:
case QTest::ComparisonOperation::GreaterThanOrEqual:
return "Computed ";
}
Q_UNREACHABLE_RETURN("");
}

static const char *rightArgNameForOp(QTest::ComparisonOperation op)
{
return op == QTest::ComparisonOperation::CustomCompare ? "Expected " : "Baseline ";
switch (op) {
case QTest::ComparisonOperation::CustomCompare:
return "Expected ";
case QTest::ComparisonOperation::ThreeWayCompare:
return "Right ";
case QTest::ComparisonOperation::Equal:
case QTest::ComparisonOperation::NotEqual:
case QTest::ComparisonOperation::LessThan:
case QTest::ComparisonOperation::LessThanOrEqual:
case QTest::ComparisonOperation::GreaterThan:
case QTest::ComparisonOperation::GreaterThanOrEqual:
return "Baseline ";
}
Q_UNREACHABLE_RETURN("");
}

static int approx_wide_len(const char *s)
Expand Down Expand Up @@ -632,6 +658,8 @@ static const char *macroNameForOp(QTest::ComparisonOperation op)
return "QCOMPARE_GT";
case ComparisonOperation::GreaterThanOrEqual:
return "QCOMPARE_GE";
case ComparisonOperation::ThreeWayCompare:
return "QCOMPARE_3WAY";
}
Q_UNREACHABLE_RETURN("");
}
Expand All @@ -642,6 +670,8 @@ static const char *failureMessageForOp(QTest::ComparisonOperation op)
switch (op) {
case ComparisonOperation::CustomCompare:
return "Compared values are not the same"; /* not used */
case ComparisonOperation::ThreeWayCompare:
return "The result of operator<=>() is not what was expected";
case ComparisonOperation::Equal:
return "The computed value is expected to be equal to the baseline, but is not";
case ComparisonOperation::NotEqual:
Expand Down Expand Up @@ -696,4 +726,61 @@ bool QTestResult::reportResult(bool success, const void *lhs, const void *rhs,
return checkStatement(success, msg, file, line);
}

bool QTestResult::report3WayResult(bool success,
const char *failureMessage,
const void *lhs, const void *rhs,
const char *(*lhsFormatter)(const void*),
const char *(*rhsFormatter)(const void*),
const char *lhsExpression, const char *rhsExpression,
const char *(*orderFormatter)(const void*),
const void *actualOrder, const void *expectedOrder,
const char *expectedExpression,
const char *file, int line)
{
char msg[maxMsgLen];
msg[0] = '\0';

QTEST_ASSERT(lhsExpression);
QTEST_ASSERT(rhsExpression);
QTEST_ASSERT(expectedExpression);
const char *macroName = macroNameForOp(QTest::ComparisonOperation::ThreeWayCompare);
const std::string actualExpression = std::string(lhsExpression) + " <=> " + rhsExpression;

if (QTestLog::verboseLevel() >= 2) {
std::snprintf(msg, maxMsgLen, "%s(%s, %s, %s)",
macroName, lhsExpression, rhsExpression, expectedExpression);
QTestLog::info(msg, file, line);
}

if (success) {
if (QTest::expectFailMode) {
std::snprintf(msg, maxMsgLen, "%s(%s, %s, %s) returned TRUE unexpectedly.",
macroName, lhsExpression, rhsExpression, expectedExpression);
}
return checkStatement(success, msg, file, line);
}
const std::unique_ptr<const char[]> lhsStr{lhsFormatter(lhs)};
const std::unique_ptr<const char[]> rhsStr{rhsFormatter(rhs)};

const std::unique_ptr<const char[]> actual{orderFormatter(actualOrder)};
const std::unique_ptr<const char[]> expected{orderFormatter(expectedOrder)};

if (!failureMessage)
failureMessage = failureMessageForOp(QTest::ComparisonOperation::ThreeWayCompare);

// Left and Right compared parameters of QCOMPARE_3WAY
formatFailMessage(msg, maxMsgLen, failureMessage,
lhsStr.get(), rhsStr.get(),
lhsExpression, rhsExpression,
QTest::ComparisonOperation::ThreeWayCompare);

// Actual and Expected results of comparison
formatFailMessage(msg + strlen(msg), maxMsgLen - strlen(msg), "",
actual.get(), expected.get(),
actualExpression.c_str(), expectedExpression,
QTest::ComparisonOperation::CustomCompare);

return checkStatement(success, msg, file, line);
}

QT_END_NAMESPACE
11 changes: 11 additions & 0 deletions src/testlib/qtestresult_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ class Q_TESTLIB_EXPORT QTestResult
QTest::ComparisonOperation op, const char *file, int line,
const char *failureMessage = nullptr);

static bool report3WayResult(bool success,
const char *failureMessage,
const void *lhs, const void *rhs,
const char *(*lhsFormatter)(const void *),
const char *(*rhsFormatter)(const void *),
const char *lhsExpression, const char *rhsExpression,
const char *(*orderFormatter)(const void *),
const void *actualOrder, const void *expectedOrder,
const char *expectedExpression,
const char *file, int line);

private:
Q_DISABLE_COPY(QTestResult)
};
Expand Down
1 change: 1 addition & 0 deletions src/testlib/qttestglobal.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ namespace QTest
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
ThreeWayCompare,
};
}

Expand Down
Loading

0 comments on commit 454f010

Please sign in to comment.