diff options
author | Erik Verbruggen <erik.verbruggen@theqtcompany.com> | 2015-03-18 12:44:22 +0100 |
---|---|---|
committer | Erik Verbruggen <erik.verbruggen@theqtcompany.com> | 2015-04-10 12:59:52 +0000 |
commit | b2f6406b6f3c6374df2751a4669fbe51f04d09e7 (patch) | |
tree | dff4b41b6f596cfc6600133f608e262ede2f0b56 /tests/auto/corelib | |
parent | e616bd2641b9cf6a18cabeae3f9c425f91bfc4b3 (diff) |
QStateMachine: remove conflicting transitions after selection.
After selecting all (enabled) transitions for a microstep, filter out
any conflicting transition. The actual conflict resulution is done by
ordering the transitions in order of the states that selected them.
For example: if an event would trigger two transitions in a parallel
state where one would exit that state and the other would not, this
filtering prevents the state machine from selecting both states (as this
case is an invalid state of the whole machine).
This also fixes the exit set calculation for parallel states when one of
its substates is exited and subsequently re-entered in the same
transition. Previously, the parallel state was not exited, and
subsequent re-entry was ignored (because it was still active). Now it is
correctly exited and re-entered.
A side-effect of the transition ordering mentioned above is it also
fixes the non-deterministic behavior of which of the conflicting
transitions is taken.
[ChangeLog][QtCore] Fixed an issue where the state machine could end up
in an invalid state when transitions from a parallel state were not
checked for conflicts.
[ChangeLog][QtCore] Fixed a case where a parallel state was not exited
and re-entered when one of its substates was exited and subsequently
re-entered.
[ChangeLog][QtCore] Fixed the non-deterministic behavior of picking a
transition from a set of conflicting transitions.
Task-number: QTBUG-44783
Change-Id: I2ee72b6a2f552077bfa7aa4d369474ab62f4c2f0
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@theqtcompany.com>
Reviewed-by: Kevin Funk <kevin.funk@kdab.com>
Diffstat (limited to 'tests/auto/corelib')
-rw-r--r-- | tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp | 99 |
1 files changed, 89 insertions, 10 deletions
diff --git a/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp b/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp index 605d1e7393..96d0a62f6b 100644 --- a/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp +++ b/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp @@ -246,6 +246,7 @@ private slots: void childModeConstructor(); void qtbug_44963(); + void qtbug_44783(); }; class TestState : public QState @@ -910,8 +911,11 @@ void tst_QStateMachine::historyStateHasNowhereToGo() QStateMachine machine; QState *initialState = new QState(&machine); + initialState->setObjectName("initialState"); machine.setInitialState(initialState); - machine.setErrorState(new QState(&machine)); // avoid warnings + QState *errorState = new QState(&machine); + errorState->setObjectName("errorState"); + machine.setErrorState(errorState); // avoid warnings QState *brokenState = new QState(&machine); brokenState->setObjectName("brokenState"); @@ -919,7 +923,9 @@ void tst_QStateMachine::historyStateHasNowhereToGo() QHistoryState *historyState = new QHistoryState(brokenState); historyState->setObjectName("historyState"); - initialState->addTransition(new EventTransition(QEvent::User, historyState)); + EventTransition *t = new EventTransition(QEvent::User, historyState); + t->setObjectName("initialState->historyState"); + initialState->addTransition(t); machine.start(); QCoreApplication::processEvents(); @@ -4274,6 +4280,11 @@ void tst_QStateMachine::transitionsFromParallelStateWithNoChildren() void tst_QStateMachine::parallelStateTransition() { + // This test checks if the parallel state is exited and re-entered if one compound state + // is exited and subsequently re-entered. When the parallel state is exited, the other compound + // state in the parallel state has to be exited too. When the parallel state is re-entered, the + // other state also needs to be re-entered. + QStateMachine machine; QState *parallelState = new QState(QState::ParallelStates, &machine); @@ -4302,12 +4313,16 @@ void tst_QStateMachine::parallelStateTransition() s1OtherChild->setObjectName("s1OtherChild"); DEFINE_ACTIVE_SPY(s1OtherChild); + // The following transition will exit s1 (which means that parallelState is also exited), and + // subsequently re-entered (which means that parallelState is also re-entered). EventTransition *et = new EventTransition(QEvent::User, s1OtherChild); et->setObjectName("s1->s1OtherChild"); s1->addTransition(et); machine.start(); QCoreApplication::processEvents(); + + // Initial entrance of the parallel state and its sub-states: TEST_ACTIVE_CHANGED(parallelState, 1); TEST_ACTIVE_CHANGED(s1, 1); TEST_ACTIVE_CHANGED(s1InitialChild, 1); @@ -4325,22 +4340,22 @@ void tst_QStateMachine::parallelStateTransition() machine.postEvent(new QEvent(QEvent::User)); QCoreApplication::processEvents(); - TEST_ACTIVE_CHANGED(parallelState, 1); - TEST_ACTIVE_CHANGED(s1, 3); - TEST_ACTIVE_CHANGED(s1InitialChild, 2); - TEST_ACTIVE_CHANGED(s2, 3); - TEST_ACTIVE_CHANGED(s2InitialChild, 3); - TEST_ACTIVE_CHANGED(s1OtherChild, 1); + TEST_ACTIVE_CHANGED(parallelState, 3); // initial + exit + entry + TEST_ACTIVE_CHANGED(s1, 3); // initial + exit + entry + TEST_ACTIVE_CHANGED(s1InitialChild, 2); // initial + exit + TEST_ACTIVE_CHANGED(s2, 3); // initial + exit due to parent exit + entry due to parent re-entry + TEST_ACTIVE_CHANGED(s2InitialChild, 3); // initial + exit due to parent exit + re-entry due to parent re-entry + TEST_ACTIVE_CHANGED(s1OtherChild, 1); // entry due to transition QVERIFY(machine.isRunning()); + // Check that s1InitialChild is not in the configuration, because although s1 is re-entered, + // another child state (s1OtherChild) is active, so the initial child should not be activated. QVERIFY(machine.configuration().contains(parallelState)); - QVERIFY(machine.configuration().contains(s1)); QVERIFY(machine.configuration().contains(s2)); QVERIFY(machine.configuration().contains(s1OtherChild)); QVERIFY(machine.configuration().contains(s2InitialChild)); QCOMPARE(machine.configuration().size(), 5); - } void tst_QStateMachine::nestedRestoreProperties() @@ -6256,5 +6271,69 @@ void tst_QStateMachine::qtbug_44963() QVERIFY(machine.isRunning()); } +void tst_QStateMachine::qtbug_44783() +{ + SignalEmitter emitter; + + QStateMachine machine; + QState s(&machine); + QState p(QState::ParallelStates, &s); + QState p1(&p); + QState p1_1(&p1); + QState p1_2(&p1); + QState p2(&p); + QState s1(&machine); + + machine.setInitialState(&s); + s.setInitialState(&p); + p1.setInitialState(&p1_1); + p1_1.addTransition(&emitter, SIGNAL(signalWithNoArg()), &p1_2)->setObjectName("p1_1->p1_2"); + p2.addTransition(&emitter, SIGNAL(signalWithNoArg()), &s1)->setObjectName("p2->s1"); + + s.setObjectName("s"); + p.setObjectName("p"); + p1.setObjectName("p1"); + p1_1.setObjectName("p1_1"); + p1_2.setObjectName("p1_2"); + p2.setObjectName("p2"); + s1.setObjectName("s1"); + + machine.start(); + + QTRY_COMPARE(machine.configuration().contains(&s), true); + QTRY_COMPARE(machine.configuration().contains(&p), true); + QTRY_COMPARE(machine.configuration().contains(&p1), true); + QTRY_COMPARE(machine.configuration().contains(&p1_1), true); + QTRY_COMPARE(machine.configuration().contains(&p1_2), false); + QTRY_COMPARE(machine.configuration().contains(&p2), true); + QTRY_COMPARE(machine.configuration().contains(&s1), false); + + emitter.emitSignalWithNoArg(); + + // Only one of the following two can be true, because the two possible transitions conflict. + if (machine.configuration().contains(&s1)) { + // the transition p2 -> s1 was taken, not p1_1 -> p1_2, so: + // the parallel state exited, so none of the states inside it are active + QTRY_COMPARE(machine.configuration().contains(&s), false); + QTRY_COMPARE(machine.configuration().contains(&p), false); + QTRY_COMPARE(machine.configuration().contains(&p1), false); + QTRY_COMPARE(machine.configuration().contains(&p1_1), false); + QTRY_COMPARE(machine.configuration().contains(&p1_2), false); + QTRY_COMPARE(machine.configuration().contains(&p2), false); + } else { + // the transition p1_1 -> p1_2 was taken, not p2 -> s1, so: + // the parallel state was not exited and the state is the same as the start state with one + // difference: p1_1 inactive and p1_2 active: + QTRY_COMPARE(machine.configuration().contains(&s), true); + QTRY_COMPARE(machine.configuration().contains(&p), true); + QTRY_COMPARE(machine.configuration().contains(&p1), true); + QTRY_COMPARE(machine.configuration().contains(&p1_1), false); + QTRY_COMPARE(machine.configuration().contains(&p1_2), true); + QTRY_COMPARE(machine.configuration().contains(&p2), true); + } + + QVERIFY(machine.isRunning()); +} + QTEST_MAIN(tst_QStateMachine) #include "tst_qstatemachine.moc" |