diff options
author | Ivan Solovev <ivan.solovev@qt.io> | 2023-08-28 18:40:38 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2023-10-06 17:53:57 +0000 |
commit | b23811e6afd92ca1411d3cc508d68381c88b3318 (patch) | |
tree | 284a78a72d664cba5f04d25c0bb9794fe19e4c18 | |
parent | 9f5e13842d624f75ff059a29e763d02a07829971 (diff) |
QScxmlStateMachine: adjust the test and fix binding loops
Adjust to the changes in QTestPrivate that allow to catch binding loops
when testing bindable properties.
Fix the binding loops identified by the updated tests.
After fixing all obvious binding loops, it was discovered that the
call to QObject::objectName() in the tableData property setter creates
a binding loop on its own.
Fix it by accessing QObjectPrivate's internals instead of calling
a public getter.
Also add a test to verify that the logic of updating the objectName
did not change.
Task-number: QTBUG-116542
Pick-to: 6.5
Change-Id: Idc849dd9841dc70df2d36524cb5bb0a331255f39
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
(cherry picked from commit 77b44612e8898657bd235156b9872c12ca4aea80)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r-- | src/scxml/qscxmldatamodel.cpp | 7 | ||||
-rw-r--r-- | src/scxml/qscxmlstatemachine.cpp | 24 | ||||
-rw-r--r-- | tests/auto/statemachine/tst_statemachine.cpp | 51 |
3 files changed, 63 insertions, 19 deletions
diff --git a/src/scxml/qscxmldatamodel.cpp b/src/scxml/qscxmldatamodel.cpp index 298981e..eaa15e8 100644 --- a/src/scxml/qscxmldatamodel.cpp +++ b/src/scxml/qscxmldatamodel.cpp @@ -95,10 +95,11 @@ void QScxmlDataModel::setStateMachine(QScxmlStateMachine *stateMachine) { Q_D(QScxmlDataModel); - if (d->m_stateMachine.value() == nullptr && stateMachine != nullptr) { + if (d->m_stateMachine.valueBypassingBindings() == nullptr && stateMachine != nullptr) { // the binding is removed only on the first valid set - // as the later attempts are ignored (removed when value is set below) - d->m_stateMachine = stateMachine; + // as the later attempts are ignored + d->m_stateMachine.removeBindingUnlessInWrapper(); + d->m_stateMachine.setValueBypassingBindings(stateMachine); stateMachine->setDataModel(this); d->m_stateMachine.notify(); } diff --git a/src/scxml/qscxmlstatemachine.cpp b/src/scxml/qscxmlstatemachine.cpp index d0b62fc..1ccd37d 100644 --- a/src/scxml/qscxmlstatemachine.cpp +++ b/src/scxml/qscxmlstatemachine.cpp @@ -675,7 +675,8 @@ void QScxmlStateMachinePrivate::updateMetaCache() m_stateIndexToSignalIndex.clear(); m_stateNameToSignalIndex.clear(); - if (!m_tableData.value()) + const QScxmlTableData *tableData = m_tableData.valueBypassingBindings(); + if (!tableData) return; if (!m_stateTable) @@ -687,7 +688,7 @@ void QScxmlStateMachinePrivate::updateMetaCache() const auto &s = m_stateTable->state(i); if (!s.isHistoryState() && s.type != StateTable::State::Invalid) { m_stateIndexToSignalIndex.insert(i, signalIndex); - m_stateNameToSignalIndex.insert(m_tableData.value()->string(s.name), + m_stateNameToSignalIndex.insert(tableData->string(s.name), signalIndex + methodOffset); ++signalIndex; @@ -1650,10 +1651,11 @@ void QScxmlStateMachine::setDataModel(QScxmlDataModel *model) { Q_D(QScxmlStateMachine); - if (d->m_dataModel.value() == nullptr && model != nullptr) { + if (d->m_dataModel.valueBypassingBindings() == nullptr && model != nullptr) { // the binding is removed only on the first valid set - // as the later attempts are ignored (removed when value is set below) - d->m_dataModel = model; + // as the later attempts are ignored + d->m_dataModel.removeBindingUnlessInWrapper(); + d->m_dataModel.setValueBypassingBindings(model); model->setStateMachine(this); d->m_dataModel.notify(); emit dataModelChanged(model); @@ -1706,16 +1708,18 @@ void QScxmlStateMachine::setTableData(QScxmlTableData *tableData) { Q_D(QScxmlStateMachine); - if (d->m_tableData.value() == tableData) { - d->m_tableData.removeBindingUnlessInWrapper(); + d->m_tableData.removeBindingUnlessInWrapper(); + if (d->m_tableData.valueBypassingBindings() == tableData) return; - } - d->m_tableData = tableData; + d->m_tableData.setValueBypassingBindings(tableData); if (tableData) { d->m_stateTable = reinterpret_cast<const QScxmlExecutableContent::StateTable *>( tableData->stateMachineTable()); - if (objectName().isEmpty()) { + // cannot use objectName() here, because it creates binding loop + const QString currentObjectName = d->extraData + ? d->extraData->objectName.valueBypassingBindings() : QString(); + if (currentObjectName.isEmpty()) { setObjectName(tableData->name()); } if (d->m_stateTable->maxServiceId != QScxmlExecutableContent::StateTable::InvalidIndex) { diff --git a/tests/auto/statemachine/tst_statemachine.cpp b/tests/auto/statemachine/tst_statemachine.cpp index 5b717e9..6080d42 100644 --- a/tests/auto/statemachine/tst_statemachine.cpp +++ b/tests/auto/statemachine/tst_statemachine.cpp @@ -38,6 +38,8 @@ private Q_SLOTS: void logWithoutExpr(); void bindings(); + + void setTableDataUpdatesObjectNames(); }; void tst_StateMachine::stateNames_data() @@ -449,11 +451,16 @@ void tst_StateMachine::bindings() return; } + using StateMachinePtr = std::unique_ptr<QScxmlStateMachine>; + // -- QScxmlStateMachine::initialValues QVariantMap map1{{"map", 1}}; QVariantMap map2{{"map", 2}}; QTestPrivate::testReadWritePropertyBasics<QScxmlStateMachine, QVariantMap>( - *stateMachine1, map1, map2, "initialValues"); + *stateMachine1, map1, map2, "initialValues", + []() { + return StateMachinePtr{QScxmlStateMachine::fromFile(QString("not_a_real_file"))}; + }); if (QTest::currentTestFailed()) { qWarning() << "QScxmlStateMachine::initialValues bindable test failed."; return; @@ -468,7 +475,10 @@ void tst_StateMachine::bindings() MockLoader loader1; MockLoader loader2; QTestPrivate::testReadWritePropertyBasics<QScxmlStateMachine, QScxmlCompiler::Loader*>( - *stateMachine1, &loader1, &loader2, "loader"); + *stateMachine1, &loader1, &loader2, "loader", + []() { + return StateMachinePtr{QScxmlStateMachine::fromFile(QString("not_a_real_file"))}; + }); if (QTest::currentTestFailed()) { qWarning() << "QScxmlStateMachine::loader bindable test failed."; return; @@ -483,18 +493,25 @@ void tst_StateMachine::bindings() QScxmlNullDataModel model1; // data can only change once QTestPrivate::testWriteOncePropertyBasics<QScxmlStateMachine, QScxmlDataModel*>( - *stateMachine2, nullptr, &model1, "dataModel"); + *stateMachine2, nullptr, &model1, "dataModel", true, + []() { + return StateMachinePtr{QScxmlStateMachine::fromFile(QString("not_a_real_file"))}; + }); if (QTest::currentTestFailed()) { qWarning() << "QScxmlStateMachine::dataModel bindable test failed."; return; } // -- QScxmlStateMachine::tableData - // Use the statemachine to generate the tabledDatas for testing + // Use the statemachine to generate the tableData for testing std::unique_ptr<QScxmlStateMachine> stateMachine4( QScxmlStateMachine::fromFile(QString(":/tst_statemachine/invoke.scxml"))); QTestPrivate::testReadWritePropertyBasics<QScxmlStateMachine, QScxmlTableData*>( - *stateMachine2, stateMachine1.get()->tableData(), stateMachine4.get()->tableData(), "tableData"); + *stateMachine2, stateMachine1.get()->tableData(), stateMachine4.get()->tableData(), + "tableData", + []() { + return StateMachinePtr{QScxmlStateMachine::fromFile(QString("not_a_real_file"))}; + }); if (QTest::currentTestFailed()) { qWarning() << "QScxmlStateMachine::tableData bindable test failed."; return; @@ -528,7 +545,7 @@ void tst_StateMachine::bindings() std::unique_ptr<QScxmlStateMachine> stateMachine5( QScxmlStateMachine::fromFile(QString("not_a_real_file"))); // data can only change once - QTestPrivate::testWriteOncePropertyBasics<QScxmlDataModel, QScxmlStateMachine*>( + QTestPrivate::testWriteOncePropertyBasics<QScxmlNullDataModel, QScxmlStateMachine*>( dataModel1, nullptr, stateMachine5.get(), "stateMachine"); if (QTest::currentTestFailed()) { qWarning() << "QScxmlDataModel::stateMachine bindable test failed."; @@ -536,6 +553,28 @@ void tst_StateMachine::bindings() } } +void tst_StateMachine::setTableDataUpdatesObjectNames() +{ + std::unique_ptr<QScxmlStateMachine> stateMachine1( + QScxmlStateMachine::fromFile(QString(":/tst_statemachine/emptylog.scxml"))); + const QString sm1ObjectName = stateMachine1->objectName(); + std::unique_ptr<QScxmlStateMachine> stateMachine2( + QScxmlStateMachine::fromFile(QString(":/tst_statemachine/eventoccurred.scxml"))); + const QString sm2ObjectName = stateMachine2->objectName(); + QCOMPARE_NE(sm1ObjectName, sm2ObjectName); + + std::unique_ptr<QScxmlStateMachine> sm( + QScxmlStateMachine::fromFile(QString("not_a_real_file"))); + QVERIFY(sm->objectName().isEmpty()); + // no name set, so update object name + sm->setTableData(stateMachine1->tableData()); + QCOMPARE_EQ(sm->objectName(), sm1ObjectName); + + // object name already set, so do not update + sm->setTableData(stateMachine2->tableData()); + QCOMPARE_EQ(sm->objectName(), sm1ObjectName); // did not change +} + QTEST_MAIN(tst_StateMachine) #include "tst_statemachine.moc" |