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.cpp1391
1 files changed, 1187 insertions, 204 deletions
diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
index 6062c23189..cc7edb8bf2 100644
--- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
+++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
@@ -1,38 +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_QDOC)
+#include <source_location>
+#define QT_SOURCE_LOCATION_NAMESPACE std
+#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;
@@ -44,10 +29,12 @@ class tst_QProperty : public QObject
{
Q_OBJECT
private slots:
+ void inheritQUntypedPropertyData();
void functorBinding();
void basicDependencies();
void multipleDependencies();
void bindingWithDeletedDependency();
+ void dependencyChangeDuringDestruction();
void recursiveDependency();
void bindingAfterUse();
void bindingFunctionDtorCalled();
@@ -55,6 +42,7 @@ private slots:
void avoidDependencyAllocationAfterFirstEval();
void boolProperty();
void takeBinding();
+ void stickyBinding();
void replaceBinding();
void changeHandler();
void propertyChangeHandlerApi();
@@ -73,28 +61,110 @@ private slots:
void genericPropertyBindingBool();
void setBindingFunctor();
void multipleObservers();
- void propertyAlias();
void arrowAndStarOperator();
void notifiedProperty();
void typeNoOperatorEqual();
void bindingValueReplacement();
void quntypedBindableApi();
+ void readonlyConstQBindable();
+ void qobjectBindableManualNotify();
+ void qobjectBindableReallocatedBindingStorage();
+ void qobjectBindableSignalTakingNewValue();
void testNewStuff();
void qobjectObservers();
void compatBindings();
void metaProperty();
- void aliasOnMetaProperty();
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 markDirty();
+ 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; });
@@ -126,8 +196,8 @@ void tst_QProperty::multipleDependencies()
QProperty<int> sum;
sum.setBinding([&]() { return firstDependency + secondDependency; });
- QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 0);
- QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 0);
+ QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1);
+ QCOMPARE(QPropertyBindingDataPointer::get(secondDependency).observerCount(), 1);
QCOMPARE(sum.value(), int(3));
QCOMPARE(QPropertyBindingDataPointer::get(firstDependency).observerCount(), 1);
@@ -182,6 +252,35 @@ void tst_QProperty::bindingWithDeletedDependency()
QCOMPARE(propertySelector.value(), staticProperty.value());
}
+class ChangeDuringDtorTester : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int prop READ prop WRITE setProp BINDABLE bindableProp)
+
+public:
+ void setProp(int i) { m_prop = i;}
+ int prop() const { return m_prop; }
+ QBindable<int> bindableProp() { return &m_prop; }
+private:
+ Q_OBJECT_COMPAT_PROPERTY(ChangeDuringDtorTester, int, m_prop, &ChangeDuringDtorTester::setProp)
+};
+
+void tst_QProperty::dependencyChangeDuringDestruction()
+{
+ auto tester = std::make_unique<ChangeDuringDtorTester>();
+ QProperty<int> iprop {42};
+ tester->bindableProp().setBinding(Qt::makePropertyBinding(iprop));
+ QObject::connect(tester.get(), &QObject::destroyed, [&](){
+ iprop = 12;
+ });
+ bool failed = false;
+ auto handler = tester->bindableProp().onValueChanged([&](){
+ failed = true;
+ });
+ tester.reset();
+ QVERIFY(!failed);
+}
+
void tst_QProperty::recursiveDependency()
{
QProperty<int> first(1);
@@ -218,6 +317,7 @@ void tst_QProperty::bindingAfterUse()
void tst_QProperty::bindingFunctionDtorCalled()
{
+ DtorCounter::counter = 0;
DtorCounter dc;
{
QProperty<int> prop;
@@ -257,12 +357,12 @@ void tst_QProperty::avoidDependencyAllocationAfterFirstEval()
QCOMPARE(propWithBinding.value(), int(11));
- QVERIFY(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr());
- QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()->dependencyObserverCount, 2u);
+ QVERIFY(QPropertyBindingDataPointer::get(propWithBinding).binding());
+ QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).binding()->dependencyObserverCount, 2u);
firstDependency = 100;
QCOMPARE(propWithBinding.value(), int(110));
- QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).bindingPtr()->dependencyObserverCount, 2u);
+ QCOMPARE(QPropertyBindingDataPointer::get(propWithBinding).binding()->dependencyObserverCount, 2u);
}
void tst_QProperty::boolProperty()
@@ -302,6 +402,30 @@ void tst_QProperty::takeBinding()
QVERIFY(!existingBinding.isNull());
}
+void tst_QProperty::stickyBinding()
+{
+ QProperty<int> prop;
+ QProperty<int> prop2 {2};
+ prop.setBinding([&](){ return prop2.value(); });
+ QCOMPARE(prop.value(), 2);
+ auto privBinding = QPropertyBindingPrivate::get(prop.binding());
+ // If we make a binding sticky,
+ privBinding->setSticky();
+ // then writing to the property does not remove it
+ prop = 1;
+ QVERIFY(prop.hasBinding());
+ // but the value still changes.
+ QCOMPARE(prop.value(), 1);
+ // The binding continues to work normally.
+ prop2 = 3;
+ QCOMPARE(prop.value(), 3);
+ // If we remove the stickiness
+ privBinding->setSticky(false);
+ // the binding goes away on the next write
+ prop = 42;
+ QVERIFY(!prop.hasBinding());
+}
+
void tst_QProperty::replaceBinding()
{
QProperty<int> first(100);
@@ -332,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);
}
@@ -375,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);
@@ -434,7 +558,7 @@ void tst_QProperty::dontTriggerDependenciesIfUnchangedValue()
void tst_QProperty::bindingSourceLocation()
{
#if defined(QT_PROPERTY_COLLECT_BINDING_LOCATION)
- auto bindingLine = std::experimental::source_location::current().line() + 1;
+ auto bindingLine = QT_SOURCE_LOCATION_NAMESPACE::source_location::current().line() + 1;
auto binding = Qt::makePropertyBinding([]() { return 42; });
QCOMPARE(QPropertyBindingPrivate::get(binding)->sourceLocation().line, bindingLine);
#else
@@ -469,35 +593,34 @@ class BindingLoopTester : public QObject
BindingLoopTester() {}
int eagerProp() {return eagerData.value();}
- void setEagerProp(int i) { eagerData.setValue(i); }
+ void setEagerProp(int i) { eagerData.setValue(i); eagerData.notify(); }
QBindable<int> bindableEagerProp() {return QBindable<int>(&eagerData);}
Q_OBJECT_COMPAT_PROPERTY(BindingLoopTester, int, eagerData, &BindingLoopTester::setEagerProp)
int eagerProp2() {return eagerData2.value();}
- void setEagerProp2(int i) { eagerData2.setValue(i); }
+ void setEagerProp2(int i) { eagerData2.setValue(i); eagerData2.notify(); }
QBindable<int> bindableEagerProp2() {return QBindable<int>(&eagerData2);}
Q_OBJECT_COMPAT_PROPERTY(BindingLoopTester, int, eagerData2, &BindingLoopTester::setEagerProp2)
};
void tst_QProperty::bindingLoop()
{
- QScopedPointer<QProperty<int>> firstProp;
+ QProperty<int> firstProp;
QProperty<int> secondProp([&]() -> int {
- return firstProp ? firstProp->value() : 0;
+ return firstProp.value();
});
QProperty<int> thirdProp([&]() -> int {
return secondProp.value();
});
- firstProp.reset(new QProperty<int>());
- firstProp->setBinding([&]() -> int {
- return secondProp.value();
+ firstProp.setBinding([&]() -> int {
+ return secondProp.value() + thirdProp.value();
});
- QCOMPARE(thirdProp.value(), 0);
- QCOMPARE(secondProp.binding().error().type(), QPropertyBindingError::BindingLoop);
+ thirdProp.setValue(10);
+ QCOMPARE(firstProp.binding().error().type(), QPropertyBindingError::BindingLoop);
{
@@ -536,7 +659,7 @@ public:
#define GEN(N) \
int prop##N() {return propData##N.value();} \
- void setProp##N(int i) { propData##N.setValue(i); } \
+ void setProp##N(int i) { if (i == propData##N) return; propData##N.setValue(i); propData##N.notify(); } \
QBindable<int> bindableProp##N() {return QBindable<int>(&propData##N);} \
Q_OBJECT_COMPAT_PROPERTY(ReallocTester, int, propData##N, &ReallocTester::setProp##N)
GEN(1)
@@ -668,7 +791,7 @@ void tst_QProperty::genericPropertyBinding()
{
QUntypedPropertyBinding doubleBinding(QMetaType::fromType<double>(),
- [](const QMetaType &, void *) -> bool {
+ [](QMetaType , void *) -> bool {
Q_ASSERT(false);
return true;
}, QPropertyBindingSourceLocation());
@@ -676,7 +799,7 @@ void tst_QProperty::genericPropertyBinding()
}
QUntypedPropertyBinding intBinding(QMetaType::fromType<int>(),
- [](const QMetaType &metaType, void *dataPtr) -> bool {
+ [](QMetaType metaType, void *dataPtr) -> bool {
Q_ASSERT(metaType.id() == qMetaTypeId<int>());
int *intPtr = reinterpret_cast<int*>(dataPtr);
@@ -696,7 +819,7 @@ void tst_QProperty::genericPropertyBindingBool()
QVERIFY(!property.value());
QUntypedPropertyBinding boolBinding(QMetaType::fromType<bool>(),
- [](const QMetaType &, void *dataPtr) -> bool {
+ [](QMetaType, void *dataPtr) -> bool {
auto boolPtr = reinterpret_cast<bool *>(dataPtr);
*boolPtr = true;
return true;
@@ -752,52 +875,6 @@ void tst_QProperty::multipleObservers()
QCOMPARE(property.value(), 22);
}
-void tst_QProperty::propertyAlias()
-{
- QScopedPointer<QProperty<int>> property(new QProperty<int>);
- property->setValue(5);
- QPropertyAlias alias(property.get());
- QVERIFY(alias.isValid());
- QCOMPARE(alias.value(), 5);
-
- int value1 = 1;
- auto changeHandler = alias.onValueChanged([&]() { value1 = alias.value(); });
- QCOMPARE(value1, 1);
-
- int value2 = 2;
- auto subscribeHandler = alias.subscribe([&]() { value2 = alias.value(); });
- QCOMPARE(value2, 5);
-
- alias.setValue(6);
- QVERIFY(alias.isValid());
- QCOMPARE(alias.value(), 6);
- QCOMPARE(value1, 6);
- QCOMPARE(value2, 6);
-
- alias.setBinding([]() { return 12; });
- QCOMPARE(value1, 12);
- QCOMPARE(value2, 12);
- QCOMPARE(alias.value(), 12);
-
- alias.setValue(22);
- QCOMPARE(value1, 22);
- QCOMPARE(value2, 22);
- QCOMPARE(alias.value(), 22);
-
- property.reset();
-
- QVERIFY(!alias.isValid());
- QCOMPARE(alias.value(), int());
- QCOMPARE(value1, 22);
- QCOMPARE(value2, 22);
-
- // Does not crash
- alias.setValue(25);
- QCOMPARE(alias.value(), int());
- QCOMPARE(value1, 22);
- QCOMPARE(value2, 22);
-}
-
void tst_QProperty::arrowAndStarOperator()
{
QString str("Hello");
@@ -857,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();
@@ -887,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();
@@ -895,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();
@@ -904,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();
@@ -1026,6 +1103,45 @@ void tst_QProperty::quntypedBindableApi()
QCOMPARE(iprop.value(), 42);
QUntypedBindable propLess;
QVERIFY(propLess.takeBinding().isNull());
+
+ QUntypedBindable invalidBindable;
+#ifndef QT_NO_DEBUG
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, "setBinding: Could not set binding via bindable interface. The QBindable is invalid.");
+#endif
+ invalidBindable.setBinding(Qt::makePropertyBinding(iprop));
+
+ QUntypedBindable readOnlyBindable(static_cast<const QProperty<int> *>(&iprop) );
+ QVERIFY(readOnlyBindable.isReadOnly());
+#ifndef QT_NO_DEBUG
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, "setBinding: Could not set binding via bindable interface. The QBindable is read-only.");
+#endif
+ readOnlyBindable.setBinding(Qt::makePropertyBinding(iprop));
+
+ QProperty<float> fprop;
+#ifndef QT_NO_DEBUG
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, "setBinding: Could not set binding as the property expects it to be of type int but got float instead.");
+#endif
+ bindable.setBinding(Qt::makePropertyBinding(fprop));
+}
+
+void tst_QProperty::readonlyConstQBindable()
+{
+ QProperty<int> i {42};
+ const QBindable<int> bindableI(const_cast<const QProperty<int> *>(&i));
+
+ // check that read-only operations work with a const QBindable
+ QVERIFY(bindableI.isReadOnly());
+ QVERIFY(!bindableI.hasBinding());
+ // we can still create a binding to a read only bindable through the interface
+ QProperty<int> j;
+ j.setBinding(bindableI.makeBinding());
+ QCOMPARE(j.value(), bindableI.value());
+ int counter = 0;
+ auto observer = bindableI.subscribe([&](){++counter;});
+ QCOMPARE(counter, 1);
+ auto observer2 = bindableI.onValueChanged([&](){++counter;});
+ i = 0;
+ QCOMPARE(counter, 3);
}
class MyQObject : public QObject
@@ -1038,7 +1154,7 @@ class MyQObject : public QObject
Q_PROPERTY(int compat READ compat WRITE setCompat NOTIFY compatChanged)
signals:
- void fooChanged();
+ void fooChanged(int newFoo);
void barChanged();
void compatChanged();
@@ -1064,6 +1180,7 @@ public:
if (i < 0)
i = 0;
compatData.setValue(i);
+ compatData.notify();
emit compatChanged();
}
@@ -1087,9 +1204,95 @@ public:
Q_OBJECT_COMPAT_PROPERTY(MyQObject, int, compatData, &MyQObject::setCompat)
};
+
+void tst_QProperty::qobjectBindableManualNotify()
+{
+ // Given an object of type MyQObject,
+ MyQObject object;
+ // track its foo property's change count
+ auto bindable = object.bindableFoo();
+ int fooChangeCount = 0;
+ auto changeHandler = bindable.onValueChanged([&](){++fooChangeCount;});
+ // and how many changed signals it emits.
+ QSignalSpy fooChangedSpy(&object, &MyQObject::fooChanged);
+
+ // If we bypass the bindings system,
+ object.fooData.setValueBypassingBindings(42);
+ // there is no change.
+ QCOMPARE(fooChangeCount, 0);
+ QCOMPARE(fooChangedSpy.size(), 0);
+ // Once we notify manually
+ object.fooData.notify();
+ // observers are notified and the signal arrives.
+ QCOMPARE(fooChangeCount, 1);
+ QCOMPARE(fooChangedSpy.size(), 1);
+
+ // If we set a binding
+ int i = 1;
+ object.fooData.setBinding([&](){return i;});
+ // then the value changes
+ QCOMPARE(object.foo(), 1);
+ // and the change and signal count are incremented.
+ QCOMPARE(fooChangeCount, 2);
+ QCOMPARE(fooChangedSpy.size(), 2);
+ // Changing a non-property won't trigger any notification.
+ i = 2;
+ QCOMPARE(fooChangeCount, 2);
+ QCOMPARE(fooChangedSpy.size(), 2);
+ // Manually triggering the notification
+ object.fooData.notify();
+ // increments the change count
+ QCOMPARE(fooChangeCount, 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,
+ MyQObject object;
+ // and tracking the values emitted via its fooChanged signal,
+ int newValue = -1;
+ QObject::connect(&object, &MyQObject::fooChanged, [&](int i){ newValue = i; } );
+
+ // when we change the property's value via the bindable interface
+ object.bindableFoo().setValue(1);
+ // we obtain the newly set value.
+ QCOMPARE(newValue, 1);
+
+ // The same holds true when we set a binding
+ QProperty<int> i {2};
+ object.bindableFoo().setBinding(Qt::makePropertyBinding(i));
+ QCOMPARE(newValue, 2);
+ // and when the binding gets reevaluated to a new value
+ i = 3;
+ QCOMPARE(newValue, 3);
+}
+
void tst_QProperty::testNewStuff()
{
MyQObject testReadOnly;
+#ifndef QT_NO_DEBUG
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, "setBinding: Could not set binding via bindable interface. The QBindable is read-only.");
+#endif
testReadOnly.bindableFoo().setBinding([](){return 42;});
auto bindable = const_cast<const MyQObject&>(testReadOnly).bindableFoo();
QVERIFY(bindable.hasBinding());
@@ -1176,7 +1379,9 @@ void tst_QProperty::qobjectObservers()
MyQObject object;
int onValueChangedCalled = 0;
{
- auto handler = object.bindableFoo().onValueChanged([&onValueChangedCalled]() { ++onValueChangedCalled;});
+ auto handler = object.bindableFoo().onValueChanged([&onValueChangedCalled]() {
+ ++onValueChangedCalled;
+ });
QCOMPARE(onValueChangedCalled, 0);
object.setFoo(10);
@@ -1295,42 +1500,6 @@ void tst_QProperty::metaProperty()
QCOMPARE(object.fooData.value(), 1);
}
-void tst_QProperty::aliasOnMetaProperty()
-{
- MyQObject object;
- QPropertyAlias<int> alias(object.bindableFoo());
-
- QVERIFY(alias.isValid());
- QCOMPARE(alias.value(), object.foo());
- QVERIFY(!alias.hasBinding());
-
- object.setFoo(42);
- QCOMPARE(alias.value(), 42);
-
- auto f = [&object]() -> int {
- return object.barData;
- };
- object.bindableFoo().setBinding(f);
- QVERIFY(alias.hasBinding());
- QCOMPARE(alias.value(), object.bar());
-
- object.setBar(111);
- QCOMPARE(alias.value(), 111);
-
- int changedCount = 0;
- auto observer = alias.onValueChanged([&changedCount]() { ++changedCount; });
- QCOMPARE(changedCount, 0);
- object.setBar(666);
- QCOMPARE(changedCount, 1);
-
- alias.setBinding([&object]() { return object.read(); });
- QCOMPARE(changedCount, 2);
- QCOMPARE(alias.value(), 0);
- object.readData = 100;
- QCOMPARE(changedCount, 3);
- QCOMPARE(alias.value(), 100);
-}
-
void tst_QProperty::modifyObserverListWhileIterating()
{
struct DestructingObserver : QPropertyObserver {
@@ -1349,6 +1518,7 @@ void tst_QProperty::modifyObserverListWhileIterating()
DestructingObserver observer = {};
char* memory;
~ObserverOrUninit() {}
+ ObserverOrUninit() {}
};
{
// observer deletes itself while running the notification
@@ -1382,18 +1552,87 @@ 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)
- public:
+ 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) { }
int prop1() {return prop1Data.value();}
- void setProp1(int i) { prop1Data.setValue(i); }
+ void setProp1(int i) { if (i == prop1Data) return; prop1Data.setValue(i); prop1Data.notify(); }
QBindable<int> bindableProp1() {return QBindable<int>(&prop1Data);}
- Q_OBJECT_COMPAT_PROPERTY(CompatPropertyTester, int, prop1Data, &CompatPropertyTester::setProp1)
+ int prop2() { return prop2Data.value(); }
+ void setProp2(int i)
+ {
+ if (i == prop2Data)
+ return;
+ prop2Data.setValue(i);
+ prop2Data.notify();
+ }
+ QBindable<int> bindableProp2() { return QBindable<int>(&prop2Data); }
+
+ int prop3() { return prop3Data.value(); }
+ void setProp3(int i)
+ {
+ if (i == prop3Data)
+ return;
+ prop3Data.setValue(i);
+ prop3Data.notify();
+ }
+ 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)
+ Q_OBJECT_COMPAT_PROPERTY(CompatPropertyTester, int, prop2Data, &CompatPropertyTester::setProp2,
+ &CompatPropertyTester::prop2Changed)
+ 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()
@@ -1407,6 +1646,72 @@ void tst_QProperty::compatPropertyNoDobuleNotification()
QCOMPARE(counter, 1);
}
+void tst_QProperty::compatPropertySignals()
+{
+ CompatPropertyTester tester;
+
+ // Compat property with signal. Signal has parameter.
+ QProperty<int> prop2Observer;
+ prop2Observer.setBinding(tester.bindableProp2().makeBinding());
+
+ QSignalSpy prop2Spy(&tester, &CompatPropertyTester::prop2Changed);
+
+ tester.setProp2(10);
+
+ QCOMPARE(prop2Observer.value(), 10);
+ 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);
+
+ // Compat property with signal and default value. Signal has no parameter.
+ QProperty<int> prop3Observer;
+ prop3Observer.setBinding(tester.bindableProp3().makeBinding());
+ QCOMPARE(prop3Observer.value(), 1);
+
+ QSignalSpy prop3Spy(&tester, &CompatPropertyTester::prop3Changed);
+
+ tester.setProp3(5);
+
+ QCOMPARE(prop3Observer.value(), 5);
+ 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
{
Q_OBJECT
@@ -1421,9 +1726,9 @@ signals:
void prop3Changed();
public:
- void setProp1(int val) { prop1Data.setValue(val); emit prop1Changed();}
- void setProp2(int val) { prop2Data.setValue(val); emit prop2Changed();}
- void setProp3(int val) { prop3Data.setValue(val); emit prop3Changed();}
+ void setProp1(int val) { prop1Data.setValue(val); prop1Data.notify();}
+ void setProp2(int val) { prop2Data.setValue(val); prop2Data.notify();}
+ void setProp3(int val) { prop3Data.setValue(val); prop3Data.notify();}
int prop1() { return prop1Data; }
int prop2() { return prop2Data; }
@@ -1434,9 +1739,9 @@ public:
QBindable<int> bindableProp3() { return QBindable<int>(&prop3Data); }
private:
- Q_OBJECT_COMPAT_PROPERTY(FakeDependencyCreator, int, prop1Data, &FakeDependencyCreator::setProp1);
- Q_OBJECT_COMPAT_PROPERTY(FakeDependencyCreator, int, prop2Data, &FakeDependencyCreator::setProp2);
- Q_OBJECT_COMPAT_PROPERTY(FakeDependencyCreator, int, prop3Data, &FakeDependencyCreator::setProp3);
+ Q_OBJECT_COMPAT_PROPERTY(FakeDependencyCreator, int, prop1Data, &FakeDependencyCreator::setProp1, &FakeDependencyCreator::prop1Changed);
+ Q_OBJECT_COMPAT_PROPERTY(FakeDependencyCreator, int, prop2Data, &FakeDependencyCreator::setProp2, &FakeDependencyCreator::prop2Changed);
+ Q_OBJECT_COMPAT_PROPERTY(FakeDependencyCreator, int, prop3Data, &FakeDependencyCreator::setProp3, &FakeDependencyCreator::prop3Changed);
};
void tst_QProperty::noFakeDependencies()
@@ -1456,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;
@@ -1518,82 +2140,443 @@ void tst_QProperty::bindablePropertyWithInitialization()
QCOMPARE(tester.prop3().anotherValue, 20);
}
-class MarkDirtyTester : public QObject
+void tst_QProperty::noDoubleNotification()
{
- Q_OBJECT
-public:
- Q_PROPERTY(int value1 READ value1 WRITE setValue1 BINDABLE bindableValue1)
- Q_PROPERTY(int value2 READ value2 WRITE setValue1 BINDABLE bindableValue2)
- Q_PROPERTY(int computed READ computed BINDABLE bindableComputed)
+ /* dependency graph for this test
+ x --> y means y depends on x
+ a-->b-->d
+ \ ^
+ \->c--/
+ */
+ QProperty<int> a(0);
+ QProperty<int> b;
+ b.setBinding([&](){ return a.value(); });
+ QProperty<int> c;
+ c.setBinding([&](){ return a.value(); });
+ QProperty<int> d;
+ d.setBinding([&](){ return b.value() + c.value(); });
+ int nNotifications = 0;
+ int expected = 0;
+ auto connection = d.subscribe([&](){
+ ++nNotifications;
+ QCOMPARE(d.value(), expected);
+ });
+ QCOMPARE(nNotifications, 1);
+ expected = 2;
+ a = 1;
+ QCOMPARE(nNotifications, 2);
+ expected = 4;
+ a = 2;
+ QCOMPARE(nNotifications, 3);
+}
- inline static int staticValue = 0;
+void tst_QProperty::groupedNotifications()
+{
+ QProperty<int> a(0);
+ QProperty<int> b;
+ b.setBinding([&](){ return a.value(); });
+ QProperty<int> c;
+ c.setBinding([&](){ return a.value(); });
+ QProperty<int> d;
+ QProperty<int> e;
+ e.setBinding([&](){ return b.value() + c.value() + d.value(); });
+ int nNotifications = 0;
+ int expected = 0;
+ auto connection = e.subscribe([&](){
+ ++nNotifications;
+ QCOMPARE(e.value(), expected);
+ });
+ QCOMPARE(nNotifications, 1);
- int value1() const {return m_value1;}
- void setValue1(int val) {m_value1 = val;}
- QBindable<int> bindableValue1() {return { &m_value1 };}
+ expected = 2;
+ {
+ 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);
- int value2() const {return m_value2;}
- void setValue2(int val) { m_value2.setValue(val); }
- QBindable<int> bindableValue2() {return { &m_value2 };}
+ expected = 7;
+ {
+ 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);
+ QCOMPARE(nNotifications, 3);
- int computed() const { return staticValue + m_value1; }
- QBindable<int> bindableComputed() {return {&m_computed};}
- void incrementStaticValue() {
- ++staticValue;
- m_computed.markDirty();
- }
+}
- void markValue1Dirty() {
- m_value1.markDirty();
- }
+void tst_QProperty::groupedNotificationConsistency()
+{
+ QProperty<int> i(0);
+ QProperty<int> j(0);
+ bool areEqual = true;
+
+ auto observer = i.onValueChanged([&](){
+ areEqual = i == j;
+ });
+
+ i = 1;
+ j = 1;
+ QVERIFY(!areEqual); // value changed runs before j = 1
- void markValue2Dirty() {
- m_value2.markDirty();
+ {
+ const QScopedPropertyUpdateGroup guard;
+ i = 2;
+ j = 2;
}
-private:
- Q_OBJECT_BINDABLE_PROPERTY(MarkDirtyTester, int, m_value1, nullptr)
- Q_OBJECT_COMPAT_PROPERTY(MarkDirtyTester, int, m_value2, &MarkDirtyTester::setValue2)
- Q_OBJECT_COMPUTED_PROPERTY(MarkDirtyTester, int, m_computed, &MarkDirtyTester::computed)
-};
+ QVERIFY(areEqual); // value changed runs after everything has been evaluated
+}
-void tst_QProperty::markDirty()
+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; } );
{
- QProperty<int> testProperty;
- int changeCounter = 0;
- auto handler = testProperty.onValueChanged([&](){++changeCounter;});
- testProperty.markDirty();
- QCOMPARE(changeCounter, 1);
+ 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;
+ QProperty<int> j;
+ int bindingEvaluationCounter = 0;
+ i.setBinding([&](){
+ bindingEvaluationCounter++;
+ return j.value();
+ });
+ QCOMPARE(bindingEvaluationCounter, 1);
+ // Sanity check: if we force a binding reevaluation,
+ j = 42;
+ // the binding function will be called again.
+ QCOMPARE(bindingEvaluationCounter, 2);
+ // If we keep referencing the binding
+ auto keptBinding = i.binding();
+ // but have it not installed on a property
+ i = 10;
+ QVERIFY(!i.hasBinding());
+ QVERIFY(!keptBinding.isNull());
+ // then changing a dependency
+ j = 12;
+ // does not lead to the binding being reevaluated.
+ QCOMPARE(bindingEvaluationCounter, 2);
+}
+
+void tst_QProperty::notify()
+{
+ QProperty<int> testProperty(0);
+ QList<int> recordedValues;
+ int value = 0;
+ QPropertyNotifier notifier;
+
{
- MarkDirtyTester dirtyTester;
- int computedChangeCounter = 0;
- int value1ChangeCounter = 0;
- auto handler = dirtyTester.bindableComputed().onValueChanged([&](){
- computedChangeCounter++;
+ QPropertyNotifier handler = testProperty.addNotifier([&]() {
+ recordedValues << testProperty;
});
- auto handler2 = dirtyTester.bindableValue1().onValueChanged([&](){
- value1ChangeCounter++;
+ notifier = testProperty.addNotifier([&]() {
+ value = testProperty;
});
- dirtyTester.incrementStaticValue();
- QCOMPARE(computedChangeCounter, 1);
- QCOMPARE(dirtyTester.computed(), 1);
- dirtyTester.markValue1Dirty();
- QCOMPARE(value1ChangeCounter, 1);
- QCOMPARE(computedChangeCounter, 1);
+
+ testProperty = 1;
+ testProperty = 2;
+ }
+ QCOMPARE(value, 2);
+ testProperty = 3;
+ QCOMPARE(value, 3);
+ notifier = {};
+ testProperty = 4;
+ QCOMPARE(value, 3);
+
+ QCOMPARE(recordedValues.size(), 2);
+ QCOMPARE(recordedValues.at(0), 1);
+ QCOMPARE(recordedValues.at(1), 2);
+}
+
+void tst_QProperty::bindableInterfaceOfCompatPropertyUsesSetter()
+{
+ MyQObject obj;
+ QBindable<int> bindable = obj.bindableCompat();
+ QCOMPARE(obj.setCompatCalled, 0);
+ bindable.setValue(42);
+ 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)
{
- MarkDirtyTester dirtyTester;
- int changeCounter = 0;
- auto handler = dirtyTester.bindableValue2().onValueChanged([&](){
- changeCounter++;
- });
- dirtyTester.markValue2Dirty();
- QCOMPARE(changeCounter, 1);
+ 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
+
#include "tst_qproperty.moc"