diff options
Diffstat (limited to 'tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp')
-rw-r--r-- | tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp | 504 |
1 files changed, 463 insertions, 41 deletions
diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp index 6667d95e0d..23d41cafb2 100644 --- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp +++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp @@ -1,7 +1,7 @@ // 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> @@ -76,6 +76,7 @@ CHECK_GET(MyVariant, const &&); #include <QUrl> #include <QUuid> +#include <private/qcomparisontesthelper_p.h> #include <private/qlocale_p.h> #include <private/qmetatype_p.h> #include "tst_qvariant_common.h" @@ -174,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(); @@ -284,6 +291,7 @@ private slots: void variantHash(); void convertToQUint8() const; + void compareCompiles() const; void compareNumerics_data() const; void compareNumerics() const; void comparePointers() const; @@ -299,6 +307,7 @@ private slots: void loadBrokenUserType(); void invalidDate() const; + void compareCustomTypes_data() const; void compareCustomTypes() const; void timeToDateTime() const; void copyingUserTypes() const; @@ -371,13 +380,19 @@ 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(); @@ -398,6 +413,8 @@ private: 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); @@ -428,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"); @@ -475,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()); @@ -686,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 @@ -695,7 +824,6 @@ void tst_QVariant::convert() QCOMPARE(var.toInt(), 0); } - void tst_QVariant::toInt_data() { QTest::addColumn<QVariant>("value"); @@ -1882,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) @@ -2855,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"); @@ -2872,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: @@ -3018,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); @@ -3027,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); @@ -3037,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); @@ -3100,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{} }; - MyClass myClass; + const QVariant nonObjV1 = QVariant::fromValue<const void*>(&arr[0]); + const QVariant nonObjV2 = QVariant::fromValue<const void*>(&arr[1]); - QVariant v = QVariant::fromValue<void *>(&myClass); - QVariant v2 = QVariant::fromValue<void *>(&myClass); + Qt::partial_ordering expectedOrdering = Qt::partial_ordering::equivalent; + QCOMPARE(QVariant::compare(nonObjV1, nonObjV1), expectedOrdering); + QT_TEST_EQUALITY_OPS(nonObjV1, nonObjV1, is_eq(expectedOrdering)); - QCOMPARE(v, v2); + expectedOrdering = Qt::partial_ordering::less; + QCOMPARE(QVariant::compare(nonObjV1, nonObjV2), expectedOrdering); + QT_TEST_EQUALITY_OPS(nonObjV1, nonObjV2, is_eq(expectedOrdering)); + + class QObjectClass : public QObject + { + public: + QObjectClass(QObject *parent = nullptr) : QObject(parent) {} + }; + const QObjectClass c1; + const QObjectClass c2; + + 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 {}; @@ -3317,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 { @@ -4266,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; } @@ -5388,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; @@ -5395,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(' '); @@ -5417,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() @@ -5569,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() { { @@ -5590,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 {}; @@ -5660,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"); @@ -5670,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() @@ -5700,6 +5981,33 @@ 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); @@ -5720,6 +6028,120 @@ void tst_QVariant::copyNonDefaultConstructible() 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}); |