diff options
Diffstat (limited to 'tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp')
-rw-r--r-- | tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp | 865 |
1 files changed, 793 insertions, 72 deletions
diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp index d747b6baec..23d41cafb2 100644 --- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp +++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp @@ -1,48 +1,96 @@ // Copyright (C) 2021 The Qt Company Ltd. // Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com> // Copyright (C) 2016 Intel Corporation. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <qvariant.h> + +// don't assume <type_traits> +template <typename T, typename U> +constexpr inline bool my_is_same_v = false; +template <typename T> +constexpr inline bool my_is_same_v<T, T> = true; + +#define CHECK_IMPL(func, arg, Variant, cvref, R) \ + static_assert(my_is_same_v<decltype( func < arg >(std::declval< Variant cvref >())), R cvref >) + +#define CHECK_GET_IF(Variant, cvref) \ + CHECK_IMPL(get_if, int, Variant, cvref *, int) + +#define CHECK_GET(Variant, cvref) \ + CHECK_IMPL(get, int, Variant, cvref, int) + +CHECK_GET_IF(QVariant, /* unadorned */); +CHECK_GET_IF(QVariant, const); + +CHECK_GET(QVariant, &); +CHECK_GET(QVariant, const &); +CHECK_GET(QVariant, &&); +CHECK_GET(QVariant, const &&); + +// check for a type derived from QVariant: + +struct MyVariant : QVariant +{ + using QVariant::QVariant; +}; + +CHECK_GET_IF(MyVariant, /* unadorned */); +CHECK_GET_IF(MyVariant, const); + +CHECK_GET(MyVariant, &); +CHECK_GET(MyVariant, const &); +CHECK_GET(MyVariant, &&); +CHECK_GET(MyVariant, const &&); + +#undef CHECK_GET_IF +#undef CHECK_GET +#undef CHECK_IMPL #include <QTest> -#include <QQueue> -#include <QStack> -#include <QSet> -#include <qvariant.h> -#include <qbitarray.h> -#include <qbytearraylist.h> -#include <qdatetime.h> -#include <qmap.h> -#include <QHash> -#include <qiodevice.h> -#include <qurl.h> -#include <qlocale.h> -#include <qdebug.h> -#include <qjsondocument.h> -#include <quuid.h> - -#include <limits.h> -#include <float.h> -#include <cmath> -#include <variant> -#include <QRegularExpression> -#include <QDir> +// Please stick to alphabetic order. +#include <QAssociativeIterable> +#include <QBitArray> #include <QBuffer> +#include <QByteArrayList> +#include <QDateTime> +#include <QDebug> +#include <QDir> +#include <QEasingCurve> +#include <QMap> +#include <QIODevice> +#include <QHash> #include <QJsonArray> +#include <QJsonDocument> #include <QJsonObject> -#include <QEasingCurve> -#include <QSequentialIterable> -#include <QAssociativeIterable> +#include <QLocale> +#include <QQueue> +#include <QRegularExpression> #include <QScopeGuard> -#include "qnumeric.h" +#include <QSequentialIterable> +#include <QSet> +#include <QStack> +#include <QTimeZone> +#include <QtNumeric> +#include <QUrl> +#include <QUuid> +#include <private/qcomparisontesthelper_p.h> #include <private/qlocale_p.h> #include <private/qmetatype_p.h> #include "tst_qvariant_common.h" +#include <limits> +#include <float.h> +#include <cmath> +#include <variant> #include <unordered_map> +using namespace Qt::StringLiterals; + class CustomNonQObject; +struct NonDefaultConstructible; template<typename T, typename = void> struct QVariantFromValueCompiles @@ -96,7 +144,6 @@ public: tst_QVariant(QObject *parent = nullptr) : QObject(parent), customNonQObjectPointer(0) { - } @@ -128,6 +175,12 @@ private slots: void canConvert_data(); void canConvert(); + + void canConvertAndConvert_ReturnFalse_WhenConvertingBetweenPointerAndValue_data(); + void canConvertAndConvert_ReturnFalse_WhenConvertingBetweenPointerAndValue(); + + void canConvertAndConvert_ReturnFalse_WhenConvertingQObjectBetweenPointerAndValue(); + void convert(); void toSize_data(); @@ -238,6 +291,7 @@ private slots: void variantHash(); void convertToQUint8() const; + void compareCompiles() const; void compareNumerics_data() const; void compareNumerics() const; void comparePointers() const; @@ -253,6 +307,7 @@ private slots: void loadBrokenUserType(); void invalidDate() const; + void compareCustomTypes_data() const; void compareCustomTypes() const; void timeToDateTime() const; void copyingUserTypes() const; @@ -325,14 +380,41 @@ private slots: void preferDirectConversionOverInterfaces(); void mutableView(); + void canViewAndView_ReturnFalseAndDefault_WhenConvertingBetweenPointerAndValue(); + void moveOperations(); void equalsWithoutMetaObject(); void constructFromIncompatibleMetaType_data(); void constructFromIncompatibleMetaType(); + void constructFromQtLT65MetaType(); void copyNonDefaultConstructible(); + void inplaceConstruct(); + void emplace(); + + void getIf_int() { getIf_impl(42); } + void getIf_QString() { getIf_impl(u"string"_s); }; + void getIf_NonDefaultConstructible(); + void getIfSpecial(); + + void get_int() { get_impl(42); } + void get_QString() { get_impl(u"string"_s); } + void get_NonDefaultConstructible(); + private: + using StdVariant = std::variant<std::monostate, + // list here all the types with which we instantiate getIf_impl: + int, + QString, + NonDefaultConstructible + >; + template <typename T> + void getIf_impl(T t) const; + template <typename T> + void get_impl(T t) const; + template<typename T> + void canViewAndView_ReturnFalseAndDefault_WhenConvertingBetweenPointerAndValue_impl(const QByteArray &typeName); void dataStream_data(QDataStream::Version version); void loadQVariantFromDataStream(QDataStream::Version version); void saveQVariantFromDataStream(QDataStream::Version version); @@ -363,11 +445,21 @@ void tst_QVariant::constructor() QVERIFY(var3.isNull()); QVERIFY(var3.isValid()); + QVariant var3a = QVariant::fromMetaType(QMetaType::fromType<QString>()); + QCOMPARE(var3a.typeName(), "QString"); + QVERIFY(var3a.isNull()); + QVERIFY(var3a.isValid()); + QVariant var4 {QMetaType()}; QCOMPARE(var4.typeId(), QMetaType::UnknownType); QVERIFY(var4.isNull()); QVERIFY(!var4.isValid()); + QVariant var4a = QVariant::fromMetaType(QMetaType()); + QCOMPARE(var4a.typeId(), QMetaType::UnknownType); + QVERIFY(var4a.isNull()); + QVERIFY(!var4a.isValid()); + QVariant var5(QLatin1String("hallo")); QCOMPARE(var5.typeId(), QMetaType::QString); QCOMPARE(var5.typeName(), "QString"); @@ -410,6 +502,14 @@ void tst_QVariant::constructor_invalid() } { QTest::ignoreMessage(QtWarningMsg, QRegularExpression("^Trying to construct an instance of an invalid type")); + QVariant variant = QVariant::fromMetaType(QMetaType(typeId)); + QVERIFY(!variant.isValid()); + QVERIFY(variant.isNull()); + QCOMPARE(variant.typeId(), int(QMetaType::UnknownType)); + QCOMPARE(variant.userType(), int(QMetaType::UnknownType)); + } + { + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("^Trying to construct an instance of an invalid type")); QVariant variant(QMetaType(typeId), /* copy */ nullptr); QVERIFY(!variant.isValid()); QVERIFY(variant.isNull()); @@ -621,6 +721,100 @@ QT_WARNING_POP #endif // QT_DEPRECATED_SINCE(6, 0) } +namespace { + +// Used for testing canConvert/convert of QObject derived types +struct QObjectDerived : QObject +{ + Q_OBJECT +}; + +// Adds a test table row for checking value <-> pointer conversion +// If type is a pointer, the target type is value type and vice versa. +template<typename T> +void addRowForPointerValueConversion() +{ + using ValueType = std::remove_pointer_t<T>; + if constexpr (!std::is_same_v<ValueType, std::nullptr_t>) { + + static ValueType instance{}; // static since we may need a pointer to a valid object + + QVariant variant; + if constexpr (std::is_pointer_v<T>) + variant = QVariant::fromValue(&instance); + else + variant = QVariant::fromValue(instance); + + // Toggle pointer/value type + using TargetType = std::conditional_t<std::is_pointer_v<T>, ValueType, T *>; + + const QMetaType fromType = QMetaType::fromType<T>(); + const QMetaType toType = QMetaType::fromType<TargetType>(); + + QTest::addRow("%s->%s", fromType.name(), toType.name()) + << variant << QMetaType::fromType<TargetType>(); + } +} + +} // namespace + +void tst_QVariant::canConvertAndConvert_ReturnFalse_WhenConvertingBetweenPointerAndValue_data() +{ + QTest::addColumn<QVariant>("variant"); + QTest::addColumn<QMetaType>("targetType"); + +#define ADD_ROW(typeName, typeNameId, realType) \ + addRowForPointerValueConversion<realType>(); \ + addRowForPointerValueConversion<realType *>(); + + // Add rows for static primitive types + QT_FOR_EACH_STATIC_PRIMITIVE_NON_VOID_TYPE(ADD_ROW) + + // Add rows for static core types + QT_FOR_EACH_STATIC_CORE_CLASS(ADD_ROW) +#undef ADD_ROW + +} + +void tst_QVariant::canConvertAndConvert_ReturnFalse_WhenConvertingBetweenPointerAndValue() +{ + QFETCH(QVariant, variant); + QFETCH(QMetaType, targetType); + + QVERIFY(!variant.canConvert(targetType)); + + QVERIFY(!variant.convert(targetType)); + + // As per the documentation, when QVariant::convert fails, the + // QVariant is cleared and changed to the requested type. + QVERIFY(variant.isNull()); + QCOMPARE(variant.metaType(), targetType); +} + +void tst_QVariant::canConvertAndConvert_ReturnFalse_WhenConvertingQObjectBetweenPointerAndValue() +{ + // Types derived from QObject are non-copyable and require their own test. + // We only test pointer -> value conversion, because constructing a QVariant + // from a non-copyable object will just set the QVariant to null. + + QObjectDerived object; + QVariant variant = QVariant::fromValue(&object); + + constexpr QMetaType targetType = QMetaType::fromType<QObjectDerived>(); + QVERIFY(!variant.canConvert(targetType)); + + QTest::ignoreMessage( + QtWarningMsg, + QRegularExpression(".*does not support destruction and copy construction")); + + QVERIFY(!variant.convert(targetType)); + + // When the QVariant::convert fails, the QVariant is cleared, and since the target type is + // invalid for QVariant, the QVariant's type is also cleared to an unknown type. + QVERIFY(variant.isNull()); + QCOMPARE(variant.metaType(), QMetaType()); +} + void tst_QVariant::convert() { // verify that after convert(), the variant's type has been changed @@ -630,7 +824,6 @@ void tst_QVariant::convert() QCOMPARE(var.toInt(), 0); } - void tst_QVariant::toInt_data() { QTest::addColumn<QVariant>("value"); @@ -1280,8 +1473,9 @@ void tst_QVariant::toDateTime_data() << QDateTime( QDate( 2002, 10, 10 ), QTime( 12, 34, 56 ) ); QTest::newRow( "qdate" ) << QVariant( QDate( 2002, 10, 10 ) ) << QDateTime( QDate( 2002, 10, 10 ), QTime( 0, 0, 0 ) ); QTest::newRow( "qstring" ) << QVariant( QString( "2002-10-10T12:34:56" ) ) << QDateTime( QDate( 2002, 10, 10 ), QTime( 12, 34, 56 ) ); - QTest::newRow( "qstring-utc" ) << QVariant( QString( "2002-10-10T12:34:56Z" ) ) - << QDateTime( QDate( 2002, 10, 10 ), QTime( 12, 34, 56 ), Qt::UTC ); + QTest::newRow("qstring-utc") + << QVariant(QString("2002-10-10T12:34:56Z")) + << QDateTime(QDate(2002, 10, 10), QTime(12, 34, 56), QTimeZone::UTC); QTest::newRow( "qstring-with-ms" ) << QVariant( QString( "2002-10-10T12:34:56.789" ) ) << QDateTime( QDate( 2002, 10, 10 ), QTime( 12, 34, 56, 789 ) ); } @@ -1566,6 +1760,10 @@ void tst_QVariant::operator_eq_eq_data() QVariant mIntString(QByteArray("-42")); QVariant mIntQString(QString("-42")); + QVariant mIntZero(0); + QVariant mIntStringZero(QByteArray("0")); + QVariant mIntQStringZero(QString("0")); + QVariant mUInt(42u); QVariant mUIntString(QByteArray("42")); QVariant mUIntQString(QString("42")); @@ -1606,6 +1804,9 @@ void tst_QVariant::operator_eq_eq_data() QVariant mBoolString(QByteArray("false")); QVariant mBoolQString(QString("false")); + QVariant mTextString(QByteArray("foobar")); + QVariant mTextQString(QString("foobar")); + QTest::newRow( "double_int" ) << QVariant(42.0) << QVariant(42) << true; QTest::newRow( "float_int" ) << QVariant(42.f) << QVariant(42) << true; QTest::newRow( "mInt_mIntString" ) << mInt << mIntString << false; @@ -1613,6 +1814,21 @@ void tst_QVariant::operator_eq_eq_data() QTest::newRow( "mInt_mIntQString" ) << mInt << mIntQString << true; QTest::newRow( "mIntQString_mInt" ) << mIntQString << mInt << true; + QTest::newRow( "mIntZero_mIntStringZero" ) << mIntZero << mIntStringZero << false; + QTest::newRow( "mIntStringZero_mIntZero" ) << mIntStringZero << mIntZero << false; + QTest::newRow( "mIntZero_mIntQStringZero" ) << mIntZero << mIntQStringZero << true; + QTest::newRow( "mIntQStringZero_mIntZero" ) << mIntQStringZero << mIntZero << true; + + QTest::newRow( "mInt_mTextString" ) << mInt << mTextString << false; + QTest::newRow( "mTextString_mInt" ) << mTextString << mInt << false; + QTest::newRow( "mInt_mTextQString" ) << mInt << mTextQString << false; + QTest::newRow( "mTextQString_mInt" ) << mTextQString << mInt << false; + + QTest::newRow( "mIntZero_mTextString" ) << mIntZero << mTextString << false; + QTest::newRow( "mTextString_mIntZero" ) << mTextString << mIntZero << false; + QTest::newRow( "mIntZero_mTextQString" ) << mIntZero << mTextQString << false; + QTest::newRow( "mTextQString_mIntZero" ) << mTextQString << mIntZero << false; + QTest::newRow( "mUInt_mUIntString" ) << mUInt << mUIntString << false; QTest::newRow( "mUIntString_mUInt" ) << mUIntString << mUInt << false; QTest::newRow( "mUInt_mUIntQString" ) << mUInt << mUIntQString << true; @@ -1623,6 +1839,11 @@ void tst_QVariant::operator_eq_eq_data() QTest::newRow( "mDouble_mDoubleQString" ) << mDouble << mDoubleQString << true; QTest::newRow( "mDoubleQString_mDouble" ) << mDoubleQString << mDouble << true; + QTest::newRow( "mDouble_mTextString" ) << mDouble << mTextString << false; + QTest::newRow( "mTextString_mDouble" ) << mTextString << mDouble << false; + QTest::newRow( "mDouble_mTextQString" ) << mDouble << mTextQString << false; + QTest::newRow( "mTextQString_mDouble" ) << mTextQString << mDouble << false; + QTest::newRow( "mFloat_mFloatString" ) << mFloat << mFloatString << false; QTest::newRow( "mFloatString_mFloat" ) << mFloatString << mFloat << false; QTest::newRow( "mFloat_mFloatQString" ) << mFloat << mFloatQString << true; @@ -1789,7 +2010,7 @@ void tst_QVariant::operator_eq_eq() QFETCH( QVariant, left ); QFETCH( QVariant, right ); QFETCH( bool, equal ); - QCOMPARE( left == right, equal ); + QT_TEST_EQUALITY_OPS(left, right, equal); } #if QT_DEPRECATED_SINCE(6, 0) @@ -1966,6 +2187,8 @@ void tst_QVariant::userType() QVariant userVar; userVar.setValue(data); + QVERIFY(QMetaType::fromName("MyType").isValid()); + QCOMPARE(QMetaType::fromName("MyType"), QMetaType::fromType<MyType>()); QVERIFY(userVar.typeId() > QMetaType::User); QCOMPARE(userVar.userType(), qMetaTypeId<MyType>()); QCOMPARE(userVar.typeName(), "MyType"); @@ -2089,7 +2312,15 @@ void tst_QVariant::podUserType() pod.a = 10; pod.b = 20; + // one of these two must register the type + // (QVariant::fromValue calls QMetaType::fromType) QVariant pod_as_variant = QVariant::fromValue(pod); + QMetaType mt = QMetaType::fromType<MyTypePOD>(); + QCOMPARE(pod_as_variant.metaType(), mt); + QCOMPARE(pod_as_variant.metaType().name(), mt.name()); + QCOMPARE(QMetaType::fromName(mt.name()), mt); + QCOMPARE_NE(pod_as_variant.typeId(), 0); + MyTypePOD pod2 = qvariant_cast<MyTypePOD>(pod_as_variant); QCOMPARE(pod.a, pod2.a); @@ -2752,6 +2983,11 @@ void tst_QVariant::convertToQUint8() const } } +void tst_QVariant::compareCompiles() const +{ + QTestPrivate::testEqualityOperatorsCompile<QVariant>(); +} + void tst_QVariant::compareNumerics_data() const { QTest::addColumn<QVariant>("v1"); @@ -2769,9 +3005,11 @@ void tst_QVariant::compareNumerics_data() const QString::number(v.toULongLong()) : QString::number(v.toLongLong()); switch (v.typeId()) { - case QMetaType::Char: case QMetaType::Char16: + return QString::number(qvariant_cast<char16_t>(v)); case QMetaType::Char32: + return QString::number(qvariant_cast<char32_t>(v)); + case QMetaType::Char: case QMetaType::UChar: return QString::number(v.toUInt()); case QMetaType::SChar: @@ -2915,7 +3153,7 @@ QT_WARNING_POP addComparePair(LLONG_MIN, quint64(LLONG_MIN) + 1); addComparePair(LLONG_MIN + 1, quint64(LLONG_MIN) + 1); addComparePair(LLONG_MIN, LLONG_MAX - 1); - addComparePair(LLONG_MIN, LLONG_MAX); + // addComparePair(LLONG_MIN, LLONG_MAX); // already added by addSingleType() // floating point addComparePair(0.f, 0); @@ -2924,7 +3162,6 @@ QT_WARNING_POP addComparePair(0.f, Q_UINT64_C(0)); addComparePair(0.f, 0.); addComparePair(0.f, 1.); - addComparePair(0.f, 1.); addComparePair(float(1 << 24), 1 << 24); addComparePair(float(1 << 24) - 1, (1 << 24) - 1); addComparePair(-float(1 << 24), 1 << 24); @@ -2934,7 +3171,7 @@ QT_WARNING_POP addComparePair(qQNaN(), std::numeric_limits<float>::quiet_NaN()); if (sizeof(qreal) == sizeof(double)) { addComparePair(std::numeric_limits<float>::min(), std::numeric_limits<double>::min()); - addComparePair(std::numeric_limits<float>::min(), std::numeric_limits<double>::min()); + addComparePair(std::numeric_limits<float>::min(), std::numeric_limits<double>::max()); addComparePair(std::numeric_limits<float>::max(), std::numeric_limits<double>::min()); addComparePair(std::numeric_limits<float>::max(), std::numeric_limits<double>::max()); addComparePair(double(Q_INT64_C(1) << 53), Q_INT64_C(1) << 53); @@ -2997,25 +3234,38 @@ void tst_QVariant::compareNumerics() const QFETCH(QPartialOrdering, result); QCOMPARE(QVariant::compare(v1, v2), result); - QEXPECT_FAIL("invalid-invalid", "needs fixing", Continue); - if (result == QPartialOrdering::Equivalent) - QCOMPARE_EQ(v1, v2); - else - QCOMPARE_NE(v1, v2); + QEXPECT_FAIL("invalid-invalid", "needs fixing", Abort); + QT_TEST_EQUALITY_OPS(v1, v2, is_eq(result)); } void tst_QVariant::comparePointers() const { - class MyClass - { - }; + class NonQObjectClass {}; + const std::array<NonQObjectClass, 2> arr{ NonQObjectClass{}, NonQObjectClass{} }; + + const QVariant nonObjV1 = QVariant::fromValue<const void*>(&arr[0]); + const QVariant nonObjV2 = QVariant::fromValue<const void*>(&arr[1]); + + Qt::partial_ordering expectedOrdering = Qt::partial_ordering::equivalent; + QCOMPARE(QVariant::compare(nonObjV1, nonObjV1), expectedOrdering); + QT_TEST_EQUALITY_OPS(nonObjV1, nonObjV1, is_eq(expectedOrdering)); - MyClass myClass; + expectedOrdering = Qt::partial_ordering::less; + QCOMPARE(QVariant::compare(nonObjV1, nonObjV2), expectedOrdering); + QT_TEST_EQUALITY_OPS(nonObjV1, nonObjV2, is_eq(expectedOrdering)); - QVariant v = QVariant::fromValue<void *>(&myClass); - QVariant v2 = QVariant::fromValue<void *>(&myClass); + class QObjectClass : public QObject + { + public: + QObjectClass(QObject *parent = nullptr) : QObject(parent) {} + }; + const QObjectClass c1; + const QObjectClass c2; - QCOMPARE(v, v2); + const QVariant objV1 = QVariant::fromValue(&c1); + const QVariant objV2 = QVariant::fromValue(&c2); + QT_TEST_EQUALITY_OPS(objV1, objV1, true); + QT_TEST_EQUALITY_OPS(objV1, objV2, false); } struct Data {}; @@ -3214,35 +3464,49 @@ Q_DECLARE_METATYPE(WontCompare); struct WillCompare { int x; + + friend bool operator==(const WillCompare &a, const WillCompare &b) + { return a.x == b.x; } + friend bool operator<(const WillCompare &a, const WillCompare &b) + { return a.x < b.x; } }; -bool operator==(const WillCompare &a, const WillCompare &b) { return a.x == b.x; } Q_DECLARE_METATYPE(WillCompare); -void tst_QVariant::compareCustomTypes() const +void tst_QVariant::compareCustomTypes_data() const { - { - WontCompare f1{0}; - const QVariant variant1(QVariant::fromValue(f1)); + QTest::addColumn<QVariant>("v1"); + QTest::addColumn<QVariant>("v2"); + QTest::addColumn<Qt::partial_ordering>("expectedOrdering"); - WontCompare f2{1}; - const QVariant variant2(QVariant::fromValue(f2)); + QTest::newRow("same_uncomparable") + << QVariant::fromValue(WontCompare{0}) + << QVariant::fromValue(WontCompare{0}) + << Qt::partial_ordering::unordered; - /* No comparison operator exists. */ - QVERIFY(variant1 != variant2); - QVERIFY(variant1 != variant1); - QVERIFY(variant2 != variant2); - } - { - WillCompare f1{0}; - const QVariant variant1(QVariant::fromValue(f1)); + QTest::newRow("same_comparable") + << QVariant::fromValue(WillCompare{0}) + << QVariant::fromValue(WillCompare{0}) + << Qt::partial_ordering::equivalent; - WillCompare f2 {1}; - const QVariant variant2(QVariant::fromValue(f2)); + QTest::newRow("different_comparable") + << QVariant::fromValue(WillCompare{1}) + << QVariant::fromValue(WillCompare{0}) + << Qt::partial_ordering::greater; - QVERIFY(variant1 != variant2); - QCOMPARE(variant1, variant1); - QCOMPARE(variant2, variant2); - } + QTest::newRow("qdatetime_vs_comparable") + << QVariant::fromValue(QDateTime::currentDateTimeUtc()) + << QVariant::fromValue(WillCompare{0}) + << Qt::partial_ordering::unordered; +} + +void tst_QVariant::compareCustomTypes() const +{ + QFETCH(const QVariant, v1); + QFETCH(const QVariant, v2); + QFETCH(const Qt::partial_ordering, expectedOrdering); + + QCOMPARE(QVariant::compare(v1, v2), expectedOrdering); + QT_TEST_EQUALITY_OPS(v1, v2, is_eq(expectedOrdering)); } void tst_QVariant::timeToDateTime() const { @@ -4163,7 +4427,8 @@ void tst_QVariant::dataStream_data(QDataStream::Version version) path = path.prepend(":/stream/").append("/"); QDir dir(path); uint i = 0; - foreach (const QFileInfo &fileInfo, dir.entryInfoList(QStringList() << "*.bin")) { + const auto entries = dir.entryInfoList(QStringList{u"*.bin"_s}); + for (const QFileInfo &fileInfo : entries) { QTest::newRow((path + fileInfo.fileName()).toLatin1()) << fileInfo.filePath(); i += 1; } @@ -4980,6 +5245,13 @@ template <auto value> static void testVariantEnum() QVERIFY(var2.convert(QMetaType::fromType<int>())); QCOMPARE(var2.value<int>(), static_cast<int>(value)); + QVariant strVar = QString::number(qToUnderlying(value)); + QVariant baVar = QByteArray::number(qToUnderlying(value)); + QCOMPARE(strVar.value<Enum>(), value); + QCOMPARE(baVar.value<Enum>(), value); + QCOMPARE(var.value<QString>(), strVar); + QCOMPARE(var.value<QByteArray>(), baVar); + // unary + to silence gcc warning if (losslessConvertToInt) { int intValue = static_cast<int>(value); @@ -5278,6 +5550,16 @@ void tst_QVariant::shouldDeleteVariantDataWorksForAssociative() void tst_QVariant::fromStdVariant() { +#define CHECK_EQUAL(lhs, rhs, type) do { \ + QCOMPARE(lhs.typeId(), rhs.typeId()); \ + if (lhs.isNull()) { \ + QVERIFY(rhs.isNull()); \ + } else { \ + QVERIFY(!rhs.isNull()); \ + QCOMPARE(get< type >(lhs), get< type >(rhs)); \ + } \ + } while (false) + { typedef std::variant<int, bool> intorbool_t; intorbool_t stdvar = 5; @@ -5285,21 +5567,38 @@ void tst_QVariant::fromStdVariant() QVERIFY(!qvar.isNull()); QCOMPARE(qvar.typeId(), QMetaType::Int); QCOMPARE(qvar.value<int>(), std::get<int>(stdvar)); + { + const auto qv2 = QVariant::fromStdVariant(std::move(stdvar)); + CHECK_EQUAL(qv2, qvar, int); + } + stdvar = true; qvar = QVariant::fromStdVariant(stdvar); QVERIFY(!qvar.isNull()); QCOMPARE(qvar.typeId(), QMetaType::Bool); QCOMPARE(qvar.value<bool>(), std::get<bool>(stdvar)); + { + const auto qv2 = QVariant::fromStdVariant(std::move(stdvar)); + CHECK_EQUAL(qv2, qvar, bool); + } } { std::variant<std::monostate, int> stdvar; QVariant qvar = QVariant::fromStdVariant(stdvar); QVERIFY(!qvar.isValid()); + { + const auto qv2 = QVariant::fromStdVariant(std::move(stdvar)); + CHECK_EQUAL(qv2, qvar, int); // fake type, they're empty + } stdvar = -4; qvar = QVariant::fromStdVariant(stdvar); QVERIFY(!qvar.isNull()); QCOMPARE(qvar.typeId(), QMetaType::Int); QCOMPARE(qvar.value<int>(), std::get<int>(stdvar)); + { + const auto qv2 = QVariant::fromStdVariant(std::move(stdvar)); + CHECK_EQUAL(qv2, qvar, int); + } } { std::variant<int, bool, QChar> stdvar = QChar::fromLatin1(' '); @@ -5307,7 +5606,25 @@ void tst_QVariant::fromStdVariant() QVERIFY(!qvar.isNull()); QCOMPARE(qvar.typeId(), QMetaType::QChar); QCOMPARE(qvar.value<QChar>(), std::get<QChar>(stdvar)); + { + const auto qv2 = QVariant::fromStdVariant(std::move(stdvar)); + CHECK_EQUAL(qv2, qvar, QChar); + } } + // rvalue fromStdVariant() actually moves: + { + const auto foo = u"foo"_s; + std::variant<QString, QByteArray> stdvar = foo; + QVariant qvar = QVariant::fromStdVariant(std::move(stdvar)); + const auto ps = get_if<QString>(&stdvar); + QVERIFY(ps); + QVERIFY(ps->isNull()); // QString was moved from + QVERIFY(!qvar.isNull()); + QCOMPARE(qvar.typeId(), QMetaType::QString); + QCOMPARE(get<QString>(qvar), foo); + } + +#undef CHECK_EQUAL } void tst_QVariant::qt4UuidDataStream() @@ -5459,6 +5776,48 @@ void tst_QVariant::mutableView() QCOMPARE(extracted.text, nullptr); } +template<typename T> +void tst_QVariant::canViewAndView_ReturnFalseAndDefault_WhenConvertingBetweenPointerAndValue_impl( + const QByteArray &typeName) +{ + T instance{}; + + // Value -> Pointer + QVariant value = QVariant::fromValue(instance); + QVERIFY2(!value.canView<T *>(), typeName); + QCOMPARE(value.view<T *>(), nullptr); // Expect default constructed pointer + + // Pointer -> Value + QVariant pointer = QVariant::fromValue(&instance); + QVERIFY2(!pointer.canView<T>(), typeName); + QCOMPARE(pointer.view<T>(), T{}); // Expect default constructed. Note: Weak test since instance + // is default constructed, but we detect data corruption +} + +void tst_QVariant::canViewAndView_ReturnFalseAndDefault_WhenConvertingBetweenPointerAndValue() +{ +#define ADD_TEST_IMPL(typeName, typeNameId, realType) \ + canViewAndView_ReturnFalseAndDefault_WhenConvertingBetweenPointerAndValue_impl<realType>( \ + #typeName); + + // Add tests for static primitive types + QT_FOR_EACH_STATIC_PRIMITIVE_NON_VOID_TYPE(ADD_TEST_IMPL) + + // Add tests for static core types + QT_FOR_EACH_STATIC_CORE_CLASS(ADD_TEST_IMPL) +#undef ADD_TEST_IMPL +} + +struct MoveTester +{ + bool wasMoved = false; + MoveTester() = default; + MoveTester(const MoveTester &) {}; // non-trivial on purpose + MoveTester(MoveTester &&other) { other.wasMoved = true; } + MoveTester& operator=(const MoveTester &) = default; + MoveTester& operator=(MoveTester &&other) {other.wasMoved = true; return *this;} +}; + void tst_QVariant::moveOperations() { { @@ -5480,6 +5839,30 @@ void tst_QVariant::moveOperations() v = QVariant::fromValue(list); v2 = std::move(v); QVERIFY(v2.value<std::list<int>>() == list); + + { + MoveTester tester; + QVariant::fromValue(tester); + QVERIFY(!tester.wasMoved); + QVariant::fromValue(std::move(tester)); + QVERIFY(tester.wasMoved); + } + { + const MoveTester tester; + QVariant::fromValue(std::move(tester)); + QVERIFY(!tester.wasMoved); // we don't want to move from const variables + } + { + QVariant var(std::in_place_type<MoveTester>); + const auto p = get_if<MoveTester>(&var); + QVERIFY(p); + auto &tester = *p; + QVERIFY(!tester.wasMoved); + [[maybe_unused]] auto copy = var.value<MoveTester>(); + QVERIFY(!tester.wasMoved); + [[maybe_unused]] auto moved = std::move(var).value<MoveTester>(); + QVERIFY(tester.wasMoved); + } } class NoMetaObject : public QObject {}; @@ -5550,6 +5933,13 @@ private: ~Indestructible() {} }; +struct NotCopyable +{ + NotCopyable() = default; + NotCopyable(const NotCopyable&) = delete; + NotCopyable &operator=(const NotCopyable &) = delete; +}; + void tst_QVariant::constructFromIncompatibleMetaType_data() { QTest::addColumn<QMetaType>("type"); @@ -5560,6 +5950,7 @@ void tst_QVariant::constructFromIncompatibleMetaType_data() addRow(QMetaType::fromType<NonDefaultConstructible>()); addRow(QMetaType::fromType<QObject>()); addRow(QMetaType::fromType<Indestructible>()); + addRow(QMetaType::fromType<NotCopyable>()); } void tst_QVariant::constructFromIncompatibleMetaType() @@ -5590,23 +5981,353 @@ void tst_QVariant::constructFromIncompatibleMetaType() QVERIFY(!QVariant(regular).convert(type)); } +void tst_QVariant::constructFromQtLT65MetaType() +{ + auto qsizeIface = QtPrivate::qMetaTypeInterfaceForType<QSize>(); + + QtPrivate::QMetaTypeInterface qsize64Iface = { + /*revision*/0, + 8, + 8, + QMetaType::NeedsConstruction | QMetaType::NeedsDestruction, + 0, + qsizeIface->metaObjectFn, + "FakeQSize", + qsizeIface->defaultCtr, + qsizeIface->copyCtr, + qsizeIface->moveCtr, + /*dtor =*/ nullptr, + qsizeIface->equals, + qsizeIface->lessThan, + qsizeIface->debugStream, + qsizeIface->dataStreamOut, + qsizeIface->dataStreamIn, + /*legacyregop =*/ nullptr + }; + QVariant var{ QMetaType(&qsize64Iface) }; + QVERIFY(var.isValid()); +} + void tst_QVariant::copyNonDefaultConstructible() { NonDefaultConstructible ndc(42); - QVariant var(QMetaType::fromType<NonDefaultConstructible>(), &ndc); + QVariant var = QVariant::fromValue(ndc); QVERIFY(var.isDetached()); QCOMPARE(var.metaType(), QMetaType::fromType<NonDefaultConstructible>()); QVERIFY(var.constData() != &ndc); // qvariant_cast<T> and QVariant::value<T> don't compile - QCOMPARE(*static_cast<const NonDefaultConstructible *>(var.constData()), ndc); + QCOMPARE(get<NonDefaultConstructible>(std::as_const(var)), ndc); QVariant var2 = var; var2.detach(); // force another copy QVERIFY(var2.isDetached()); QVERIFY(var2.constData() != var.constData()); + QCOMPARE(get<NonDefaultConstructible>(std::as_const(var2)), + get<NonDefaultConstructible>(std::as_const(var))); QCOMPARE(var2, var); } +void tst_QVariant::inplaceConstruct() +{ + { + NonDefaultConstructible ndc(42); + QVariant var(std::in_place_type<NonDefaultConstructible>, 42); + QVERIFY(get_if<NonDefaultConstructible>(&var)); + QCOMPARE(get<NonDefaultConstructible>(var), ndc); + } + + { + std::vector<int> vec {1, 2, 3, 4}; + QVariant var(std::in_place_type<std::vector<int>>, {1, 2, 3, 4}); + QVERIFY(get_if<std::vector<int>>(&var)); + QCOMPARE(get<std::vector<int>>(var), vec); + } +} + +struct LargerThanInternalQVariantStorage { + char data[6 * sizeof(void *)]; +}; + +struct alignas(256) LargerThanInternalQVariantStorageOveraligned { + char data[6 * sizeof(void *)]; +}; + +struct alignas(128) SmallerAlignmentEvenLargerSize { + char data[17 * sizeof(void *)]; +}; + +void tst_QVariant::emplace() +{ + { + // can emplace non default constructible + can emplace on null variant + NonDefaultConstructible ndc(42); + QVariant var; + var.emplace<NonDefaultConstructible>(42); + QVERIFY(get_if<NonDefaultConstructible>(&var)); + QCOMPARE(get<NonDefaultConstructible>(var), ndc); + } + { + // can emplace using ctor taking initializer_list + QVariant var; + var.emplace<std::vector<int>>({0, 1, 2, 3, 4}); + auto vecPtr = get_if<std::vector<int>>(&var); + QVERIFY(vecPtr); + QCOMPARE(vecPtr->size(), 5U); + for (int i = 0; i < 5; ++i) + QCOMPARE(vecPtr->at(size_t(i)), i); + } + // prequisites for the test + QCOMPARE_LE(sizeof(std::vector<int>), sizeof(std::string)); + QCOMPARE(alignof(std::vector<int>), alignof(std::string)); + { + // emplace can reuse storage + auto var = QVariant::fromValue(std::string{}); + QVERIFY(var.data_ptr().is_shared); + auto data = var.constData(); + std::vector<int> &vec = var.emplace<std::vector<int>>(3, 42); + /* alignment is the same, so the pointer is exactly the same; + no offset change */ + auto expected = std::vector<int>{42, 42, 42}; + QCOMPARE(get_if<std::vector<int>>(&var), &vec); + QCOMPARE(get<std::vector<int>>(var), expected); + QCOMPARE(var.constData(), data); + } + { + // emplace can't reuse storage if the variant is shared + auto var = QVariant::fromValue(std::string{}); + [[maybe_unused]] QVariant causesSharing = var; + QVERIFY(var.data_ptr().is_shared); + auto data = var.constData(); + var.emplace<std::vector<int>>(3, 42); + auto expected = std::vector<int>{42, 42, 42}; + QVERIFY(get_if<std::vector<int>>(&var)); + QCOMPARE(get<std::vector<int>>(var), expected); + QCOMPARE_NE(var.constData(), data); + } + { + // emplace puts element into the correct place - non-shared + QVERIFY(QVariant::Private::canUseInternalSpace(QMetaType::fromType<QString>().iface())); + QVariant var; + var.emplace<QString>(QChar('x')); + QVERIFY(!var.data_ptr().is_shared); + } + { + // emplace puts element into the correct place - shared + QVERIFY(!QVariant::Private::canUseInternalSpace(QMetaType::fromType<std::string>().iface())); + QVariant var; + var.emplace<std::string>(42, 'x'); + QVERIFY(var.data_ptr().is_shared); + } + { + // emplace does not reuse the storage if alignment is too large + auto iface = QMetaType::fromType<LargerThanInternalQVariantStorage>().iface(); + QVERIFY(!QVariant::Private::canUseInternalSpace(iface)); + auto var = QVariant::fromValue(LargerThanInternalQVariantStorage{}); + auto data = var.constData(); + var.emplace<LargerThanInternalQVariantStorageOveraligned>(); + QCOMPARE_NE(var.constData(), data); + } + { + // emplace does reuse the storage if new alignment and size are together small enough + auto iface = QMetaType::fromType<LargerThanInternalQVariantStorageOveraligned>().iface(); + QVERIFY(!QVariant::Private::canUseInternalSpace(iface)); + auto var = QVariant::fromValue(LargerThanInternalQVariantStorageOveraligned{}); + auto data = var.constData(); + var.emplace<SmallerAlignmentEvenLargerSize>(); + // no exact match below - the alignment is after all different + QCOMPARE_LE(quintptr(var.constData()), quintptr(data)); + QCOMPARE_LE(quintptr(var.constData()), + quintptr(data) + sizeof(LargerThanInternalQVariantStorageOveraligned)); + } +} + +void tst_QVariant::getIf_NonDefaultConstructible() +{ + getIf_impl(NonDefaultConstructible{42}); +} + +void tst_QVariant::getIfSpecial() +{ + QVariant v{QString{}}; // used to be a null QVariant in Qt 5 + QCOMPARE_NE(get_if<QString>(&v), nullptr); // not anymore... +} + +void tst_QVariant::get_NonDefaultConstructible() +{ + get_impl(NonDefaultConstructible{42}); +} + +template <typename T> +T mutate(const T &t) { return t + t; } +template <> +NonDefaultConstructible mutate(const NonDefaultConstructible &t) +{ + return NonDefaultConstructible{t.i + t.i}; +} + +template <typename T> +QVariant make_null_QVariant_of_type() +{ + return QVariant(QMetaType::fromType<T>()); +} + +template <typename T> +void tst_QVariant::getIf_impl(T t) const +{ + QVariant v = QVariant::fromValue(t); + + QVariant null; + QVERIFY(null.isNull()); + + [[maybe_unused]] + QVariant nulT; + if constexpr (std::is_default_constructible_v<T>) { + // typed null QVariants don't work with non-default-constuctable types + nulT = make_null_QVariant_of_type<T>(); + QVERIFY(nulT.isNull()); + } + + QVariant date = QDate(2023, 3, 3); + static_assert(!std::is_same_v<T, QDate>); + + // for behavioral comparison: + StdVariant stdn = {}, stdv = t; + + // returns nullptr on type mismatch: + { + // const + QCOMPARE_EQ(get_if<T>(&std::as_const(stdn)), nullptr); + QCOMPARE_EQ(get_if<T>(&std::as_const(date)), nullptr); + // mutable + QCOMPARE_EQ(get_if<T>(&stdn), nullptr); + QCOMPARE_EQ(get_if<T>(&date), nullptr); + } + + // returns nullptr on null variant (QVariant only): + { + QCOMPARE_EQ(get_if<T>(&std::as_const(null)), nullptr); + QCOMPARE_EQ(get_if<T>(&null), nullptr); + if constexpr (std::is_default_constructible_v<T>) { + // const access return nullptr + QCOMPARE_EQ(get_if<T>(&std::as_const(nulT)), nullptr); + // but mutable access makes typed null QVariants non-null (like data()) + QCOMPARE_NE(get_if<T>(&nulT), nullptr); + QVERIFY(!nulT.isNull()); + nulT = make_null_QVariant_of_type<T>(); // reset to null state + } + } + + // const access: + { + auto ps = get_if<T>(&std::as_const(stdv)); + static_assert(std::is_same_v<decltype(ps), const T*>); + QCOMPARE_NE(ps, nullptr); + QCOMPARE_EQ(*ps, t); + + auto pv = get_if<T>(&std::as_const(v)); + static_assert(std::is_same_v<decltype(ps), const T*>); + QCOMPARE_NE(pv, nullptr); + QCOMPARE_EQ(*pv, t); + } + + // mutable access: + { + T t2 = mutate(t); + + auto ps = get_if<T>(&stdv); + static_assert(std::is_same_v<decltype(ps), T*>); + QCOMPARE_NE(ps, nullptr); + QCOMPARE_EQ(*ps, t); + *ps = t2; + auto ps2 = get_if<T>(&stdv); + QCOMPARE_NE(ps2, nullptr); + QCOMPARE_EQ(*ps2, t2); + + auto pv = get_if<T>(&v); + static_assert(std::is_same_v<decltype(pv), T*>); + QCOMPARE_NE(pv, nullptr); + QCOMPARE_EQ(*pv, t); + *pv = t2; + auto pv2 = get_if<T>(&v); + QCOMPARE_NE(pv2, nullptr); + QCOMPARE_EQ(*pv2, t2); + + // typed null QVariants become non-null (data() behavior): + if constexpr (std::is_default_constructible_v<T>) { + QVERIFY(nulT.isNull()); + auto pn = get_if<T>(&nulT); + QVERIFY(!nulT.isNull()); + static_assert(std::is_same_v<decltype(pn), T*>); + QCOMPARE_NE(pn, nullptr); + QCOMPARE_EQ(*pn, T{}); + *pn = t2; + auto pn2 = get_if<T>(&nulT); + QCOMPARE_NE(pn2, nullptr); + QCOMPARE_EQ(*pn2, t2); + } + } +} + +template <typename T> +void tst_QVariant::get_impl(T t) const +{ + QVariant v = QVariant::fromValue(t); + + // for behavioral comparison: + StdVariant stdv = t; + + #define FOR_EACH_CVREF(op) \ + op(/*unadorned*/, &&) \ + op(&, &) \ + op(&&, &&) \ + op(const, const &&) \ + op(const &, const &) \ + op(const &&, const &&) \ + /* end */ + + + #define CHECK_RETURN_TYPE_OF(Variant, cvref_in, cvref_out) \ + static_assert(std::is_same_v< \ + decltype(get<T>(std::declval<Variant cvref_in >())), \ + T cvref_out \ + >); \ + /* end */ + #define CHECK_RETURN_TYPE(cvref_in, cvref_out) \ + CHECK_RETURN_TYPE_OF(StdVariant, cvref_in, cvref_out) \ + CHECK_RETURN_TYPE_OF(QVariant, cvref_in, cvref_out) \ + /* end */ + FOR_EACH_CVREF(CHECK_RETURN_TYPE) + #undef CHECK_RETURN_TYPE + + #undef FOR_EACH_CVREF + + // const access: + { + auto &&rs = get<T>(std::as_const(stdv)); + QCOMPARE_EQ(rs, t); + + auto &&rv = get<T>(std::as_const(v)); + QCOMPARE_EQ(rv, t); + } + + // mutable access: + { + T t2 = mutate(t); + + auto &&rs = get<T>(stdv); + QCOMPARE_EQ(rs, t); + rs = t2; + auto &&rs2 = get<T>(stdv); + QCOMPARE_EQ(rs2, t2); + + auto &&rv = get<T>(v); + QCOMPARE_EQ(rv, t); + rv = t2; + auto &&rv2 = get<T>(v); + QCOMPARE_EQ(rv2, t2); + } +} + QTEST_MAIN(tst_QVariant) #include "tst_qvariant.moc" |