diff options
Diffstat (limited to 'tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp')
-rw-r--r-- | tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp | 847 |
1 files changed, 783 insertions, 64 deletions
diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp index 6e63792a11..cc7edb8bf2 100644 --- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp +++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp @@ -1,47 +1,23 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QObject> #include <QSignalSpy> #include <qtest.h> #include <qproperty.h> #include <private/qproperty_p.h> +#include <private/qobject_p.h> -#if __has_include(<source_location>) && __cplusplus >= 202002L && !defined(Q_CLANG_QDOC) +#if __has_include(<source_location>) && __cplusplus >= 202002L && !defined(Q_QDOC) #include <source_location> #define QT_SOURCE_LOCATION_NAMESPACE std -#elif __has_include(<experimental/source_location>) && __cplusplus >= 201703L && !defined(Q_CLANG_QDOC) +#elif __has_include(<experimental/source_location>) && !defined(Q_QDOC) #include <experimental/source_location> #define QT_SOURCE_LOCATION_NAMESPACE std::experimental #endif using namespace QtPrivate; - +using namespace Qt::StringLiterals; struct DtorCounter { static inline int counter = 0; @@ -53,6 +29,7 @@ class tst_QProperty : public QObject { Q_OBJECT private slots: + void inheritQUntypedPropertyData(); void functorBinding(); void basicDependencies(); void multipleDependencies(); @@ -91,6 +68,7 @@ private slots: void quntypedBindableApi(); void readonlyConstQBindable(); void qobjectBindableManualNotify(); + void qobjectBindableReallocatedBindingStorage(); void qobjectBindableSignalTakingNewValue(); void testNewStuff(); @@ -99,15 +77,22 @@ private slots: void metaProperty(); void modifyObserverListWhileIterating(); + void noDoubleCapture(); void compatPropertyNoDobuleNotification(); void compatPropertySignals(); void noFakeDependencies(); +#if QT_CONFIG(thread) + void threadSafety(); + void threadSafety2(); +#endif // QT_CONFIG(thread) void bindablePropertyWithInitialization(); void noDoubleNotification(); void groupedNotifications(); void groupedNotificationConsistency(); + void bindingGroupMovingBindingData(); + void bindingGroupBindingDeleted(); void uninstalledBindingDoesNotEvaluate(); void notify(); @@ -115,8 +100,71 @@ private slots: void bindableInterfaceOfCompatPropertyUsesSetter(); void selfBindingShouldNotCrash(); + + void qpropertyAlias(); + void scheduleNotify(); + + void notifyAfterAllDepsGone(); + + void propertyAdaptorBinding(); + void propertyUpdateViaSignaledProperty(); + + void derefFromObserver(); }; +namespace { +template <class T> +constexpr auto isDerivedFromQUntypedPropertyData = std::is_base_of_v<QUntypedPropertyData, T>; + +template <typename Property> +constexpr auto isDerivedFromQUntypedPropertyDataFunc(const Property &property) +{ + Q_UNUSED(property); + return isDerivedFromQUntypedPropertyData<Property>; +} + +template <typename Property> +constexpr auto isDerivedFromQUntypedPropertyDataFunc(Property *property) +{ + Q_UNUSED(property); + return isDerivedFromQUntypedPropertyData<Property>; +} +} // namespace + +void tst_QProperty::inheritQUntypedPropertyData() +{ + class propertyPublic : public QUntypedPropertyData + { + }; + class propertyPrivate : private QUntypedPropertyData + { + }; + + // Compile time test + static_assert(isDerivedFromQUntypedPropertyData<propertyPublic>); + static_assert(isDerivedFromQUntypedPropertyData<propertyPrivate>); + static_assert(isDerivedFromQUntypedPropertyData<QPropertyData<int>>); + static_assert(isDerivedFromQUntypedPropertyData<QProperty<int>>); + + // Run time test + propertyPublic _propertyPublic; + propertyPrivate _propertyPrivate; + QPropertyData<int> qpropertyData; + QProperty<int> qproperty; + std::unique_ptr<propertyPublic> _propertyPublicPtr{ new propertyPublic }; + std::unique_ptr<propertyPrivate> _propertyPrivatePtr{ new propertyPrivate }; + std::unique_ptr<QPropertyData<int>> qpropertyDataPtr{ new QPropertyData<int> }; + std::unique_ptr<QProperty<int>> qpropertyPtr{ new QProperty<int> }; + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPublic)); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPrivate)); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qpropertyData)); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qproperty)); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPublicPtr.get())); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPrivatePtr.get())); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qpropertyDataPtr.get())); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qpropertyPtr.get())); +} + void tst_QProperty::functorBinding() { QProperty<int> property([]() { return 42; }); @@ -269,6 +317,7 @@ void tst_QProperty::bindingAfterUse() void tst_QProperty::bindingFunctionDtorCalled() { + DtorCounter::counter = 0; DtorCounter dc; { QProperty<int> prop; @@ -407,7 +456,7 @@ void tst_QProperty::changeHandler() } testProperty = 3; - QCOMPARE(recordedValues.count(), 2); + QCOMPARE(recordedValues.size(), 2); QCOMPARE(recordedValues.at(0), 1); QCOMPARE(recordedValues.at(1), 2); } @@ -450,7 +499,7 @@ void tst_QProperty::subscribe() } testProperty = 3; - QCOMPARE(recordedValues.count(), 3); + QCOMPARE(recordedValues.size(), 3); QCOMPARE(recordedValues.at(0), 42); QCOMPARE(recordedValues.at(1), 1); QCOMPARE(recordedValues.at(2), 2); @@ -885,7 +934,7 @@ void tst_QProperty::notifiedProperty() check(); instance.property.setValue(42); - QCOMPARE(instance.recordedValues.count(), 1); + QCOMPARE(instance.recordedValues.size(), 1); QCOMPARE(instance.recordedValues.at(0), 42); instance.recordedValues.clear(); check(); @@ -915,7 +964,7 @@ void tst_QProperty::notifiedProperty() subscribedCount = 0; QCOMPARE(instance.property.value(), 100); - QCOMPARE(instance.recordedValues.count(), 1); + QCOMPARE(instance.recordedValues.size(), 1); QCOMPARE(instance.recordedValues.at(0), 100); instance.recordedValues.clear(); check(); @@ -923,7 +972,7 @@ void tst_QProperty::notifiedProperty() injectedValue = 200; QCOMPARE(instance.property.value(), 200); - QCOMPARE(instance.recordedValues.count(), 1); + QCOMPARE(instance.recordedValues.size(), 1); QCOMPARE(instance.recordedValues.at(0), 200); instance.recordedValues.clear(); check(); @@ -932,7 +981,7 @@ void tst_QProperty::notifiedProperty() injectedValue = 400; QCOMPARE(instance.property.value(), 400); - QCOMPARE(instance.recordedValues.count(), 1); + QCOMPARE(instance.recordedValues.size(), 1); QCOMPARE(instance.recordedValues.at(0), 400); instance.recordedValues.clear(); check(); @@ -1171,12 +1220,12 @@ void tst_QProperty::qobjectBindableManualNotify() object.fooData.setValueBypassingBindings(42); // there is no change. QCOMPARE(fooChangeCount, 0); - QCOMPARE(fooChangedSpy.count(), 0); + QCOMPARE(fooChangedSpy.size(), 0); // Once we notify manually object.fooData.notify(); // observers are notified and the signal arrives. QCOMPARE(fooChangeCount, 1); - QCOMPARE(fooChangedSpy.count(), 1); + QCOMPARE(fooChangedSpy.size(), 1); // If we set a binding int i = 1; @@ -1185,20 +1234,37 @@ void tst_QProperty::qobjectBindableManualNotify() QCOMPARE(object.foo(), 1); // and the change and signal count are incremented. QCOMPARE(fooChangeCount, 2); - QCOMPARE(fooChangedSpy.count(), 2); + QCOMPARE(fooChangedSpy.size(), 2); // Changing a non-property won't trigger any notification. i = 2; QCOMPARE(fooChangeCount, 2); - QCOMPARE(fooChangedSpy.count(), 2); + QCOMPARE(fooChangedSpy.size(), 2); // Manually triggering the notification object.fooData.notify(); // increments the change count QCOMPARE(fooChangeCount, 3); - QCOMPARE(fooChangedSpy.count(), 3); + QCOMPARE(fooChangedSpy.size(), 3); // but doesn't actually cause a binding reevaluation. QCOMPARE(object.foo(), 1); } + +struct ReallocObject : QObject { + ReallocObject() + { v.setBinding([this] { return x.value() + y.value() + z.value(); }); } + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, v) + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, x) + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, y) + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, z) +}; + +void tst_QProperty::qobjectBindableReallocatedBindingStorage() +{ + ReallocObject object; + object.x = 1; + QCOMPARE(object.v.value(), 1); +} + void tst_QProperty::qobjectBindableSignalTakingNewValue() { // Given an object of type MyQObject, @@ -1452,6 +1518,7 @@ void tst_QProperty::modifyObserverListWhileIterating() DestructingObserver observer = {}; char* memory; ~ObserverOrUninit() {} + ObserverOrUninit() {} }; { // observer deletes itself while running the notification @@ -1485,12 +1552,29 @@ void tst_QProperty::modifyObserverListWhileIterating() } } +void tst_QProperty::noDoubleCapture() +{ + QProperty<long long> size; + size = 3; + QProperty<int> max; + max.setBinding([&size]() -> int { + // each loop run attempts to capture size + for (int i = 0; i < size; ++i) {} + return size.value(); + }); + auto bindingPriv = QPropertyBindingPrivate::get(max.binding()); + QCOMPARE(bindingPriv->dependencyObserverCount, 1U); + size = 4; // should not crash + QCOMPARE(max.value(), 4); +} + class CompatPropertyTester : public QObject { Q_OBJECT Q_PROPERTY(int prop1 READ prop1 WRITE setProp1 BINDABLE bindableProp1) Q_PROPERTY(int prop2 READ prop2 WRITE setProp2 NOTIFY prop2Changed BINDABLE bindableProp2) Q_PROPERTY(int prop3 READ prop3 WRITE setProp3 NOTIFY prop3Changed BINDABLE bindableProp3) + Q_PROPERTY(int prop4 READ prop4 WRITE setProp4 NOTIFY prop4Changed BINDABLE bindableProp4) public: CompatPropertyTester(QObject *parent = nullptr) : QObject(parent) { } @@ -1518,9 +1602,25 @@ public: } QBindable<int> bindableProp3() { return QBindable<int>(&prop3Data); } + int prop4() const + { + auto val = prop4Data.value(); + return val == 0 ? 42 : val; + } + + void setProp4(int i) + { + if (i == prop4Data) + return; + prop4Data.setValue(i); + prop4Data.notify(); + } + QBindable<int> bindableProp4() { return QBindable<int>(&prop4Data); } + signals: void prop2Changed(int value); void prop3Changed(); + void prop4Changed(int value); private: Q_OBJECT_COMPAT_PROPERTY(CompatPropertyTester, int, prop1Data, &CompatPropertyTester::setProp1) @@ -1529,6 +1629,10 @@ private: Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(CompatPropertyTester, int, prop3Data, &CompatPropertyTester::setProp3, &CompatPropertyTester::prop3Changed, 1) + Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(CompatPropertyTester, int, prop4Data, + &CompatPropertyTester::setProp4, + &CompatPropertyTester::prop4Changed, + &CompatPropertyTester::prop4, 0) }; void tst_QProperty::compatPropertyNoDobuleNotification() @@ -1555,8 +1659,8 @@ void tst_QProperty::compatPropertySignals() tester.setProp2(10); QCOMPARE(prop2Observer.value(), 10); - QCOMPARE(prop2Spy.count(), 1); - const QList<QVariant> arguments = prop2Spy.takeFirst(); + QCOMPARE(prop2Spy.size(), 1); + QList<QVariant> arguments = prop2Spy.takeFirst(); QCOMPARE(arguments.size(), 1); QCOMPARE(arguments.at(0).metaType().id(), QMetaType::Int); QCOMPARE(arguments.at(0).toInt(), 10); @@ -1571,7 +1675,41 @@ void tst_QProperty::compatPropertySignals() tester.setProp3(5); QCOMPARE(prop3Observer.value(), 5); - QCOMPARE(prop3Spy.count(), 1); + QCOMPARE(prop3Spy.size(), 1); + + // Compat property with signal, default value, and custom setter. Signal has parameter. + QProperty<int> prop4Observer; + prop4Observer.setBinding(tester.bindableProp4().makeBinding()); + QCOMPARE(prop4Observer.value(), 42); + + QSignalSpy prop4Spy(&tester, &CompatPropertyTester::prop4Changed); + + tester.setProp4(10); + + QCOMPARE(prop4Observer.value(), 10); + QCOMPARE(prop4Spy.size(), 1); + arguments = prop4Spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + QCOMPARE(arguments.at(0).metaType().id(), QMetaType::Int); + QCOMPARE(arguments.at(0).toInt(), 10); + + tester.setProp4(42); + + QCOMPARE(prop4Observer.value(), 42); + QCOMPARE(prop4Spy.size(), 1); + arguments = prop4Spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + QCOMPARE(arguments.at(0).metaType().id(), QMetaType::Int); + QCOMPARE(arguments.at(0).toInt(), 42); + + tester.setProp4(0); + + QCOMPARE(prop4Observer.value(), 42); + QCOMPARE(prop4Spy.size(), 1); + arguments = prop4Spy.takeFirst(); + QCOMPARE(arguments.size(), 1); + QCOMPARE(arguments.at(0).metaType().id(), QMetaType::Int); + QCOMPARE(arguments.at(0).toInt(), 42); } class FakeDependencyCreator : public QObject @@ -1623,6 +1761,323 @@ void tst_QProperty::noFakeDependencies() QCOMPARE(old, bindingFunctionCalled); } +class PropertyAdaptorTester : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int foo READ foo WRITE setFoo NOTIFY fooChanged) + Q_PROPERTY(int foo1 READ foo WRITE setFoo) + +signals: + void dummySignal1(); + void dummySignal2(); + void dummySignal3(); + void dummySignal4(); + void dummySignal5(); + void dummySignal6(); + void dummySignal7(); + void dummySignal8(); + void fooChanged(int newFoo); + +public slots: + void fooHasChanged() { fooChangedCount++; } + +public: + int foo() const { return fooData; } + void setFoo(int i) + { + if (i != fooData) { + fooData = i; + fooChanged(fooData); + } + } + +public: + int fooData = 0; + int fooChangedCount = 0; +}; + +void tst_QProperty::propertyAdaptorBinding() +{ + QProperty<int> source { 5 }; + QProperty<int> dest1 { 99 }; + QProperty<int> dest2 { 98 }; + + // Check binding of non BINDABLE property + PropertyAdaptorTester object; + // set up a dummy connection (needed to verify that the QBindable avoids an out-of-bounds read) + QObject::connect(&object, &PropertyAdaptorTester::dummySignal1, [](){}); + QBindable<int> binding(&object, "foo"); + QObject::connect(&object, &PropertyAdaptorTester::fooChanged, &object, + &PropertyAdaptorTester::fooHasChanged); + binding.setBinding([&]() { return source + 1; }); + QCOMPARE(object.foo(), 6); + QCOMPARE(object.fooChangedCount, 1); + + struct MyBindable : QBindable<int> { + using QBindable<int>::QBindable; + QtPrivate::QPropertyAdaptorSlotObject* data() { + return static_cast<QtPrivate::QPropertyAdaptorSlotObject*>(QUntypedBindable::data); + } + } dataBinding(&object, "foo"); + QPropertyBindingDataPointer data{&dataBinding.data()->bindingData()}; + + QCOMPARE(data.observerCount(), 0); + dest1.setBinding(binding.makeBinding()); + QCOMPARE(data.observerCount(), 1); + dest2.setBinding([=]() { return binding.value() + 1; }); + binding = {}; + QCOMPARE(data.observerCount(), 2); + + // Check addNotifer + { + int local_foo = 0; + auto notifier = QBindable<int>(&object, "foo").addNotifier([&]() { local_foo++; }); + QCOMPARE(data.observerCount(), 3); + QCOMPARE(object.foo(), 6); + QCOMPARE(dest1.value(), 6); + QCOMPARE(dest2.value(), 7); + QCOMPARE(local_foo, 0); + QCOMPARE(object.fooChangedCount, 1); + + source = 7; + QCOMPARE(object.foo(), 8); + QCOMPARE(dest1.value(), 8); + QCOMPARE(dest2.value(), 9); + QCOMPARE(local_foo, 1); + QCOMPARE(object.fooChangedCount, 2); + } + + QCOMPARE(data.observerCount(), 2); + + // Check a new QBindable object can override the existing binding + QBindable<int>(&object, "foo").setValue(10); + QCOMPARE(object.foo(), 10); + QCOMPARE(dest1.value(), 10); + QCOMPARE(dest2.value(), 11); + QCOMPARE(object.fooChangedCount, 3); + source.setValue(99); + QCOMPARE(object.foo(), 10); + QCOMPARE(dest1.value(), 10); + QCOMPARE(dest2.value(), 11); + QCOMPARE(object.fooChangedCount, 3); + object.setFoo(12); + QCOMPARE(object.foo(), 12); + QCOMPARE(dest1.value(), 12); + QCOMPARE(dest2.value(), 13); + QCOMPARE(object.fooChangedCount, 4); + + // Check binding multiple notifiers + QProperty<int> source2 { 20 }; + source.setValue(21); + binding = QBindable<int>(&object, "foo"); + binding.setBinding([&]() { return source + source2; }); + QCOMPARE(object.foo(), 41); + QCOMPARE(dest1.value(), 41); + QCOMPARE(object.fooChangedCount, 5); + source.setValue(22); + QCOMPARE(object.foo(), 42); + QCOMPARE(dest1.value(), 42); + QCOMPARE(object.fooChangedCount, 6); + source2.setValue(21); + QCOMPARE(object.foo(), 43); + QCOMPARE(dest1.value(), 43); + QCOMPARE(object.fooChangedCount, 7); + + // Check update group + { + const QScopedPropertyUpdateGroup guard; + source.setValue(23); + source2.setValue(22); + QCOMPARE(object.foo(), 43); + QCOMPARE(dest1.value(), 43); + QCOMPARE(object.fooChangedCount, 7); + } + QCOMPARE(object.foo(), 45); + QCOMPARE(dest1.value(), 45); + QCOMPARE(object.fooChangedCount, 8); + + PropertyAdaptorTester object2; + PropertyAdaptorTester object3; + + // Check multiple observers + QBindable<int> binding2(&object2, "foo"); + QBindable<int> binding3(&object3, "foo"); + binding.setBinding([=]() { return binding2.value(); }); + binding3.setBinding([=]() { return binding.value(); }); + QCOMPARE(object.foo(), 0); + QCOMPARE(object2.foo(), 0); + QCOMPARE(object3.foo(), 0); + QCOMPARE(dest1.value(), 0); + object2.setFoo(1); + QCOMPARE(object.foo(), 1); + QCOMPARE(object2.foo(), 1); + QCOMPARE(object3.foo(), 1); + QCOMPARE(dest1.value(), 1); + + // Check interoperation with BINDABLE properties + MyQObject bindableObject; + bindableObject.fooData.setBinding([]() { return 5; }); + QVERIFY(bindableObject.fooData.hasBinding()); + QVERIFY(!bindableObject.barData.hasBinding()); + QVERIFY(QBindable<int>(&bindableObject, "foo").hasBinding()); + QBindable<int> bindableBar(&bindableObject, "bar"); + QVERIFY(!bindableBar.hasBinding()); + bindableBar.setBinding([]() { return 6; }); + QVERIFY(bindableBar.hasBinding()); + QVERIFY(bindableObject.barData.hasBinding()); + + // Check bad arguments +#ifndef QT_NO_DEBUG + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property is not valid"); +#endif + QVERIFY(!QBindable<int>(&object, QMetaProperty{}).isValid()); +#ifndef QT_NO_DEBUG + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property foo1 has no notify signal"); +#endif + QVERIFY(!QBindable<int>(&object, "foo1").isValid()); +#ifndef QT_NO_DEBUG + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property foo of type int does not match requested type bool"); +#endif + QVERIFY(!QBindable<bool>(&object, "foo").isValid()); +#ifndef QT_NO_DEBUG + QTest::ignoreMessage(QtMsgType::QtWarningMsg, + "QUntypedBindable: Property foo does not belong to this object"); +#endif + QObject qobj; + QVERIFY(!QBindable<int>( + &qobj, + object.metaObject()->property(object.metaObject()->indexOfProperty("foo"))) + .isValid()); +#ifndef QT_NO_DEBUG + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: No property named fizz"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property is not valid"); +#endif + QVERIFY(!QBindable<int>(&object, "fizz").isValid()); +} + +#if QT_CONFIG(thread) +struct ThreadSafetyTester : public QObject +{ + Q_OBJECT + +public: + ThreadSafetyTester(QObject *parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE bool hasCorrectStatus() const + { + return qGetBindingStorage(this)->status({}) == QtPrivate::getBindingStatus({}); + } + + Q_INVOKABLE bool bindingTest() + { + QProperty<QString> name(u"inThread"_s); + bindableObjectName().setBinding([&]() -> QString { return name; }); + name = u"inThreadChanged"_s; + const bool nameChangedCorrectly = objectName() == name; + bindableObjectName().takeBinding(); + return nameChangedCorrectly; + } +}; + + +void tst_QProperty::threadSafety() +{ + QThread workerThread; + auto cleanup = qScopeGuard([&](){ + QMetaObject::invokeMethod(&workerThread, "quit"); + workerThread.wait(); + }); + QScopedPointer<ThreadSafetyTester> scopedObj1(new ThreadSafetyTester); + auto obj1 = scopedObj1.data(); + auto child1 = new ThreadSafetyTester(obj1); + obj1->moveToThread(&workerThread); + const auto mainThreadBindingStatus = QtPrivate::getBindingStatus({}); + QCOMPARE(qGetBindingStorage(child1)->status({}), nullptr); + workerThread.start(); + + bool correctStatus = false; + bool ok = QMetaObject::invokeMethod(obj1, "hasCorrectStatus", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, correctStatus)); + QVERIFY(ok); + QVERIFY(correctStatus); + + bool bindingWorks = false; + ok = QMetaObject::invokeMethod(obj1, "bindingTest", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, bindingWorks)); + QVERIFY(ok); + QVERIFY(bindingWorks); + + correctStatus = false; + ok = QMetaObject::invokeMethod(child1, "hasCorrectStatus", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, correctStatus)); + QVERIFY(ok); + QVERIFY(correctStatus); + + QScopedPointer scopedObj2(new ThreadSafetyTester); + auto obj2 = scopedObj2.data(); + QCOMPARE(qGetBindingStorage(obj2)->status({}), mainThreadBindingStatus); + + obj2->setObjectName("moved"); + QCOMPARE(obj2->objectName(), "moved"); + + obj2->moveToThread(&workerThread); + correctStatus = false; + ok = QMetaObject::invokeMethod(obj2, "hasCorrectStatus", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, correctStatus)); + + QVERIFY(ok); + QVERIFY(correctStatus); + // potentially unsafe, but should still work (no writes in owning thread) + QCOMPARE(obj2->objectName(), "moved"); + + + QScopedPointer scopedObj3(new ThreadSafetyTester); + auto obj3 = scopedObj3.data(); + obj3->setObjectName("moved"); + QCOMPARE(obj3->objectName(), "moved"); + obj3->moveToThread(nullptr); + QCOMPARE(obj2->objectName(), "moved"); + obj3->setObjectName("moved again"); + QCOMPARE(obj3->objectName(), "moved again"); +} + +class QPropertyUsingThread : public QThread +{ +public: + QPropertyUsingThread(QObject **dest, QThread *destThread) : dest(dest), destThread(destThread) {} + void run() override + { + scopedObj1.reset(new ThreadSafetyTester()); + scopedObj1->setObjectName("test"); + QObject *child = new ThreadSafetyTester(scopedObj1.get()); + child->setObjectName("child"); + exec(); + scopedObj1->moveToThread(destThread); + *dest = scopedObj1.release(); + } + std::unique_ptr<ThreadSafetyTester> scopedObj1; + QObject **dest; + QThread *destThread; +}; + +void tst_QProperty::threadSafety2() +{ + std::unique_ptr<QObject> movedObj; + { + QObject *tmp = nullptr; + QPropertyUsingThread workerThread(&tmp, QThread::currentThread()); + workerThread.start(); + workerThread.quit(); + workerThread.wait(); + movedObj.reset(tmp); + } + + QCOMPARE(movedObj->objectName(), "test"); + QCOMPARE(movedObj->children().first()->objectName(), "child"); +} +#endif // QT_CONFIG(thread) + struct CustomType { CustomType() = default; @@ -1734,27 +2189,29 @@ void tst_QProperty::groupedNotifications() QCOMPARE(nNotifications, 1); expected = 2; - Qt::beginPropertyUpdateGroup(); - a = 1; - QCOMPARE(b.value(), 0); - QCOMPARE(c.value(), 0); - QCOMPARE(d.value(), 0); - QCOMPARE(nNotifications, 1); - Qt::endPropertyUpdateGroup(); + { + const QScopedPropertyUpdateGroup guard; + a = 1; + QCOMPARE(b.value(), 0); + QCOMPARE(c.value(), 0); + QCOMPARE(d.value(), 0); + QCOMPARE(nNotifications, 1); + } QCOMPARE(b.value(), 1); QCOMPARE(c.value(), 1); QCOMPARE(e.value(), 2); QCOMPARE(nNotifications, 2); expected = 7; - Qt::beginPropertyUpdateGroup(); - a = 2; - d = 3; - QCOMPARE(b.value(), 1); - QCOMPARE(c.value(), 1); - QCOMPARE(d.value(), 3); - QCOMPARE(nNotifications, 2); - Qt::endPropertyUpdateGroup(); + { + const QScopedPropertyUpdateGroup guard; + a = 2; + d = 3; + QCOMPARE(b.value(), 1); + QCOMPARE(c.value(), 1); + QCOMPARE(d.value(), 3); + QCOMPARE(nNotifications, 2); + } QCOMPARE(b.value(), 2); QCOMPARE(c.value(), 2); QCOMPARE(e.value(), 7); @@ -1777,13 +2234,71 @@ void tst_QProperty::groupedNotificationConsistency() j = 1; QVERIFY(!areEqual); // value changed runs before j = 1 - Qt::beginPropertyUpdateGroup(); - i = 2; - j = 2; - Qt::endPropertyUpdateGroup(); + { + const QScopedPropertyUpdateGroup guard; + i = 2; + j = 2; + } QVERIFY(areEqual); // value changed runs after everything has been evaluated } +void tst_QProperty::bindingGroupMovingBindingData() +{ + auto tester = std::make_unique<ClassWithNotifiedProperty>(); + auto testerPriv = QObjectPrivate::get(tester.get()); + + auto dummyNotifier = tester->property.addNotifier([](){}); + auto bindingData = testerPriv->bindingStorage.bindingData(&tester->property); + QVERIFY(bindingData); // we have a notifier, so there should be binding data + + Qt::beginPropertyUpdateGroup(); + auto cleanup = qScopeGuard([](){ Qt::endPropertyUpdateGroup(); }); + tester->property = 42; + QCOMPARE(testerPriv->bindingStorage.bindingData(&tester->property), bindingData); + auto proxyData = QPropertyBindingDataPointer::proxyData(bindingData); + // as we've modified the property, we now should have a proxy for the delayed notification + QVERIFY(proxyData); + // trigger binding data reallocation + std::array<QUntypedPropertyData, 10> propertyDataArray; + for (auto&& data: propertyDataArray) + testerPriv->bindingStorage.bindingData(&data, true); + // binding data has moved + QVERIFY(testerPriv->bindingStorage.bindingData(&tester->property) != bindingData); + bindingData = testerPriv->bindingStorage.bindingData(&tester->property); + // the proxy data has been updated + QCOMPARE(proxyData->originalBindingData, bindingData); + + tester.reset(); + // the property data is gone, proxyData should have been informed + QCOMPARE(proxyData->originalBindingData, nullptr); + QVERIFY(proxyData); +} + +void tst_QProperty::bindingGroupBindingDeleted() +{ + auto deleter = std::make_unique<ClassWithNotifiedProperty>(); + auto toBeDeleted = std::make_unique<ClassWithNotifiedProperty>(); + + bool calledHandler = false; + deleter->property.setBinding([&](){ + int newValue = toBeDeleted->property; + if (newValue == 42) + toBeDeleted.reset(); + return newValue; + }); + auto handler = toBeDeleted->property.onValueChanged([&]() { calledHandler = true; } ); + { + Qt::beginPropertyUpdateGroup(); + auto cleanup = qScopeGuard([](){ Qt::endPropertyUpdateGroup(); }); + QVERIFY(toBeDeleted); + toBeDeleted->property = 42; + // ASAN should not complain here + } + QVERIFY(!toBeDeleted); + // the change notification is sent, even if the binding is deleted during evaluation + QVERIFY(calledHandler); +} + void tst_QProperty::uninstalledBindingDoesNotEvaluate() { QProperty<int> i; @@ -1835,7 +2350,7 @@ void tst_QProperty::notify() testProperty = 4; QCOMPARE(value, 3); - QCOMPARE(recordedValues.count(), 2); + QCOMPARE(recordedValues.size(), 2); QCOMPARE(recordedValues.at(0), 1); QCOMPARE(recordedValues.at(1), 2); } @@ -1856,6 +2371,210 @@ void tst_QProperty::selfBindingShouldNotCrash() QVERIFY(i.binding().error().hasError()); } +void tst_QProperty::qpropertyAlias() +{ +#if QT_DEPRECATED_SINCE(6, 6) + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED + std::unique_ptr<QProperty<int>> i {new QProperty<int>}; + QPropertyAlias<int> alias(i.get()); + QVERIFY(alias.isValid()); + alias.setValue(42); + QCOMPARE(i->value(), 42); + QProperty<int> j; + bool notifierCalled = false; + auto myNotifier = alias.addNotifier([&](){notifierCalled = true;}); + i->setBinding([&]() -> int { return j; }); + QVERIFY(notifierCalled); + j.setValue(42); + QCOMPARE(alias.value(), 42); + i.reset(); + QVERIFY(!alias.isValid()); + QT_WARNING_POP +#endif +} + +void tst_QProperty::scheduleNotify() +{ + int notifications = 0; + QProperty<int> p; + QCOMPARE(p.value(), 0); + const auto handler = p.addNotifier([&](){ ++notifications; }); + QCOMPARE(notifications, 0); + QPropertyBinding<int> b([]() { return 0; }, QPropertyBindingSourceLocation()); + QPropertyBindingPrivate::get(b)->scheduleNotify(); + QCOMPARE(notifications, 0); + p.setBinding(b); + QCOMPARE(notifications, 1); + QCOMPARE(p.value(), 0); +} + +void tst_QProperty::notifyAfterAllDepsGone() +{ + bool b = true; + QProperty<int> iprop; + QProperty<int> jprop(42); + iprop.setBinding([&](){ + if (b) + return jprop.value(); + return 13; + }); + int changeCounter = 0; + auto keepAlive = iprop.onValueChanged([&](){ changeCounter++; }); + QCOMPARE(iprop.value(), 42); + jprop = 44; + QCOMPARE(iprop.value(), 44); + QCOMPARE(changeCounter, 1); + b = false; + jprop = 43; + QCOMPARE(iprop.value(), 13); + QCOMPARE(changeCounter, 2); +} + +class TestObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(int signaled READ signaled WRITE setSignaled NOTIFY signaledChanged FINAL) + Q_PROPERTY(int bindable1 READ bindable1 WRITE setBindable1 BINDABLE bindable1Bindable NOTIFY bindable1Changed FINAL) + Q_PROPERTY(int bindable2 READ bindable2 WRITE setBindable2 BINDABLE bindable2Bindable NOTIFY bindable2Changed FINAL) + +public: + int signaled() const + { + return m_signaled; + } + + void setSignaled(int newSignaled) + { + if (m_signaled == newSignaled) + return; + m_signaled = newSignaled; + emit signaledChanged(); + } + + int bindable1() const + { + return m_bindable1; + } + + void setBindable1(int newBindable1) + { + if (m_bindable1 == newBindable1) + return; + m_bindable1 = newBindable1; + emit bindable1Changed(); + } + + QBindable<int> bindable1Bindable() + { + return QBindable<int>(&m_bindable1); + } + + int bindable2() const + { + return m_bindable2; + } + + void setBindable2(int newBindable2) + { + if (m_bindable2 == newBindable2) + return; + m_bindable2 = newBindable2; + emit bindable2Changed(); + } + + QBindable<int> bindable2Bindable() + { + return QBindable<int>(&m_bindable2); + } + +signals: + void signaledChanged(); + void bindable1Changed(); + void bindable2Changed(); + +private: + int m_signaled = 0; + Q_OBJECT_COMPAT_PROPERTY(TestObject, int, m_bindable1, &TestObject::setBindable1, &TestObject::bindable1Changed); + Q_OBJECT_COMPAT_PROPERTY(TestObject, int, m_bindable2, &TestObject::setBindable2, &TestObject::bindable2Changed); +}; + +void tst_QProperty::propertyUpdateViaSignaledProperty() +{ + TestObject o; + QProperty<int> rootTrigger; + QProperty<int> signalTrigger; + + o.bindable1Bindable().setBinding([&]() { + return rootTrigger.value(); + }); + + QObject::connect(&o, &TestObject::bindable1Changed, &o, [&]() { + // Signaled changes only once, doesn't actually depend on bindable1. + // In reality, there could be some complicated calculation behind this that changes + // on certain checkpoints, but not on every iteration. + o.setSignaled(40); + }); + + o.bindable2Bindable().setBinding([&]() { + return signalTrigger.value() - o.bindable1(); + }); + + QObject::connect(&o, &TestObject::signaledChanged, &o, [&]() { + signalTrigger.setValue(o.signaled()); + }); + + rootTrigger.setValue(2); + QCOMPARE(o.bindable1(), 2); + QCOMPARE(o.bindable2(), 38); + rootTrigger.setValue(3); + QCOMPARE(o.bindable1(), 3); + QCOMPARE(o.bindable2(), 37); + rootTrigger.setValue(4); + QCOMPARE(o.bindable1(), 4); + QCOMPARE(o.bindable2(), 36); +} + +void tst_QProperty::derefFromObserver() +{ + int triggered = 0; + QProperty<int> source(11); + + DtorCounter::counter = 0; + DtorCounter dc; + + QProperty<int> target([&triggered, &source, dc]() mutable { + dc.shouldIncrement = true; + return ++triggered + source.value(); + }); + QCOMPARE(triggered, 1); + + { + auto propObserver = std::make_unique<QPropertyObserver>(); + QPropertyObserverPointer propObserverPtr { propObserver.get() }; + propObserverPtr.setBindingToNotify(QPropertyBindingPrivate::get(target.binding())); + + QBindingObserverPtr bindingPtr(propObserver.get()); + + QCOMPARE(triggered, 1); + source = 25; + QCOMPARE(triggered, 2); + QCOMPARE(target, 27); + + target.setBinding([]() { return 8; }); + QCOMPARE(target, 8); + + // The QBindingObserverPtr still holds on to the binding. + QCOMPARE(dc.counter, 0); + } + + // The binding is actually gone now. + QCOMPARE(dc.counter, 1); + + source = 26; + QCOMPARE(triggered, 2); + QCOMPARE(target, 8); +} + QTEST_MAIN(tst_QProperty); #undef QT_SOURCE_LOCATION_NAMESPACE |