diff options
author | Simon Hausmann <simon.hausmann@qt.io> | 2019-07-10 09:01:14 +0200 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@qt.io> | 2020-03-16 18:19:45 +0100 |
commit | 9f9049b486a47aef0c7e2e3852b20aa4ffdce748 (patch) | |
tree | 619d55c8c97c7616c257b4f0da7fa2478637c2a2 /tests/auto/corelib | |
parent | 3c7c60e322f934aaac7e4c1941401a03488383be (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/corelib')
-rw-r--r-- | tests/auto/corelib/kernel/kernel.pro | 6 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qproperty/qproperty.pro | 4 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp | 615 |
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" |