Extend QTestPrivate property tests - actual implementation [2/2]

This patch provides the actual implementation to detect binding loops
in property setters.

These test will help to catch all the existing binding loops that were
introduced when migrating to new bindable properties.

The logic of the new tests is taken from
tst_QObject::objectNameBinding(), but generalized to be applicable to
all bindable properties.

The original code from tst_QObject can now be removed.

The patch effectively reverts f791570b86
because a lambda returning a nullptr now means that the binding loop
test should be skipped, which is not a good default behavior.
Now when all the existing bindable properties are fixed, it's fine to
give a compilation error when adding new tests, if the class is not
default-constructible.

Task-number: QTBUG-116345
Pick-to: 6.6 6.5
Change-Id: I059d444d4bb023c050a22e5b1974565e4f581b5c
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Ivan Solovev 2023-08-24 16:54:45 +02:00
parent 457309c9fa
commit f5a5c59918
2 changed files with 37 additions and 30 deletions

View File

@ -88,7 +88,9 @@ namespace QTestPrivate {
\c TestedClass. This instance is used to test for binding loops. By default,
the method returns a default-constructed \c TestedClass. A custom
\a helperConstructor should be provided if \c TestedClass is not
default-constructible. (NOTE: The parameter is currently unused!)
default-constructible. Some very specific properties cannot be tested for
binding loops. Pass a lambda that returns an \c {std::nullptr} as
\a helperConstructor in such case.
\note Any test calling this method will need to call
\code
@ -108,12 +110,7 @@ void testReadWritePropertyBasics(
std::function<char *(const PropertyType &)> represent =
[](const PropertyType &val) { return QTest::toString(val); },
std::function<std::unique_ptr<TestedClass>(void)> helperConstructor =
[]() {
if constexpr (std::is_default_constructible_v<TestedClass>)
return std::make_unique<TestedClass>();
else
return std::unique_ptr<TestedClass>();
})
[]() { return std::make_unique<TestedClass>(); })
{
// get the property
const QMetaObject *metaObject = instance.metaObject();
@ -203,7 +200,23 @@ void testReadWritePropertyBasics(
if (spy)
QCOMPARE(spy->size(), 4);
Q_UNUSED(helperConstructor);
// test binding loop
if (std::unique_ptr<TestedClass> helperObj = std::move(helperConstructor())) {
// Reset to 'initial', so that the binding loop test could check the
// 'changed' value, because some tests already rely on the 'instance' to
// have the 'changed' value once this test passes
testedObj.setProperty(propertyName, QVariant::fromValue(initial));
const QPropertyBinding<PropertyType> binding([&]() {
QObject *obj = static_cast<QObject *>(helperObj.get());
obj->setProperty(propertyName, QVariant::fromValue(changed));
return obj->property(propertyName).template value<PropertyType>();
}, {});
bindable.setBinding(binding);
QPROPERTY_TEST_COMPARISON_HELPER(
testedObj.property(propertyName).template value<PropertyType>(), changed,
comparator, represent);
QVERIFY2(!binding.error().hasError(), qPrintable(binding.error().description()));
}
}
/*!
@ -264,7 +277,9 @@ void testReadWritePropertyBasics(
\c TestedClass. This instance is used to test for binding loops. By default,
the method returns a default-constructed \c TestedClass. A custom
\a helperConstructor should be provided if \c TestedClass is not
default-constructible. (NOTE: The parameter is currently unused!)
default-constructible. Some very specific properties cannot be tested for
binding loops. Pass a lambda that returns an \c {std::nullptr} as
\a helperConstructor in such case.
\note Any test calling this method will need to call
\code
@ -286,15 +301,8 @@ void testWriteOncePropertyBasics(
std::function<char *(const PropertyType &)> represent =
[](const PropertyType &val) { return QTest::toString(val); },
std::function<std::unique_ptr<TestedClass>(void)> helperConstructor =
[]() {
if constexpr (std::is_default_constructible_v<TestedClass>)
return std::make_unique<TestedClass>();
else
return std::unique_ptr<TestedClass>();
})
[]() { return std::make_unique<TestedClass>(); })
{
Q_UNUSED(helperConstructor);
// get the property
const QMetaObject *metaObject = instance.metaObject();
QMetaProperty metaProperty = metaObject->property(metaObject->indexOfProperty(propertyName));
@ -327,10 +335,19 @@ void testWriteOncePropertyBasics(
propObserver.setBinding(bindable.makeBinding());
QPROPERTY_TEST_COMPARISON_HELPER(propObserver.value(), prior, comparator, represent);
// Create a binding that sets the 'changed' value to the property
QProperty<PropertyType> propSetter(changed);
// Create a binding that sets the 'changed' value to the property.
// This also tests binding loops.
QVERIFY(!bindable.hasBinding());
bindable.setBinding(Qt::makePropertyBinding(propSetter));
std::unique_ptr<TestedClass> helperObj(std::move(helperConstructor()));
QProperty<PropertyType> propSetter(changed); // if the helperConstructor() returns nullptr
const QPropertyBinding<PropertyType> binding = helperObj
? Qt::makePropertyBinding([&]() {
QObject *obj = static_cast<QObject *>(helperObj.get());
obj->setProperty(propertyName, QVariant::fromValue(changed));
return obj->property(propertyName).template value<PropertyType>();
})
: Qt::makePropertyBinding(propSetter);
bindable.setBinding(binding);
QVERIFY(bindable.hasBinding());
QPROPERTY_TEST_COMPARISON_HELPER(

View File

@ -8270,16 +8270,6 @@ void tst_QObject::objectNameBinding()
QObject obj;
QTestPrivate::testReadWritePropertyBasics<QObject, QString>(obj, "test1", "test2",
"objectName");
const QPropertyBinding<QString> binding([]() {
QObject obj2;
obj2.setObjectName(QLatin1String("no loop"));
return obj2.objectName();
}, {});
obj.bindableObjectName().setBinding(binding);
QCOMPARE(obj.objectName(), QLatin1String("no loop"));
QVERIFY2(!binding.error().hasError(), qPrintable(binding.error().description()));
}
namespace EmitToDestroyedClass {