summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp')
-rw-r--r--tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp856
1 files changed, 792 insertions, 64 deletions
diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
index fb99ed1880..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,22 +77,94 @@ 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();
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; });
@@ -267,6 +317,7 @@ void tst_QProperty::bindingAfterUse()
void tst_QProperty::bindingFunctionDtorCalled()
{
+ DtorCounter::counter = 0;
DtorCounter dc;
{
QProperty<int> prop;
@@ -405,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);
}
@@ -448,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);
@@ -883,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();
@@ -913,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();
@@ -921,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();
@@ -930,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();
@@ -1169,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;
@@ -1183,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,
@@ -1450,6 +1518,7 @@ void tst_QProperty::modifyObserverListWhileIterating()
DestructingObserver observer = {};
char* memory;
~ObserverOrUninit() {}
+ ObserverOrUninit() {}
};
{
// observer deletes itself while running the notification
@@ -1483,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) { }
@@ -1516,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)
@@ -1527,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()
@@ -1553,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);
@@ -1569,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
@@ -1621,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;
@@ -1732,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);
@@ -1775,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;
@@ -1833,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);
}
@@ -1847,6 +2364,217 @@ void tst_QProperty::bindableInterfaceOfCompatPropertyUsesSetter()
QCOMPARE(obj.setCompatCalled, 1);
}
+void tst_QProperty::selfBindingShouldNotCrash()
+{
+ QProperty<int> i;
+ i.setBinding([&](){ return i+1; });
+ 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