summaryrefslogtreecommitdiffstats
path: root/src/testlib/qcomparisontesthelper_p.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/testlib/qcomparisontesthelper_p.h')
-rw-r--r--src/testlib/qcomparisontesthelper_p.h373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/testlib/qcomparisontesthelper_p.h b/src/testlib/qcomparisontesthelper_p.h
new file mode 100644
index 0000000000..afeb1088c4
--- /dev/null
+++ b/src/testlib/qcomparisontesthelper_p.h
@@ -0,0 +1,373 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCOMPARISONTESTHELPER_P_H
+#define QCOMPARISONTESTHELPER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/q20type_traits.h>
+#include <QtCore/qxptype_traits.h>
+#include <QtTest/qtest.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QTestPrivate {
+
+#ifdef __cpp_lib_three_way_comparison
+template <typename LT, typename RT>
+using HasThreeWayComparisonOp = decltype(std::declval<LT>() <=> std::declval<RT>());
+
+template <typename LT, typename RT>
+constexpr bool implementsThreeWayComparisonOp_v = qxp::is_detected_v<HasThreeWayComparisonOp,
+ LT, RT>;
+#endif
+
+Q_TESTLIB_EXPORT QByteArray formatTypeWithCRefImpl(QMetaType type, bool isConst,
+ bool isRef, bool isRvalueRef);
+
+template <typename T>
+QByteArray formatTypeWithCRef()
+{
+ return formatTypeWithCRefImpl(QMetaType::fromType<q20::remove_cvref_t<T>>(),
+ std::is_const_v<std::remove_reference_t<T>>,
+ std::is_reference_v<T>,
+ std::is_rvalue_reference_v<T>);
+}
+
+#define FOR_EACH_CREF(Func, Left, Right, Op, Result) \
+ Func(Left &, Right &, Op, Result) \
+ Func(Left &, Right const &, Op, Result) \
+ Func(Left &, Right &&, Op, Result) \
+ Func(Left &, Right const &&, Op, Result) \
+ Func(Left const &, Right &, Op, Result) \
+ Func(Left const &, Right const &, Op, Result) \
+ Func(Left const &, Right &&, Op, Result) \
+ Func(Left const &, Right const &&, Op, Result) \
+ Func(Left &&, Right &, Op, Result) \
+ Func(Left &&, Right const &, Op, Result) \
+ Func(Left &&, Right &&, Op, Result) \
+ Func(Left &&, Right const &&, Op, Result) \
+ Func(Left const &&, Right &, Op, Result) \
+ Func(Left const &&, Right const &, Op, Result) \
+ Func(Left const &&, Right &&, Op, Result) \
+ Func(Left const &&, Right const &&, Op, Result) \
+ /* END */
+
+#define CHECK_SINGLE_OPERATOR(Left, Right, Op, Result) \
+ do { \
+ constexpr bool qtest_op_check_isImplNoexcept \
+ = noexcept(std::declval<Left>() Op std::declval<Right>()); \
+ if constexpr (!qtest_op_check_isImplNoexcept) { \
+ QEXPECT_FAIL("", QByteArray("(" + formatTypeWithCRef<Left>() \
+ + " " #Op " " + formatTypeWithCRef<Right>() \
+ + ") is not noexcept").constData(), \
+ Continue); \
+ /* Ideally, operators should be noexcept, so warn if they are not. */ \
+ /* Do not make it a hard error, because the fix is not always trivial. */ \
+ QVERIFY(qtest_op_check_isImplNoexcept); \
+ } \
+ static_assert(std::is_convertible_v<decltype( \
+ std::declval<Left>() Op std::declval<Right>()), Result>); \
+ if constexpr (!std::is_same_v<Left, Right>) { \
+ static_assert(std::is_convertible_v<decltype( \
+ std::declval<Right>() Op std::declval<Left>()), Result>); \
+ } \
+ } while (false); \
+ /* END */
+
+/*!
+ \internal
+
+ This function checks that the types \c LeftType and \c RightType properly
+ define {in}equality operators (== and !=). The checks are performed for
+ all combinations of cvref-qualified lvalues and rvalues.
+*/
+template <typename LeftType, typename RightType = LeftType>
+void testEqualityOperatorsCompile()
+{
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, ==, bool)
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, !=, bool)
+}
+
+/*!
+ \internal
+
+ This function checks that the types \c LeftType and \c RightType properly
+ define all comparison operators (==, !=, <, >, <=, >=). The checks are
+ performed for all combinations of cvref-qualified lvalues and rvalues.
+
+ If compiled in C++20 mode, also checks \c {operator<=>()} if that is
+ implemented.
+*/
+template <typename LeftType, typename RightType = LeftType>
+void testAllComparisonOperatorsCompile()
+{
+ testEqualityOperatorsCompile<LeftType, RightType>();
+ if (QTest::currentTestFailed())
+ return;
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, >, bool)
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, <, bool)
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, >=, bool)
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, <=, bool)
+#ifdef __cpp_lib_three_way_comparison
+ if constexpr (implementsThreeWayComparisonOp_v<LeftType, RightType>) {
+ FOR_EACH_CREF(CHECK_SINGLE_OPERATOR, LeftType, RightType, <=>, std::partial_ordering)
+ }
+#endif
+}
+
+#undef CHECK_SINGLE_OPERATOR
+#undef FOR_EACH_CREF
+
+#define CHECK_RUNTIME_CREF(Func, Left, Right, Op, Expected) \
+ do { \
+ Func(Left, Right, Op, Expected); \
+ Func(std::as_const(Left), Right, Op, Expected); \
+ Func(Left, std::as_const(Right), Op, Expected); \
+ Func(std::as_const(Left), std::as_const(Right), Op, Expected); \
+ } while (false) \
+ /* END */
+
+#define CHECK_RUNTIME_LR(Left, Right, Op, Expected) \
+ do { \
+ QCOMPARE_EQ(Left Op Right, Expected); \
+ QCOMPARE_EQ(std::move(Left) Op Right, Expected); \
+ QCOMPARE_EQ(Left Op std::move(Right), Expected); \
+ QCOMPARE_EQ(std::move(Left) Op std::move(Right), Expected); \
+ } while (false) \
+ /* END */
+
+#ifdef __cpp_lib_three_way_comparison
+
+// Hide the macro under an ifdef, because it otherwise triggers a warning
+// in Clang C++17 build.
+#define CHECK_RUNTIME_3WAY(Left, Right, Op, Expected) \
+ do { \
+ QCOMPARE_EQ((Left <=> Right) Op 0, Expected); \
+ QCOMPARE_EQ((std::move(Left) <=> Right) Op 0, Expected); \
+ QCOMPARE_EQ((Left <=> std::move(Right)) Op 0, Expected); \
+ QCOMPARE_EQ((std::move(Left) <=> std::move(Right)) Op 0, Expected); \
+ } while (false) \
+ /* END */
+
+#endif // __cpp_lib_three_way_comparison
+
+/*!
+ \internal
+ Basic testing of equality operators.
+
+ The helper function tests {in}equality operators (== and !=) for the \a lhs
+ operand of type \c {LeftType} and the \a rhs operand of type \c {RightType},
+ plus the reverse order of the operands.
+
+ The \a expectedEqual parameter is an expected result for \c {operator==()}.
+
+ \note Any test calling this method will need to check the test state after
+ doing so, if there is any later code in the test.
+
+ \code
+ QTime early(12, 34, 56, 00);
+ QTime later(12, 34, 56, 01);
+ QTestPrivate::testEqualityOperators(early, later, false);
+ if (QTest:currentTestFailed())
+ return;
+ \endcode
+*/
+template <typename LeftType, typename RightType>
+void testEqualityOperators(LeftType lhs, RightType rhs, bool expectedEqual)
+{
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, ==, expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, !=, !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, ==, expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, !=, !expectedEqual);
+}
+
+/*!
+ \internal
+ Basic testing of equality and relation operators.
+
+ The helper function tests all six relation and equality operators
+ (==, !=, <, >, <=, >=) for the \a lhs operand of type \c {LeftType} and
+ the \a rhs operand of type \c {RightType} and all six for the reverse
+ order of the operands.
+
+ If compiled in C++20 mode, also checks \c {operator<=>()} if that is
+ implemented.
+
+ When compiled in C++17 mode, the \c OrderingType must be one of
+ Qt::partial_ordering, Qt::strong_ordering, or Qt::weak_ordering.
+ In C++20 mode, also the \c {std::*_ordering} types can be used.
+
+ The \a expectedOrdering parameter provides the expected
+ relation between \a lhs and \a rhs.
+
+ \note Any test calling this method will need to check the test state after
+ doing so, if there is any later code in the test.
+
+ \code
+ QDateTime now = QDateTime::currentDateTime();
+ QDateTime later = now.addMSec(1);
+ QTestPrivate::testComparisonOperators(now, later, Qt::weak_ordering::less);
+ if (QTest:currentTestFailed())
+ return;
+ \endcode
+*/
+template <typename LeftType, typename RightType, typename OrderingType>
+void testAllComparisonOperators(LeftType lhs, RightType rhs, OrderingType expectedOrdering)
+{
+ constexpr bool isQOrderingType = std::is_same_v<OrderingType, Qt::partial_ordering>
+ || std::is_same_v<OrderingType, Qt::weak_ordering>
+ || std::is_same_v<OrderingType, Qt::strong_ordering>;
+#ifdef __cpp_lib_three_way_comparison
+ constexpr bool isStdOrderingType = std::is_same_v<OrderingType, std::partial_ordering>
+ || std::is_same_v<OrderingType, std::weak_ordering>
+ || std::is_same_v<OrderingType, std::strong_ordering>;
+#else
+ constexpr bool isStdOrderingType = false;
+#endif
+
+ static_assert(isQOrderingType || isStdOrderingType,
+ "Please provide, as the expectedOrdering parameter, a value "
+ "of one of the Qt::{partial,weak,strong}_ordering or "
+ "std::{partial,weak,strong}_ordering types.");
+
+ // We have all sorts of operator==() between Q*Ordering and std::*_ordering
+ // types, so we can just compare to Qt::partial_ordering.
+ const bool expectedEqual = expectedOrdering == Qt::partial_ordering::equivalent;
+ const bool expectedLess = expectedOrdering == Qt::partial_ordering::less;
+ const bool expectedUnordered = expectedOrdering == Qt::partial_ordering::unordered;
+
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, ==,
+ !expectedUnordered && expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, !=,
+ expectedUnordered || !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, <,
+ !expectedUnordered && expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, >,
+ !expectedUnordered && !expectedLess && !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, <=,
+ !expectedUnordered && (expectedEqual || expectedLess));
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, lhs, rhs, >=,
+ !expectedUnordered && !expectedLess);
+#ifdef __cpp_lib_three_way_comparison
+ if constexpr (implementsThreeWayComparisonOp_v<LeftType, RightType>) {
+ if constexpr (std::is_convertible_v<OrderingType, std::strong_ordering>)
+ static_assert(std::is_same_v<decltype(lhs <=> rhs), std::strong_ordering>);
+ else if constexpr (std::is_convertible_v<OrderingType, std::weak_ordering>)
+ static_assert(std::is_same_v<decltype(lhs <=> rhs), std::weak_ordering>);
+ else
+ static_assert(std::is_same_v<decltype(lhs <=> rhs), std::partial_ordering>);
+
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, ==,
+ !expectedUnordered && expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, !=,
+ expectedUnordered || !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, <,
+ !expectedUnordered && expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, >,
+ !expectedUnordered && !expectedLess && !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, <=,
+ !expectedUnordered && (expectedEqual || expectedLess));
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, lhs, rhs, >=,
+ !expectedUnordered && !expectedLess);
+ }
+#endif
+
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, ==,
+ !expectedUnordered && expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, !=,
+ expectedUnordered || !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, <,
+ !expectedUnordered && !expectedLess && !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, >,
+ !expectedUnordered && expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, <=,
+ !expectedUnordered && !expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_LR, rhs, lhs, >=,
+ !expectedUnordered && (expectedEqual || expectedLess));
+#ifdef __cpp_lib_three_way_comparison
+ if constexpr (implementsThreeWayComparisonOp_v<LeftType, RightType>) {
+ if constexpr (std::is_convertible_v<OrderingType, std::strong_ordering>)
+ static_assert(std::is_same_v<decltype(rhs <=> lhs), std::strong_ordering>);
+ else if constexpr (std::is_convertible_v<OrderingType, std::weak_ordering>)
+ static_assert(std::is_same_v<decltype(rhs <=> lhs), std::weak_ordering>);
+ else
+ static_assert(std::is_same_v<decltype(rhs <=> lhs), std::partial_ordering>);
+
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, ==,
+ !expectedUnordered && expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, !=,
+ expectedUnordered || !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, <,
+ !expectedUnordered && !expectedLess && !expectedEqual);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, >,
+ !expectedUnordered && expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, <=,
+ !expectedUnordered && !expectedLess);
+ CHECK_RUNTIME_CREF(CHECK_RUNTIME_3WAY, rhs, lhs, >=,
+ !expectedUnordered && (expectedEqual || expectedLess));
+ }
+#endif
+}
+
+#ifdef __cpp_lib_three_way_comparison
+#undef CHECK_RUNTIME_3WAY
+#endif
+#undef CHECK_RUNTIME_LR
+#undef CHECK_RUNTIME_CREF
+
+} // namespace QTestPrivate
+
+/*!
+ \internal
+
+ A helper macro that calls QTestPrivate::testEqualityOperators(), checks the
+ test's state after the function is executed, and generates a meaningful
+ debug message with the original file and line numbers if the test has
+ failed.
+*/
+#define QT_TEST_EQUALITY_OPS(Left, Right, Expected) \
+ do { \
+ auto report = qScopeGuard([] { \
+ qDebug("testEqualityOperators(" #Left ", " #Right ", " #Expected ") " \
+ "failed in " __FILE__ " on line %d", __LINE__); \
+ }); \
+ QTestPrivate::testEqualityOperators(Left, Right, Expected); \
+ if (QTest::currentTestFailed()) \
+ return; \
+ report.dismiss(); \
+ } while (false)
+
+/*!
+ \internal
+
+ A helper macro that calls QTestPrivate::testAllComparisonOperators(), checks
+ the test's state after the function is executed, and generates a meaningful
+ debug message with the original file and line numbers if the test has
+ failed.
+*/
+#define QT_TEST_ALL_COMPARISON_OPS(Left, Right, Expected) \
+ do { \
+ auto report = qScopeGuard([] { \
+ qDebug("testAllComparisonOperators(" #Left ", " #Right ", " #Expected ") " \
+ "failed in " __FILE__ " on line %d", __LINE__); \
+ }); \
+ QTestPrivate::testAllComparisonOperators(Left, Right, Expected); \
+ if (QTest::currentTestFailed()) \
+ return; \
+ report.dismiss(); \
+ } while (false)
+
+QT_END_NAMESPACE
+
+#endif // QCOMPARISONTESTHELPER_P_H