From c9a5ccb268b5e2b2ce0743989c44f808b538ba9b Mon Sep 17 00:00:00 2001 From: Holger Ihrig Date: Fri, 26 Aug 2011 12:03:32 +0200 Subject: Moving relevant tests to corelib/statemachine Task-number: QTBUG-21066 Change-Id: I6dfff1c3b3ad2119a588dc52eb5a2e67255284c1 Reviewed-on: http://codereview.qt.nokia.com/3658 Reviewed-by: Qt Sanity Bot Reviewed-by: Sergio Ahumada --- tests/auto/corelib/statemachine/qstate/qstate.pro | 6 + .../corelib/statemachine/qstate/tst_qstate.cpp | 408 ++ .../statemachine/qstatemachine/qstatemachine.pro | 5 + .../qstatemachine/tst_qstatemachine.cpp | 4448 ++++++++++++++++++++ tests/auto/corelib/statemachine/statemachine.pro | 4 + 5 files changed, 4871 insertions(+) create mode 100644 tests/auto/corelib/statemachine/qstate/qstate.pro create mode 100644 tests/auto/corelib/statemachine/qstate/tst_qstate.cpp create mode 100644 tests/auto/corelib/statemachine/qstatemachine/qstatemachine.pro create mode 100644 tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp create mode 100644 tests/auto/corelib/statemachine/statemachine.pro (limited to 'tests/auto/corelib/statemachine') diff --git a/tests/auto/corelib/statemachine/qstate/qstate.pro b/tests/auto/corelib/statemachine/qstate/qstate.pro new file mode 100644 index 0000000000..6ee7e0cd00 --- /dev/null +++ b/tests/auto/corelib/statemachine/qstate/qstate.pro @@ -0,0 +1,6 @@ +load(qttest_p4) +QT = core +SOURCES += tst_qstate.cpp + + +CONFIG += parallel_test diff --git a/tests/auto/corelib/statemachine/qstate/tst_qstate.cpp b/tests/auto/corelib/statemachine/qstate/tst_qstate.cpp new file mode 100644 index 0000000000..5a9c8d9996 --- /dev/null +++ b/tests/auto/corelib/statemachine/qstate/tst_qstate.cpp @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include "qstate.h" +#include "qstatemachine.h" +#include "qsignaltransition.h" + +// Will try to wait for the condition while allowing event processing +#define QTRY_COMPARE(__expr, __expected) \ + do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if ((__expr) != (__expected)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && ((__expr) != (__expected)); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + QCOMPARE(__expr, __expected); \ + } while(0) + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QState : public QObject +{ + Q_OBJECT + +public: + tst_QState(); + virtual ~tst_QState(); + +private slots: +#if 0 + void test(); +#endif + void assignProperty(); + void assignPropertyTwice(); + void historyInitialState(); + void transitions(); + +private: + bool functionCalled; +}; + +tst_QState::tst_QState() : functionCalled(false) +{ +} + +tst_QState::~tst_QState() +{ +} + +#if 0 +void tst_QState::test() +{ + QStateMachine machine; + QState *s1 = new QState(&machine); + + QCOMPARE(s1->machine(), &machine); + QCOMPARE(s1->parentState(), &machine); + QCOMPARE(s1->initialState(), (QState*)0); + QVERIFY(s1->childStates().isEmpty()); + QVERIFY(s1->transitions().isEmpty()); + + QCOMPARE(s1->isFinal(), false); + s1->setFinal(true); + QCOMPARE(s1->isFinal(), true); + s1->setFinal(false); + QCOMPARE(s1->isFinal(), false); + + QCOMPARE(s1->isParallel(), false); + s1->setParallel(true); + QCOMPARE(s1->isParallel(), true); + s1->setParallel(false); + QCOMPARE(s1->isParallel(), false); + + QCOMPARE(s1->isAtomic(), true); + QCOMPARE(s1->isCompound(), false); + QCOMPARE(s1->isComplex(), false); + + QState *s11 = new QState(s1); + QCOMPARE(s11->parentState(), s1); + QCOMPARE(s11->isAtomic(), true); + QCOMPARE(s11->isCompound(), false); + QCOMPARE(s11->isComplex(), false); + QCOMPARE(s11->machine(), s1->machine()); + QVERIFY(s11->isDescendantOf(s1)); + + QCOMPARE(s1->initialState(), (QState*)0); + QCOMPARE(s1->childStates().size(), 1); + QCOMPARE(s1->childStates().at(0), s11); + + QCOMPARE(s1->isAtomic(), false); + QCOMPARE(s1->isCompound(), true); + QCOMPARE(s1->isComplex(), true); + + s1->setParallel(true); + QCOMPARE(s1->isAtomic(), false); + QCOMPARE(s1->isCompound(), false); + QCOMPARE(s1->isComplex(), true); + + QState *s12 = new QState(s1); + QCOMPARE(s12->parentState(), s1); + QCOMPARE(s12->isAtomic(), true); + QCOMPARE(s12->isCompound(), false); + QCOMPARE(s12->isComplex(), false); + QCOMPARE(s12->machine(), s1->machine()); + QVERIFY(s12->isDescendantOf(s1)); + QVERIFY(!s12->isDescendantOf(s11)); + + QCOMPARE(s1->initialState(), (QState*)0); + QCOMPARE(s1->childStates().size(), 2); + QCOMPARE(s1->childStates().at(0), s11); + QCOMPARE(s1->childStates().at(1), s12); + + QCOMPARE(s1->isAtomic(), false); + QCOMPARE(s1->isCompound(), false); + QCOMPARE(s1->isComplex(), true); + + s1->setParallel(false); + QCOMPARE(s1->isAtomic(), false); + QCOMPARE(s1->isCompound(), true); + QCOMPARE(s1->isComplex(), true); + + s1->setInitialState(s11); + QCOMPARE(s1->initialState(), s11); + + s1->setInitialState(0); + QCOMPARE(s1->initialState(), (QState*)0); + + s1->setInitialState(s12); + QCOMPARE(s1->initialState(), s12); + + QState *s13 = new QState(); + s1->setInitialState(s13); + QCOMPARE(s13->parentState(), s1); + QCOMPARE(s1->childStates().size(), 3); + QCOMPARE(s1->childStates().at(0), s11); + QCOMPARE(s1->childStates().at(1), s12); + QCOMPARE(s1->childStates().at(2), s13); + QVERIFY(s13->isDescendantOf(s1)); + + QVERIFY(s12->childStates().isEmpty()); + + QState *s121 = new QState(s12); + QCOMPARE(s121->parentState(), s12); + QCOMPARE(s121->isAtomic(), true); + QCOMPARE(s121->isCompound(), false); + QCOMPARE(s121->isComplex(), false); + QCOMPARE(s121->machine(), s12->machine()); + QVERIFY(s121->isDescendantOf(s12)); + QVERIFY(s121->isDescendantOf(s1)); + QVERIFY(!s121->isDescendantOf(s11)); + + QCOMPARE(s12->childStates().size(), 1); + QCOMPARE(s12->childStates().at(0), (QState*)s121); + + QCOMPARE(s1->childStates().size(), 3); + QCOMPARE(s1->childStates().at(0), s11); + QCOMPARE(s1->childStates().at(1), s12); + QCOMPARE(s1->childStates().at(2), s13); + + s11->addTransition(s12); + QCOMPARE(s11->transitions().size(), 1); + QCOMPARE(s11->transitions().at(0)->sourceState(), s11); + QCOMPARE(s11->transitions().at(0)->targetStates().size(), 1); + QCOMPARE(s11->transitions().at(0)->targetStates().at(0), s12); + QCOMPARE(s11->transitions().at(0)->eventType(), QEvent::None); + + QState *s14 = new QState(); + s12->addTransition(QList() << s13 << s14); + QCOMPARE(s12->transitions().size(), 1); + QCOMPARE(s12->transitions().at(0)->sourceState(), s12); + QCOMPARE(s12->transitions().at(0)->targetStates().size(), 2); + QCOMPARE(s12->transitions().at(0)->targetStates().at(0), s13); + QCOMPARE(s12->transitions().at(0)->targetStates().at(1), s14); + QCOMPARE(s12->transitions().at(0)->eventType(), QEvent::None); + + s13->addTransition(this, SIGNAL(destroyed()), s14); + QCOMPARE(s13->transitions().size(), 1); + QCOMPARE(s13->transitions().at(0)->sourceState(), s13); + QCOMPARE(s13->transitions().at(0)->targetStates().size(), 1); + QCOMPARE(s13->transitions().at(0)->targetStates().at(0), s14); + QCOMPARE(s13->transitions().at(0)->eventType(), QEvent::Signal); + QVERIFY(qobject_cast(s13->transitions().at(0)) != 0); + + delete s13->transitions().at(0); + QCOMPARE(s13->transitions().size(), 0); + + s12->addTransition(this, SIGNAL(destroyed()), s11); + QCOMPARE(s12->transitions().size(), 2); +} +#endif + +class TestClass: public QObject +{ + Q_OBJECT +public: + TestClass() : called(false) {} + bool called; + +public slots: + void slot() { called = true; } + + +}; + +void tst_QState::assignProperty() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("fooBar", 10); + + QState *s1 = new QState(&machine); + s1->assignProperty(object, "fooBar", 20); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("fooBar").toInt(), 20); +} + +void tst_QState::assignPropertyTwice() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("fooBar", 10); + + QState *s1 = new QState(&machine); + s1->assignProperty(object, "fooBar", 20); + s1->assignProperty(object, "fooBar", 30); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("fooBar").toInt(), 30); +} + +class EventTestTransition: public QAbstractTransition +{ +public: + EventTestTransition(QEvent::Type type, QState *targetState) + : QAbstractTransition(), m_type(type) + { + setTargetState(targetState); + } + +protected: + bool eventTest(QEvent *e) + { + return e->type() == m_type; + } + + void onTransition(QEvent *) {} + +private: + QEvent::Type m_type; + +}; + +void tst_QState::historyInitialState() +{ + QStateMachine machine; + + QState *s1 = new QState(&machine); + + QState *s2 = new QState(&machine); + QHistoryState *h1 = new QHistoryState(s2); + + s2->setInitialState(h1); + + QState *s3 = new QState(s2); + h1->setDefaultState(s3); + + QState *s4 = new QState(s2); + + s1->addTransition(new EventTestTransition(QEvent::User, s2)); + s2->addTransition(new EventTestTransition(QEvent::User, s1)); + s3->addTransition(new EventTestTransition(QEvent::Type(QEvent::User+1), s4)); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s3)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s3)); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User+1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s4)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s4)); +} + +void tst_QState::transitions() +{ + QState s1; + QState s2; + + QVERIFY(s1.transitions().isEmpty()); + + QAbstractTransition *t1 = s1.addTransition(this, SIGNAL(destroyed()), &s2); + QVERIFY(t1 != 0); + QCOMPARE(s1.transitions().count(), 1); + QCOMPARE(s1.transitions().first(), t1); + QVERIFY(s2.transitions().isEmpty()); + + s1.removeTransition(t1); + QVERIFY(s1.transitions().isEmpty()); + + s1.addTransition(t1); + QCOMPARE(s1.transitions().count(), 1); + QCOMPARE(s1.transitions().first(), t1); + + QAbstractTransition *t2 = new QEventTransition(&s1); + QCOMPARE(s1.transitions().count(), 2); + QVERIFY(s1.transitions().contains(t1)); + QVERIFY(s1.transitions().contains(t2)); + + // Transitions from child states should not be reported. + QState *s21 = new QState(&s2); + QAbstractTransition *t3 = s21->addTransition(this, SIGNAL(destroyed()), &s2); + QVERIFY(s2.transitions().isEmpty()); + QCOMPARE(s21->transitions().count(), 1); + QCOMPARE(s21->transitions().first(), t3); +} + +QTEST_MAIN(tst_QState) +#include "tst_qstate.moc" diff --git a/tests/auto/corelib/statemachine/qstatemachine/qstatemachine.pro b/tests/auto/corelib/statemachine/qstatemachine/qstatemachine.pro new file mode 100644 index 0000000000..9d057eb05f --- /dev/null +++ b/tests/auto/corelib/statemachine/qstatemachine/qstatemachine.pro @@ -0,0 +1,5 @@ +load(qttest_p4) +QT += widgets +QT += core core-private gui widgets +SOURCES += tst_qstatemachine.cpp + diff --git a/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp b/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp new file mode 100644 index 0000000000..08181c17de --- /dev/null +++ b/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp @@ -0,0 +1,4448 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "qstatemachine.h" +#include "qstate.h" +#include "qhistorystate.h" +#include "qkeyeventtransition.h" +#include "qmouseeventtransition.h" +#include "private/qstate_p.h" +#include "private/qstatemachine_p.h" + +// Will try to wait for the condition while allowing event processing +#define QTRY_COMPARE(__expr, __expected) \ + do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if ((__expr) != (__expected)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && ((__expr) != (__expected)); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + QCOMPARE(__expr, __expected); \ + } while(0) + +//TESTED_CLASS= +//TESTED_FILES= + +static int globalTick; + +// Run exec for a maximum of TIMEOUT msecs +#define QCOREAPPLICATION_EXEC(TIMEOUT) \ +{ \ + QTimer timer; \ + timer.setSingleShot(true); \ + timer.setInterval(TIMEOUT); \ + timer.start(); \ + connect(&timer, SIGNAL(timeout()), QCoreApplication::instance(), SLOT(quit())); \ + QCoreApplication::exec(); \ +} + +class SignalEmitter : public QObject +{ +Q_OBJECT + public: + SignalEmitter(QObject *parent = 0) + : QObject(parent) {} + void emitSignalWithNoArg() + { emit signalWithNoArg(); } + void emitSignalWithIntArg(int arg) + { emit signalWithIntArg(arg); } + void emitSignalWithStringArg(const QString &arg) + { emit signalWithStringArg(arg); } + void emitSignalWithDefaultArg() + { emit signalWithDefaultArg(); } +Q_SIGNALS: + void signalWithNoArg(); + void signalWithIntArg(int); + void signalWithStringArg(const QString &); + void signalWithDefaultArg(int i = 42); +}; + +class tst_QStateMachine : public QObject +{ + Q_OBJECT +public: + tst_QStateMachine(); + virtual ~tst_QStateMachine(); + +private slots: + void init(); + void cleanup(); + + void rootState(); + void machineWithParent(); + void addAndRemoveState(); + void stateEntryAndExit(); + void assignProperty(); + void assignPropertyWithAnimation(); + void postEvent(); + void cancelDelayedEvent(); + void postDelayedEventAndStop(); + void stopAndPostEvent(); + void stateFinished(); + void parallelStates(); + void parallelRootState(); + void allSourceToTargetConfigurations(); + void signalTransitions(); + void eventTransitions(); + void graphicsSceneEventTransitions(); + void historyStates(); + void startAndStop(); + void targetStateWithNoParent(); + void targetStateDeleted(); + void transitionToRootState(); + void transitionFromRootState(); + void transitionEntersParent(); + + void defaultErrorState(); + void customGlobalErrorState(); + void customLocalErrorStateInBrokenState(); + void customLocalErrorStateInOtherState(); + void customLocalErrorStateInParentOfBrokenState(); + void customLocalErrorStateOverridesParent(); + void errorStateHasChildren(); + void errorStateHasErrors(); + void errorStateIsRootState(); + void errorStateEntersParentFirst(); + void customErrorStateIsNull(); + void clearError(); + void historyStateHasNowhereToGo(); + void historyStateAsInitialState(); + void historyStateAfterRestart(); + void brokenStateIsNeverEntered(); + void customErrorStateNotInGraph(); + void transitionToStateNotInGraph(); + void restoreProperties(); + + void defaultGlobalRestorePolicy(); + void globalRestorePolicySetToRestore(); + void globalRestorePolicySetToDontRestore(); + + void noInitialStateForInitialState(); + + //void restorePolicyNotInherited(); + //void mixedRestoreProperties(); + //void setRestorePolicyToDoNotRestore(); + //void setGlobalRestorePolicyToGlobalRestore(); + //void restorePolicyOnChildState(); + + void transitionWithParent(); + void transitionsFromParallelStateWithNoChildren(); + void parallelStateTransition(); + void parallelStateAssignmentsDone(); + void nestedRestoreProperties(); + void nestedRestoreProperties2(); + + void simpleAnimation(); + void twoAnimations(); + void twoAnimatedTransitions(); + void playAnimationTwice(); + void nestedTargetStateForAnimation(); + void propertiesAssignedSignalTransitionsReuseAnimationGroup(); + void animatedGlobalRestoreProperty(); + void specificTargetValueOfAnimation(); + + void addDefaultAnimation(); + void addDefaultAnimationWithUnusedAnimation(); + void removeDefaultAnimation(); + void overrideDefaultAnimationWithSpecific(); + +// void addDefaultAnimationForSource(); +// void addDefaultAnimationForTarget(); +// void removeDefaultAnimationForSource(); +// void removeDefaultAnimationForTarget(); +// void overrideDefaultAnimationWithSource(); +// void overrideDefaultAnimationWithTarget(); +// void overrideDefaultSourceAnimationWithSpecific(); +// void overrideDefaultTargetAnimationWithSpecific(); +// void overrideDefaultTargetAnimationWithSource(); + + void nestedStateMachines(); + void goToState(); + + void task260403_clonedSignals(); + void postEventFromOtherThread(); + void eventFilterForApplication(); + void eventClassesExported(); + void stopInTransitionToFinalState(); + void stopInEventTest_data(); + void stopInEventTest(); +}; + +tst_QStateMachine::tst_QStateMachine() +{ +} + +tst_QStateMachine::~tst_QStateMachine() +{ +} + +class TestState : public QState +{ +public: + enum Event { + Entry, + Exit + }; + TestState(QState *parent) + : QState(parent) {} + QList > events; +protected: + virtual void onEntry(QEvent *) { + events.append(qMakePair(globalTick++, Entry)); + } + virtual void onExit(QEvent *) { + events.append(qMakePair(globalTick++, Exit)); + } +}; + +class TestTransition : public QAbstractTransition +{ +public: + TestTransition(QAbstractState *target) + : QAbstractTransition() + { setTargetState(target); } + QList triggers; +protected: + virtual bool eventTest(QEvent *) { + return true; + } + virtual void onTransition(QEvent *) { + triggers.append(globalTick++); + } +}; + +void tst_QStateMachine::init() +{ +} + +void tst_QStateMachine::cleanup() +{ +} + +class EventTransition : public QAbstractTransition +{ +public: + EventTransition(QEvent::Type type, QAbstractState *target, QState *parent = 0) + : QAbstractTransition(parent), m_type(type) + { setTargetState(target); } +protected: + virtual bool eventTest(QEvent *e) { + return (e->type() == m_type); + } + virtual void onTransition(QEvent *) {} +private: + QEvent::Type m_type; +}; + +void tst_QStateMachine::transitionToRootState() +{ + QStateMachine machine; + machine.setObjectName("machine"); + + QState *initialState = new QState(); + initialState->setObjectName("initial"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QAbstractTransition *trans = new EventTransition(QEvent::User, &machine); + initialState->addTransition(trans); + QCOMPARE(trans->sourceState(), initialState); + QCOMPARE(trans->targetState(), static_cast(&machine)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(initialState)); + + machine.postEvent(new QEvent(QEvent::User)); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: No common ancestor for targets and source of transition from state 'initial'"); + QCoreApplication::processEvents(); + QVERIFY(machine.configuration().isEmpty()); + QVERIFY(!machine.isRunning()); +} + +void tst_QStateMachine::transitionFromRootState() +{ + QStateMachine machine; + QState *root = &machine; + QState *s1 = new QState(root); + EventTransition *trans = new EventTransition(QEvent::User, s1); + root->addTransition(trans); + QCOMPARE(trans->sourceState(), root); + QCOMPARE(trans->targetState(), static_cast(s1)); +} + +void tst_QStateMachine::transitionEntersParent() +{ + QStateMachine machine; + + QObject *entryController = new QObject(&machine); + entryController->setObjectName("entryController"); + entryController->setProperty("greatGrandParentEntered", false); + entryController->setProperty("grandParentEntered", false); + entryController->setProperty("parentEntered", false); + entryController->setProperty("stateEntered", false); + + QState *greatGrandParent = new QState(); + greatGrandParent->setObjectName("grandParent"); + greatGrandParent->assignProperty(entryController, "greatGrandParentEntered", true); + machine.addState(greatGrandParent); + machine.setInitialState(greatGrandParent); + + QState *grandParent = new QState(greatGrandParent); + grandParent->setObjectName("grandParent"); + grandParent->assignProperty(entryController, "grandParentEntered", true); + + QState *parent = new QState(grandParent); + parent->setObjectName("parent"); + parent->assignProperty(entryController, "parentEntered", true); + + QState *state = new QState(parent); + state->setObjectName("state"); + state->assignProperty(entryController, "stateEntered", true); + + QState *initialStateOfGreatGrandParent = new QState(greatGrandParent); + initialStateOfGreatGrandParent->setObjectName("initialStateOfGreatGrandParent"); + greatGrandParent->setInitialState(initialStateOfGreatGrandParent); + + initialStateOfGreatGrandParent->addTransition(new EventTransition(QEvent::User, state)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), true); + QCOMPARE(entryController->property("grandParentEntered").toBool(), false); + QCOMPARE(entryController->property("parentEntered").toBool(), false); + QCOMPARE(entryController->property("stateEntered").toBool(), false); + QCOMPARE(machine.configuration().count(), 2); + QVERIFY(machine.configuration().contains(greatGrandParent)); + QVERIFY(machine.configuration().contains(initialStateOfGreatGrandParent)); + + entryController->setProperty("greatGrandParentEntered", false); + entryController->setProperty("grandParentEntered", false); + entryController->setProperty("parentEntered", false); + entryController->setProperty("stateEntered", false); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), false); + QCOMPARE(entryController->property("grandParentEntered").toBool(), true); + QCOMPARE(entryController->property("parentEntered").toBool(), true); + QCOMPARE(entryController->property("stateEntered").toBool(), true); + QCOMPARE(machine.configuration().count(), 4); + QVERIFY(machine.configuration().contains(greatGrandParent)); + QVERIFY(machine.configuration().contains(grandParent)); + QVERIFY(machine.configuration().contains(parent)); + QVERIFY(machine.configuration().contains(state)); +} + +void tst_QStateMachine::defaultErrorState() +{ + QStateMachine machine; + QCOMPARE(machine.errorState(), reinterpret_cast(0)); + + QState *brokenState = new QState(); + brokenState->setObjectName("MyInitialState"); + + machine.addState(brokenState); + machine.setInitialState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'MyInitialState'"); + + // initialState has no initial state + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); + QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'MyInitialState'")); + QCOMPARE(machine.isRunning(), false); +} + +class CustomErrorState: public QState +{ +public: + CustomErrorState(QStateMachine *machine, QState *parent = 0) + : QState(parent), error(QStateMachine::NoError), m_machine(machine) + { + } + + void onEntry(QEvent *) + { + error = m_machine->error(); + errorString = m_machine->errorString(); + } + + QStateMachine::Error error; + QString errorString; + +private: + QStateMachine *m_machine; +}; + +void tst_QStateMachine::customGlobalErrorState() +{ + QStateMachine machine; + + CustomErrorState *customErrorState = new CustomErrorState(&machine); + customErrorState->setObjectName("customErrorState"); + machine.addState(customErrorState); + machine.setErrorState(customErrorState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + machine.addState(brokenState); + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.errorState(), static_cast(customErrorState)); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(initialState)); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(initialState)); + + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), true); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(customErrorState)); + QCOMPARE(customErrorState->error, QStateMachine::NoInitialStateError); + QCOMPARE(customErrorState->errorString, QString::fromLatin1("Missing initial state in compound state 'brokenState'")); + QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); + QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'brokenState'")); +} + +void tst_QStateMachine::customLocalErrorStateInBrokenState() +{ + QStateMachine machine; + CustomErrorState *customErrorState = new CustomErrorState(&machine); + machine.addState(customErrorState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + machine.addState(brokenState); + brokenState->setErrorState(customErrorState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), true); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(customErrorState)); + QCOMPARE(customErrorState->error, QStateMachine::NoInitialStateError); +} + +void tst_QStateMachine::customLocalErrorStateInOtherState() +{ + QStateMachine machine; + CustomErrorState *customErrorState = new CustomErrorState(&machine); + machine.addState(customErrorState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + QTest::ignoreMessage(QtWarningMsg, "QState::setErrorState: error state cannot belong to a different state machine"); + initialState->setErrorState(customErrorState); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + + machine.addState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'brokenState'"); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), false); +} + +void tst_QStateMachine::customLocalErrorStateInParentOfBrokenState() +{ + QStateMachine machine; + CustomErrorState *customErrorState = new CustomErrorState(&machine); + machine.addState(customErrorState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *parentOfBrokenState = new QState(); + machine.addState(parentOfBrokenState); + parentOfBrokenState->setObjectName("parentOfBrokenState"); + parentOfBrokenState->setErrorState(customErrorState); + + QState *brokenState = new QState(parentOfBrokenState); + brokenState->setObjectName("brokenState"); + parentOfBrokenState->setInitialState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), true); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(customErrorState)); +} + +void tst_QStateMachine::customLocalErrorStateOverridesParent() +{ + QStateMachine machine; + CustomErrorState *customErrorStateForParent = new CustomErrorState(&machine); + machine.addState(customErrorStateForParent); + + CustomErrorState *customErrorStateForBrokenState = new CustomErrorState(&machine); + machine.addState(customErrorStateForBrokenState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *parentOfBrokenState = new QState(); + machine.addState(parentOfBrokenState); + parentOfBrokenState->setObjectName("parentOfBrokenState"); + parentOfBrokenState->setErrorState(customErrorStateForParent); + + QState *brokenState = new QState(parentOfBrokenState); + brokenState->setObjectName("brokenState"); + brokenState->setErrorState(customErrorStateForBrokenState); + parentOfBrokenState->setInitialState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(customErrorStateForBrokenState)); + QCOMPARE(customErrorStateForBrokenState->error, QStateMachine::NoInitialStateError); + QCOMPARE(customErrorStateForParent->error, QStateMachine::NoError); +} + +void tst_QStateMachine::errorStateHasChildren() +{ + QStateMachine machine; + CustomErrorState *customErrorState = new CustomErrorState(&machine); + customErrorState->setObjectName("customErrorState"); + machine.addState(customErrorState); + + machine.setErrorState(customErrorState); + + QState *childOfErrorState = new QState(customErrorState); + childOfErrorState->setObjectName("childOfErrorState"); + customErrorState->setInitialState(childOfErrorState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + machine.addState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), true); + QCOMPARE(machine.configuration().count(), 2); + QVERIFY(machine.configuration().contains(customErrorState)); + QVERIFY(machine.configuration().contains(childOfErrorState)); +} + + +void tst_QStateMachine::errorStateHasErrors() +{ + QStateMachine machine; + CustomErrorState *customErrorState = new CustomErrorState(&machine); + customErrorState->setObjectName("customErrorState"); + machine.addState(customErrorState); + + machine.setErrorState(customErrorState); + + QState *childOfErrorState = new QState(customErrorState); + childOfErrorState->setObjectName("childOfErrorState"); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + machine.addState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'customErrorState'"); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), false); + QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); + QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'customErrorState'")); +} + +void tst_QStateMachine::errorStateIsRootState() +{ + QStateMachine machine; + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::setErrorState: root state cannot be error state"); + machine.setErrorState(&machine); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + machine.addState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'brokenState'"); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), false); +} + +void tst_QStateMachine::errorStateEntersParentFirst() +{ + QStateMachine machine; + + QObject *entryController = new QObject(&machine); + entryController->setObjectName("entryController"); + entryController->setProperty("greatGrandParentEntered", false); + entryController->setProperty("grandParentEntered", false); + entryController->setProperty("parentEntered", false); + entryController->setProperty("errorStateEntered", false); + + QState *greatGrandParent = new QState(); + greatGrandParent->setObjectName("greatGrandParent"); + greatGrandParent->assignProperty(entryController, "greatGrandParentEntered", true); + machine.addState(greatGrandParent); + machine.setInitialState(greatGrandParent); + + QState *grandParent = new QState(greatGrandParent); + grandParent->setObjectName("grandParent"); + grandParent->assignProperty(entryController, "grandParentEntered", true); + + QState *parent = new QState(grandParent); + parent->setObjectName("parent"); + parent->assignProperty(entryController, "parentEntered", true); + + QState *errorState = new QState(parent); + errorState->setObjectName("errorState"); + errorState->assignProperty(entryController, "errorStateEntered", true); + machine.setErrorState(errorState); + + QState *initialStateOfGreatGrandParent = new QState(greatGrandParent); + initialStateOfGreatGrandParent->setObjectName("initialStateOfGreatGrandParent"); + greatGrandParent->setInitialState(initialStateOfGreatGrandParent); + + QState *brokenState = new QState(greatGrandParent); + brokenState->setObjectName("brokenState"); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialStateOfGreatGrandParent->addTransition(new EventTransition(QEvent::User, brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), true); + QCOMPARE(entryController->property("grandParentEntered").toBool(), false); + QCOMPARE(entryController->property("parentEntered").toBool(), false); + QCOMPARE(entryController->property("errorStateEntered").toBool(), false); + QCOMPARE(machine.configuration().count(), 2); + QVERIFY(machine.configuration().contains(greatGrandParent)); + QVERIFY(machine.configuration().contains(initialStateOfGreatGrandParent)); + + entryController->setProperty("greatGrandParentEntered", false); + entryController->setProperty("grandParentEntered", false); + entryController->setProperty("parentEntered", false); + entryController->setProperty("errorStateEntered", false); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), false); + QCOMPARE(entryController->property("grandParentEntered").toBool(), true); + QCOMPARE(entryController->property("parentEntered").toBool(), true); + QCOMPARE(entryController->property("errorStateEntered").toBool(), true); + QCOMPARE(machine.configuration().count(), 4); + QVERIFY(machine.configuration().contains(greatGrandParent)); + QVERIFY(machine.configuration().contains(grandParent)); + QVERIFY(machine.configuration().contains(parent)); + QVERIFY(machine.configuration().contains(errorState)); +} + +void tst_QStateMachine::customErrorStateIsNull() +{ + QStateMachine machine; + machine.setErrorState(0); + + QState *initialState = new QState(); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + machine.addState(brokenState); + + new QState(brokenState); + initialState->addTransition(new EventTransition(QEvent::User, brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state ''"); + QCoreApplication::processEvents(); + + QCOMPARE(machine.errorState(), reinterpret_cast(0)); + QCOMPARE(machine.isRunning(), false); +} + +void tst_QStateMachine::clearError() +{ + QStateMachine machine; + machine.setErrorState(new QState(&machine)); // avoid warnings + + QState *brokenState = new QState(&machine); + brokenState->setObjectName("brokenState"); + machine.setInitialState(brokenState); + new QState(brokenState); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), true); + QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); + QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'brokenState'")); + + machine.clearError(); + + QCOMPARE(machine.error(), QStateMachine::NoError); + QVERIFY(machine.errorString().isEmpty()); +} + +void tst_QStateMachine::historyStateAsInitialState() +{ + QStateMachine machine; + + QHistoryState *hs = new QHistoryState(&machine); + machine.setInitialState(hs); + + QState *s1 = new QState(&machine); + hs->setDefaultState(s1); + + QState *s2 = new QState(&machine); + + QHistoryState *s2h = new QHistoryState(s2); + s2->setInitialState(s2h); + + QState *s21 = new QState(s2); + s2h->setDefaultState(s21); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s21)); +} + +void tst_QStateMachine::historyStateHasNowhereToGo() +{ + QStateMachine machine; + + QState *initialState = new QState(&machine); + machine.setInitialState(initialState); + machine.setErrorState(new QState(&machine)); // avoid warnings + + QState *brokenState = new QState(&machine); + brokenState->setObjectName("brokenState"); + brokenState->setInitialState(new QState(brokenState)); + + QHistoryState *historyState = new QHistoryState(brokenState); + historyState->setObjectName("historyState"); + initialState->addTransition(new EventTransition(QEvent::User, historyState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), true); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(machine.errorState())); + QCOMPARE(machine.error(), QStateMachine::NoDefaultStateInHistoryStateError); + QCOMPARE(machine.errorString(), QString::fromLatin1("Missing default state in history state 'historyState'")); +} + +void tst_QStateMachine::historyStateAfterRestart() +{ + // QTBUG-8842 + QStateMachine machine; + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + QState *s2 = new QState(&machine); + QState *s21 = new QState(s2); + QState *s22 = new QState(s2); + QHistoryState *s2h = new QHistoryState(s2); + s2h->setDefaultState(s21); + s1->addTransition(new EventTransition(QEvent::User, s2h)); + s21->addTransition(new EventTransition(QEvent::User, s22)); + s2->addTransition(new EventTransition(QEvent::User, s1)); + + for (int x = 0; x < 2; ++x) { + QSignalSpy startedSpy(&machine, SIGNAL(started())); + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(s1)); + + // s1 -> s2h -> s21 (default state) + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().count(), 2); + QVERIFY(machine.configuration().contains(s2)); + // This used to fail on the 2nd run because the + // history had not been cleared. + QVERIFY(machine.configuration().contains(s21)); + + // s21 -> s22 + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().count(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s22)); + + // s2 -> s1 (s22 saved in s2h) + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(s1)); + + // s1 -> s2h -> s22 (saved state) + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().count(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s22)); + + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + machine.stop(); + QTRY_COMPARE(stoppedSpy.count(), 1); + } +} + +void tst_QStateMachine::brokenStateIsNeverEntered() +{ + QStateMachine machine; + + QObject *entryController = new QObject(&machine); + entryController->setProperty("brokenStateEntered", false); + entryController->setProperty("childStateEntered", false); + entryController->setProperty("errorStateEntered", false); + + QState *initialState = new QState(&machine); + machine.setInitialState(initialState); + + QState *errorState = new QState(&machine); + errorState->assignProperty(entryController, "errorStateEntered", true); + machine.setErrorState(errorState); + + QState *brokenState = new QState(&machine); + brokenState->assignProperty(entryController, "brokenStateEntered", true); + brokenState->setObjectName("brokenState"); + + QState *childState = new QState(brokenState); + childState->assignProperty(entryController, "childStateEntered", true); + + initialState->addTransition(new EventTransition(QEvent::User, brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(entryController->property("errorStateEntered").toBool(), true); + QCOMPARE(entryController->property("brokenStateEntered").toBool(), false); + QCOMPARE(entryController->property("childStateEntered").toBool(), false); +} + +void tst_QStateMachine::transitionToStateNotInGraph() +{ + QStateMachine machine; + + QState *initialState = new QState(&machine); + initialState->setObjectName("initialState"); + machine.setInitialState(initialState); + + QState independentState; + independentState.setObjectName("independentState"); + initialState->addTransition(&independentState); + + machine.start(); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: No common ancestor for targets and source of transition from state 'initialState'"); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), false); +} + +void tst_QStateMachine::customErrorStateNotInGraph() +{ + QStateMachine machine; + + QState errorState; + errorState.setObjectName("errorState"); + QTest::ignoreMessage(QtWarningMsg, "QState::setErrorState: error state cannot belong to a different state machine"); + machine.setErrorState(&errorState); + QCOMPARE(machine.errorState(), reinterpret_cast(0)); + + QState *initialBrokenState = new QState(&machine); + initialBrokenState->setObjectName("initialBrokenState"); + machine.setInitialState(initialBrokenState); + new QState(initialBrokenState); + + machine.start(); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'initialBrokenState'"); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), false); +} + +void tst_QStateMachine::restoreProperties() +{ + QStateMachine machine; + QCOMPARE(machine.globalRestorePolicy(), QStateMachine::DontRestoreProperties); + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QObject *object = new QObject(&machine); + object->setProperty("a", 1); + object->setProperty("b", 2); + + QState *S1 = new QState(); + S1->setObjectName("S1"); + S1->assignProperty(object, "a", 3); + machine.addState(S1); + + QState *S2 = new QState(); + S2->setObjectName("S2"); + S2->assignProperty(object, "b", 5); + machine.addState(S2); + + QState *S3 = new QState(); + S3->setObjectName("S3"); + machine.addState(S3); + + QFinalState *S4 = new QFinalState(); + machine.addState(S4); + + S1->addTransition(new EventTransition(QEvent::User, S2)); + S2->addTransition(new EventTransition(QEvent::User, S3)); + S3->addTransition(S4); + + machine.setInitialState(S1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 3); + QCOMPARE(object->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 1); + QCOMPARE(object->property("b").toInt(), 5); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 1); + QCOMPARE(object->property("b").toInt(), 2); +} + +void tst_QStateMachine::rootState() +{ + QStateMachine machine; + QCOMPARE(qobject_cast(machine.parentState()), (QState*)0); + QCOMPARE(machine.machine(), (QStateMachine*)0); + + QState *s1 = new QState(&machine); + QCOMPARE(s1->parentState(), static_cast(&machine)); + + QState *s2 = new QState(); + s2->setParent(&machine); + QCOMPARE(s2->parentState(), static_cast(&machine)); +} + +void tst_QStateMachine::machineWithParent() +{ + QObject object; + QStateMachine *machine = new QStateMachine(&object); + QCOMPARE(machine->parent(), &object); + QCOMPARE(machine->parentState(), static_cast(0)); +} + +void tst_QStateMachine::addAndRemoveState() +{ +#ifdef QT_BUILD_INTERNAL + QStateMachine machine; + QStatePrivate *root_d = QStatePrivate::get(&machine); + QCOMPARE(root_d->childStates().size(), 0); + + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::addState: cannot add null state"); + machine.addState(0); + + QState *s1 = new QState(); + QCOMPARE(s1->parentState(), (QState*)0); + QCOMPARE(s1->machine(), (QStateMachine*)0); + machine.addState(s1); + QCOMPARE(s1->machine(), static_cast(&machine)); + QCOMPARE(s1->parentState(), static_cast(&machine)); + QCOMPARE(root_d->childStates().size(), 1); + QCOMPARE(root_d->childStates().at(0), (QAbstractState*)s1); + + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::addState: state has already been added to this machine"); + machine.addState(s1); + + QState *s2 = new QState(); + QCOMPARE(s2->parentState(), (QState*)0); + machine.addState(s2); + QCOMPARE(s2->parentState(), static_cast(&machine)); + QCOMPARE(root_d->childStates().size(), 2); + QCOMPARE(root_d->childStates().at(0), (QAbstractState*)s1); + QCOMPARE(root_d->childStates().at(1), (QAbstractState*)s2); + + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::addState: state has already been added to this machine"); + machine.addState(s2); + + machine.removeState(s1); + QCOMPARE(s1->parentState(), (QState*)0); + QCOMPARE(root_d->childStates().size(), 1); + QCOMPARE(root_d->childStates().at(0), (QAbstractState*)s2); + + machine.removeState(s2); + QCOMPARE(s2->parentState(), (QState*)0); + QCOMPARE(root_d->childStates().size(), 0); + + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::removeState: cannot remove null state"); + machine.removeState(0); + + { + QStateMachine machine2; + { + QString warning; + warning.sprintf("QStateMachine::removeState: state %p's machine (%p) is different from this machine (%p)", + &machine2, (void*)0, &machine); + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); + machine.removeState(&machine2); + } + // ### check this behavior + machine.addState(&machine2); + QCOMPARE(machine2.parent(), (QObject*)&machine); + } + + delete s1; + delete s2; + // ### how to deal with this? + // machine.removeState(machine.errorState()); +#endif +} + +void tst_QStateMachine::stateEntryAndExit() +{ + // Two top-level states + { + QStateMachine machine; + + TestState *s1 = new TestState(&machine); + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: cannot add transition to null state"); + s1->addTransition((QAbstractState*)0); + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: cannot add null transition"); + s1->addTransition((QAbstractTransition*)0); + QTest::ignoreMessage(QtWarningMsg, "QState::removeTransition: cannot remove null transition"); + s1->removeTransition((QAbstractTransition*)0); + + TestState *s2 = new TestState(&machine); + QFinalState *s3 = new QFinalState(&machine); + + TestTransition *t = new TestTransition(s2); + QCOMPARE(t->machine(), (QStateMachine*)0); + QCOMPARE(t->sourceState(), (QState*)0); + QCOMPARE(t->targetState(), (QAbstractState*)s2); + QCOMPARE(t->targetStates().size(), 1); + QCOMPARE(t->targetStates().at(0), (QAbstractState*)s2); + t->setTargetState(0); + QCOMPARE(t->targetState(), (QAbstractState*)0); + QVERIFY(t->targetStates().isEmpty()); + t->setTargetState(s2); + QCOMPARE(t->targetState(), (QAbstractState*)s2); + QTest::ignoreMessage(QtWarningMsg, "QAbstractTransition::setTargetStates: target state(s) cannot be null"); + t->setTargetStates(QList() << 0); + QCOMPARE(t->targetState(), (QAbstractState*)s2); + t->setTargetStates(QList() << s2); + QCOMPARE(t->targetState(), (QAbstractState*)s2); + QCOMPARE(t->targetStates().size(), 1); + QCOMPARE(t->targetStates().at(0), (QAbstractState*)s2); + s1->addTransition(t); + QCOMPARE(t->sourceState(), (QState*)s1); + QCOMPARE(t->machine(), &machine); + + { + QAbstractTransition *trans = s2->addTransition(s3); + QVERIFY(trans != 0); + QCOMPARE(trans->sourceState(), (QState*)s2); + QCOMPARE(trans->targetState(), (QAbstractState*)s3); + { + QString warning; + warning.sprintf("QState::removeTransition: transition %p's source state (%p) is different from this state (%p)", trans, s2, s1); + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); + s1->removeTransition(trans); + } + s2->removeTransition(trans); + QCOMPARE(trans->sourceState(), (QState*)0); + QCOMPARE(trans->targetState(), (QAbstractState*)s3); + s2->addTransition(trans); + QCOMPARE(trans->sourceState(), (QState*)s2); + } + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s1); + QCOMPARE(machine.initialState(), (QAbstractState*)s1); + { + QString warning; + warning.sprintf("QState::setInitialState: state %p is not a child of this state (%p)", &machine, &machine); + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); + machine.setInitialState(&machine); + QCOMPARE(machine.initialState(), (QAbstractState*)s1); + } + QVERIFY(machine.configuration().isEmpty()); + globalTick = 0; + QVERIFY(!machine.isRunning()); + QSignalSpy s1EnteredSpy(s1, SIGNAL(entered())); + QSignalSpy s1ExitedSpy(s1, SIGNAL(exited())); + QSignalSpy tTriggeredSpy(t, SIGNAL(triggered())); + QSignalSpy s2EnteredSpy(s2, SIGNAL(entered())); + QSignalSpy s2ExitedSpy(s2, SIGNAL(exited())); + machine.start(); + + QTRY_COMPARE(startedSpy.count(), 1); + QTRY_COMPARE(finishedSpy.count(), 1); + QTRY_COMPARE(stoppedSpy.count(), 0); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(s3)); + + // s1 is entered + QCOMPARE(s1->events.count(), 2); + QCOMPARE(s1->events.at(0).first, 0); + QCOMPARE(s1->events.at(0).second, TestState::Entry); + // s1 is exited + QCOMPARE(s1->events.at(1).first, 1); + QCOMPARE(s1->events.at(1).second, TestState::Exit); + // t is triggered + QCOMPARE(t->triggers.count(), 1); + QCOMPARE(t->triggers.at(0), 2); + // s2 is entered + QCOMPARE(s2->events.count(), 2); + QCOMPARE(s2->events.at(0).first, 3); + QCOMPARE(s2->events.at(0).second, TestState::Entry); + // s2 is exited + QCOMPARE(s2->events.at(1).first, 4); + QCOMPARE(s2->events.at(1).second, TestState::Exit); + + QCOMPARE(s1EnteredSpy.count(), 1); + QCOMPARE(s1ExitedSpy.count(), 1); + QCOMPARE(tTriggeredSpy.count(), 1); + QCOMPARE(s2EnteredSpy.count(), 1); + QCOMPARE(s2ExitedSpy.count(), 1); + } + // Two top-level states, one has two child states + { + QStateMachine machine; + + TestState *s1 = new TestState(&machine); + TestState *s11 = new TestState(s1); + TestState *s12 = new TestState(s1); + TestState *s2 = new TestState(&machine); + QFinalState *s3 = new QFinalState(&machine); + s1->setInitialState(s11); + TestTransition *t1 = new TestTransition(s12); + s11->addTransition(t1); + TestTransition *t2 = new TestTransition(s2); + s12->addTransition(t2); + s2->addTransition(s3); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s1); + globalTick = 0; + machine.start(); + + QTRY_COMPARE(startedSpy.count(), 1); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(s3)); + + // s1 is entered + QCOMPARE(s1->events.count(), 2); + QCOMPARE(s1->events.at(0).first, 0); + QCOMPARE(s1->events.at(0).second, TestState::Entry); + // s11 is entered + QCOMPARE(s11->events.count(), 2); + QCOMPARE(s11->events.at(0).first, 1); + QCOMPARE(s11->events.at(0).second, TestState::Entry); + // s11 is exited + QCOMPARE(s11->events.at(1).first, 2); + QCOMPARE(s11->events.at(1).second, TestState::Exit); + // t1 is triggered + QCOMPARE(t1->triggers.count(), 1); + QCOMPARE(t1->triggers.at(0), 3); + // s12 is entered + QCOMPARE(s12->events.count(), 2); + QCOMPARE(s12->events.at(0).first, 4); + QCOMPARE(s12->events.at(0).second, TestState::Entry); + // s12 is exited + QCOMPARE(s12->events.at(1).first, 5); + QCOMPARE(s12->events.at(1).second, TestState::Exit); + // s1 is exited + QCOMPARE(s1->events.at(1).first, 6); + QCOMPARE(s1->events.at(1).second, TestState::Exit); + // t2 is triggered + QCOMPARE(t2->triggers.count(), 1); + QCOMPARE(t2->triggers.at(0), 7); + // s2 is entered + QCOMPARE(s2->events.count(), 2); + QCOMPARE(s2->events.at(0).first, 8); + QCOMPARE(s2->events.at(0).second, TestState::Entry); + // s2 is exited + QCOMPARE(s2->events.at(1).first, 9); + QCOMPARE(s2->events.at(1).second, TestState::Exit); + } +} + +void tst_QStateMachine::assignProperty() +{ + QStateMachine machine; + QState *s1 = new QState(&machine); + + QTest::ignoreMessage(QtWarningMsg, "QState::assignProperty: cannot assign property 'foo' of null object"); + s1->assignProperty(0, "foo", QVariant()); + + s1->assignProperty(s1, "objectName", "s1"); + QFinalState *s2 = new QFinalState(&machine); + s1->addTransition(s2); + machine.setInitialState(s1); + machine.start(); + QTRY_COMPARE(s1->objectName(), QString::fromLatin1("s1")); + + s1->assignProperty(s1, "objectName", "foo"); + machine.start(); + QTRY_COMPARE(s1->objectName(), QString::fromLatin1("foo")); + + s1->assignProperty(s1, "noSuchProperty", 123); + machine.start(); + QTRY_COMPARE(s1->dynamicPropertyNames().size(), 1); + QCOMPARE(s1->dynamicPropertyNames().at(0), QByteArray("noSuchProperty")); + QCOMPARE(s1->objectName(), QString::fromLatin1("foo")); + + { + QSignalSpy propertiesAssignedSpy(s1, SIGNAL(propertiesAssigned())); + machine.start(); + QTRY_COMPARE(propertiesAssignedSpy.count(), 1); + } + + // nested states + { + QState *s11 = new QState(s1); + QString str = QString::fromLatin1("set by nested state"); + s11->assignProperty(s11, "objectName", str); + s1->setInitialState(s11); + machine.start(); + QTRY_COMPARE(s11->objectName(), str); + } +} + +void tst_QStateMachine::assignPropertyWithAnimation() +{ + // Single animation + { + QStateMachine machine; + QVERIFY(machine.isAnimated()); + machine.setAnimated(false); + QVERIFY(!machine.isAnimated()); + machine.setAnimated(true); + QVERIFY(machine.isAnimated()); + QObject obj; + obj.setProperty("foo", 321); + obj.setProperty("bar", 654); + QState *s1 = new QState(&machine); + s1->assignProperty(&obj, "foo", 123); + QState *s2 = new QState(&machine); + s2->assignProperty(&obj, "foo", 456); + s2->assignProperty(&obj, "bar", 789); + QAbstractTransition *trans = s1->addTransition(s2); + QVERIFY(trans->animations().isEmpty()); + QTest::ignoreMessage(QtWarningMsg, "QAbstractTransition::addAnimation: cannot add null animation"); + trans->addAnimation(0); + QPropertyAnimation anim(&obj, "foo"); + anim.setDuration(250); + trans->addAnimation(&anim); + QCOMPARE(trans->animations().size(), 1); + QCOMPARE(trans->animations().at(0), (QAbstractAnimation*)&anim); + QCOMPARE(anim.parent(), (QObject*)0); + QTest::ignoreMessage(QtWarningMsg, "QAbstractTransition::removeAnimation: cannot remove null animation"); + trans->removeAnimation(0); + trans->removeAnimation(&anim); + QVERIFY(trans->animations().isEmpty()); + trans->addAnimation(&anim); + QCOMPARE(trans->animations().size(), 1); + QCOMPARE(trans->animations().at(0), (QAbstractAnimation*)&anim); + QFinalState *s3 = new QFinalState(&machine); + s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3); + + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(obj.property("foo").toInt(), 456); + QCOMPARE(obj.property("bar").toInt(), 789); + } + // Two animations + { + QStateMachine machine; + QObject obj; + obj.setProperty("foo", 321); + obj.setProperty("bar", 654); + QState *s1 = new QState(&machine); + s1->assignProperty(&obj, "foo", 123); + QState *s2 = new QState(&machine); + s2->assignProperty(&obj, "foo", 456); + s2->assignProperty(&obj, "bar", 789); + QAbstractTransition *trans = s1->addTransition(s2); + QPropertyAnimation anim(&obj, "foo"); + anim.setDuration(150); + trans->addAnimation(&anim); + QPropertyAnimation anim2(&obj, "bar"); + anim2.setDuration(150); + trans->addAnimation(&anim2); + QFinalState *s3 = new QFinalState(&machine); + s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3); + + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(obj.property("foo").toInt(), 456); + QCOMPARE(obj.property("bar").toInt(), 789); + } + // Animation group + { + QStateMachine machine; + QObject obj; + obj.setProperty("foo", 321); + obj.setProperty("bar", 654); + QState *s1 = new QState(&machine); + s1->assignProperty(&obj, "foo", 123); + s1->assignProperty(&obj, "bar", 321); + QState *s2 = new QState(&machine); + s2->assignProperty(&obj, "foo", 456); + s2->assignProperty(&obj, "bar", 654); + s2->assignProperty(&obj, "baz", 789); + QAbstractTransition *trans = s1->addTransition(s2); + QSequentialAnimationGroup group; + group.addAnimation(new QPropertyAnimation(&obj, "foo")); + group.addAnimation(new QPropertyAnimation(&obj, "bar")); + trans->addAnimation(&group); + QFinalState *s3 = new QFinalState(&machine); + s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3); + + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(obj.property("foo").toInt(), 456); + QCOMPARE(obj.property("bar").toInt(), 654); + QCOMPARE(obj.property("baz").toInt(), 789); + } + // Nested states + { + QStateMachine machine; + QObject obj; + obj.setProperty("foo", 321); + obj.setProperty("bar", 654); + QState *s1 = new QState(&machine); + QCOMPARE(s1->childMode(), QState::ExclusiveStates); + s1->setChildMode(QState::ParallelStates); + QCOMPARE(s1->childMode(), QState::ParallelStates); + s1->setChildMode(QState::ExclusiveStates); + QCOMPARE(s1->childMode(), QState::ExclusiveStates); + QCOMPARE(s1->initialState(), (QAbstractState*)0); + s1->setObjectName("s1"); + s1->assignProperty(&obj, "foo", 123); + s1->assignProperty(&obj, "bar", 456); + QState *s2 = new QState(&machine); + s2->setObjectName("s2"); + s2->assignProperty(&obj, "foo", 321); + QState *s21 = new QState(s2); + s21->setObjectName("s21"); + s21->assignProperty(&obj, "bar", 654); + QState *s22 = new QState(s2); + s22->setObjectName("s22"); + s22->assignProperty(&obj, "bar", 789); + s2->setInitialState(s21); + QCOMPARE(s2->initialState(), (QAbstractState*)s21); + + QAbstractTransition *trans = s1->addTransition(s2); + QPropertyAnimation anim(&obj, "foo"); + anim.setDuration(500); + trans->addAnimation(&anim); + QPropertyAnimation anim2(&obj, "bar"); + anim2.setDuration(250); + trans->addAnimation(&anim2); + + s21->addTransition(s21, SIGNAL(propertiesAssigned()), s22); + + QFinalState *s3 = new QFinalState(&machine); + s22->addTransition(s2, SIGNAL(propertiesAssigned()), s3); + + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(obj.property("foo").toInt(), 321); + QCOMPARE(obj.property("bar").toInt(), 789); + } + // Aborted animation + { + QStateMachine machine; + SignalEmitter emitter; + QObject obj; + obj.setProperty("foo", 321); + obj.setProperty("bar", 654); + QState *group = new QState(&machine); + QState *s1 = new QState(group); + group->setInitialState(s1); + s1->assignProperty(&obj, "foo", 123); + QState *s2 = new QState(group); + s2->assignProperty(&obj, "foo", 456); + s2->assignProperty(&obj, "bar", 789); + QAbstractTransition *trans = s1->addTransition(&emitter, SIGNAL(signalWithNoArg()), s2); + QPropertyAnimation anim(&obj, "foo"); + anim.setDuration(8000); + trans->addAnimation(&anim); + QPropertyAnimation anim2(&obj, "bar"); + anim2.setDuration(8000); + trans->addAnimation(&anim2); + QState *s3 = new QState(group); + s3->assignProperty(&obj, "foo", 911); + s2->addTransition(&emitter, SIGNAL(signalWithNoArg()), s3); + + machine.setInitialState(group); + machine.start(); + QTRY_COMPARE(machine.configuration().contains(s1), true); + QSignalSpy propertiesAssignedSpy(s2, SIGNAL(propertiesAssigned())); + emitter.emitSignalWithNoArg(); + QTRY_COMPARE(machine.configuration().contains(s2), true); + QVERIFY(propertiesAssignedSpy.isEmpty()); + emitter.emitSignalWithNoArg(); // will cause animations from s1-->s2 to abort + QTRY_COMPARE(machine.configuration().contains(s3), true); + QVERIFY(propertiesAssignedSpy.isEmpty()); + QCOMPARE(obj.property("foo").toInt(), 911); + QCOMPARE(obj.property("bar").toInt(), 789); + } +} + +struct StringEvent : public QEvent +{ +public: + StringEvent(const QString &val) + : QEvent(QEvent::Type(QEvent::User+2)), + value(val) {} + + QString value; +}; + +class StringTransition : public QAbstractTransition +{ +public: + StringTransition(const QString &value, QAbstractState *target) + : QAbstractTransition(), m_value(value) + { setTargetState(target); } + +protected: + virtual bool eventTest(QEvent *e) + { + if (e->type() != QEvent::Type(QEvent::User+2)) + return false; + StringEvent *se = static_cast(e); + return (m_value == se->value) && (!m_cond.isValid() || (m_cond.indexIn(m_value) != -1)); + } + virtual void onTransition(QEvent *) {} + +private: + QString m_value; + QRegExp m_cond; +}; + +class StringEventPoster : public QState +{ +public: + StringEventPoster(const QString &value, QState *parent = 0) + : QState(parent), m_value(value), m_delay(-1) {} + + void setString(const QString &value) + { m_value = value; } + void setDelay(int delay) + { m_delay = delay; } + +protected: + virtual void onEntry(QEvent *) + { + if (m_delay == -1) + machine()->postEvent(new StringEvent(m_value)); + else + machine()->postDelayedEvent(new StringEvent(m_value), m_delay); + } + virtual void onExit(QEvent *) {} + +private: + QString m_value; + int m_delay; +}; + +void tst_QStateMachine::postEvent() +{ + for (int x = 0; x < 2; ++x) { + QStateMachine machine; + { + QEvent e(QEvent::None); + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::postEvent: cannot post event when the state machine is not running"); + machine.postEvent(&e); + } + StringEventPoster *s1 = new StringEventPoster("a"); + if (x == 1) + s1->setDelay(100); + QFinalState *s2 = new QFinalState; + s1->addTransition(new StringTransition("a", s2)); + machine.addState(s1); + machine.addState(s2); + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); + + s1->setString("b"); + QFinalState *s3 = new QFinalState(); + machine.addState(s3); + s1->addTransition(new StringTransition("b", s3)); + finishedSpy.clear(); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s3)); + } +} + +void tst_QStateMachine::cancelDelayedEvent() +{ + QStateMachine machine; + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::cancelDelayedEvent: the machine is not running"); + QVERIFY(!machine.cancelDelayedEvent(-1)); + + QState *s1 = new QState(&machine); + QFinalState *s2 = new QFinalState(&machine); + s1->addTransition(new StringTransition("a", s2)); + machine.setInitialState(s1); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + int id1 = machine.postDelayedEvent(new StringEvent("c"), 50000); + QVERIFY(id1 != -1); + int id2 = machine.postDelayedEvent(new StringEvent("b"), 25000); + QVERIFY(id2 != -1); + QVERIFY(id2 != id1); + int id3 = machine.postDelayedEvent(new StringEvent("a"), 100); + QVERIFY(id3 != -1); + QVERIFY(id3 != id2); + QVERIFY(machine.cancelDelayedEvent(id1)); + QVERIFY(!machine.cancelDelayedEvent(id1)); + QVERIFY(machine.cancelDelayedEvent(id2)); + QVERIFY(!machine.cancelDelayedEvent(id2)); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); +} + +void tst_QStateMachine::postDelayedEventAndStop() +{ + QStateMachine machine; + QState *s1 = new QState(&machine); + QFinalState *s2 = new QFinalState(&machine); + s1->addTransition(new StringTransition("a", s2)); + machine.setInitialState(s1); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + int id1 = machine.postDelayedEvent(new StringEvent("a"), 0); + QVERIFY(id1 != -1); + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + machine.stop(); + QTRY_COMPARE(stoppedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.start(); + QTRY_COMPARE(startedSpy.count(), 2); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + int id2 = machine.postDelayedEvent(new StringEvent("a"), 1000); + QVERIFY(id2 != -1); + machine.stop(); + QTRY_COMPARE(stoppedSpy.count(), 2); + machine.start(); + QTRY_COMPARE(startedSpy.count(), 3); + QTestEventLoop::instance().enterLoop(2); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); +} + +void tst_QStateMachine::stopAndPostEvent() +{ + QStateMachine machine; + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + QSignalSpy startedSpy(&machine, SIGNAL(started())); + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + machine.stop(); + QCOMPARE(stoppedSpy.count(), 0); + machine.postEvent(new QEvent(QEvent::User)); + QTRY_COMPARE(stoppedSpy.count(), 1); + QCoreApplication::processEvents(); +} + +void tst_QStateMachine::stateFinished() +{ + QStateMachine machine; + QState *s1 = new QState(&machine); + QState *s1_1 = new QState(s1); + QFinalState *s1_2 = new QFinalState(s1); + s1_1->addTransition(s1_2); + s1->setInitialState(s1_1); + QFinalState *s2 = new QFinalState(&machine); + s1->addTransition(s1, SIGNAL(finished()), s2); + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); +} + +void tst_QStateMachine::parallelStates() +{ + QStateMachine machine; + + QState *s1 = new QState(QState::ParallelStates); + QCOMPARE(s1->childMode(), QState::ParallelStates); + QState *s1_1 = new QState(s1); + QState *s1_1_1 = new QState(s1_1); + QFinalState *s1_1_f = new QFinalState(s1_1); + s1_1_1->addTransition(s1_1_f); + s1_1->setInitialState(s1_1_1); + QState *s1_2 = new QState(s1); + QState *s1_2_1 = new QState(s1_2); + QFinalState *s1_2_f = new QFinalState(s1_2); + s1_2_1->addTransition(s1_2_f); + s1_2->setInitialState(s1_2_1); + { + QString warning; + warning.sprintf("QState::setInitialState: ignoring attempt to set initial state of parallel state group %p", s1); + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); + s1->setInitialState(0); + } + machine.addState(s1); + + QFinalState *s2 = new QFinalState(); + machine.addState(s2); + + s1->addTransition(s1, SIGNAL(finished()), s2); + + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); +} + +void tst_QStateMachine::parallelRootState() +{ + QStateMachine machine; + QState *root = &machine; + QCOMPARE(root->childMode(), QState::ExclusiveStates); + root->setChildMode(QState::ParallelStates); + QCOMPARE(root->childMode(), QState::ParallelStates); + + QState *s1 = new QState(root); + QFinalState *s1_f = new QFinalState(s1); + s1->setInitialState(s1_f); + QState *s2 = new QState(root); + QFinalState *s2_f = new QFinalState(s2); + s2->setInitialState(s2_f); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start: No initial state set for machine. Refusing to start."); + machine.start(); + QCoreApplication::processEvents(); + QEXPECT_FAIL("", "parallel root state is not supported (task 256587)", Continue); + QCOMPARE(startedSpy.count(), 1); +} + +void tst_QStateMachine::allSourceToTargetConfigurations() +{ + QStateMachine machine; + QState *s0 = new QState(&machine); + s0->setObjectName("s0"); + QState *s1 = new QState(s0); + s1->setObjectName("s1"); + QState *s11 = new QState(s1); + s11->setObjectName("s11"); + QState *s2 = new QState(s0); + s2->setObjectName("s2"); + QState *s21 = new QState(s2); + s21->setObjectName("s21"); + QState *s211 = new QState(s21); + s211->setObjectName("s211"); + QFinalState *f = new QFinalState(&machine); + f->setObjectName("f"); + + s0->setInitialState(s1); + s1->setInitialState(s11); + s2->setInitialState(s21); + s21->setInitialState(s211); + + s11->addTransition(new StringTransition("g", s211)); + s1->addTransition(new StringTransition("a", s1)); + s1->addTransition(new StringTransition("b", s11)); + s1->addTransition(new StringTransition("c", s2)); + s1->addTransition(new StringTransition("d", s0)); + s1->addTransition(new StringTransition("f", s211)); + s211->addTransition(new StringTransition("d", s21)); + s211->addTransition(new StringTransition("g", s0)); + s211->addTransition(new StringTransition("h", f)); + s21->addTransition(new StringTransition("b", s211)); + s2->addTransition(new StringTransition("c", s1)); + s2->addTransition(new StringTransition("f", s11)); + s0->addTransition(new StringTransition("e", s211)); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new StringEvent("a")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("b")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("c")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("d")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("e")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("f")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("g")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("h")); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); +} + +class TestSignalTransition : public QSignalTransition +{ +public: + TestSignalTransition(QState *sourceState = 0) + : QSignalTransition(sourceState), m_sender(0) + {} + TestSignalTransition(QObject *sender, const char *signal, + QAbstractState *target) + : QSignalTransition(sender, signal), m_sender(0) + { setTargetState(target); } + QObject *senderReceived() const { + return m_sender; + } + int signalIndexReceived() const { + return m_signalIndex; + } + QVariantList argumentsReceived() const { + return m_args; + } +protected: + bool eventTest(QEvent *e) { + if (!QSignalTransition::eventTest(e)) + return false; + QStateMachine::SignalEvent *se = static_cast(e); + m_sender = se->sender(); + m_signalIndex = se->signalIndex(); + m_args = se->arguments(); + return true; + } +private: + QObject *m_sender; + int m_signalIndex; + QVariantList m_args; +}; + +void tst_QStateMachine::signalTransitions() +{ + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: sender cannot be null"); + QCOMPARE(s0->addTransition(0, SIGNAL(noSuchSignal()), 0), (QSignalTransition*)0); + + SignalEmitter emitter; + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: signal cannot be null"); + QCOMPARE(s0->addTransition(&emitter, 0, 0), (QSignalTransition*)0); + + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: cannot add transition to null state"); + QCOMPARE(s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), 0), (QSignalTransition*)0); + + QFinalState *s1 = new QFinalState(&machine); + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: no such signal SignalEmitter::noSuchSignal()"); + QCOMPARE(s0->addTransition(&emitter, SIGNAL(noSuchSignal()), s1), (QSignalTransition*)0); + + QSignalTransition *trans = s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), s1); + QVERIFY(trans != 0); + QCOMPARE(trans->sourceState(), s0); + QCOMPARE(trans->targetState(), (QAbstractState*)s1); + QCOMPARE(trans->senderObject(), (QObject*)&emitter); + QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithNoArg()))); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + emitter.emitSignalWithNoArg(); + + QTRY_COMPARE(finishedSpy.count(), 1); + + emitter.emitSignalWithNoArg(); + + trans->setSignal(SIGNAL(signalWithIntArg(int))); + QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithIntArg(int)))); + machine.start(); + QCoreApplication::processEvents(); + emitter.emitSignalWithIntArg(123); + QTRY_COMPARE(finishedSpy.count(), 2); + + machine.start(); + QCoreApplication::processEvents(); + trans->setSignal(SIGNAL(signalWithNoArg())); + QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithNoArg()))); + emitter.emitSignalWithNoArg(); + QTRY_COMPARE(finishedSpy.count(), 3); + + SignalEmitter emitter2; + machine.start(); + QCoreApplication::processEvents(); + trans->setSenderObject(&emitter2); + emitter2.emitSignalWithNoArg(); + QTRY_COMPARE(finishedSpy.count(), 4); + + machine.start(); + QCoreApplication::processEvents(); + QTest::ignoreMessage(QtWarningMsg, "QSignalTransition: no such signal: SignalEmitter::noSuchSignal()"); + trans->setSignal(SIGNAL(noSuchSignal())); + QCOMPARE(trans->signal(), QByteArray(SIGNAL(noSuchSignal()))); + } + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + SignalEmitter emitter; + QSignalTransition *trans = s0->addTransition(&emitter, "signalWithNoArg()", s1); + QVERIFY(trans != 0); + QCOMPARE(trans->sourceState(), s0); + QCOMPARE(trans->targetState(), (QAbstractState*)s1); + QCOMPARE(trans->senderObject(), (QObject*)&emitter); + QCOMPARE(trans->signal(), QByteArray("signalWithNoArg()")); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + emitter.emitSignalWithNoArg(); + + QTRY_COMPARE(finishedSpy.count(), 1); + + trans->setSignal("signalWithIntArg(int)"); + QCOMPARE(trans->signal(), QByteArray("signalWithIntArg(int)")); + machine.start(); + QCoreApplication::processEvents(); + emitter.emitSignalWithIntArg(123); + QTRY_COMPARE(finishedSpy.count(), 2); + } + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + SignalEmitter emitter; + TestSignalTransition *trans = new TestSignalTransition(&emitter, SIGNAL(signalWithIntArg(int)), s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + emitter.emitSignalWithIntArg(123); + + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(trans->senderReceived(), (QObject*)&emitter); + QCOMPARE(trans->signalIndexReceived(), emitter.metaObject()->indexOfSignal("signalWithIntArg(int)")); + QCOMPARE(trans->argumentsReceived().size(), 1); + QCOMPARE(trans->argumentsReceived().at(0).toInt(), 123); + } + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + SignalEmitter emitter; + TestSignalTransition *trans = new TestSignalTransition(&emitter, SIGNAL(signalWithStringArg(QString)), s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QString testString = QString::fromLatin1("hello"); + emitter.emitSignalWithStringArg(testString); + + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(trans->senderReceived(), (QObject*)&emitter); + QCOMPARE(trans->signalIndexReceived(), emitter.metaObject()->indexOfSignal("signalWithStringArg(QString)")); + QCOMPARE(trans->argumentsReceived().size(), 1); + QCOMPARE(trans->argumentsReceived().at(0).toString(), testString); + } + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + + TestSignalTransition *trans = new TestSignalTransition(); + QCOMPARE(trans->senderObject(), (QObject*)0); + QCOMPARE(trans->signal(), QByteArray()); + + SignalEmitter emitter; + trans->setSenderObject(&emitter); + QCOMPARE(trans->senderObject(), (QObject*)&emitter); + trans->setSignal(SIGNAL(signalWithNoArg())); + QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithNoArg()))); + trans->setTargetState(s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + emitter.emitSignalWithNoArg(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } + // Multiple transitions for same (object,signal) + { + QStateMachine machine; + SignalEmitter emitter; + QState *s0 = new QState(&machine); + QState *s1 = new QState(&machine); + QSignalTransition *t0 = s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), s1); + QSignalTransition *t1 = s1->addTransition(&emitter, SIGNAL(signalWithNoArg()), s0); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + emitter.emitSignalWithNoArg(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + s0->removeTransition(t0); + emitter.emitSignalWithNoArg(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + emitter.emitSignalWithNoArg(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + s1->removeTransition(t1); + emitter.emitSignalWithNoArg(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + s0->addTransition(t0); + s1->addTransition(t1); + emitter.emitSignalWithNoArg(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + } + // multiple signal transitions from same source + { + QStateMachine machine; + SignalEmitter emitter; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), s1); + QFinalState *s2 = new QFinalState(&machine); + s0->addTransition(&emitter, SIGNAL(signalWithIntArg(int)), s2); + QFinalState *s3 = new QFinalState(&machine); + s0->addTransition(&emitter, SIGNAL(signalWithStringArg(QString)), s3); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + emitter.emitSignalWithNoArg(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.start(); + QTRY_COMPARE(startedSpy.count(), 2); + emitter.emitSignalWithIntArg(123); + QTRY_COMPARE(finishedSpy.count(), 2); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); + + machine.start(); + QTRY_COMPARE(startedSpy.count(), 3); + emitter.emitSignalWithStringArg("hello"); + QTRY_COMPARE(finishedSpy.count(), 3); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s3)); + } + // signature normalization + { + QStateMachine machine; + SignalEmitter emitter; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + QSignalTransition *t0 = s0->addTransition(&emitter, SIGNAL( signalWithNoArg( ) ), s1); + QVERIFY(t0 != 0); + QCOMPARE(t0->signal(), QByteArray(SIGNAL( signalWithNoArg( ) ))); + + QSignalTransition *t1 = s0->addTransition(&emitter, SIGNAL( signalWithStringArg( const QString & ) ), s1); + QVERIFY(t1 != 0); + QCOMPARE(t1->signal(), QByteArray(SIGNAL( signalWithStringArg( const QString & ) ))); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + QCOMPARE(finishedSpy.count(), 0); + + emitter.emitSignalWithNoArg(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } +} + +class TestEventTransition : public QEventTransition +{ +public: + TestEventTransition(QState *sourceState = 0) + : QEventTransition(sourceState), + m_eventSource(0), m_eventType(QEvent::None) + {} + TestEventTransition(QObject *object, QEvent::Type type, + QAbstractState *target) + : QEventTransition(object, type), + m_eventSource(0), m_eventType(QEvent::None) + { setTargetState(target); } + QObject *eventSourceReceived() const { + return m_eventSource; + } + QEvent::Type eventTypeReceived() const { + return m_eventType; + } +protected: + bool eventTest(QEvent *e) { + if (!QEventTransition::eventTest(e)) + return false; + QStateMachine::WrappedEvent *we = static_cast(e); + m_eventSource = we->object(); + m_eventType = we->event()->type(); + return true; + } +private: + QObject *m_eventSource; + QEvent::Type m_eventType; +}; + +void tst_QStateMachine::eventTransitions() +{ + QPushButton button; + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + + QMouseEventTransition *trans; + trans = new QMouseEventTransition(&button, QEvent::MouseButtonPress, Qt::LeftButton); + QCOMPARE(trans->targetState(), (QAbstractState*)0); + trans->setTargetState(s1); + QCOMPARE(trans->eventType(), QEvent::MouseButtonPress); + QCOMPARE(trans->button(), Qt::LeftButton); + QCOMPARE(trans->targetState(), (QAbstractState*)s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::mousePress(&button, Qt::LeftButton); + QTRY_COMPARE(finishedSpy.count(), 1); + + QTest::mousePress(&button, Qt::LeftButton); + + trans->setEventType(QEvent::MouseButtonRelease); + QCOMPARE(trans->eventType(), QEvent::MouseButtonRelease); + machine.start(); + QCoreApplication::processEvents(); + QTest::mouseRelease(&button, Qt::LeftButton); + QTRY_COMPARE(finishedSpy.count(), 2); + + machine.start(); + QCoreApplication::processEvents(); + trans->setEventType(QEvent::MouseButtonPress); + QTest::mousePress(&button, Qt::LeftButton); + QTRY_COMPARE(finishedSpy.count(), 3); + + QPushButton button2; + machine.start(); + QCoreApplication::processEvents(); + trans->setEventSource(&button2); + QTest::mousePress(&button2, Qt::LeftButton); + QTRY_COMPARE(finishedSpy.count(), 4); + } + for (int x = 0; x < 2; ++x) { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + + QEventTransition *trans; + if (x == 0) { + trans = new QEventTransition(); + QCOMPARE(trans->eventSource(), (QObject*)0); + QCOMPARE(trans->eventType(), QEvent::None); + trans->setEventSource(&button); + trans->setEventType(QEvent::MouseButtonPress); + trans->setTargetState(s1); + } else if (x == 1) { + trans = new QEventTransition(&button, QEvent::MouseButtonPress); + trans->setTargetState(s1); + } + QCOMPARE(trans->eventSource(), (QObject*)&button); + QCOMPARE(trans->eventType(), QEvent::MouseButtonPress); + QCOMPARE(trans->targetState(), (QAbstractState*)s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + + QMouseEventTransition *trans = new QMouseEventTransition(); + QCOMPARE(trans->eventSource(), (QObject*)0); + QCOMPARE(trans->eventType(), QEvent::None); + QCOMPARE(trans->button(), Qt::NoButton); + trans->setEventSource(&button); + trans->setEventType(QEvent::MouseButtonPress); + trans->setButton(Qt::LeftButton); + trans->setTargetState(s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } + + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + + QKeyEventTransition *trans = new QKeyEventTransition(&button, QEvent::KeyPress, Qt::Key_A); + QCOMPARE(trans->eventType(), QEvent::KeyPress); + QCOMPARE(trans->key(), (int)Qt::Key_A); + trans->setTargetState(s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::keyPress(&button, Qt::Key_A); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + + QKeyEventTransition *trans = new QKeyEventTransition(); + QCOMPARE(trans->eventSource(), (QObject*)0); + QCOMPARE(trans->eventType(), QEvent::None); + QCOMPARE(trans->key(), 0); + trans->setEventSource(&button); + trans->setEventType(QEvent::KeyPress); + trans->setKey(Qt::Key_A); + trans->setTargetState(s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::keyPress(&button, Qt::Key_A); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } + // Multiple transitions for same (object,event) + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QState *s1 = new QState(&machine); + QEventTransition *t0 = new QEventTransition(&button, QEvent::MouseButtonPress); + t0->setTargetState(s1); + s0->addTransition(t0); + QEventTransition *t1 = new QEventTransition(&button, QEvent::MouseButtonPress); + t1->setTargetState(s0); + s1->addTransition(t1); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + s0->removeTransition(t0); + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + s1->removeTransition(t1); + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + s0->addTransition(t0); + s1->addTransition(t1); + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + } + // multiple event transitions from same source + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + QFinalState *s2 = new QFinalState(&machine); + QEventTransition *t0 = new QEventTransition(&button, QEvent::MouseButtonPress); + t0->setTargetState(s1); + s0->addTransition(t0); + QEventTransition *t1 = new QEventTransition(&button, QEvent::MouseButtonRelease); + t1->setTargetState(s2); + s0->addTransition(t1); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + QTest::mousePress(&button, Qt::LeftButton); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.start(); + QTRY_COMPARE(startedSpy.count(), 2); + QTest::mouseRelease(&button, Qt::LeftButton); + QTRY_COMPARE(finishedSpy.count(), 2); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); + } + // custom event + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + + QEventTransition *trans = new QEventTransition(&button, QEvent::Type(QEvent::User+1)); + trans->setTargetState(s1); + s0->addTransition(trans); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + machine.setInitialState(s0); + machine.start(); + QTest::ignoreMessage(QtWarningMsg, "QObject event transitions are not supported for custom types"); + QTRY_COMPARE(startedSpy.count(), 1); + } + // custom transition + { + QStateMachine machine; + QState *s0 = new QState(&machine); + QFinalState *s1 = new QFinalState(&machine); + + TestEventTransition *trans = new TestEventTransition(&button, QEvent::MouseButtonPress, s1); + s0->addTransition(trans); + QCOMPARE(trans->eventSourceReceived(), (QObject*)0); + QCOMPARE(trans->eventTypeReceived(), QEvent::None); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); + + QCOMPARE(trans->eventSourceReceived(), (QObject*)&button); + QCOMPARE(trans->eventTypeReceived(), QEvent::MouseButtonPress); + } +} + +void tst_QStateMachine::graphicsSceneEventTransitions() +{ + QGraphicsScene scene; + QGraphicsTextItem *textItem = scene.addText("foo"); + + QStateMachine machine; + QState *s1 = new QState(&machine); + QFinalState *s2 = new QFinalState(&machine); + QEventTransition *t = new QEventTransition(textItem, QEvent::GraphicsSceneMouseMove); + t->setTargetState(s2); + s1->addTransition(t); + machine.setInitialState(s1); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + QVERIFY(finishedSpy.count() == 0); + QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseMove); + scene.sendEvent(textItem, &mouseEvent); + QTRY_COMPARE(finishedSpy.count(), 1); +} + +void tst_QStateMachine::historyStates() +{ + for (int x = 0; x < 2; ++x) { + QStateMachine machine; + QState *root = &machine; + QState *s0 = new QState(root); + QState *s00 = new QState(s0); + QState *s01 = new QState(s0); + QHistoryState *s0h; + if (x == 0) { + s0h = new QHistoryState(s0); + QCOMPARE(s0h->historyType(), QHistoryState::ShallowHistory); + s0h->setHistoryType(QHistoryState::DeepHistory); + } else { + s0h = new QHistoryState(QHistoryState::DeepHistory, s0); + } + QCOMPARE(s0h->historyType(), QHistoryState::DeepHistory); + s0h->setHistoryType(QHistoryState::ShallowHistory); + QCOMPARE(s0h->historyType(), QHistoryState::ShallowHistory); + QCOMPARE(s0h->defaultState(), (QAbstractState*)0); + s0h->setDefaultState(s00); + QCOMPARE(s0h->defaultState(), (QAbstractState*)s00); + QString warning; + warning.sprintf("QHistoryState::setDefaultState: state %p does not belong to this history state's group (%p)", s0, s0); + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); + s0h->setDefaultState(s0); + QState *s1 = new QState(root); + QFinalState *s2 = new QFinalState(root); + + s00->addTransition(new StringTransition("a", s01)); + s0->addTransition(new StringTransition("b", s1)); + s1->addTransition(new StringTransition("c", s0h)); + s0->addTransition(new StringTransition("d", s2)); + + root->setInitialState(s0); + s0->setInitialState(s00); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s0)); + QVERIFY(machine.configuration().contains(s00)); + + machine.postEvent(new StringEvent("a")); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s0)); + QVERIFY(machine.configuration().contains(s01)); + + machine.postEvent(new StringEvent("b")); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.postEvent(new StringEvent("c")); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s0)); + QVERIFY(machine.configuration().contains(s01)); + + machine.postEvent(new StringEvent("d")); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); + + QTRY_COMPARE(finishedSpy.count(), 1); + } +} + +void tst_QStateMachine::startAndStop() +{ + QStateMachine machine; + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + QVERIFY(!machine.isRunning()); + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start: No initial state set for machine. Refusing to start."); + machine.start(); + QCOMPARE(startedSpy.count(), 0); + QCOMPARE(stoppedSpy.count(), 0); + QCOMPARE(finishedSpy.count(), 0); + QVERIFY(!machine.isRunning()); + machine.stop(); + QCOMPARE(startedSpy.count(), 0); + QCOMPARE(stoppedSpy.count(), 0); + QCOMPARE(finishedSpy.count(), 0); + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + machine.start(); + QTRY_COMPARE(machine.isRunning(), true); + QTRY_COMPARE(startedSpy.count(), 1); + QCOMPARE(stoppedSpy.count(), 0); + QCOMPARE(finishedSpy.count(), 0); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(s1)); + + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start(): already running"); + machine.start(); + + machine.stop(); + QTRY_COMPARE(machine.isRunning(), false); + QTRY_COMPARE(stoppedSpy.count(), 1); + QCOMPARE(startedSpy.count(), 1); + QCOMPARE(finishedSpy.count(), 0); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.start(); + machine.stop(); + QTRY_COMPARE(startedSpy.count(), 2); + QCOMPARE(stoppedSpy.count(), 2); +} + +void tst_QStateMachine::targetStateWithNoParent() +{ + QStateMachine machine; + QState *s1 = new QState(&machine); + s1->setObjectName("s1"); + QState s2; + s1->addTransition(&s2); + machine.setInitialState(s1); + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: No common ancestor for targets and source of transition from state 's1'"); + QTRY_COMPARE(startedSpy.count(), 1); + QCOMPARE(machine.isRunning(), false); + QCOMPARE(stoppedSpy.count(), 1); + QCOMPARE(finishedSpy.count(), 0); + QCOMPARE(machine.error(), QStateMachine::NoCommonAncestorForTransitionError); +} + +void tst_QStateMachine::targetStateDeleted() +{ + QStateMachine machine; + QState *s1 = new QState(&machine); + s1->setObjectName("s1"); + QState *s2 = new QState(&machine); + QAbstractTransition *trans = s1->addTransition(s2); + delete s2; + QCOMPARE(trans->targetState(), (QAbstractState*)0); + QVERIFY(trans->targetStates().isEmpty()); +} + +void tst_QStateMachine::defaultGlobalRestorePolicy() +{ + QStateMachine machine; + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("a", 1); + propertyHolder->setProperty("b", 2); + + QState *s1 = new QState(&machine); + s1->assignProperty(propertyHolder, "a", 3); + + QState *s2 = new QState(&machine); + s2->assignProperty(propertyHolder, "b", 4); + + QState *s3 = new QState(&machine); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); +} + +void tst_QStateMachine::noInitialStateForInitialState() +{ + QStateMachine machine; + + QState *initialState = new QState(&machine); + initialState->setObjectName("initialState"); + machine.setInitialState(initialState); + + QState *childState = new QState(initialState); + (void)childState; + + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: " + "Missing initial state in compound state 'initialState'"); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.isRunning(), false); + QCOMPARE(int(machine.error()), int(QStateMachine::NoInitialStateError)); +} + +/* +void tst_QStateMachine::restorePolicyNotInherited() +{ + QStateMachine machine; + + QObject *propertyHolder = new QObject(); + propertyHolder->setProperty("a", 1); + propertyHolder->setProperty("b", 2); + + QState *parentState = new QState(&machine); + parentState->setObjectName("parentState"); + parentState->setRestorePolicy(QState::RestoreProperties); + + QState *s1 = new QState(parentState); + s1->setObjectName("s1"); + s1->assignProperty(propertyHolder, "a", 3); + parentState->setInitialState(s1); + + QState *s2 = new QState(parentState); + s2->setObjectName("s2"); + s2->assignProperty(propertyHolder, "b", 4); + + QState *s3 = new QState(parentState); + s3->setObjectName("s3"); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(parentState); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + +}*/ + +void tst_QStateMachine::globalRestorePolicySetToDontRestore() +{ + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::DontRestoreProperties); + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("a", 1); + propertyHolder->setProperty("b", 2); + + QState *s1 = new QState(&machine); + s1->assignProperty(propertyHolder, "a", 3); + + QState *s2 = new QState(&machine); + s2->assignProperty(propertyHolder, "b", 4); + + QState *s3 = new QState(&machine); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); +} + +/* +void tst_QStateMachine::setRestorePolicyToDoNotRestore() +{ + QObject *object = new QObject(); + object->setProperty("a", 1); + object->setProperty("b", 2); + + QStateMachine machine; + + QState *S1 = new QState(); + S1->setObjectName("S1"); + S1->assignProperty(object, "a", 3); + S1->setRestorePolicy(QState::DoNotRestoreProperties); + machine.addState(S1); + + QState *S2 = new QState(); + S2->setObjectName("S2"); + S2->assignProperty(object, "b", 5); + S2->setRestorePolicy(QState::DoNotRestoreProperties); + machine.addState(S2); + + QState *S3 = new QState(); + S3->setObjectName("S3"); + S3->setRestorePolicy(QState::DoNotRestoreProperties); + machine.addState(S3); + + QFinalState *S4 = new QFinalState(); + machine.addState(S4); + + S1->addTransition(new EventTransition(QEvent::User, S2)); + S2->addTransition(new EventTransition(QEvent::User, S3)); + S3->addTransition(S4); + + machine.setInitialState(S1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 3); + QCOMPARE(object->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 3); + QCOMPARE(object->property("b").toInt(), 5); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 3); + QCOMPARE(object->property("b").toInt(), 5); +} + +void tst_QStateMachine::setGlobalRestorePolicyToGlobalRestore() +{ + s_countWarnings = false; + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::GlobalRestorePolicy); + + QCOMPARE(machine.globalRestorePolicy(), QStateMachine::DoNotRestoreProperties); + QCOMPARE(s_msgType, QtWarningMsg); + + s_msgType = QtDebugMsg; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + machine.setGlobalRestorePolicy(QStateMachine::GlobalRestorePolicy); + + QCOMPARE(machine.globalRestorePolicy(), QStateMachine::RestoreProperties); + QCOMPARE(s_msgType, QtWarningMsg); +} + + +void tst_QStateMachine::restorePolicyOnChildState() +{ + QStateMachine machine; + + QObject *propertyHolder = new QObject(); + propertyHolder->setProperty("a", 1); + propertyHolder->setProperty("b", 2); + + QState *parentState = new QState(&machine); + parentState->setObjectName("parentState"); + + QState *s1 = new QState(parentState); + s1->setRestorePolicy(QState::RestoreProperties); + s1->setObjectName("s1"); + s1->assignProperty(propertyHolder, "a", 3); + parentState->setInitialState(s1); + + QState *s2 = new QState(parentState); + s2->setRestorePolicy(QState::RestoreProperties); + s2->setObjectName("s2"); + s2->assignProperty(propertyHolder, "b", 4); + + QState *s3 = new QState(parentState); + s3->setRestorePolicy(QState::RestoreProperties); + s3->setObjectName("s3"); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(parentState); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 1); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 1); + QCOMPARE(propertyHolder->property("b").toInt(), 2); +} +*/ + +void tst_QStateMachine::globalRestorePolicySetToRestore() +{ + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("a", 1); + propertyHolder->setProperty("b", 2); + + QState *s1 = new QState(&machine); + s1->assignProperty(propertyHolder, "a", 3); + + QState *s2 = new QState(&machine); + s2->assignProperty(propertyHolder, "b", 4); + + QState *s3 = new QState(&machine); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 1); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 1); + QCOMPARE(propertyHolder->property("b").toInt(), 2); +} + +/* +void tst_QStateMachine::mixedRestoreProperties() +{ + QStateMachine machine; + + QObject *propertyHolder = new QObject(); + propertyHolder->setProperty("a", 1); + + QState *s1 = new QState(&machine); + s1->setRestorePolicy(QState::RestoreProperties); + s1->assignProperty(propertyHolder, "a", 3); + + QState *s2 = new QState(&machine); + s2->assignProperty(propertyHolder, "a", 4); + + QState *s3 = new QState(&machine); + + QState *s4 = new QState(&machine); + s4->assignProperty(propertyHolder, "a", 5); + + QState *s5 = new QState(&machine); + s5->setRestorePolicy(QState::RestoreProperties); + s5->assignProperty(propertyHolder, "a", 6); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + s3->addTransition(new EventTransition(QEvent::User, s4)); + s4->addTransition(new EventTransition(QEvent::User, s5)); + s5->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + // Enter s1, save current + QCOMPARE(propertyHolder->property("a").toInt(), 3); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + // Enter s2, restorePolicy == DoNotRestore, so restore all properties + QCOMPARE(propertyHolder->property("a").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + // Enter s3 + QCOMPARE(propertyHolder->property("a").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + // Enter s4 + QCOMPARE(propertyHolder->property("a").toInt(), 5); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + // Enter s5, save current + QCOMPARE(propertyHolder->property("a").toInt(), 6); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + // Enter s3, restore + QCOMPARE(propertyHolder->property("a").toInt(), 5); +} +*/ + +void tst_QStateMachine::transitionWithParent() +{ + QStateMachine machine; + QState *s1 = new QState(&machine); + QState *s2 = new QState(&machine); + EventTransition *trans = new EventTransition(QEvent::User, s2, s1); + QCOMPARE(trans->sourceState(), s1); + QCOMPARE(trans->targetState(), (QAbstractState*)s2); + QCOMPARE(trans->targetStates().size(), 1); + QCOMPARE(trans->targetStates().at(0), (QAbstractState*)s2); +} + +void tst_QStateMachine::simpleAnimation() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("fooBar", 1.0); + + QState *s1 = new QState(&machine); + QState *s2 = new QState(&machine); + s2->assignProperty(object, "fooBar", 2.0); + + EventTransition *et = new EventTransition(QEvent::User, s2); + QPropertyAnimation *animation = new QPropertyAnimation(object, "fooBar", s2); + et->addAnimation(animation); + s1->addTransition(et); + + QState *s3 = new QState(&machine); + s2->addTransition(animation, SIGNAL(finished()), s3); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("fooBar").toDouble(), 2.0); +} + +class SlotCalledCounter: public QObject +{ + Q_OBJECT +public: + SlotCalledCounter() : counter(0) {} + + int counter; + +public slots: + void slot() { counter++; } +}; + +void tst_QStateMachine::twoAnimations() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + object->setProperty("bar", 3.0); + + QState *s1 = new QState(&machine); + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + s2->assignProperty(object, "bar", 10.0); + + QPropertyAnimation *animationFoo = new QPropertyAnimation(object, "foo", s2); + QPropertyAnimation *animationBar = new QPropertyAnimation(object, "bar", s2); + animationBar->setDuration(900); + + SlotCalledCounter counter; + connect(animationFoo, SIGNAL(finished()), &counter, SLOT(slot())); + connect(animationBar, SIGNAL(finished()), &counter, SLOT(slot())); + + EventTransition *et = new EventTransition(QEvent::User, s2); + et->addAnimation(animationFoo); + et->addAnimation(animationBar); + s1->addTransition(et); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); + QCOMPARE(object->property("bar").toDouble(), 10.0); + + QCOMPARE(counter.counter, 2); +} + +void tst_QStateMachine::twoAnimatedTransitions() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(&machine); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 5.0); + QPropertyAnimation *fooAnimation = new QPropertyAnimation(object, "foo", s2); + EventTransition *trans = new EventTransition(QEvent::User, s2); + s1->addTransition(trans); + trans->addAnimation(fooAnimation); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + s2->addTransition(fooAnimation, SIGNAL(finished()), s3); + + QState *s4 = new QState(&machine); + s4->assignProperty(object, "foo", 2.0); + QPropertyAnimation *fooAnimation2 = new QPropertyAnimation(object, "foo", s4); + trans = new EventTransition(QEvent::User, s4); + s3->addTransition(trans); + trans->addAnimation(fooAnimation2); + + QState *s5 = new QState(&machine); + QObject::connect(s5, SIGNAL(entered()), QApplication::instance(), SLOT(quit())); + s4->addTransition(fooAnimation2, SIGNAL(finished()), s5); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 5.0); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s5)); + QCOMPARE(object->property("foo").toDouble(), 2.0); +} + +void tst_QStateMachine::playAnimationTwice() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(&machine); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 5.0); + QPropertyAnimation *fooAnimation = new QPropertyAnimation(object, "foo", s2); + EventTransition *trans = new EventTransition(QEvent::User, s2); + s1->addTransition(trans); + trans->addAnimation(fooAnimation); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + s2->addTransition(fooAnimation, SIGNAL(finished()), s3); + + QState *s4 = new QState(&machine); + s4->assignProperty(object, "foo", 2.0); + trans = new EventTransition(QEvent::User, s4); + s3->addTransition(trans); + trans->addAnimation(fooAnimation); + + QState *s5 = new QState(&machine); + QObject::connect(s5, SIGNAL(entered()), QApplication::instance(), SLOT(quit())); + s4->addTransition(fooAnimation, SIGNAL(finished()), s5); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 5.0); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s5)); + QCOMPARE(object->property("foo").toDouble(), 2.0); +} + +void tst_QStateMachine::nestedTargetStateForAnimation() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + object->setProperty("bar", 3.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(&machine); + QState *s2 = new QState(&machine); + + s2->assignProperty(object, "foo", 2.0); + + QState *s2Child = new QState(s2); + s2Child->assignProperty(object, "bar", 10.0); + s2->setInitialState(s2Child); + + QState *s2Child2 = new QState(s2); + s2Child2->assignProperty(object, "bar", 11.0); + QAbstractTransition *at = new EventTransition(QEvent::User, s2Child2); + s2Child->addTransition(at); + + QPropertyAnimation *animation = new QPropertyAnimation(object, "bar", s2); + animation->setDuration(2000); + connect(animation, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(animation); + + at = new EventTransition(QEvent::User, s2); + s1->addTransition(at); + + animation = new QPropertyAnimation(object, "foo", s2); + connect(animation, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(animation); + + animation = new QPropertyAnimation(object, "bar", s2); + connect(animation, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(animation); + + QState *s3 = new QState(&machine); + s2->addTransition(s2Child, SIGNAL(propertiesAssigned()), s3); + + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + machine.postEvent(new QEvent(QEvent::User)); + + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); + QCOMPARE(object->property("bar").toDouble(), 10.0); + QCOMPARE(counter.counter, 2); +} + +void tst_QStateMachine::propertiesAssignedSignalTransitionsReuseAnimationGroup() +{ + QStateMachine machine; + QObject *object = new QObject(&machine); + object->setProperty("foo", 0); + + QState *s1 = new QState(&machine); + s1->assignProperty(object, "foo", 123); + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 456); + QState *s3 = new QState(&machine); + s3->assignProperty(object, "foo", 789); + QFinalState *s4 = new QFinalState(&machine); + + QParallelAnimationGroup animationGroup; + animationGroup.addAnimation(new QPropertyAnimation(object, "foo")); + QSignalSpy animationFinishedSpy(&animationGroup, SIGNAL(finished())); + s1->addTransition(s1, SIGNAL(propertiesAssigned()), s2)->addAnimation(&animationGroup); + s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3)->addAnimation(&animationGroup); + s3->addTransition(s3, SIGNAL(propertiesAssigned()), s4); + + machine.setInitialState(s1); + QSignalSpy machineFinishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(machineFinishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s4)); + QCOMPARE(object->property("foo").toInt(), 789); + + QCOMPARE(animationFinishedSpy.count(), 2); +} + +void tst_QStateMachine::animatedGlobalRestoreProperty() +{ + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(&machine); + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + + QState *s4 = new QState(&machine); + QObject::connect(s4, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + QAbstractTransition *at = new EventTransition(QEvent::User, s2); + s1->addTransition(at); + QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", s2); + connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(pa); + + at = s2->addTransition(pa, SIGNAL(finished()), s3); + pa = new QPropertyAnimation(object, "foo", s3); + connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(pa); + + at = s3->addTransition(pa, SIGNAL(finished()), s4); + pa = new QPropertyAnimation(object, "foo", s4); + connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(pa); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s4)); + QCOMPARE(object->property("foo").toDouble(), 1.0); + QCOMPARE(counter.counter, 2); +} + +void tst_QStateMachine::specificTargetValueOfAnimation() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(&machine); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QPropertyAnimation *anim = new QPropertyAnimation(object, "foo"); + anim->setEndValue(10.0); + EventTransition *trans = new EventTransition(QEvent::User, s2); + s1->addTransition(trans); + trans->addAnimation(anim); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + s2->addTransition(anim, SIGNAL(finished()), s3); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); + QCOMPARE(anim->endValue().toDouble(), 10.0); + + delete anim; +} + +void tst_QStateMachine::addDefaultAnimation() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(&machine); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", &machine); + machine.addDefaultAnimation(pa); + s2->addTransition(pa, SIGNAL(finished()), s3); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); + + delete object; +} + +void tst_QStateMachine::addDefaultAnimationWithUnusedAnimation() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + object->setProperty("bar", 2.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(&machine); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", &machine); + connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); + machine.addDefaultAnimation(pa); + s2->addTransition(pa, SIGNAL(finished()), s3); + + pa = new QPropertyAnimation(object, "bar", &machine); + connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); + machine.addDefaultAnimation(pa); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); + QCOMPARE(counter.counter, 1); +} + +void tst_QStateMachine::removeDefaultAnimation() +{ + QStateMachine machine; + + QObject propertyHolder; + propertyHolder.setProperty("foo", 0); + + QCOMPARE(machine.defaultAnimations().size(), 0); + + QPropertyAnimation *anim = new QPropertyAnimation(&propertyHolder, "foo"); + + machine.addDefaultAnimation(anim); + + QCOMPARE(machine.defaultAnimations().size(), 1); + QVERIFY(machine.defaultAnimations().contains(anim)); + + machine.removeDefaultAnimation(anim); + + QCOMPARE(machine.defaultAnimations().size(), 0); + + machine.addDefaultAnimation(anim); + + QPropertyAnimation *anim2 = new QPropertyAnimation(&propertyHolder, "foo"); + machine.addDefaultAnimation(anim2); + + QCOMPARE(machine.defaultAnimations().size(), 2); + QVERIFY(machine.defaultAnimations().contains(anim)); + QVERIFY(machine.defaultAnimations().contains(anim2)); + + machine.removeDefaultAnimation(anim); + + QCOMPARE(machine.defaultAnimations().size(), 1); + QVERIFY(machine.defaultAnimations().contains(anim2)); + + machine.removeDefaultAnimation(anim2); + QCOMPARE(machine.defaultAnimations().size(), 0); + + delete anim; + delete anim2; +} + +void tst_QStateMachine::overrideDefaultAnimationWithSpecific() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + QAbstractTransition *at = new EventTransition(QEvent::User, s2); + s1->addTransition(at); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimation(defaultAnimation); + at->addAnimation(moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped + + delete defaultAnimation; + delete moreSpecificAnimation; +} + +/* +void tst_QStateMachine::addDefaultAnimationForSource() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(&machine); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", &machine); + machine.addDefaultAnimationForSourceState(s1, pa); + s2->addTransition(pa, SIGNAL(finished()), s3); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); +} + +void tst_QStateMachine::addDefaultAnimationForTarget() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(&machine); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", &machine); + machine.addDefaultAnimationForTargetState(s2, pa); + s2->addTransition(pa, SIGNAL(finished()), s3); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); +} + +void tst_QStateMachine::removeDefaultAnimationForSource() +{ + QStateMachine machine; + + QCOMPARE(machine.defaultAnimationsForSourceState(&machine).size(), 0); + + QPropertyAnimation *anim = new QPropertyAnimation(this, "foo"); + + machine.addDefaultAnimationForSourceState(&machine, anim); + + QCOMPARE(machine.defaultAnimations().size(), 0); + QCOMPARE(machine.defaultAnimationsForTargetState(&machine).size(), 0); + QCOMPARE(machine.defaultAnimationsForSourceState(&machine).size(), 1); + QVERIFY(machine.defaultAnimationsForSourceState(&machine).contains(anim)); + + machine.removeDefaultAnimationForTargetState(&machine, anim); + + QCOMPARE(machine.defaultAnimations().size(), 0); + QCOMPARE(machine.defaultAnimationsForTargetState(&machine).size(), 0); + QCOMPARE(machine.defaultAnimationsForSourceState(&machine).size(), 1); + QVERIFY(machine.defaultAnimationsForSourceState(&machine).contains(anim)); + + machine.removeDefaultAnimationForSourceState(&machine, anim); + + QCOMPARE(machine.defaultAnimationsForSourceState(&machine).size(), 0); + + machine.addDefaultAnimationForSourceState(&machine, anim); + + QPropertyAnimation *anim2 = new QPropertyAnimation(this, "foo"); + machine.addDefaultAnimationForSourceState(&machine, anim2); + + QCOMPARE(machine.defaultAnimationsForSourceState(&machine).size(), 2); + QVERIFY(machine.defaultAnimationsForSourceState(&machine).contains(anim)); + QVERIFY(machine.defaultAnimationsForSourceState(&machine).contains(anim2)); + + machine.removeDefaultAnimationForSourceState(&machine, anim); + + QCOMPARE(machine.defaultAnimationsForSourceState(&machine).size(), 1); + QVERIFY(machine.defaultAnimationsForSourceState(&machine).contains(anim2)); + + machine.removeDefaultAnimationForSourceState(&machine, anim2); + QCOMPARE(machine.defaultAnimationsForSourceState(&machine).size(), 0); +} + +void tst_QStateMachine::removeDefaultAnimationForTarget() +{ + QStateMachine machine; + + QCOMPARE(machine.defaultAnimationsForTargetState(&machine).size(), 0); + + QPropertyAnimation *anim = new QPropertyAnimation(this, "foo"); + + machine.addDefaultAnimationForTargetState(&machine, anim); + + QCOMPARE(machine.defaultAnimations().size(), 0); + QCOMPARE(machine.defaultAnimationsForSourceState(&machine).size(), 0); + QCOMPARE(machine.defaultAnimationsForTargetState(&machine).size(), 1); + QVERIFY(machine.defaultAnimationsForTargetState(&machine).contains(anim)); + + machine.removeDefaultAnimationForSourceState(&machine, anim); + + QCOMPARE(machine.defaultAnimations().size(), 0); + QCOMPARE(machine.defaultAnimationsForSourceState(&machine).size(), 0); + QCOMPARE(machine.defaultAnimationsForTargetState(&machine).size(), 1); + QVERIFY(machine.defaultAnimationsForTargetState(&machine).contains(anim)); + + machine.removeDefaultAnimationForTargetState(&machine, anim); + + QCOMPARE(machine.defaultAnimationsForTargetState(&machine).size(), 0); + + machine.addDefaultAnimationForTargetState(&machine, anim); + + QPropertyAnimation *anim2 = new QPropertyAnimation(this, "foo"); + machine.addDefaultAnimationForTargetState(&machine, anim2); + + QCOMPARE(machine.defaultAnimationsForTargetState(&machine).size(), 2); + QVERIFY(machine.defaultAnimationsForTargetState(&machine).contains(anim)); + QVERIFY(machine.defaultAnimationsForTargetState(&machine).contains(anim2)); + + machine.removeDefaultAnimationForTargetState(&machine, anim); + + QCOMPARE(machine.defaultAnimationsForTargetState(&machine).size(), 1); + QVERIFY(machine.defaultAnimationsForTargetState(&machine).contains(anim2)); + + machine.removeDefaultAnimationForTargetState(&machine, anim2); + QCOMPARE(machine.defaultAnimationsForTargetState(&machine).size(), 0); +} + +void tst_QStateMachine::overrideDefaultAnimationWithSource() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimation(defaultAnimation); + machine.addDefaultAnimationForSourceState(s1, moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped +} + +void tst_QStateMachine::overrideDefaultAnimationWithTarget() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimation(defaultAnimation); + machine.addDefaultAnimationForTargetState(s2, moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped + +} + +void tst_QStateMachine::overrideDefaultSourceAnimationWithSpecific() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + QAbstractTransition *at = s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimationForSourceState(s1, defaultAnimation); + at->addAnimation(moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped +} + +void tst_QStateMachine::overrideDefaultTargetAnimationWithSpecific() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + QAbstractTransition *at = s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimationForTargetState(s2, defaultAnimation); + at->addAnimation(moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped +} + +void tst_QStateMachine::overrideDefaultTargetAnimationWithSource() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + + QState *s2 = new QState(&machine); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(&machine); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimationForTargetState(s2, defaultAnimation); + machine.addDefaultAnimationForSourceState(s1, moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped +} + +*/ + +void tst_QStateMachine::parallelStateAssignmentsDone() +{ + QStateMachine machine; + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("foo", 123); + propertyHolder->setProperty("bar", 456); + propertyHolder->setProperty("zoot", 789); + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + + QState *parallelState = new QState(QState::ParallelStates, &machine); + parallelState->assignProperty(propertyHolder, "foo", 321); + + QState *s2 = new QState(parallelState); + s2->assignProperty(propertyHolder, "bar", 654); + + QState *s3 = new QState(parallelState); + s3->assignProperty(propertyHolder, "zoot", 987); + + s1->addTransition(new EventTransition(QEvent::User, parallelState)); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("foo").toInt(), 123); + QCOMPARE(propertyHolder->property("bar").toInt(), 456); + QCOMPARE(propertyHolder->property("zoot").toInt(), 789); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("foo").toInt(), 321); + QCOMPARE(propertyHolder->property("bar").toInt(), 654); + QCOMPARE(propertyHolder->property("zoot").toInt(), 987); +} + +void tst_QStateMachine::transitionsFromParallelStateWithNoChildren() +{ + QStateMachine machine; + + QState *parallelState = new QState(QState::ParallelStates, &machine); + machine.setInitialState(parallelState); + + QState *s1 = new QState(&machine); + parallelState->addTransition(new EventTransition(QEvent::User, s1)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(1, machine.configuration().size()); + QVERIFY(machine.configuration().contains(parallelState)); + + machine.postEvent(new QEvent(QEvent::User)); + + QCoreApplication::processEvents(); + + QCOMPARE(1, machine.configuration().size()); + QVERIFY(machine.configuration().contains(s1)); +} + +void tst_QStateMachine::parallelStateTransition() +{ + QStateMachine machine; + + QState *parallelState = new QState(QState::ParallelStates, &machine); + machine.setInitialState(parallelState); + + QState *s1 = new QState(parallelState); + QState *s2 = new QState(parallelState); + + QState *s1InitialChild = new QState(s1); + s1->setInitialState(s1InitialChild); + + QState *s2InitialChild = new QState(s2); + s2->setInitialState(s2InitialChild); + + QState *s1OtherChild = new QState(s1); + + s1->addTransition(new EventTransition(QEvent::User, s1OtherChild)); + + machine.start(); + QCoreApplication::processEvents(); + + QVERIFY(machine.configuration().contains(parallelState)); + QVERIFY(machine.configuration().contains(s1)); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s1InitialChild)); + QVERIFY(machine.configuration().contains(s2InitialChild)); + QCOMPARE(machine.configuration().size(), 5); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + 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() +{ + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("foo", 1); + propertyHolder->setProperty("bar", 2); + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + + QState *s2 = new QState(&machine); + s2->assignProperty(propertyHolder, "foo", 3); + + QState *s21 = new QState(s2); + s21->assignProperty(propertyHolder, "bar", 4); + s2->setInitialState(s21); + + QState *s22 = new QState(s2); + s22->assignProperty(propertyHolder, "bar", 5); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s21->addTransition(new EventTransition(QEvent::User, s22)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + QCOMPARE(propertyHolder->property("foo").toInt(), 1); + QCOMPARE(propertyHolder->property("bar").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s21)); + QCOMPARE(propertyHolder->property("foo").toInt(), 3); + QCOMPARE(propertyHolder->property("bar").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s22)); + QCOMPARE(propertyHolder->property("foo").toInt(), 3); + QCOMPARE(propertyHolder->property("bar").toInt(), 5); +} + +void tst_QStateMachine::nestedRestoreProperties2() +{ + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("foo", 1); + propertyHolder->setProperty("bar", 2); + + QState *s1 = new QState(&machine); + machine.setInitialState(s1); + + QState *s2 = new QState(&machine); + s2->assignProperty(propertyHolder, "foo", 3); + + QState *s21 = new QState(s2); + s21->assignProperty(propertyHolder, "bar", 4); + s2->setInitialState(s21); + + QState *s22 = new QState(s2); + s22->assignProperty(propertyHolder, "foo", 6); + s22->assignProperty(propertyHolder, "bar", 5); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s21->addTransition(new EventTransition(QEvent::User, s22)); + s22->addTransition(new EventTransition(QEvent::User, s21)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + QCOMPARE(propertyHolder->property("foo").toInt(), 1); + QCOMPARE(propertyHolder->property("bar").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s21)); + QCOMPARE(propertyHolder->property("foo").toInt(), 3); + QCOMPARE(propertyHolder->property("bar").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s22)); + QCOMPARE(propertyHolder->property("foo").toInt(), 6); + QCOMPARE(propertyHolder->property("bar").toInt(), 5); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s21)); + QCOMPARE(propertyHolder->property("foo").toInt(), 3); + QCOMPARE(propertyHolder->property("bar").toInt(), 4); + +} + +void tst_QStateMachine::nestedStateMachines() +{ + QStateMachine machine; + QState *group = new QState(&machine); + group->setChildMode(QState::ParallelStates); + QStateMachine *subMachines[3]; + for (int i = 0; i < 3; ++i) { + QState *subGroup = new QState(group); + QStateMachine *subMachine = new QStateMachine(subGroup); + { + QState *initial = new QState(subMachine); + QFinalState *done = new QFinalState(subMachine); + initial->addTransition(new EventTransition(QEvent::User, done)); + subMachine->setInitialState(initial); + } + QFinalState *subMachineDone = new QFinalState(subGroup); + subMachine->addTransition(subMachine, SIGNAL(finished()), subMachineDone); + subGroup->setInitialState(subMachine); + subMachines[i] = subMachine; + } + QFinalState *final = new QFinalState(&machine); + group->addTransition(group, SIGNAL(finished()), final); + machine.setInitialState(group); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + QTRY_COMPARE(machine.configuration().count(), 1+2*3); + QVERIFY(machine.configuration().contains(group)); + for (int i = 0; i < 3; ++i) + QVERIFY(machine.configuration().contains(subMachines[i])); + + QCoreApplication::processEvents(); // starts the submachines + + for (int i = 0; i < 3; ++i) + subMachines[i]->postEvent(new QEvent(QEvent::User)); + + QTRY_COMPARE(finishedSpy.count(), 1); +} + +void tst_QStateMachine::goToState() +{ + QStateMachine machine; + QState *s1 = new QState(&machine); + QState *s2 = new QState(&machine); + machine.setInitialState(s1); + QSignalSpy startedSpy(&machine, SIGNAL(started())); + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + + QStateMachinePrivate::get(&machine)->goToState(s2); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); + + QStateMachinePrivate::get(&machine)->goToState(s2); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); + + QStateMachinePrivate::get(&machine)->goToState(s1); + QStateMachinePrivate::get(&machine)->goToState(s2); + QStateMachinePrivate::get(&machine)->goToState(s1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); + + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + // go to state in group + QState *s2_1 = new QState(s2); + s2->setInitialState(s2_1); + QStateMachinePrivate::get(&machine)->goToState(s2_1); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s2_1)); +} + +class CloneSignalTransition : public QSignalTransition +{ +public: + CloneSignalTransition(QObject *sender, const char *signal, QAbstractState *target) + : QSignalTransition(sender, signal) + { + setTargetState(target); + } + + void onTransition(QEvent *e) + { + QSignalTransition::onTransition(e); + QStateMachine::SignalEvent *se = static_cast(e); + eventSignalIndex = se->signalIndex(); + } + + int eventSignalIndex; +}; + +void tst_QStateMachine::task260403_clonedSignals() +{ + SignalEmitter emitter; + QStateMachine machine; + QState *s1 = new QState(&machine); + QState *s2 = new QState(&machine); + CloneSignalTransition *t1 = new CloneSignalTransition(&emitter, SIGNAL(signalWithDefaultArg()), s2); + s1->addTransition(t1); + + machine.setInitialState(s1); + machine.start(); + QTest::qWait(1); + + emitter.emitSignalWithDefaultArg(); + QTest::qWait(1); + QCOMPARE(t1->eventSignalIndex, emitter.metaObject()->indexOfSignal("signalWithDefaultArg()")); +} + +class EventPosterThread : public QThread +{ + Q_OBJECT +public: + EventPosterThread(QStateMachine *machine, QObject *parent = 0) + : QThread(parent), m_machine(machine), m_count(0) + { + moveToThread(this); + QObject::connect(m_machine, SIGNAL(started()), + this, SLOT(postEvent())); + } +protected: + virtual void run() + { + exec(); + } +private Q_SLOTS: + void postEvent() + { + m_machine->postEvent(new QEvent(QEvent::User)); + if (++m_count < 10000) + QTimer::singleShot(0, this, SLOT(postEvent())); + else + quit(); + } +private: + QStateMachine *m_machine; + int m_count; +}; + +void tst_QStateMachine::postEventFromOtherThread() +{ + QStateMachine machine; + EventPosterThread poster(&machine); + StringEventPoster *s1 = new StringEventPoster("foo", &machine); + s1->addTransition(new EventTransition(QEvent::User, s1)); + QFinalState *f = new QFinalState(&machine); + s1->addTransition(&poster, SIGNAL(finished()), f); + machine.setInitialState(s1); + + poster.start(); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); +} + +void tst_QStateMachine::eventFilterForApplication() +{ + QStateMachine machine; + + QState *s1 = new QState(&machine); + { + machine.setInitialState(s1); + } + + QState *s2 = new QState(&machine); + + QEventTransition *transition = new QEventTransition(QCoreApplication::instance(), + QEvent::ApplicationActivate); + transition->setTargetState(s2); + s1->addTransition(transition); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + QCoreApplication::postEvent(QCoreApplication::instance(), + new QEvent(QEvent::ApplicationActivate)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); +} + +void tst_QStateMachine::eventClassesExported() +{ + // make sure this links + QStateMachine::WrappedEvent *wrappedEvent = new QStateMachine::WrappedEvent(0, 0); + Q_UNUSED(wrappedEvent); + QStateMachine::SignalEvent *signalEvent = new QStateMachine::SignalEvent(0, 0, QList()); + Q_UNUSED(signalEvent); +} + +void tst_QStateMachine::stopInTransitionToFinalState() +{ + QStateMachine machine; + QState *s1 = new QState(&machine); + QFinalState *s2 = new QFinalState(&machine); + QAbstractTransition *t1 = s1->addTransition(s2); + machine.setInitialState(s1); + + QObject::connect(t1, SIGNAL(triggered()), &machine, SLOT(stop())); + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + QSignalSpy s2EnteredSpy(s2, SIGNAL(entered())); + machine.start(); + + // Stopping should take precedence over finished. + QTRY_COMPARE(stoppedSpy.count(), 1); + QCOMPARE(finishedSpy.count(), 0); + QCOMPARE(s2EnteredSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); +} + +class StopInEventTestTransition : public QAbstractTransition +{ +public: + bool eventTest(QEvent *e) + { + if (e->type() == QEvent::User) + machine()->stop(); + return false; + } + void onTransition(QEvent *) + { } +}; + +void tst_QStateMachine::stopInEventTest_data() +{ + QTest::addColumn("eventPriority"); + QTest::newRow("NormalPriority") << int(QStateMachine::NormalPriority); + QTest::newRow("HighPriority") << int(QStateMachine::HighPriority); +} + +void tst_QStateMachine::stopInEventTest() +{ + QFETCH(int, eventPriority); + + QStateMachine machine; + QState *s1 = new QState(&machine); + s1->addTransition(new StopInEventTestTransition()); + machine.setInitialState(s1); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + machine.start(); + QTRY_COMPARE(startedSpy.count(), 1); + + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.postEvent(new QEvent(QEvent::User), QStateMachine::EventPriority(eventPriority)); + + QTRY_COMPARE(stoppedSpy.count(), 1); + QCOMPARE(finishedSpy.count(), 0); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); +} + +QTEST_MAIN(tst_QStateMachine) +#include "tst_qstatemachine.moc" diff --git a/tests/auto/corelib/statemachine/statemachine.pro b/tests/auto/corelib/statemachine/statemachine.pro new file mode 100644 index 0000000000..aa645ac9f4 --- /dev/null +++ b/tests/auto/corelib/statemachine/statemachine.pro @@ -0,0 +1,4 @@ +TEMPLATE=subdirs +SUBDIRS=\ + qstate \ + qstatemachine -- cgit v1.2.3