summaryrefslogtreecommitdiffstats
path: root/tests/auto
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@qt.io>2019-07-10 09:01:14 +0200
committerSimon Hausmann <simon.hausmann@qt.io>2020-03-16 18:19:45 +0100
commit9f9049b486a47aef0c7e2e3852b20aa4ffdce748 (patch)
tree619d55c8c97c7616c257b4f0da7fa2478637c2a2 /tests/auto
parent3c7c60e322f934aaac7e4c1941401a03488383be (diff)
Initial import of the Qt C++ property binding system
This implements the core value based property binding system with automatic dependency tracking. More features are to be added later, and the documentation will need further improvements as well. Change-Id: I77ec9163ba4dace6c4451f5933962ebe1b3b4b14 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tests/auto')
-rw-r--r--tests/auto/corelib/kernel/kernel.pro6
-rw-r--r--tests/auto/corelib/kernel/qproperty/qproperty.pro4
-rw-r--r--tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp615
3 files changed, 623 insertions, 2 deletions
diff --git a/tests/auto/corelib/kernel/kernel.pro b/tests/auto/corelib/kernel/kernel.pro
index 09074a9e8a..26cc00ea40 100644
--- a/tests/auto/corelib/kernel/kernel.pro
+++ b/tests/auto/corelib/kernel/kernel.pro
@@ -23,7 +23,8 @@ SUBDIRS=\
qtimer \
qtranslator \
qvariant \
- qwineventnotifier
+ qwineventnotifier \
+ qproperty
!qtHaveModule(gui): SUBDIRS -= \
qmimedata
@@ -35,7 +36,8 @@ SUBDIRS=\
!qtConfig(private_tests): SUBDIRS -= \
qsocketnotifier \
- qsharedmemory
+ qsharedmemory \
+ qproperty
# This test is only applicable on Windows
!win32*|winrt: SUBDIRS -= qwineventnotifier
diff --git a/tests/auto/corelib/kernel/qproperty/qproperty.pro b/tests/auto/corelib/kernel/qproperty/qproperty.pro
new file mode 100644
index 0000000000..bae67e70a4
--- /dev/null
+++ b/tests/auto/corelib/kernel/qproperty/qproperty.pro
@@ -0,0 +1,4 @@
+CONFIG += testcase
+TARGET = tst_qproperty
+QT = core core-private testlib
+SOURCES = tst_qproperty.cpp
diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
new file mode 100644
index 0000000000..7f64072df6
--- /dev/null
+++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
@@ -0,0 +1,615 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include <QObject>
+#include <qtest.h>
+#include <qproperty.h>
+#include <private/qproperty_p.h>
+#include <private/qpropertybinding_p.h>
+
+using namespace QtPrivate;
+
+class tst_QProperty : public QObject
+{
+ Q_OBJECT
+private slots:
+ void functorBinding();
+ void basicDependencies();
+ void multipleDependencies();
+ void bindingWithDeletedDependency();
+ void recursiveDependency();
+ void bindingAfterUse();
+ void switchBinding();
+ void avoidDependencyAllocationAfterFirstEval();
+ void propertyArrays();
+ void boolProperty();
+ void takeBinding();
+ void replaceBinding();
+ void swap();
+ void moveNotifies();
+ void moveCtor();
+ void changeHandler();
+ void propertyChangeHandlerApi();
+ void subscribe();
+ void changeHandlerThroughBindings();
+ void dontTriggerDependenciesIfUnchangedValue();
+ void bindingSourceLocation();
+ void bindingError();
+ void bindingLoop();
+ void changePropertyFromWithinChangeHandler();
+ void changePropertyFromWithinChangeHandlerThroughDependency();
+ void changePropertyFromWithinChangeHandler2();
+ void settingPropertyValueDoesRemoveBinding();
+};
+
+void tst_QProperty::functorBinding()
+{
+ QProperty<int> property([]() { return 42; });
+ QCOMPARE(property.value(), int(42));
+ property = Qt::makePropertyBinding([]() { return 100; });
+ QCOMPARE(property.value(), int(100));
+ property.setBinding([]() { return 50; });
+ QCOMPARE(property.value(), int(50));
+}
+
+void tst_QProperty::basicDependencies()
+{
+ QProperty<int> right(100);
+
+ QProperty<int> left = Qt::makePropertyBinding(right);
+
+ QCOMPARE(left.value(), int(100));
+
+ right = 42;
+
+ QCOMPARE(left.value(), int(42));
+}
+
+void tst_QProperty::multipleDependencies()
+{
+ QProperty<int> firstDependency(1);
+ QProperty<int> secondDependency(2);
+
+ QProperty<int> sum;
+ sum = Qt::makePropertyBinding([&]() { return firstDependency + secondDependency; });
+
+ QCOMPARE(QPropertyBasePointer::get(firstDependency).observerCount(), 0);
+ QCOMPARE(QPropertyBasePointer::get(secondDependency).observerCount(), 0);
+
+ QCOMPARE(sum.value(), int(3));
+ QCOMPARE(QPropertyBasePointer::get(firstDependency).observerCount(), 1);
+ QCOMPARE(QPropertyBasePointer::get(secondDependency).observerCount(), 1);
+
+ firstDependency = 10;
+
+ QCOMPARE(sum.value(), int(12));
+ QCOMPARE(QPropertyBasePointer::get(firstDependency).observerCount(), 1);
+ QCOMPARE(QPropertyBasePointer::get(secondDependency).observerCount(), 1);
+
+ secondDependency = 20;
+
+ QCOMPARE(sum.value(), int(30));
+ QCOMPARE(QPropertyBasePointer::get(firstDependency).observerCount(), 1);
+ QCOMPARE(QPropertyBasePointer::get(secondDependency).observerCount(), 1);
+
+ firstDependency = 1;
+ secondDependency = 1;
+ QCOMPARE(sum.value(), int(2));
+ QCOMPARE(QPropertyBasePointer::get(firstDependency).observerCount(), 1);
+ QCOMPARE(QPropertyBasePointer::get(secondDependency).observerCount(), 1);
+}
+
+void tst_QProperty::bindingWithDeletedDependency()
+{
+ QScopedPointer<QProperty<int>> dynamicProperty(new QProperty<int>(100));
+
+ QProperty<int> staticProperty(1000);
+
+ QProperty<bool> bindingReturnsDynamicProperty(false);
+
+ QProperty<int> propertySelector;
+ propertySelector = Qt::makePropertyBinding([&]() {
+ if (bindingReturnsDynamicProperty && !dynamicProperty.isNull())
+ return dynamicProperty->value();
+ else
+ return staticProperty.value();
+ });
+
+ QCOMPARE(propertySelector.value(), staticProperty.value());
+
+ bindingReturnsDynamicProperty = true;
+
+ QCOMPARE(propertySelector.value(), dynamicProperty->value());
+
+ dynamicProperty.reset();
+
+ QCOMPARE(propertySelector.value(), 100);
+
+ bindingReturnsDynamicProperty = false;
+
+ QCOMPARE(propertySelector.value(), staticProperty.value());
+}
+
+void tst_QProperty::recursiveDependency()
+{
+ QProperty<int> first(1);
+
+ QProperty<int> second;
+ second = Qt::makePropertyBinding(first);
+
+ QProperty<int> third;
+ third = Qt::makePropertyBinding(second);
+
+ QCOMPARE(third.value(), int(1));
+
+ first = 2;
+
+ QCOMPARE(third.value(), int(2));
+}
+
+void tst_QProperty::bindingAfterUse()
+{
+ QProperty<int> propWithBindingLater(1);
+
+ QProperty<int> propThatUsesFirstProp;
+ propThatUsesFirstProp = Qt::makePropertyBinding(propWithBindingLater);
+
+ QCOMPARE(propThatUsesFirstProp.value(), int(1));
+ QCOMPARE(QPropertyBasePointer::get(propWithBindingLater).observerCount(), 1);
+
+ QProperty<int> injectedValue(42);
+ propWithBindingLater = Qt::makePropertyBinding(injectedValue);
+
+ QCOMPARE(propThatUsesFirstProp.value(), int(42));
+ QCOMPARE(QPropertyBasePointer::get(propWithBindingLater).observerCount(), 1);
+}
+
+void tst_QProperty::switchBinding()
+{
+ QProperty<int> first(1);
+
+ QProperty<int> propWithChangingBinding;
+ propWithChangingBinding = Qt::makePropertyBinding(first);
+
+ QCOMPARE(propWithChangingBinding.value(), 1);
+
+ QProperty<int> output;
+ output = Qt::makePropertyBinding(propWithChangingBinding);
+ QCOMPARE(output.value(), 1);
+
+ QProperty<int> second(2);
+ propWithChangingBinding = Qt::makePropertyBinding(second);
+ QCOMPARE(output.value(), 2);
+}
+
+void tst_QProperty::avoidDependencyAllocationAfterFirstEval()
+{
+ QProperty<int> firstDependency(1);
+ QProperty<int> secondDependency(10);
+
+ QProperty<int> propWithBinding;
+ propWithBinding = Qt::makePropertyBinding([&]() { return firstDependency + secondDependency; });
+
+ QCOMPARE(propWithBinding.value(), int(11));
+
+ QVERIFY(QPropertyBasePointer::get(propWithBinding).bindingPtr());
+ QCOMPARE(QPropertyBasePointer::get(propWithBinding).bindingPtr()->dependencyObservers.size(), 2);
+ QVERIFY(QPropertyBasePointer::get(propWithBinding).bindingPtr()->dependencyObservers.capacity() >= 2);
+
+ firstDependency = 100;
+ QCOMPARE(propWithBinding.value(), int(110));
+ QCOMPARE(QPropertyBasePointer::get(propWithBinding).bindingPtr()->dependencyObservers.size(), 2);
+ QVERIFY(QPropertyBasePointer::get(propWithBinding).bindingPtr()->dependencyObservers.capacity() >= 2);
+}
+
+void tst_QProperty::propertyArrays()
+{
+ std::vector<QProperty<int>> properties;
+
+ int expectedSum = 0;
+ for (int i = 0; i < 10; ++i) {
+ properties.emplace_back(i);
+ expectedSum += i;
+ }
+
+ QProperty<int> sum;
+ sum = Qt::makePropertyBinding([&]() {
+ return std::accumulate(properties.begin(), properties.end(), 0);
+ });
+
+ QCOMPARE(sum.value(), expectedSum);
+
+ properties[4] = properties[4] + 42;
+ expectedSum += 42;
+ QCOMPARE(sum.value(), expectedSum);
+}
+
+void tst_QProperty::boolProperty()
+{
+ static_assert(sizeof(QProperty<bool>) == sizeof(void*), "Size of QProperty<bool> specialization must not exceed size of pointer");
+
+ QProperty<bool> first(true);
+ QProperty<bool> second(false);
+ QProperty<bool> all;
+ all = Qt::makePropertyBinding([&]() { return first && second; });
+
+ QCOMPARE(all.value(), false);
+
+ second = true;
+
+ QCOMPARE(all.value(), true);
+}
+
+void tst_QProperty::takeBinding()
+{
+ QPropertyBinding<int> existingBinding;
+ QVERIFY(existingBinding.isNull());
+
+ QProperty<int> first(100);
+ QProperty<int> second = Qt::makePropertyBinding(first);
+
+ QCOMPARE(second.value(), int(100));
+
+ existingBinding = second.takeBinding();
+ QVERIFY(!existingBinding.isNull());
+
+ first = 10;
+ QCOMPARE(second.value(), int(100));
+
+ second = 25;
+ QCOMPARE(second.value(), int(25));
+
+ second = existingBinding;
+ QCOMPARE(second.value(), int(10));
+ QVERIFY(!existingBinding.isNull());
+}
+
+void tst_QProperty::replaceBinding()
+{
+ QProperty<int> first(100);
+ QProperty<int> second = Qt::makePropertyBinding(first);
+
+ QCOMPARE(second.value(), 100);
+
+ auto constantBinding = Qt::makePropertyBinding([]() { return 42; });
+ auto oldBinding = second.setBinding(constantBinding);
+ QCOMPARE(second.value(), 42);
+
+ second = oldBinding;
+ QCOMPARE(second.value(), 100);
+}
+
+void tst_QProperty::swap()
+{
+ QProperty<int> firstDependency(1);
+ QProperty<int> secondDependency(2);
+
+ QProperty<int> first = Qt::makePropertyBinding(firstDependency);
+ QProperty<int> second = Qt::makePropertyBinding(secondDependency);
+
+ QCOMPARE(first.value(), 1);
+ QCOMPARE(second.value(), 2);
+
+ std::swap(first, second);
+
+ QCOMPARE(first.value(), 2);
+ QCOMPARE(second.value(), 1);
+
+ secondDependency = 20;
+ QCOMPARE(first.value(), 20);
+ QCOMPARE(second.value(), 1);
+
+ firstDependency = 100;
+ QCOMPARE(first.value(), 20);
+ QCOMPARE(second.value(), 100);
+}
+
+void tst_QProperty::moveNotifies()
+{
+ QProperty<int> first(1);
+ QProperty<int> second(2);
+
+ QProperty<int> propertyInTheMiddle = Qt::makePropertyBinding(first);
+
+ QProperty<int> finalProp1 = Qt::makePropertyBinding(propertyInTheMiddle);
+ QProperty<int> finalProp2 = Qt::makePropertyBinding(propertyInTheMiddle);
+
+ QCOMPARE(finalProp1.value(), 1);
+ QCOMPARE(finalProp2.value(), 1);
+
+ QCOMPARE(QPropertyBasePointer::get(propertyInTheMiddle).observerCount(), 2);
+
+ QProperty<int> other = Qt::makePropertyBinding(second);
+ QCOMPARE(other.value(), 2);
+
+ QProperty<int> otherDep = Qt::makePropertyBinding(other);
+ QCOMPARE(otherDep.value(), 2);
+ QCOMPARE(QPropertyBasePointer::get(other).observerCount(), 1);
+
+ propertyInTheMiddle = std::move(other);
+
+ QCOMPARE(QPropertyBasePointer::get(other).observerCount(), 0);
+
+ QCOMPARE(finalProp1.value(), 2);
+ QCOMPARE(finalProp2.value(), 2);
+}
+
+void tst_QProperty::moveCtor()
+{
+ QProperty<int> first(1);
+
+ QProperty<int> intermediate = Qt::makePropertyBinding(first);
+ QCOMPARE(intermediate.value(), 1);
+ QCOMPARE(QPropertyBasePointer::get(first).observerCount(), 1);
+
+ QProperty<int> targetProp(std::move(first));
+
+ QCOMPARE(QPropertyBasePointer::get(targetProp).observerCount(), 0);
+}
+
+void tst_QProperty::changeHandler()
+{
+ QProperty<int> testProperty(0);
+ QVector<int> recordedValues;
+
+ {
+ auto handler = testProperty.onValueChanged([&]() {
+ recordedValues << testProperty;
+ });
+
+ testProperty = 1;
+ testProperty = 2;
+ }
+ testProperty = 3;
+
+ QCOMPARE(recordedValues.count(), 2);
+ QCOMPARE(recordedValues.at(0), 1);
+ QCOMPARE(recordedValues.at(1), 2);
+}
+
+void tst_QProperty::propertyChangeHandlerApi()
+{
+ int changeHandlerCallCount = 0;
+ QPropertyChangeHandler handler([&changeHandlerCallCount]() {
+ ++changeHandlerCallCount;
+ });
+
+ QProperty<int> source1;
+ QProperty<int> source2;
+
+ handler.setSource(source1);
+
+ source1 = 100;
+ QCOMPARE(changeHandlerCallCount, 1);
+
+ handler.setSource(source2);
+ source1 = 101;
+ QCOMPARE(changeHandlerCallCount, 1);
+
+ source2 = 200;
+ QCOMPARE(changeHandlerCallCount, 2);
+}
+
+void tst_QProperty::subscribe()
+{
+ QProperty<int> testProperty(42);
+ QVector<int> recordedValues;
+
+ {
+ auto handler = testProperty.subscribe([&]() {
+ recordedValues << testProperty;
+ });
+
+ testProperty = 1;
+ testProperty = 2;
+ }
+ testProperty = 3;
+
+ QCOMPARE(recordedValues.count(), 3);
+ QCOMPARE(recordedValues.at(0), 42);
+ QCOMPARE(recordedValues.at(1), 1);
+ QCOMPARE(recordedValues.at(2), 2);
+}
+
+void tst_QProperty::changeHandlerThroughBindings()
+{
+ QProperty<bool> trigger(false);
+ QProperty<bool> blockTrigger(false);
+ QProperty<bool> condition = Qt::makePropertyBinding([&]() {
+ bool triggerValue = trigger;
+ bool blockTriggerValue = blockTrigger;
+ return triggerValue && !blockTriggerValue;
+ });
+ bool changeHandlerCalled = false;
+ auto handler = condition.onValueChanged([&]() {
+ changeHandlerCalled = true;
+ });
+
+ QVERIFY(!condition);
+ QVERIFY(!changeHandlerCalled);
+
+ trigger = true;
+
+ QVERIFY(condition);
+ QVERIFY(changeHandlerCalled);
+ changeHandlerCalled = false;
+
+ trigger = false;
+
+ QVERIFY(!condition);
+ QVERIFY(changeHandlerCalled);
+ changeHandlerCalled = false;
+
+ blockTrigger = true;
+
+ QVERIFY(!condition);
+ QVERIFY(!changeHandlerCalled);
+}
+
+void tst_QProperty::dontTriggerDependenciesIfUnchangedValue()
+{
+ QProperty<int> property(42);
+
+ bool triggered = false;
+ QProperty<int> observer = Qt::makePropertyBinding([&]() { triggered = true; return property.value(); });
+
+ QCOMPARE(observer.value(), 42);
+ QVERIFY(triggered);
+ triggered = false;
+ property = 42;
+ QCOMPARE(observer.value(), 42);
+ QVERIFY(!triggered);
+}
+
+void tst_QProperty::bindingSourceLocation()
+{
+#if defined(QT_PROPERTY_COLLECT_BINDING_LOCATION)
+ auto bindingLine = std::experimental::source_location::current().line() + 1;
+ auto binding = Qt::makePropertyBinding([]() { return 42; });
+ QCOMPARE(QPropertyBindingPrivate::get(binding)->location.line, bindingLine);
+#else
+ QSKIP("Skipping this in the light of missing binding source location support");
+#endif
+}
+
+void tst_QProperty::bindingError()
+{
+ QProperty<int> prop = Qt::makePropertyBinding([]() -> std::variant<int, QPropertyBindingError> {
+ QPropertyBindingError error(QPropertyBindingError::UnknownError);
+ error.setDescription(QLatin1String("my error"));
+ return error;
+ });
+ QCOMPARE(prop.value(), 0);
+ QCOMPARE(prop.binding().error().description(), QString("my error"));
+}
+
+void tst_QProperty::bindingLoop()
+{
+ QScopedPointer<QProperty<int>> firstProp;
+
+ QProperty<int> secondProp = Qt::makePropertyBinding([&]() -> int {
+ return firstProp ? firstProp->value() : 0;
+ });
+
+ QProperty<int> thirdProp = Qt::makePropertyBinding([&]() -> int {
+ return secondProp.value();
+ });
+
+ firstProp.reset(new QProperty<int>());
+ *firstProp = Qt::makePropertyBinding([&]() -> int {
+ return secondProp.value();
+ });
+
+ QCOMPARE(thirdProp.value(), 0);
+ QCOMPARE(secondProp.binding().error().type(), QPropertyBindingError::BindingLoop);
+}
+
+void tst_QProperty::changePropertyFromWithinChangeHandler()
+{
+ QProperty<int> property(100);
+ bool resetPropertyOnChange = false;
+ int changeHandlerCallCount = 0;
+
+ auto handler = property.onValueChanged([&]() {
+ ++changeHandlerCallCount;
+ if (resetPropertyOnChange)
+ property = 100;
+ });
+
+ QCOMPARE(property.value(), 100);
+
+ resetPropertyOnChange = true;
+ property = 42;
+ QCOMPARE(property.value(), 100);
+ // changing the property value inside the change handler won't result in the change
+ // handler being called again.
+ QCOMPARE(changeHandlerCallCount, 1);
+ changeHandlerCallCount = 0;
+}
+
+void tst_QProperty::changePropertyFromWithinChangeHandlerThroughDependency()
+{
+ QProperty<int> sourceProperty(100);
+ QProperty<int> property = Qt::makePropertyBinding(sourceProperty);
+ bool resetPropertyOnChange = false;
+ int changeHandlerCallCount = 0;
+
+ auto handler = property.onValueChanged([&]() {
+ ++changeHandlerCallCount;
+ if (resetPropertyOnChange)
+ sourceProperty = 100;
+ });
+
+ QCOMPARE(property.value(), 100);
+
+ resetPropertyOnChange = true;
+ sourceProperty = 42;
+ QCOMPARE(property.value(), 100);
+ // changing the property value inside the change handler won't result in the change
+ // handler being called again.
+ QCOMPARE(changeHandlerCallCount, 1);
+ changeHandlerCallCount = 0;
+}
+
+void tst_QProperty::changePropertyFromWithinChangeHandler2()
+{
+ QProperty<int> property(100);
+ int changeHandlerCallCount = 0;
+
+ auto handler = property.onValueChanged([&]() {
+ ++changeHandlerCallCount;
+ property = property.value() + 1;
+ });
+
+ QCOMPARE(property.value(), 100);
+
+ property = 42;
+ QCOMPARE(property.value(), 43);
+}
+
+void tst_QProperty::settingPropertyValueDoesRemoveBinding()
+{
+ QProperty<int> source(42);
+
+ QProperty<int> property = Qt::makePropertyBinding(source);
+
+ QCOMPARE(property.value(), 42);
+ QVERIFY(!property.binding().isNull());
+
+ property = 100;
+ QCOMPARE(property.value(), 100);
+ QVERIFY(property.binding().isNull());
+
+ source = 1;
+ QCOMPARE(property.value(), 100);
+ QVERIFY(property.binding().isNull());
+}
+
+QTEST_MAIN(tst_QProperty);
+
+#include "tst_qproperty.moc"