// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include <QtTest/QtTest>
#include <QtTest/private/qpropertytesthelper_p.h>
#include <QtCore/QCoreApplication>
#ifndef QT_NO_WIDGETS
#include <QtWidgets/QPushButton>
#include <QtWidgets/QGraphicsScene>
#include <QtWidgets/QGraphicsSceneEvent>
#include <QtWidgets/QGraphicsTextItem>
#endif

#include "qeventtransition.h"
#include "qfinalstate.h"
#include "qstatemachine.h"
#include "qstate.h"
#include "qhistorystate.h"
#include "qsignaltransition.h"
#if QT_CONFIG(qeventtransition)
#include "qkeyeventtransition.h"
#include "qmouseeventtransition.h"
#endif
#include "private/qstate_p.h"
#include "private/qstatemachine_p.h"

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(); \
}

#define TEST_RUNNING_CHANGED(RUNNING) \
{ \
    QTRY_COMPARE(runningSpy.count(), 1); \
    QList<QVariant> runningArgs = runningSpy.takeFirst(); \
    QVERIFY(runningArgs.at(0).typeId() == QMetaType::Bool); \
    QVERIFY(runningArgs.at(0).toBool() == RUNNING); \
    QCOMPARE(machine.isRunning(), runningArgs.at(0).toBool()); \
}

#define TEST_RUNNING_CHANGED_STARTED_STOPPED \
{ \
    QTRY_COMPARE(runningSpy.count(), 2); \
    QList<QVariant> runningArgs = runningSpy.takeFirst(); \
    QVERIFY(runningArgs.at(0).typeId() == QMetaType::Bool); \
    QVERIFY(runningArgs.at(0).toBool() == true); \
    runningArgs = runningSpy.takeFirst(); \
    QVERIFY(runningArgs.at(0).typeId() == QMetaType::Bool); \
    QVERIFY(runningArgs.at(0).toBool() == false); \
    QCOMPARE(machine.isRunning(), runningArgs.at(0).toBool()); \
}

#define DEFINE_ACTIVE_SPY(VAR) \
    QSignalSpy VAR##_activeSpy(VAR, &QState::activeChanged); \
    QVERIFY(VAR##_activeSpy.isValid());

#define TEST_ACTIVE_CHANGED(VAR, COUNT) \
{ \
    QTRY_COMPARE(VAR##_activeSpy.count(), COUNT); \
    bool active = true; \
    for (const QList<QVariant> &activeArgs : static_cast<QList<QList<QVariant> > >(VAR##_activeSpy)) { \
        QVERIFY(activeArgs.at(0).typeId() == QMetaType::Bool); \
        QVERIFY(activeArgs.at(0).toBool() == active); \
        active = !active; \
    } \
    QCOMPARE(VAR->active(), !active); \
}

class SignalEmitter : public QObject
{
Q_OBJECT
    public:
    SignalEmitter(QObject *parent = 0)
        : QObject(parent) {}
public Q_SLOTS:
    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
private slots:
    void rootState();
    void machineWithParent();
#ifdef QT_BUILD_INTERNAL
    void addAndRemoveState();
#endif
    void stateEntryAndExit();
    void assignProperty();
    void assignPropertyWithAnimation();
    void postEvent();
    void cancelDelayedEvent();
    void postDelayedEventAndStop();
    void postDelayedEventFromThread();
    void stopAndPostEvent();
    void stateFinished();
    void parallelStates();
    void parallelRootState();
    void allSourceToTargetConfigurations();
    void signalTransitions();
#ifndef QT_NO_WIDGETS
    void eventTransitions();
    void graphicsSceneEventTransitions();
#endif
    void historyStates();
    void startAndStop();
    void setRunning();
    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 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 nestedStateMachines();
    void goToState();
    void goToStateFromSourceWithTransition();

    void clonedSignals();
    void postEventFromOtherThread();
#ifndef QT_NO_WIDGETS
    void eventFilterForApplication();
#endif
    void eventClassesExported();
    void stopInTransitionToFinalState();
    void stopInEventTest_data();
    void stopInEventTest();

    void testIncrementReceivers();
    void initialStateIsEnteredBeforeStartedEmitted();
    void deletePropertyAssignmentObjectBeforeEntry();
    void deletePropertyAssignmentObjectBeforeRestore();
    void deleteInitialState();
    void setPropertyAfterRestore();
    void transitionWithNoTarget_data();
    void transitionWithNoTarget();
    void initialStateIsFinal();

    void restorePropertiesSimple();
    void restoreProperties2();
    void restoreProperties3();
    void restoreProperties4();
    void restorePropertiesSelfTransition();
    void changeStateWhileAnimatingProperty();
    void propertiesAreAssignedBeforeEntryCallbacks_data();
    void propertiesAreAssignedBeforeEntryCallbacks();

    void multiTargetTransitionInsideParallelStateGroup();
    void signalTransitionNormalizeSignature();
#ifdef Q_COMPILER_DELEGATING_CONSTRUCTORS
    void createPointerToMemberSignalTransition();
#endif
    void createSignalTransitionWhenRunning();
    void createEventTransitionWhenRunning();
    void signalTransitionSenderInDifferentThread();
    void signalTransitionSenderInDifferentThread2();
    void signalTransitionRegistrationThreadSafety();
    void childModeConstructor();

    void qtbug_44963();
    void qtbug_44783();
    void internalTransition();
    void conflictingTransition();
    void conflictingTransition2();
    void qtbug_46059();
    void qtbug_46703();
    void postEventFromBeginSelectTransitions();
    void dontProcessSlotsWhenMachineIsNotRunning();

    void cancelDelayedEventWithChrono();
    void postDelayedEventWithChronoAndStop();
    void postDelayedEventWithChronoFromThread();
    void bindings();
};

class TestState : public QState
{
public:
    enum Event {
        Entry,
        Exit
    };
    TestState(QState *parent, const QString &objectName = QString())
        : QState(parent)
    { setObjectName(objectName); }
    TestState(ChildMode mode, const QString &objectName = QString())
        : QState(mode)
    { setObjectName(objectName); }
    QList<QPair<int, Event> > events;
protected:
    virtual void onEntry(QEvent *) override {
        events.append(qMakePair(globalTick++, Entry));
    }
    virtual void onExit(QEvent *) override {
        events.append(qMakePair(globalTick++, Exit));
    }
};

class TestTransition : public QAbstractTransition
{
public:
    TestTransition(QAbstractState *target, const QString &objectName = QString())
        : QAbstractTransition()
    { setTargetState(target); setObjectName(objectName); }
    QList<int> triggers;
protected:
    virtual bool eventTest(QEvent *) override {
        return true;
    }
    virtual void onTransition(QEvent *) override {
        triggers.append(globalTick++);
    }
};

class EventTransition : public QAbstractTransition
{
public:
    EventTransition(QEvent::Type type, QAbstractState *target, QState *parent = 0)
        : QAbstractTransition(parent), m_type(type)
    { setTargetState(target); }
    EventTransition(QEvent::Type type, const QList<QAbstractState *> &targets, QState *parent = 0)
        : QAbstractTransition(parent), m_type(type)
    { setTargetStates(targets); }
protected:
    virtual bool eventTest(QEvent *e) override {
        return (e->type() == m_type);
    }
    virtual void onTransition(QEvent *) override {}
private:
    QEvent::Type m_type;
};

void tst_QStateMachine::transitionToRootState()
{
    QStateMachine machine;
    machine.setObjectName("machine");

    QState *initialState = new QState();
    DEFINE_ACTIVE_SPY(initialState);
    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<QAbstractState *>(&machine));

    machine.start();

    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(initialState));
    TEST_ACTIVE_CHANGED(initialState, 1);

    machine.postEvent(new QEvent(QEvent::User));
    QTest::ignoreMessage(QtWarningMsg,
                         "Unrecoverable error detected in running state machine: "
                         "Child mode of state machine 'machine' is not 'ExclusiveStates'.");
    QTRY_VERIFY(machine.configuration().isEmpty());
    QTRY_VERIFY(!machine.isRunning());
    TEST_ACTIVE_CHANGED(initialState, 2);
}

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<QAbstractState *>(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();

    QTRY_COMPARE(entryController->property("greatGrandParentEntered").toBool(), true);
    QTRY_COMPARE(entryController->property("grandParentEntered").toBool(), false);
    QTRY_COMPARE(entryController->property("parentEntered").toBool(), false);
    QTRY_COMPARE(entryController->property("stateEntered").toBool(), false);
    QTRY_COMPARE(machine.configuration().size(), 2);
    QTRY_VERIFY(machine.configuration().contains(greatGrandParent));
    QTRY_VERIFY(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();

    QTRY_COMPARE(entryController->property("greatGrandParentEntered").toBool(), false);
    QTRY_COMPARE(entryController->property("grandParentEntered").toBool(), true);
    QTRY_COMPARE(entryController->property("parentEntered").toBool(), true);
    QTRY_COMPARE(entryController->property("stateEntered").toBool(), true);
    QTRY_COMPARE(machine.configuration().size(), 4);
    QTRY_VERIFY(machine.configuration().contains(greatGrandParent));
    QTRY_VERIFY(machine.configuration().contains(grandParent));
    QTRY_VERIFY(machine.configuration().contains(parent));
    QTRY_VERIFY(machine.configuration().contains(state));
}

void tst_QStateMachine::defaultErrorState()
{
    QStateMachine machine;
    QCOMPARE(machine.errorState(), reinterpret_cast<QAbstractState *>(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();

    QTRY_COMPARE(machine.error(), QStateMachine::NoInitialStateError);
    QTRY_COMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'MyInitialState'"));
    QTRY_COMPARE(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 *) override
    {
        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();

    QTRY_COMPARE(machine.errorState(), static_cast<QAbstractState*>(customErrorState));
    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(initialState));

    machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1)));
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(initialState));

    QCoreApplication::processEvents();

    QTRY_COMPARE(machine.isRunning(), true);
    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(customErrorState));
    QTRY_COMPARE(customErrorState->error, QStateMachine::NoInitialStateError);
    QTRY_COMPARE(customErrorState->errorString, QString::fromLatin1("Missing initial state in compound state 'brokenState'"));
    QTRY_COMPARE(machine.error(), QStateMachine::NoInitialStateError);
    QTRY_COMPARE(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();

    QTRY_COMPARE(machine.isRunning(), true);
    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(customErrorState));
    QTRY_COMPARE(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();

    QTRY_COMPARE(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();

    QTRY_COMPARE(machine.isRunning(), true);
    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(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();

    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(customErrorStateForBrokenState));
    QTRY_COMPARE(customErrorStateForBrokenState->error, QStateMachine::NoInitialStateError);
    QTRY_COMPARE(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();

    QTRY_COMPARE(machine.isRunning(), true);
    QTRY_COMPARE(machine.configuration().size(), 2);
    QTRY_VERIFY(machine.configuration().contains(customErrorState));
    QTRY_VERIFY(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();

    QTRY_COMPARE(machine.isRunning(), false);
    QTRY_COMPARE(machine.error(), QStateMachine::NoInitialStateError);
    QTRY_COMPARE(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();

    QTRY_COMPARE(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();

    QTRY_COMPARE(entryController->property("greatGrandParentEntered").toBool(), true);
    QTRY_COMPARE(entryController->property("grandParentEntered").toBool(), false);
    QTRY_COMPARE(entryController->property("parentEntered").toBool(), false);
    QTRY_COMPARE(entryController->property("errorStateEntered").toBool(), false);
    QTRY_COMPARE(machine.configuration().size(), 2);
    QTRY_VERIFY(machine.configuration().contains(greatGrandParent));
    QTRY_VERIFY(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();

    QTRY_COMPARE(entryController->property("greatGrandParentEntered").toBool(), false);
    QTRY_COMPARE(entryController->property("grandParentEntered").toBool(), true);
    QTRY_COMPARE(entryController->property("parentEntered").toBool(), true);
    QTRY_COMPARE(entryController->property("errorStateEntered").toBool(), true);
    QTRY_COMPARE(machine.configuration().size(), 4);
    QTRY_VERIFY(machine.configuration().contains(greatGrandParent));
    QTRY_VERIFY(machine.configuration().contains(grandParent));
    QTRY_VERIFY(machine.configuration().contains(parent));
    QTRY_VERIFY(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();

    QTRY_COMPARE(machine.errorState(), reinterpret_cast<QAbstractState *>(0));
    QTRY_COMPARE(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();

    QTRY_COMPARE(machine.isRunning(), true);
    QTRY_COMPARE(machine.error(), QStateMachine::NoInitialStateError);
    QTRY_COMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'brokenState'"));

    machine.clearError();

    QTRY_COMPARE(machine.error(), QStateMachine::NoError);
    QTRY_VERIFY(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();

    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(s1));

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    QTRY_COMPARE(machine.configuration().size(), 2);
    QTRY_VERIFY(machine.configuration().contains(s2));
    QTRY_VERIFY(machine.configuration().contains(s21));
}

void tst_QStateMachine::historyStateHasNowhereToGo()
{
    QStateMachine machine;

    QState *initialState = new QState(&machine);
    initialState->setObjectName("initialState");
    machine.setInitialState(initialState);
    QState *errorState = new QState(&machine);
    errorState->setObjectName("errorState");
    machine.setErrorState(errorState); // avoid warnings

    QState *brokenState = new QState(&machine);
    brokenState->setObjectName("brokenState");
    brokenState->setInitialState(new QState(brokenState));

    QHistoryState *historyState = new QHistoryState(brokenState);
    historyState->setObjectName("historyState");
    EventTransition *t = new EventTransition(QEvent::User, historyState);
    t->setObjectName("initialState->historyState");
    initialState->addTransition(t);

    machine.start();
    QCoreApplication::processEvents();

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    QTRY_COMPARE(machine.isRunning(), true);
    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(machine.errorState()));
    QTRY_COMPARE(machine.error(), QStateMachine::NoDefaultStateInHistoryStateError);
    QTRY_COMPARE(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 runningSpy(&machine, &QStateMachine::runningChanged);
        QTRY_VERIFY(runningSpy.isValid());
        QSignalSpy startedSpy(&machine, &QStateMachine::started);
        QTRY_VERIFY(startedSpy.isValid());
        machine.start();
        QTRY_COMPARE(startedSpy.size(), 1);
        TEST_RUNNING_CHANGED(true);
        QTRY_COMPARE(machine.configuration().size(), 1);
        QTRY_VERIFY(machine.configuration().contains(s1));

        // s1 -> s2h -> s21 (default state)
        machine.postEvent(new QEvent(QEvent::User));
        QCoreApplication::processEvents();
        QTRY_COMPARE(machine.configuration().size(), 2);
        QTRY_VERIFY(machine.configuration().contains(s2));
        // This used to fail on the 2nd run because the
        // history had not been cleared.
        QTRY_VERIFY(machine.configuration().contains(s21));

        // s21 -> s22
        machine.postEvent(new QEvent(QEvent::User));
        QCoreApplication::processEvents();
        QTRY_COMPARE(machine.configuration().size(), 2);
        QTRY_VERIFY(machine.configuration().contains(s2));
        QTRY_VERIFY(machine.configuration().contains(s22));

        // s2 -> s1 (s22 saved in s2h)
        machine.postEvent(new QEvent(QEvent::User));
        QCoreApplication::processEvents();
        QTRY_COMPARE(machine.configuration().size(), 1);
        QTRY_VERIFY(machine.configuration().contains(s1));

        // s1 -> s2h -> s22 (saved state)
        machine.postEvent(new QEvent(QEvent::User));
        QCoreApplication::processEvents();
        QTRY_COMPARE(machine.configuration().size(), 2);
        QTRY_VERIFY(machine.configuration().contains(s2));
        QTRY_VERIFY(machine.configuration().contains(s22));

        QSignalSpy stoppedSpy(&machine, &QStateMachine::stopped);
        QTRY_VERIFY(stoppedSpy.isValid());
        machine.stop();
        QTRY_COMPARE(stoppedSpy.size(), 1);
        TEST_RUNNING_CHANGED(false);
    }
}

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();

    QTRY_COMPARE(entryController->property("errorStateEntered").toBool(), true);
    QTRY_COMPARE(entryController->property("brokenStateEntered").toBool(), false);
    QTRY_COMPARE(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: "
                                       "Child mode of state machine '' is not 'ExclusiveStates'.");
    QCoreApplication::processEvents();

    QTRY_COMPARE(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<QAbstractState *>(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(), QState::DontRestoreProperties);
    machine.setGlobalRestorePolicy(QState::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();

    QTRY_COMPARE(object->property("a").toInt(), 3);
    QTRY_COMPARE(object->property("b").toInt(), 2);

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    QTRY_COMPARE(object->property("a").toInt(), 1);
    QTRY_COMPARE(object->property("b").toInt(), 5);

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    QTRY_COMPARE(object->property("a").toInt(), 1);
    QTRY_COMPARE(object->property("b").toInt(), 2);
}

void tst_QStateMachine::rootState()
{
    QStateMachine machine;
    QCOMPARE(qobject_cast<QState*>(machine.parentState()), (QState*)0);
    QCOMPARE(machine.machine(), (QStateMachine*)0);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    QCOMPARE(s1->parentState(), static_cast<QState*>(&machine));

    QState *s2 = new QState();
    DEFINE_ACTIVE_SPY(s2);
    s2->setParent(&machine);
    QCOMPARE(s2->parentState(), static_cast<QState*>(&machine));
    TEST_ACTIVE_CHANGED(s1, 0);
    TEST_ACTIVE_CHANGED(s2, 0);
}

void tst_QStateMachine::machineWithParent()
{
    QObject object;
    QStateMachine *machine = new QStateMachine(&object);
    QCOMPARE(machine->parent(), &object);
    QCOMPARE(machine->parentState(), static_cast<QState*>(0));
}

#ifdef QT_BUILD_INTERNAL
void tst_QStateMachine::addAndRemoveState()
{
    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<QStateMachine*>(&machine));
    QCOMPARE(s1->parentState(), static_cast<QState*>(&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<QState*>(&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;
        {
            const QString warning
                = QString::asprintf("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<QAbstractState*>() << 0);
        QCOMPARE(t->targetState(), (QAbstractState*)s2);
        t->setTargetStates(QList<QAbstractState*>() << 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);
            {
                const QString warning
                    = QString::asprintf("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, &QStateMachine::started);
        QSignalSpy stoppedSpy(&machine, &QStateMachine::stopped);
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);

        QVERIFY(startedSpy.isValid());
        QVERIFY(stoppedSpy.isValid());
        QVERIFY(finishedSpy.isValid());
        QVERIFY(runningSpy.isValid());

        machine.setInitialState(s1);
        QCOMPARE(machine.initialState(), (QAbstractState*)s1);
        {
            QString warning
                = QString::asprintf("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, &TestState::entered);
        QSignalSpy s1ExitedSpy(s1, &TestState::exited);
        QSignalSpy tTriggeredSpy(t, &TestTransition::triggered);
        QSignalSpy s2EnteredSpy(s2, &TestState::entered);
        QSignalSpy s2ExitedSpy(s2, &TestState::exited);

        QVERIFY(s1EnteredSpy.isValid());
        QVERIFY(s1ExitedSpy.isValid());
        QVERIFY(tTriggeredSpy.isValid());
        QVERIFY(s2EnteredSpy.isValid());
        QVERIFY(s2ExitedSpy.isValid());

        machine.start();

        QTRY_COMPARE(startedSpy.size(), 1);
        QTRY_COMPARE(finishedSpy.size(), 1);
        QTRY_COMPARE(stoppedSpy.size(), 0);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s3));

        // s1 is entered
        QCOMPARE(s1->events.size(), 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.size(), 1);
        QCOMPARE(t->triggers.at(0), 2);
        // s2 is entered
        QCOMPARE(s2->events.size(), 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.size(), 1);
        QCOMPARE(s1ExitedSpy.size(), 1);
        QCOMPARE(tTriggeredSpy.size(), 1);
        QCOMPARE(s2EnteredSpy.size(), 1);
        QCOMPARE(s2ExitedSpy.size(), 1);
    }
    // Two top-level states, one has two child states
    {
        QStateMachine machine;

        TestState *s1 = new TestState(&machine, "s1");
        TestState *s11 = new TestState(s1, "s11");
        TestState *s12 = new TestState(s1, "s12");
        TestState *s2 = new TestState(&machine, "s2");
        QFinalState *s3 = new QFinalState(&machine);
        s3->setObjectName("s3");
        s1->setInitialState(s11);
        TestTransition *t1 = new TestTransition(s12, "t1");
        s11->addTransition(t1);
        TestTransition *t2 = new TestTransition(s2, "t2");
        s12->addTransition(t2);
        s2->addTransition(s3);

        QSignalSpy startedSpy(&machine, &QStateMachine::started);
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(startedSpy.isValid());
        QVERIFY(finishedSpy.isValid());
        QVERIFY(runningSpy.isValid());
        machine.setInitialState(s1);
        globalTick = 0;
        machine.start();

        QTRY_COMPARE(startedSpy.size(), 1);
        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s3));

        // s1 is entered
        QCOMPARE(s1->events.size(), 2);
        QCOMPARE(s1->events.at(0).first, 0);
        QCOMPARE(s1->events.at(0).second, TestState::Entry);
        // s11 is entered
        QCOMPARE(s11->events.size(), 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.size(), 1);
        QCOMPARE(t1->triggers.at(0), 3);
        // s12 is entered
        QCOMPARE(s12->events.size(), 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.size(), 1);
        QCOMPARE(t2->triggers.at(0), 7);
        // s2 is entered
        QCOMPARE(s2->events.size(), 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);
    DEFINE_ACTIVE_SPY(s1);

    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"));
    TEST_ACTIVE_CHANGED(s1, 2);

    s1->assignProperty(s1, "objectName", "foo");
    machine.start();
    QTRY_COMPARE(s1->objectName(), QString::fromLatin1("foo"));
    TEST_ACTIVE_CHANGED(s1, 4);

    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"));
    TEST_ACTIVE_CHANGED(s1, 6);

    {
        QSignalSpy propertiesAssignedSpy(s1, &QState::propertiesAssigned);
        QVERIFY(propertiesAssignedSpy.isValid());
        machine.start();
        QTRY_COMPARE(propertiesAssignedSpy.size(), 1);
        TEST_ACTIVE_CHANGED(s1, 8);
    }

    // nested states
    {
        QState *s11 = new QState(s1);
        DEFINE_ACTIVE_SPY(s11);
        QString str = QString::fromLatin1("set by nested state");
        s11->assignProperty(s11, "objectName", str);
        s1->setInitialState(s11);
        machine.start();
        QTRY_COMPARE(s11->objectName(), str);
        TEST_ACTIVE_CHANGED(s1, 10);
        TEST_ACTIVE_CHANGED(s11, 2);
    }
}

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);
        DEFINE_ACTIVE_SPY(s1);
        s1->assignProperty(&obj, "foo", 123);
        QState *s2 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s2);
        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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.start();
        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        QCOMPARE(obj.property("foo").toInt(), 456);
        QCOMPARE(obj.property("bar").toInt(), 789);
        TEST_ACTIVE_CHANGED(s1, 2);
        TEST_ACTIVE_CHANGED(s2, 2);
    }
    // Two animations
    {
        QStateMachine machine;
        QObject obj;
        obj.setProperty("foo", 321);
        obj.setProperty("bar", 654);
        QState *s1 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s1);
        s1->assignProperty(&obj, "foo", 123);
        QState *s2 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s2);
        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, &QStateMachine::finished);
        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(finishedSpy.isValid());
        QVERIFY(runningSpy.isValid());
        machine.start();
        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        QCOMPARE(obj.property("foo").toInt(), 456);
        QCOMPARE(obj.property("bar").toInt(), 789);
        TEST_ACTIVE_CHANGED(s1, 2);
        TEST_ACTIVE_CHANGED(s2, 2);
    }
    // Animation group
    {
        QStateMachine machine;
        QObject obj;
        obj.setProperty("foo", 321);
        obj.setProperty("bar", 654);
        QState *s1 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s1);
        s1->assignProperty(&obj, "foo", 123);
        s1->assignProperty(&obj, "bar", 321);
        QState *s2 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s2);
        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, &QStateMachine::finished);
        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(finishedSpy.isValid());
        QVERIFY(runningSpy.isValid());
        machine.start();
        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        QCOMPARE(obj.property("foo").toInt(), 456);
        QCOMPARE(obj.property("bar").toInt(), 654);
        QCOMPARE(obj.property("baz").toInt(), 789);
        TEST_ACTIVE_CHANGED(s1, 2);
        TEST_ACTIVE_CHANGED(s2, 2);
    }
    // Nested states
    {
        QStateMachine machine;
        QObject obj;
        obj.setProperty("foo", 321);
        obj.setProperty("bar", 654);
        QState *s1 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s1);
        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);
        DEFINE_ACTIVE_SPY(s2);
        s2->setObjectName("s2");
        s2->assignProperty(&obj, "foo", 321);
        QState *s21 = new QState(s2);
        DEFINE_ACTIVE_SPY(s21);
        s21->setObjectName("s21");
        s21->assignProperty(&obj, "bar", 654);
        QState *s22 = new QState(s2);
        DEFINE_ACTIVE_SPY(s22);
        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, &QStateMachine::finished);
        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(finishedSpy.isValid());
        QVERIFY(runningSpy.isValid());
        machine.start();
        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        QCOMPARE(obj.property("foo").toInt(), 321);
        QCOMPARE(obj.property("bar").toInt(), 789);
        TEST_ACTIVE_CHANGED(s1, 2);
        TEST_ACTIVE_CHANGED(s2, 2);
        TEST_ACTIVE_CHANGED(s21, 2);
        TEST_ACTIVE_CHANGED(s22, 2);
    }
    // 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);
        DEFINE_ACTIVE_SPY(s1);
        group->setInitialState(s1);
        s1->assignProperty(&obj, "foo", 123);
        QState *s2 = new QState(group);
        DEFINE_ACTIVE_SPY(s2);
        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);
        DEFINE_ACTIVE_SPY(s3);
        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, &QState::propertiesAssigned);
        QVERIFY(propertiesAssignedSpy.isValid());
        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);
        TEST_ACTIVE_CHANGED(s1, 2);
        TEST_ACTIVE_CHANGED(s2, 2);
        TEST_ACTIVE_CHANGED(s3, 1);
        QVERIFY(machine.isRunning());
    }
}

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) override
    {
        if (e->type() != QEvent::Type(QEvent::User+2))
            return false;
        StringEvent *se = static_cast<StringEvent*>(e);
        return (m_value == se->value) && (!m_cond.isValid() || m_cond.match(m_value).hasMatch());
    }
    virtual void onTransition(QEvent *) override {}

private:
    QString m_value;
    QRegularExpression 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 *) override
    {
        if (m_delay == -1)
            machine()->postEvent(new StringEvent(m_value));
        else
            machine()->postDelayedEvent(new StringEvent(m_value), m_delay);
    }
    virtual void onExit(QEvent *) override {}

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");
        DEFINE_ACTIVE_SPY(s1);
        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, &QStateMachine::finished);
        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(finishedSpy.isValid());
        QVERIFY(runningSpy.isValid());
        machine.start();
        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s2));
        TEST_ACTIVE_CHANGED(s1, 2);

        s1->setString("b");
        QFinalState *s3 = new QFinalState();
        machine.addState(s3);
        s1->addTransition(new StringTransition("b", s3));
        finishedSpy.clear();
        machine.start();
        QTRY_COMPARE(finishedSpy.size(), 1);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s3));
        TEST_ACTIVE_CHANGED(s1, 4);
    }
}

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);
    DEFINE_ACTIVE_SPY(s1);
    QFinalState *s2 = new QFinalState(&machine);
    s1->addTransition(new StringTransition("a", s2));
    machine.setInitialState(s1);

    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(startedSpy.isValid());
    QVERIFY(runningSpy.isValid());
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 1);
    TEST_RUNNING_CHANGED(true);
    TEST_ACTIVE_CHANGED(s1, 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, &QStateMachine::finished);
    QVERIFY(finishedSpy.isValid());
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED(false);
    TEST_ACTIVE_CHANGED(s1, 2);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s2));
}

void tst_QStateMachine::postDelayedEventAndStop()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    QFinalState *s2 = new QFinalState(&machine);
    s1->addTransition(new StringTransition("a", s2));
    machine.setInitialState(s1);

    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QVERIFY(startedSpy.isValid());
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 1);
    TEST_RUNNING_CHANGED(true);
    TEST_ACTIVE_CHANGED(s1, 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, &QStateMachine::stopped);
    QVERIFY(stoppedSpy.isValid());
    machine.stop();
    QTRY_COMPARE(stoppedSpy.size(), 1);
    TEST_RUNNING_CHANGED(false);
    TEST_ACTIVE_CHANGED(s1, 1);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));

    machine.start();
    QTRY_COMPARE(startedSpy.size(), 2);
    TEST_RUNNING_CHANGED(true);
    TEST_ACTIVE_CHANGED(s1, 3);
    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.size(), 2);
    TEST_RUNNING_CHANGED(false);
    TEST_ACTIVE_CHANGED(s1, 3);
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 3);
    TEST_RUNNING_CHANGED(true);
    QTestEventLoop::instance().enterLoop(2);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));
    TEST_ACTIVE_CHANGED(s1, 5);
    QVERIFY(machine.isRunning());
}

class DelayedEventPosterThread : public QThread
{
    Q_OBJECT
public:
    DelayedEventPosterThread(QStateMachine *machine, QObject *parent = 0)
        : QThread(parent), firstEventWasCancelled(false),
          m_machine(machine)
    {
        moveToThread(this);
        QObject::connect(m_machine, SIGNAL(started()),
                         this, SLOT(postEvent()));
    }

    mutable bool firstEventWasCancelled;

private Q_SLOTS:
    void postEvent()
    {
        int id = m_machine->postDelayedEvent(new QEvent(QEvent::User), 1000);
        firstEventWasCancelled = m_machine->cancelDelayedEvent(id);

        m_machine->postDelayedEvent(new QEvent(QEvent::User), 1);

        quit();
    }
private:
    QStateMachine *m_machine;
};

void tst_QStateMachine::postDelayedEventFromThread()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    QFinalState *f = new QFinalState(&machine);
    s1->addTransition(new EventTransition(QEvent::User, f));
    machine.setInitialState(s1);

    DelayedEventPosterThread poster(&machine);
    poster.start();

    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QVERIFY(finishedSpy.isValid());
    machine.start();
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    TEST_ACTIVE_CHANGED(s1, 2);
    QVERIFY(poster.firstEventWasCancelled);
}

void tst_QStateMachine::stopAndPostEvent()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QVERIFY(startedSpy.isValid());
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 1);
    TEST_RUNNING_CHANGED(true);
    TEST_ACTIVE_CHANGED(s1, 1);
    QSignalSpy stoppedSpy(&machine, &QStateMachine::stopped);
    QVERIFY(stoppedSpy.isValid());
    machine.stop();
    QCOMPARE(stoppedSpy.size(), 0);
    machine.postEvent(new QEvent(QEvent::User));
    QTRY_COMPARE(stoppedSpy.size(), 1);
    TEST_RUNNING_CHANGED(false);
    TEST_ACTIVE_CHANGED(s1, 1);
    QCoreApplication::processEvents();
}

void tst_QStateMachine::stateFinished()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    QState *s1_1 = new QState(s1);
    DEFINE_ACTIVE_SPY(s1_1);
    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 runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QVERIFY(finishedSpy.isValid());
    machine.start();
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s1_1, 2);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s2));
}

void tst_QStateMachine::parallelStates()
{
    QStateMachine machine;

    TestState *s1 = new TestState(QState::ParallelStates);
    QCOMPARE(s1->childMode(), QState::ParallelStates);
      TestState *s1_1 = new TestState(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);
      TestState *s1_2 = new TestState(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);
    {
        const QString warning
            = QString::asprintf("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 runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QVERIFY(finishedSpy.isValid());
    globalTick = 0;
    machine.start();
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s2));

    QCOMPARE(s1->events.size(), 2);
    // s1 is entered
    QCOMPARE(s1->events.at(0).first, 0);
    QCOMPARE(s1->events.at(0).second, TestState::Entry);
    // s1_1 is entered
    QCOMPARE(s1_1->events.size(), 2);
    QCOMPARE(s1_1->events.at(0).first, 1);
    QCOMPARE(s1_1->events.at(0).second, TestState::Entry);
    // s1_2 is entered
    QCOMPARE(s1_2->events.at(0).first, 2);
    QCOMPARE(s1_2->events.at(0).second, TestState::Entry);
    // s1_2 is exited
    QCOMPARE(s1_2->events.at(1).first, 3);
    QCOMPARE(s1_2->events.at(1).second, TestState::Exit);
    // s1_1 is exited
    QCOMPARE(s1_1->events.at(1).first, 4);
    QCOMPARE(s1_1->events.at(1).second, TestState::Exit);
    // s1 is exited
    QCOMPARE(s1->events.at(1).first, 5);
    QCOMPARE(s1->events.at(1).second, TestState::Exit);
}

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);
    DEFINE_ACTIVE_SPY(s1);
    QFinalState *s1_f = new QFinalState(s1);
    s1->setInitialState(s1_f);
    QState *s2 = new QState(root);
    DEFINE_ACTIVE_SPY(s2);
    QFinalState *s2_f = new QFinalState(s2);
    s2->setInitialState(s2_f);

    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QVERIFY(startedSpy.isValid());
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QVERIFY(finishedSpy.isValid());
    machine.start();
    QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: "
                                       "Child mode of state machine '' is not 'ExclusiveStates'.");
    QTRY_COMPARE(startedSpy.size(), 1);
    QCOMPARE(machine.configuration().size(), 4);
    QVERIFY(machine.configuration().contains(s1));
    QVERIFY(machine.configuration().contains(s1_f));
    QVERIFY(machine.configuration().contains(s2));
    QVERIFY(machine.configuration().contains(s2_f));
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 1);
    QVERIFY(!machine.isRunning());
}

void tst_QStateMachine::allSourceToTargetConfigurations()
{
    QStateMachine machine;
    QState *s0 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s0);
    s0->setObjectName("s0");
    QState *s1 = new QState(s0);
    DEFINE_ACTIVE_SPY(s1);
    s1->setObjectName("s1");
    QState *s11 = new QState(s1);
    DEFINE_ACTIVE_SPY(s11);
    s11->setObjectName("s11");
    QState *s2 = new QState(s0);
    DEFINE_ACTIVE_SPY(s2);
    s2->setObjectName("s2");
    QState *s21 = new QState(s2);
    DEFINE_ACTIVE_SPY(s21);
    s21->setObjectName("s21");
    QState *s211 = new QState(s21);
    DEFINE_ACTIVE_SPY(s211);
    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 runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QVERIFY(finishedSpy.isValid());
    machine.setInitialState(s0);
    machine.start();
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s0, 1);
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s21, 0);
    TEST_ACTIVE_CHANGED(s211, 0);

    machine.postEvent(new StringEvent("a"));
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s0, 1);
    TEST_ACTIVE_CHANGED(s1, 3);
    TEST_ACTIVE_CHANGED(s11, 3);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s21, 0);
    TEST_ACTIVE_CHANGED(s211, 0);

    machine.postEvent(new StringEvent("b"));
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s0, 1);
    TEST_ACTIVE_CHANGED(s1, 5);
    TEST_ACTIVE_CHANGED(s11, 5);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s21, 0);
    TEST_ACTIVE_CHANGED(s211, 0);

    machine.postEvent(new StringEvent("c"));
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s0, 1);
    TEST_ACTIVE_CHANGED(s1, 6);
    TEST_ACTIVE_CHANGED(s11, 6);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s21, 1);
    TEST_ACTIVE_CHANGED(s211, 1);

    machine.postEvent(new StringEvent("d"));
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s0, 1);
    TEST_ACTIVE_CHANGED(s1, 6);
    TEST_ACTIVE_CHANGED(s11, 6);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s21, 3);
    TEST_ACTIVE_CHANGED(s211, 3);

    machine.postEvent(new StringEvent("e"));
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s0, 3);
    TEST_ACTIVE_CHANGED(s1, 6);
    TEST_ACTIVE_CHANGED(s11, 6);
    TEST_ACTIVE_CHANGED(s2, 3);
    TEST_ACTIVE_CHANGED(s21, 5);
    TEST_ACTIVE_CHANGED(s211, 5);

    machine.postEvent(new StringEvent("f"));
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s0, 3);
    TEST_ACTIVE_CHANGED(s1, 7);
    TEST_ACTIVE_CHANGED(s11, 7);
    TEST_ACTIVE_CHANGED(s2, 4);
    TEST_ACTIVE_CHANGED(s21, 6);
    TEST_ACTIVE_CHANGED(s211, 6);

    machine.postEvent(new StringEvent("g"));
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s0, 3);
    TEST_ACTIVE_CHANGED(s1, 8);
    TEST_ACTIVE_CHANGED(s11, 8);
    TEST_ACTIVE_CHANGED(s2, 5);
    TEST_ACTIVE_CHANGED(s21, 7);
    TEST_ACTIVE_CHANGED(s211, 7);

    machine.postEvent(new StringEvent("h"));
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s0, 4);
    TEST_ACTIVE_CHANGED(s1, 8);
    TEST_ACTIVE_CHANGED(s11, 8);
    TEST_ACTIVE_CHANGED(s2, 6);
    TEST_ACTIVE_CHANGED(s21, 8);
    TEST_ACTIVE_CHANGED(s211, 8);

    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
}

class TestSignalTransition : public QSignalTransition
{
public:
    TestSignalTransition(QState *sourceState = 0)
        : QSignalTransition(sourceState),
          m_eventTestSender(0), m_eventTestSignalIndex(-1),
          m_transitionSender(0), m_transitionSignalIndex(-1)
    {}
    TestSignalTransition(QObject *sender, const char *signal,
                         QAbstractState *target)
        : QSignalTransition(sender, signal),
        m_eventTestSender(0), m_eventTestSignalIndex(-1),
        m_transitionSender(0), m_transitionSignalIndex(-1)
    { setTargetState(target); }
    QObject *eventTestSenderReceived() const {
        return m_eventTestSender;
    }
    int eventTestSignalIndexReceived() const {
        return m_eventTestSignalIndex;
    }
    QVariantList eventTestArgumentsReceived() const {
        return m_eventTestArgs;
    }
    QObject *transitionSenderReceived() const {
        return m_transitionSender;
    }
    int transitionSignalIndexReceived() const {
        return m_transitionSignalIndex;
    }
    QVariantList transitionArgumentsReceived() const {
        return m_transitionArgs;
    }
protected:
    bool eventTest(QEvent *e) override {
        if (!QSignalTransition::eventTest(e))
            return false;
        QStateMachine::SignalEvent *se = static_cast<QStateMachine::SignalEvent*>(e);
        m_eventTestSender = se->sender();
        m_eventTestSignalIndex = se->signalIndex();
        m_eventTestArgs = se->arguments();
        return true;
    }
    void onTransition(QEvent *e) override {
        QSignalTransition::onTransition(e);
        QStateMachine::SignalEvent *se = static_cast<QStateMachine::SignalEvent*>(e);
        m_transitionSender = se->sender();
        m_transitionSignalIndex = se->signalIndex();
        m_transitionArgs = se->arguments();
    }
private:
    QObject *m_eventTestSender;
    int m_eventTestSignalIndex;
    QVariantList m_eventTestArgs;
    QObject *m_transitionSender;
    int m_transitionSignalIndex;
    QVariantList m_transitionArgs;
};

void tst_QStateMachine::signalTransitions()
{
    {
        QStateMachine machine;
        QState *s0 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s0);
        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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();

        emitter.emitSignalWithNoArg();

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        TEST_ACTIVE_CHANGED(s0, 2);
        emitter.emitSignalWithNoArg();

        trans->setSignal(SIGNAL(signalWithIntArg(int)));
        QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithIntArg(int))));
        machine.start();
        QCoreApplication::processEvents();
        emitter.emitSignalWithIntArg(123);
        QTRY_COMPARE(finishedSpy.size(), 2);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        TEST_ACTIVE_CHANGED(s0, 4);

        machine.start();
        QCoreApplication::processEvents();
        trans->setSignal(SIGNAL(signalWithNoArg()));
        QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithNoArg())));
        emitter.emitSignalWithNoArg();
        QTRY_COMPARE(finishedSpy.size(), 3);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        TEST_ACTIVE_CHANGED(s0, 6);

        SignalEmitter emitter2;
        machine.start();
        QCoreApplication::processEvents();
        trans->setSenderObject(&emitter2);
        emitter2.emitSignalWithNoArg();
        QTRY_COMPARE(finishedSpy.size(), 4);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        TEST_ACTIVE_CHANGED(s0, 8);

        machine.start();
        QCoreApplication::processEvents();
        QTest::ignoreMessage(QtWarningMsg, "QSignalTransition: no such signal: SignalEmitter::noSuchSignal()");
        trans->setSignal(SIGNAL(noSuchSignal()));
        QCOMPARE(trans->signal(), QByteArray(SIGNAL(noSuchSignal())));
        TEST_RUNNING_CHANGED(true);
        TEST_ACTIVE_CHANGED(s0, 9);
        QVERIFY(machine.isRunning());
    }
    {
        QStateMachine machine;
        QState *s0 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s0);
        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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 1);
        emitter.emitSignalWithNoArg();

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        TEST_ACTIVE_CHANGED(s0, 2);

        trans->setSignal("signalWithIntArg(int)");
        QCOMPARE(trans->signal(), QByteArray("signalWithIntArg(int)"));
        machine.start();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 3);
        emitter.emitSignalWithIntArg(123);
        QTRY_COMPARE(finishedSpy.size(), 2);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        TEST_ACTIVE_CHANGED(s0, 4);
    }
    {
        QStateMachine machine;
        QState *s0 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s0);
        QFinalState *s1 = new QFinalState(&machine);
        SignalEmitter emitter;
        TestSignalTransition *trans = new TestSignalTransition(&emitter, SIGNAL(signalWithIntArg(int)), s1);
        s0->addTransition(trans);

        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 1);
        emitter.emitSignalWithIntArg(123);

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        TEST_ACTIVE_CHANGED(s0, 2);
        QCOMPARE(trans->eventTestSenderReceived(), (QObject*)&emitter);
        QCOMPARE(trans->eventTestSignalIndexReceived(), emitter.metaObject()->indexOfSignal("signalWithIntArg(int)"));
        QCOMPARE(trans->eventTestArgumentsReceived().size(), 1);
        QCOMPARE(trans->eventTestArgumentsReceived().at(0).toInt(), 123);
        QCOMPARE(trans->transitionSenderReceived(), (QObject*)&emitter);
        QCOMPARE(trans->transitionSignalIndexReceived(), emitter.metaObject()->indexOfSignal("signalWithIntArg(int)"));
        QCOMPARE(trans->transitionArgumentsReceived().size(), 1);
        QCOMPARE(trans->transitionArgumentsReceived().at(0).toInt(), 123);
    }
    {
        QStateMachine machine;
        QState *s0 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s0);
        QFinalState *s1 = new QFinalState(&machine);
        SignalEmitter emitter;
        TestSignalTransition *trans = new TestSignalTransition(&emitter, SIGNAL(signalWithStringArg(QString)), s1);
        s0->addTransition(trans);

        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 1);

        QString testString = QString::fromLatin1("hello");
        emitter.emitSignalWithStringArg(testString);

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        TEST_ACTIVE_CHANGED(s0, 2);
        QCOMPARE(trans->eventTestSenderReceived(), (QObject*)&emitter);
        QCOMPARE(trans->eventTestSignalIndexReceived(), emitter.metaObject()->indexOfSignal("signalWithStringArg(QString)"));
        QCOMPARE(trans->eventTestArgumentsReceived().size(), 1);
        QCOMPARE(trans->eventTestArgumentsReceived().at(0).toString(), testString);
        QCOMPARE(trans->transitionSenderReceived(), (QObject*)&emitter);
        QCOMPARE(trans->transitionSignalIndexReceived(), emitter.metaObject()->indexOfSignal("signalWithStringArg(QString)"));
        QCOMPARE(trans->transitionArgumentsReceived().size(), 1);
        QCOMPARE(trans->transitionArgumentsReceived().at(0).toString(), testString);
    }
    {
        QStateMachine machine;
        QState *s0 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s0);
        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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 1);

        emitter.emitSignalWithNoArg();

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
        TEST_ACTIVE_CHANGED(s0, 2);
    }
    // Multiple transitions for same (object,signal)
    {
        QStateMachine machine;
        SignalEmitter emitter;
        QState *s0 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s0);
        QState *s1 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s1);
        QSignalTransition *t0 = s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), s1);
        QSignalTransition *t1 = s1->addTransition(&emitter, SIGNAL(signalWithNoArg()), s0);

        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 1);
        TEST_ACTIVE_CHANGED(s1, 0);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s0));

        emitter.emitSignalWithNoArg();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 2);
        TEST_ACTIVE_CHANGED(s1, 1);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s1));

        s0->removeTransition(t0);
        emitter.emitSignalWithNoArg();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 3);
        TEST_ACTIVE_CHANGED(s1, 2);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s0));

        emitter.emitSignalWithNoArg();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 3);
        TEST_ACTIVE_CHANGED(s1, 2);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s0));

        s1->removeTransition(t1);
        emitter.emitSignalWithNoArg();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 3);
        TEST_ACTIVE_CHANGED(s1, 2);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s0));

        s0->addTransition(t0);
        s1->addTransition(t1);
        emitter.emitSignalWithNoArg();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 4);
        TEST_ACTIVE_CHANGED(s1, 3);
        QVERIFY(machine.isRunning());
        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);
        DEFINE_ACTIVE_SPY(s0);
        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, &QStateMachine::started);
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(startedSpy.isValid());
        QVERIFY(finishedSpy.isValid());
        QVERIFY(runningSpy.isValid());
        machine.setInitialState(s0);

        machine.start();
        TEST_ACTIVE_CHANGED(s0, 1);
        QTRY_COMPARE(startedSpy.size(), 1);
        TEST_RUNNING_CHANGED(true);
        emitter.emitSignalWithNoArg();
        TEST_ACTIVE_CHANGED(s0, 2);
        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED(false);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s1));

        machine.start();
        TEST_ACTIVE_CHANGED(s0, 3);
        QTRY_COMPARE(startedSpy.size(), 2);
        TEST_RUNNING_CHANGED(true);
        emitter.emitSignalWithIntArg(123);
        TEST_ACTIVE_CHANGED(s0, 4);
        QTRY_COMPARE(finishedSpy.size(), 2);
        TEST_RUNNING_CHANGED(false);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s2));

        machine.start();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 5);
        QTRY_COMPARE(startedSpy.size(), 3);
        TEST_RUNNING_CHANGED(true);
        emitter.emitSignalWithStringArg("hello");
        TEST_ACTIVE_CHANGED(s0, 6);
        QTRY_COMPARE(finishedSpy.size(), 3);
        TEST_RUNNING_CHANGED(false);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s3));
    }
    // signature normalization
    {
        QStateMachine machine;
        SignalEmitter emitter;
        QState *s0 = new QState(&machine);
        DEFINE_ACTIVE_SPY(s0);
        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(QString)), s1);
        QVERIFY(t1 != 0);
        QCOMPARE(t1->signal(), QByteArray(SIGNAL(signalWithStringArg(QString))));

        QSignalSpy startedSpy(&machine, &QStateMachine::started);
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(startedSpy.isValid());
        QVERIFY(finishedSpy.isValid());
        QVERIFY(runningSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 1);
        QTRY_COMPARE(startedSpy.size(), 1);
        QCOMPARE(finishedSpy.size(), 0);
        TEST_RUNNING_CHANGED(true);

        emitter.emitSignalWithNoArg();

        TEST_ACTIVE_CHANGED(s0, 2);
        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED(false);
    }
}

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) override {
        if (!QEventTransition::eventTest(e))
            return false;
        QStateMachine::WrappedEvent *we = static_cast<QStateMachine::WrappedEvent*>(e);
        m_eventSource = we->object();
        m_eventType = we->event()->type();
        return true;
    }
private:
    QObject *m_eventSource;
    QEvent::Type m_eventType;
};

#ifndef QT_NO_WIDGETS
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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();

        QTest::mousePress(&button, Qt::LeftButton);
        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;

        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.size(), 2);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;

        machine.start();
        QCoreApplication::processEvents();
        trans->setEventType(QEvent::MouseButtonPress);
        QTest::mousePress(&button, Qt::LeftButton);
        QTRY_COMPARE(finishedSpy.size(), 3);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;

        QPushButton button2;
        machine.start();
        QCoreApplication::processEvents();
        trans->setEventSource(&button2);
        QTest::mousePress(&button2, Qt::LeftButton);
        QTRY_COMPARE(finishedSpy.size(), 4);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
    }
    for (int x = 0; x < 2; ++x) {
        QStateMachine machine;
        QState *s0 = new QState(&machine);
        QFinalState *s1 = new QFinalState(&machine);

        QEventTransition *trans = 0;
        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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();

        QTest::mousePress(&button, Qt::LeftButton);
        QCoreApplication::processEvents();

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
    }
    {
        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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        TEST_RUNNING_CHANGED(true);
        QTest::mousePress(&button, Qt::LeftButton);
        QCoreApplication::processEvents();

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED(false);
    }

    {
        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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        TEST_RUNNING_CHANGED(true);

        QTest::keyPress(&button, Qt::Key_A);
        QCoreApplication::processEvents();

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED(false);
    }
    {
        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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        TEST_RUNNING_CHANGED(true);

        QTest::keyPress(&button, Qt::Key_A);
        QCoreApplication::processEvents();

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED(false);
    }
    // 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, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        QTRY_COMPARE(machine.configuration().size(), 1);
        QTRY_VERIFY(machine.configuration().contains(s0));

        QTest::mousePress(&button, Qt::LeftButton);
        QCoreApplication::processEvents();
        QTRY_COMPARE(machine.configuration().size(), 1);
        QTRY_VERIFY(machine.configuration().contains(s1));

        s0->removeTransition(t0);
        QTest::mousePress(&button, Qt::LeftButton);
        QCoreApplication::processEvents();
        QTRY_COMPARE(machine.configuration().size(), 1);
        QTRY_VERIFY(machine.configuration().contains(s0));

        QTest::mousePress(&button, Qt::LeftButton);
        QCoreApplication::processEvents();
        QTRY_COMPARE(machine.configuration().size(), 1);
        QTRY_VERIFY(machine.configuration().contains(s0));

        s1->removeTransition(t1);
        QTest::mousePress(&button, Qt::LeftButton);
        QCoreApplication::processEvents();
        QTRY_COMPARE(machine.configuration().size(), 1);
        QTRY_VERIFY(machine.configuration().contains(s0));

        s0->addTransition(t0);
        s1->addTransition(t1);
        QTest::mousePress(&button, Qt::LeftButton);
        QCoreApplication::processEvents();
        QTRY_COMPARE(machine.configuration().size(), 1);
        QTRY_VERIFY(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, &QStateMachine::started);
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(startedSpy.isValid());
        QVERIFY(finishedSpy.isValid());
        QVERIFY(runningSpy.isValid());
        machine.setInitialState(s0);

        machine.start();
        QTRY_COMPARE(startedSpy.size(), 1);
        TEST_RUNNING_CHANGED(true);
        QTest::mousePress(&button, Qt::LeftButton);
        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED(false);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s1));

        machine.start();
        QTRY_COMPARE(startedSpy.size(), 2);
        TEST_RUNNING_CHANGED(true);
        QTest::mouseRelease(&button, Qt::LeftButton);
        QTRY_COMPARE(finishedSpy.size(), 2);
        TEST_RUNNING_CHANGED(false);
        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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy startedSpy(&machine, &QStateMachine::started);
        QVERIFY(startedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QTest::ignoreMessage(QtWarningMsg, "QObject event transitions are not supported for custom types");
        QTRY_COMPARE(startedSpy.size(), 1);
        TEST_RUNNING_CHANGED(true);
    }
    // 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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.setInitialState(s0);
        machine.start();
        QCoreApplication::processEvents();
        TEST_RUNNING_CHANGED(true);

        QTest::mousePress(&button, Qt::LeftButton);
        QCoreApplication::processEvents();

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED(false);

        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, &QStateMachine::started);
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(startedSpy.isValid());
    QVERIFY(finishedSpy.isValid());
    QVERIFY(runningSpy.isValid());
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 1);
    QCOMPARE(finishedSpy.size(), 0);
    TEST_RUNNING_CHANGED(true);
    QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMouseMove);
    scene.sendEvent(textItem, &mouseEvent);
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED(false);
}
#endif

void tst_QStateMachine::historyStates()
{
    for (int x = 0; x < 2; ++x) {
        QStateMachine machine;
        QState *root = &machine;
          QState *s0 = new QState(root);
          DEFINE_ACTIVE_SPY(s0);
            QState *s00 = new QState(s0);
            DEFINE_ACTIVE_SPY(s00);
            QState *s01 = new QState(s0);
            DEFINE_ACTIVE_SPY(s01);
            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);
            const QString warning
                = QString::asprintf("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);
          DEFINE_ACTIVE_SPY(s1);
          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 runningSpy(&machine, &QStateMachine::runningChanged);
        QVERIFY(runningSpy.isValid());
        QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
        QVERIFY(finishedSpy.isValid());
        machine.start();
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 1);
        TEST_ACTIVE_CHANGED(s00, 1);
        TEST_ACTIVE_CHANGED(s01, 0);
        TEST_ACTIVE_CHANGED(s1, 0);
        QCOMPARE(machine.configuration().size(), 2);
        QVERIFY(machine.configuration().contains(s0));
        QVERIFY(machine.configuration().contains(s00));

        machine.postEvent(new StringEvent("a"));
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 1);
        TEST_ACTIVE_CHANGED(s00, 2);
        TEST_ACTIVE_CHANGED(s01, 1);
        TEST_ACTIVE_CHANGED(s1, 0);
        QCOMPARE(machine.configuration().size(), 2);
        QVERIFY(machine.configuration().contains(s0));
        QVERIFY(machine.configuration().contains(s01));

        machine.postEvent(new StringEvent("b"));
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 2);
        TEST_ACTIVE_CHANGED(s00, 2);
        TEST_ACTIVE_CHANGED(s01, 2);
        TEST_ACTIVE_CHANGED(s1, 1);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s1));

        machine.postEvent(new StringEvent("c"));
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 3);
        TEST_ACTIVE_CHANGED(s00, 2);
        TEST_ACTIVE_CHANGED(s01, 3);
        TEST_ACTIVE_CHANGED(s1, 2);
        QCOMPARE(machine.configuration().size(), 2);
        QVERIFY(machine.configuration().contains(s0));
        QVERIFY(machine.configuration().contains(s01));

        machine.postEvent(new StringEvent("d"));
        QCoreApplication::processEvents();
        TEST_ACTIVE_CHANGED(s0, 4);
        TEST_ACTIVE_CHANGED(s00, 2);
        TEST_ACTIVE_CHANGED(s01, 4);
        TEST_ACTIVE_CHANGED(s1, 2);
        QCOMPARE(machine.configuration().size(), 1);
        QVERIFY(machine.configuration().contains(s2));

        QTRY_COMPARE(finishedSpy.size(), 1);
        TEST_RUNNING_CHANGED_STARTED_STOPPED;
    }
}

void tst_QStateMachine::startAndStop()
{
    QStateMachine machine;
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QSignalSpy stoppedSpy(&machine, &QStateMachine::stopped);
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);

    QVERIFY(startedSpy.isValid());
    QVERIFY(stoppedSpy.isValid());
    QVERIFY(finishedSpy.isValid());
    QVERIFY(runningSpy.isValid());

    QVERIFY(!machine.isRunning());
    QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start: No initial state set for machine. Refusing to start.");
    machine.start();
    QCOMPARE(startedSpy.size(), 0);
    QCOMPARE(stoppedSpy.size(), 0);
    QCOMPARE(finishedSpy.size(), 0);
    QCOMPARE(runningSpy.size(), 0);
    QVERIFY(!machine.isRunning());
    machine.stop();
    QCOMPARE(startedSpy.size(), 0);
    QCOMPARE(stoppedSpy.size(), 0);
    QCOMPARE(finishedSpy.size(), 0);
    QCOMPARE(runningSpy.size(), 0);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    QTRY_COMPARE(machine.isRunning(), true);
    QTRY_COMPARE(startedSpy.size(), 1);
    QCOMPARE(stoppedSpy.size(), 0);
    QCOMPARE(finishedSpy.size(), 0);
    TEST_RUNNING_CHANGED(true);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));

    QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start(): already running");
    machine.start();
    QCOMPARE(runningSpy.size(), 0);

    machine.stop();
    TEST_ACTIVE_CHANGED(s1, 1);
    QTRY_COMPARE(machine.isRunning(), false);
    QTRY_COMPARE(stoppedSpy.size(), 1);
    QCOMPARE(startedSpy.size(), 1);
    QCOMPARE(finishedSpy.size(), 0);
    TEST_RUNNING_CHANGED(false);

    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));

    machine.start();
    TEST_ACTIVE_CHANGED(s1, 3);
    machine.stop();
    TEST_ACTIVE_CHANGED(s1, 3);
    QTRY_COMPARE(startedSpy.size(), 2);
    QTRY_COMPARE(stoppedSpy.size(), 2);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
}

void tst_QStateMachine::setRunning()
{
    QStateMachine machine;
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QSignalSpy stoppedSpy(&machine, &QStateMachine::stopped);
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);

    QVERIFY(startedSpy.isValid());
    QVERIFY(stoppedSpy.isValid());
    QVERIFY(finishedSpy.isValid());
    QVERIFY(runningSpy.isValid());

    QVERIFY(!machine.isRunning());
    QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start: No initial state set for machine. Refusing to start.");
    machine.setRunning(true);
    QCOMPARE(startedSpy.size(), 0);
    QCOMPARE(stoppedSpy.size(), 0);
    QCOMPARE(finishedSpy.size(), 0);
    QCOMPARE(runningSpy.size(), 0);
    QVERIFY(!machine.isRunning());
    machine.setRunning(false);
    QCOMPARE(startedSpy.size(), 0);
    QCOMPARE(stoppedSpy.size(), 0);
    QCOMPARE(finishedSpy.size(), 0);
    QCOMPARE(runningSpy.size(), 0);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    machine.setRunning(true);
    TEST_ACTIVE_CHANGED(s1, 1);
    QTRY_COMPARE(machine.isRunning(), true);
    QTRY_COMPARE(startedSpy.size(), 1);
    QCOMPARE(stoppedSpy.size(), 0);
    QCOMPARE(finishedSpy.size(), 0);
    TEST_RUNNING_CHANGED(true);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));

    QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start(): already running");
    machine.setRunning(true);
    TEST_ACTIVE_CHANGED(s1, 1);
    QCOMPARE(runningSpy.size(), 0);

    machine.setRunning(false);
    TEST_ACTIVE_CHANGED(s1, 1);
    QTRY_COMPARE(machine.isRunning(), false);
    QTRY_COMPARE(stoppedSpy.size(), 1);
    QCOMPARE(startedSpy.size(), 1);
    QCOMPARE(finishedSpy.size(), 0);
    TEST_RUNNING_CHANGED(false);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));

    machine.setRunning(false);
    QCOMPARE(runningSpy.size(), 0);
    TEST_ACTIVE_CHANGED(s1, 1);

    machine.start();
    TEST_ACTIVE_CHANGED(s1, 3);
    machine.setRunning(false);
    TEST_ACTIVE_CHANGED(s1, 3);
    QTRY_COMPARE(startedSpy.size(), 2);
    QTRY_COMPARE(stoppedSpy.size(), 2);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    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.setRunning(false);
    QCOMPARE(runningSpy.size(), 0);
    machine.setRunning(true);
    TEST_ACTIVE_CHANGED(s1, 6);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    QTRY_COMPARE(startedSpy.size(), 3);
    QCOMPARE(stoppedSpy.size(), 2);
    QCOMPARE(finishedSpy.size(), 1);
}

void tst_QStateMachine::targetStateWithNoParent()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    s1->setObjectName("s1");
    QState s2;
    s1->addTransition(&s2);
    machine.setInitialState(s1);
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QSignalSpy stoppedSpy(&machine, &QStateMachine::stopped);
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);

    QVERIFY(startedSpy.isValid());
    QVERIFY(stoppedSpy.isValid());
    QVERIFY(finishedSpy.isValid());
    QVERIFY(runningSpy.isValid());

    machine.start();
    QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: "
                                       "Child mode of state machine '' is not 'ExclusiveStates'.");
    TEST_ACTIVE_CHANGED(s1, 2);
    QTRY_COMPARE(startedSpy.size(), 1);
    QCOMPARE(machine.isRunning(), false);
    QCOMPARE(stoppedSpy.size(), 1);
    QCOMPARE(finishedSpy.size(), 0);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    QCOMPARE(machine.error(), QStateMachine::StateMachineChildModeSetToParallelError);
}

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);
    DEFINE_ACTIVE_SPY(s1);
    s1->assignProperty(propertyHolder, "a", 3);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(propertyHolder, "b", 4);

    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);

    s1->addTransition(new EventTransition(QEvent::User, s2));
    s2->addTransition(new EventTransition(QEvent::User, s3));

    machine.setInitialState(s1);
    machine.start();
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    QCOMPARE(propertyHolder->property("a").toInt(), 3);
    QCOMPARE(propertyHolder->property("b").toInt(), 2);

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);
    QCOMPARE(propertyHolder->property("a").toInt(), 3);
    QCOMPARE(propertyHolder->property("b").toInt(), 4);

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    QCOMPARE(propertyHolder->property("a").toInt(), 3);
    QCOMPARE(propertyHolder->property("b").toInt(), 4);
}

void tst_QStateMachine::noInitialStateForInitialState()
{
    QStateMachine machine;

    QState *initialState = new QState(&machine);
    DEFINE_ACTIVE_SPY(initialState);
    initialState->setObjectName("initialState");
    machine.setInitialState(initialState);

    QState *childState = new QState(initialState);
    DEFINE_ACTIVE_SPY(childState);
    (void)childState;

    QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: "
                                       "Missing initial state in compound state 'initialState'");
    machine.start();
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(initialState, 1);
    TEST_ACTIVE_CHANGED(childState, 0);
    QCOMPARE(machine.isRunning(), false);
    QCOMPARE(int(machine.error()), int(QStateMachine::NoInitialStateError));
}

void tst_QStateMachine::globalRestorePolicySetToDontRestore()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::DontRestoreProperties);

    QObject *propertyHolder = new QObject(&machine);
    propertyHolder->setProperty("a", 1);
    propertyHolder->setProperty("b", 2);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    s1->assignProperty(propertyHolder, "a", 3);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(propertyHolder, "b", 4);

    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);

    s1->addTransition(new EventTransition(QEvent::User, s2));
    s2->addTransition(new EventTransition(QEvent::User, s3));

    machine.setInitialState(s1);
    machine.start();
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    QCOMPARE(propertyHolder->property("a").toInt(), 3);
    QCOMPARE(propertyHolder->property("b").toInt(), 2);

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);
    QCOMPARE(propertyHolder->property("a").toInt(), 3);
    QCOMPARE(propertyHolder->property("b").toInt(), 4);

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    QCOMPARE(propertyHolder->property("a").toInt(), 3);
    QCOMPARE(propertyHolder->property("b").toInt(), 4);
}

void tst_QStateMachine::globalRestorePolicySetToRestore()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);

    QObject *propertyHolder = new QObject(&machine);
    propertyHolder->setProperty("a", 1);
    propertyHolder->setProperty("b", 2);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    s1->assignProperty(propertyHolder, "a", 3);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(propertyHolder, "b", 4);

    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);

    s1->addTransition(new EventTransition(QEvent::User, s2));
    s2->addTransition(new EventTransition(QEvent::User, s3));

    machine.setInitialState(s1);
    machine.start();
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    QCOMPARE(propertyHolder->property("a").toInt(), 3);
    QCOMPARE(propertyHolder->property("b").toInt(), 2);

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);
    QCOMPARE(propertyHolder->property("a").toInt(), 1);
    QCOMPARE(propertyHolder->property("b").toInt(), 4);

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    QCOMPARE(propertyHolder->property("a").toInt(), 1);
    QCOMPARE(propertyHolder->property("b").toInt(), 2);
}

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);
    DEFINE_ACTIVE_SPY(s1);
    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    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);
    DEFINE_ACTIVE_SPY(s3);
    s2->addTransition(animation, SIGNAL(finished()), s3);
    QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit()));

    machine.setInitialState(s1);
    machine.start();
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);

    machine.postEvent(new QEvent(QEvent::User));
    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    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);
    DEFINE_ACTIVE_SPY(s1);
    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    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);
    DEFINE_ACTIVE_SPY(s3);
    QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit()));
    s2->addTransition(s2, SIGNAL(propertiesAssigned()), s3);

    machine.setInitialState(s1);
    machine.start();
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);

    machine.postEvent(new QEvent(QEvent::User));
    QCOREAPPLICATION_EXEC(5000);
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());

    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);
    DEFINE_ACTIVE_SPY(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    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);
    DEFINE_ACTIVE_SPY(s3);
    QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit()));
    s2->addTransition(fooAnimation, SIGNAL(finished()), s3);

    QState *s4 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s4);
    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);
    DEFINE_ACTIVE_SPY(s5);
    QObject::connect(s5, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit()));
    s4->addTransition(fooAnimation2, SIGNAL(finished()), s5);

    machine.setInitialState(s1);
    machine.start();
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    TEST_ACTIVE_CHANGED(s4, 0);
    TEST_ACTIVE_CHANGED(s5, 0);

    machine.postEvent(new QEvent(QEvent::User));
    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    TEST_ACTIVE_CHANGED(s4, 0);
    TEST_ACTIVE_CHANGED(s5, 0);
    QVERIFY(machine.configuration().contains(s3));
    QCOMPARE(object->property("foo").toDouble(), 5.0);

    machine.postEvent(new QEvent(QEvent::User));
    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    TEST_ACTIVE_CHANGED(s4, 2);
    TEST_ACTIVE_CHANGED(s5, 1);
    QVERIFY(machine.isRunning());
    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);
    DEFINE_ACTIVE_SPY(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    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);
    DEFINE_ACTIVE_SPY(s3);
    QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit()));
    s2->addTransition(fooAnimation, SIGNAL(finished()), s3);

    QState *s4 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s4);
    s4->assignProperty(object, "foo", 2.0);
    trans = new EventTransition(QEvent::User, s4);
    s3->addTransition(trans);
    trans->addAnimation(fooAnimation);

    QState *s5 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s5);
    QObject::connect(s5, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit()));
    s4->addTransition(fooAnimation, SIGNAL(finished()), s5);

    machine.setInitialState(s1);
    machine.start();
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    TEST_ACTIVE_CHANGED(s4, 0);
    TEST_ACTIVE_CHANGED(s5, 0);
    machine.postEvent(new QEvent(QEvent::User));
    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    TEST_ACTIVE_CHANGED(s4, 0);
    TEST_ACTIVE_CHANGED(s5, 0);
    QVERIFY(machine.configuration().contains(s3));
    QCOMPARE(object->property("foo").toDouble(), 5.0);

    machine.postEvent(new QEvent(QEvent::User));
    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    TEST_ACTIVE_CHANGED(s4, 2);
    TEST_ACTIVE_CHANGED(s5, 1);
    QVERIFY(machine.isRunning());
    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);
    DEFINE_ACTIVE_SPY(s1);
    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);

    s2->assignProperty(object, "foo", 2.0);

    QState *s2Child = new QState(s2);
    DEFINE_ACTIVE_SPY(s2Child);
    s2Child->assignProperty(object, "bar", 10.0);
    s2->setInitialState(s2Child);

    QState *s2Child2 = new QState(s2);
    DEFINE_ACTIVE_SPY(s2Child2);
    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);
    DEFINE_ACTIVE_SPY(s3);
    s2->addTransition(s2Child, SIGNAL(propertiesAssigned()), s3);

    QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit()));

    machine.setInitialState(s1);
    machine.start();
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s2Child, 0);
    TEST_ACTIVE_CHANGED(s2Child2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    machine.postEvent(new QEvent(QEvent::User));

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s2Child, 1);
    TEST_ACTIVE_CHANGED(s2Child2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);

    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s2Child, 2);
    TEST_ACTIVE_CHANGED(s2Child2, 0);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    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);
    DEFINE_ACTIVE_SPY(s1);
    s1->assignProperty(object, "foo", 123);
    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(object, "foo", 456);
    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);
    s3->assignProperty(object, "foo", 789);
    QFinalState *s4 = new QFinalState(&machine);

    QParallelAnimationGroup animationGroup;
    animationGroup.addAnimation(new QPropertyAnimation(object, "foo"));
    QSignalSpy animationFinishedSpy(&animationGroup, &QParallelAnimationGroup::finished);
    QVERIFY(animationFinishedSpy.isValid());
    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 runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy machineFinishedSpy(&machine, &QStateMachine::finished);
    QVERIFY(machineFinishedSpy.isValid());
    machine.start();
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    QTRY_COMPARE(machineFinishedSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    QVERIFY(!machine.isRunning());
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s4));
    QCOMPARE(object->property("foo").toInt(), 789);
    QCOMPARE(animationFinishedSpy.size(), 2);

}

void tst_QStateMachine::animatedGlobalRestoreProperty()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);

    QObject *object = new QObject(&machine);
    object->setProperty("foo", 1.0);

    SlotCalledCounter counter;

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(object, "foo", 2.0);

    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);

    QState *s4 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s4);
    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();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    TEST_ACTIVE_CHANGED(s4, 0);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);
    TEST_ACTIVE_CHANGED(s4, 0);

    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    TEST_ACTIVE_CHANGED(s4, 1);
    QVERIFY(machine.isRunning());
    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);
    DEFINE_ACTIVE_SPY(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    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);
    DEFINE_ACTIVE_SPY(s3);
    QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit()));
    s2->addTransition(anim, SIGNAL(finished()), s3);

    machine.setInitialState(s1);
    machine.start();
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);

    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    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);
    DEFINE_ACTIVE_SPY(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(object, "foo", 2.0);

    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);
    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();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);

    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    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);
    DEFINE_ACTIVE_SPY(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(object, "foo", 2.0);

    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);
    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();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);

    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    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);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(object, "foo", 2.0);

    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);
    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();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);

    QCOREAPPLICATION_EXEC(5000);

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    QVERIFY(machine.configuration().contains(s3));
    QCOMPARE(counter.counter, 2); // specific animation started and stopped

    delete defaultAnimation;
    delete moreSpecificAnimation;
}

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);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);

    QState *parallelState = new QState(QState::ParallelStates, &machine);
    parallelState->assignProperty(propertyHolder, "foo", 321);

    QState *s2 = new QState(parallelState);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(propertyHolder, "bar", 654);

    QState *s3 = new QState(parallelState);
    DEFINE_ACTIVE_SPY(s3);
    s3->assignProperty(propertyHolder, "zoot", 987);

    s1->addTransition(new EventTransition(QEvent::User, parallelState));
    machine.start();
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);

    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();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    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);
    DEFINE_ACTIVE_SPY(parallelState);
    machine.setInitialState(parallelState);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    parallelState->addTransition(new EventTransition(QEvent::User, s1));

    machine.start();
    QCoreApplication::processEvents();
    TEST_ACTIVE_CHANGED(parallelState, 1);
    TEST_ACTIVE_CHANGED(s1, 0);

    QCOMPARE(1, machine.configuration().size());
    QVERIFY(machine.configuration().contains(parallelState));

    machine.postEvent(new QEvent(QEvent::User));
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(parallelState, 2);
    TEST_ACTIVE_CHANGED(s1, 1);
    QVERIFY(machine.isRunning());
    QCOMPARE(1, machine.configuration().size());
    QVERIFY(machine.configuration().contains(s1));
}

void tst_QStateMachine::parallelStateTransition()
{
    // This test checks if the parallel state is exited and re-entered if one compound state
    // is exited and subsequently re-entered. When the parallel state is exited, the other compound
    // state in the parallel state has to be exited too. When the parallel state is re-entered, the
    // other state also needs to be re-entered.

    QStateMachine machine;

    QState *parallelState = new QState(QState::ParallelStates, &machine);
    parallelState->setObjectName("parallelState");
    DEFINE_ACTIVE_SPY(parallelState);
    machine.setInitialState(parallelState);

    QState *s1 = new QState(parallelState);
    s1->setObjectName("s1");
    DEFINE_ACTIVE_SPY(s1);
    QState *s2 = new QState(parallelState);
    s2->setObjectName("s2");
    DEFINE_ACTIVE_SPY(s2);

    QState *s1InitialChild = new QState(s1);
    s1InitialChild->setObjectName("s1InitialChild");
    DEFINE_ACTIVE_SPY(s1InitialChild);
    s1->setInitialState(s1InitialChild);

    QState *s2InitialChild = new QState(s2);
    s2InitialChild->setObjectName("s2InitialChild");
    DEFINE_ACTIVE_SPY(s2InitialChild);
    s2->setInitialState(s2InitialChild);

    QState *s1OtherChild = new QState(s1);
    s1OtherChild->setObjectName("s1OtherChild");
    DEFINE_ACTIVE_SPY(s1OtherChild);

    // The following transition will exit s1 (which means that parallelState is also exited), and
    // subsequently re-entered (which means that parallelState is also re-entered).
    EventTransition *et = new EventTransition(QEvent::User, s1OtherChild);
    et->setObjectName("s1->s1OtherChild");
    s1->addTransition(et);

    machine.start();
    QCoreApplication::processEvents();

    // Initial entrance of the parallel state and its sub-states:
    TEST_ACTIVE_CHANGED(parallelState, 1);
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s1InitialChild, 1);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s2InitialChild, 1);
    TEST_ACTIVE_CHANGED(s1OtherChild, 0);

    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();

    TEST_ACTIVE_CHANGED(parallelState, 3); // initial + exit + entry
    TEST_ACTIVE_CHANGED(s1, 3); // initial + exit + entry
    TEST_ACTIVE_CHANGED(s1InitialChild, 2); // initial + exit
    TEST_ACTIVE_CHANGED(s2, 3); // initial + exit due to parent exit + entry due to parent re-entry
    TEST_ACTIVE_CHANGED(s2InitialChild, 3); // initial + exit due to parent exit + re-entry due to parent re-entry
    TEST_ACTIVE_CHANGED(s1OtherChild, 1); // entry due to transition
    QVERIFY(machine.isRunning());

    // Check that s1InitialChild is not in the configuration, because although s1 is re-entered,
    // another child state (s1OtherChild) is active, so the initial child should not be activated.
    QVERIFY(machine.configuration().contains(parallelState));
    QVERIFY(machine.configuration().contains(s1));
    QVERIFY(machine.configuration().contains(s2));
    QVERIFY(machine.configuration().contains(s1OtherChild));
    QVERIFY(machine.configuration().contains(s2InitialChild));
    QCOMPARE(machine.configuration().size(), 5);
}

void tst_QStateMachine::nestedRestoreProperties()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);

    QObject *propertyHolder = new QObject(&machine);
    propertyHolder->setProperty("foo", 1);
    propertyHolder->setProperty("bar", 2);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(propertyHolder, "foo", 3);

    QState *s21 = new QState(s2);
    DEFINE_ACTIVE_SPY(s21);
    s21->assignProperty(propertyHolder, "bar", 4);
    s2->setInitialState(s21);

    QState *s22 = new QState(s2);
    DEFINE_ACTIVE_SPY(s22);
    s22->assignProperty(propertyHolder, "bar", 5);

    s1->addTransition(new EventTransition(QEvent::User, s2));
    s21->addTransition(new EventTransition(QEvent::User, s22));

    machine.start();
    QCoreApplication::processEvents();

    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s21, 0);
    TEST_ACTIVE_CHANGED(s22, 0);
    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();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s21, 1);
    TEST_ACTIVE_CHANGED(s22, 0);
    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();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s21, 2);
    TEST_ACTIVE_CHANGED(s22, 1);
    QVERIFY(machine.isRunning());
    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(QState::RestoreProperties);

    QObject *propertyHolder = new QObject(&machine);
    propertyHolder->setProperty("foo", 1);
    propertyHolder->setProperty("bar", 2);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(propertyHolder, "foo", 3);

    QState *s21 = new QState(s2);
    DEFINE_ACTIVE_SPY(s21);
    s21->assignProperty(propertyHolder, "bar", 4);
    s2->setInitialState(s21);

    QState *s22 = new QState(s2);
    DEFINE_ACTIVE_SPY(s22);
    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();

    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s21, 0);
    TEST_ACTIVE_CHANGED(s22, 0);
    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();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s21, 1);
    TEST_ACTIVE_CHANGED(s22, 0);
    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();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s21, 2);
    TEST_ACTIVE_CHANGED(s22, 1);
    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();

    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s21, 3);
    TEST_ACTIVE_CHANGED(s22, 2);
    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);
    DEFINE_ACTIVE_SPY(group);
    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, &QStateMachine::started);
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(startedSpy.isValid());
    QVERIFY(finishedSpy.isValid());
    QVERIFY(runningSpy.isValid());
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 1);
    TEST_RUNNING_CHANGED(true);
    QTRY_COMPARE(machine.configuration().size(), 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
    TEST_ACTIVE_CHANGED(group, 1);

    for (int i = 0; i < 3; ++i)
        subMachines[i]->postEvent(new QEvent(QEvent::User));

    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED(false);
    TEST_ACTIVE_CHANGED(group, 2);
}

void tst_QStateMachine::goToState()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    QState *s2 = new QState(&machine);
    machine.setInitialState(s1);
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QVERIFY(startedSpy.isValid());
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 1);
    TEST_RUNNING_CHANGED(true);

    QStateMachinePrivate::get(&machine)->goToState(s2);
    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(s2));

    QStateMachinePrivate::get(&machine)->goToState(s2);
    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(s2));

    QStateMachinePrivate::get(&machine)->goToState(s1);
    QStateMachinePrivate::get(&machine)->goToState(s2);
    QStateMachinePrivate::get(&machine)->goToState(s1);
    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(s2));

    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(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);
    QTRY_COMPARE(machine.configuration().size(), 2);
    QTRY_VERIFY(machine.configuration().contains(s2));
    QTRY_VERIFY(machine.configuration().contains(s2_1));
}

void tst_QStateMachine::goToStateFromSourceWithTransition()
{
    // QTBUG-21813
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    s1->addTransition(new QSignalTransition);
    QState *s2 = new QState(&machine);
    machine.setInitialState(s1);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QVERIFY(startedSpy.isValid());
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 1);
    TEST_RUNNING_CHANGED(true);

    QStateMachinePrivate::get(&machine)->goToState(s2);
    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(s2));
}

class CloneSignalTransition : public QSignalTransition
{
public:
    CloneSignalTransition(QObject *sender, const char *signal, QAbstractState *target)
        : QSignalTransition(sender, signal)
    {
        setTargetState(target);
    }

    void onTransition(QEvent *e) override
    {
        QSignalTransition::onTransition(e);
        QStateMachine::SignalEvent *se = static_cast<QStateMachine::SignalEvent*>(e);
        eventSignalIndex = se->signalIndex();
    }

    int eventSignalIndex;
};

void tst_QStateMachine::clonedSignals()
{
    SignalEmitter emitter;
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    CloneSignalTransition *t1 = new CloneSignalTransition(&emitter, SIGNAL(signalWithDefaultArg()), s2);
    s1->addTransition(t1);

    machine.setInitialState(s1);
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    machine.start();
    QVERIFY(startedSpy.wait());

    QSignalSpy transitionSpy(t1, &CloneSignalTransition::triggered);
    emitter.emitSignalWithDefaultArg();
    QTRY_COMPARE(transitionSpy.size(), 1);

    QCOMPARE(t1->eventSignalIndex, emitter.metaObject()->indexOfSignal("signalWithDefaultArg()"));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    QVERIFY(machine.isRunning());
}

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() override
    {
        exec();
    }
private Q_SLOTS:
    void postEvent()
    {
        m_machine->postEvent(new QEvent(QEvent::User));
        if (++m_count < 1000)
            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 runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QVERIFY(finishedSpy.isValid());
    machine.start();
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
}

#ifndef QT_NO_WIDGETS
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();

    QTRY_COMPARE(machine.configuration().size(), 1);
    QTRY_VERIFY(machine.configuration().contains(s2));
}
#endif

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<QVariant>());
    Q_UNUSED(signalEvent);
}

void tst_QStateMachine::stopInTransitionToFinalState()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    QFinalState *s2 = new QFinalState(&machine);
    QAbstractTransition *t1 = s1->addTransition(s2);
    machine.setInitialState(s1);

    QObject::connect(t1, SIGNAL(triggered()), &machine, SLOT(stop()));
    QSignalSpy stoppedSpy(&machine, &QStateMachine::stopped);
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QSignalSpy s2EnteredSpy(s2, &QFinalState::entered);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(stoppedSpy.isValid());
    QVERIFY(finishedSpy.isValid());
    QVERIFY(s2EnteredSpy.isValid());
    QVERIFY(runningSpy.isValid());
    machine.start();
    // Stopping should take precedence over finished.
    QTRY_COMPARE(stoppedSpy.size(), 1);
    QCOMPARE(finishedSpy.size(), 0);
    QCOMPARE(s2EnteredSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s2));
    TEST_ACTIVE_CHANGED(s1, 2);
}

class StopInEventTestTransition : public QAbstractTransition
{
public:
    bool eventTest(QEvent *e) override
    {
        if (e->type() == QEvent::User)
            machine()->stop();
        return false;
    }
    void onTransition(QEvent *) override
    { }
};

void tst_QStateMachine::stopInEventTest_data()
{
    QTest::addColumn<int>("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);
    DEFINE_ACTIVE_SPY(s1);
    s1->addTransition(new StopInEventTestTransition());
    machine.setInitialState(s1);

    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QVERIFY(startedSpy.isValid());
    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    QTRY_COMPARE(startedSpy.size(), 1);
    TEST_RUNNING_CHANGED(true);

    QSignalSpy stoppedSpy(&machine, &QStateMachine::stopped);
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QVERIFY(stoppedSpy.isValid());
    QVERIFY(finishedSpy.isValid());
    machine.postEvent(new QEvent(QEvent::User), QStateMachine::EventPriority(eventPriority));

    QTRY_COMPARE(stoppedSpy.size(), 1);
    QCOMPARE(finishedSpy.size(), 0);
    TEST_RUNNING_CHANGED(false);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));
    TEST_ACTIVE_CHANGED(s1, 1);
}

class IncrementReceiversTest : public QObject
{
    Q_OBJECT
signals:
    void mySignal();
public:
    virtual void connectNotify(const QMetaMethod &signal) override
    {
        signalList.append(signal);
    }

    QList<QMetaMethod> signalList;
};

void tst_QStateMachine::testIncrementReceivers()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    QFinalState *s2 = new QFinalState(&machine);

    IncrementReceiversTest testObject;
    s1->addTransition(&testObject, SIGNAL(mySignal()), s2);

    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    machine.start();
    TEST_RUNNING_CHANGED(true);

    QMetaObject::invokeMethod(&testObject, "mySignal", Qt::QueuedConnection);

    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED(false);
    QCOMPARE(testObject.signalList.size(), 1);
    QCOMPARE(testObject.signalList.at(0), QMetaMethod::fromSignal(&IncrementReceiversTest::mySignal));
    TEST_ACTIVE_CHANGED(s1, 2);
}

void tst_QStateMachine::initialStateIsEnteredBeforeStartedEmitted()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    QFinalState *s2 = new QFinalState(&machine);

    // When started() is emitted, s1 should be the active state, and this
    // transition should trigger.
    s1->addTransition(&machine, SIGNAL(started()), s2);

    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    machine.start();
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    TEST_ACTIVE_CHANGED(s1, 2);
}

void tst_QStateMachine::deletePropertyAssignmentObjectBeforeEntry()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);

    QObject *o1 = new QObject;
    s1->assignProperty(o1, "objectName", "foo");
    delete o1;
    QObject *o2 = new QObject;
    s1->assignProperty(o2, "objectName", "bar");

    machine.start();
    // Shouldn't crash
    QTRY_VERIFY(machine.configuration().contains(s1));

    QCOMPARE(o2->objectName(), QString::fromLatin1("bar"));
    delete o2;
    TEST_ACTIVE_CHANGED(s1, 1);
    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::deletePropertyAssignmentObjectBeforeRestore()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s1->addTransition(new EventTransition(QEvent::User, s2));

    QObject *o1 = new QObject;
    s1->assignProperty(o1, "objectName", "foo");
    QObject *o2 = new QObject;
    s1->assignProperty(o2, "objectName", "bar");

    QVERIFY(o1->objectName().isEmpty());
    QVERIFY(o2->objectName().isEmpty());
    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_VERIFY(machine.configuration().contains(s1));
    QCOMPARE(o1->objectName(), QString::fromLatin1("foo"));
    QCOMPARE(o2->objectName(), QString::fromLatin1("bar"));

    delete o1;
    machine.postEvent(new QEvent(QEvent::User));
    // Shouldn't crash
    QTRY_VERIFY(machine.configuration().contains(s2));

    QVERIFY(o2->objectName().isEmpty());
    delete o2;
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::deleteInitialState()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    machine.setInitialState(s1);
    delete s1;
    QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start: No initial state set for machine. Refusing to start.");
    machine.start();
    // Shouldn't crash
    QCoreApplication::processEvents();
}

void tst_QStateMachine::setPropertyAfterRestore()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);

    QObject *object = new QObject(&machine);
    object->setProperty("a", 1);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    s1->assignProperty(object, "a", 2);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s1->addTransition(new EventTransition(QEvent::User, s2));

    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);
    s3->assignProperty(object, "a", 4);
    s2->addTransition(new EventTransition(QEvent::User, s3));

    QState *s4 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s4);
    s3->addTransition(new EventTransition(QEvent::User, s4));

    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    TEST_ACTIVE_CHANGED(s4, 0);
    QTRY_VERIFY(machine.configuration().contains(s1));
    QCOMPARE(object->property("a").toInt(), 2);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);
    TEST_ACTIVE_CHANGED(s4, 0);
    QTRY_VERIFY(machine.configuration().contains(s2));
    QCOMPARE(object->property("a").toInt(), 1); // restored

    // Set property outside of state machine; this is the value
    // that should be remembered in the next transition
    object->setProperty("a", 3);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    TEST_ACTIVE_CHANGED(s4, 0);
    QTRY_VERIFY(machine.configuration().contains(s3));
    QCOMPARE(object->property("a").toInt(), 4);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    TEST_ACTIVE_CHANGED(s4, 1);
    QVERIFY(machine.isRunning());
    QTRY_VERIFY(machine.configuration().contains(s4));
    QCOMPARE(object->property("a").toInt(), 3); // restored

    delete object;
}

void tst_QStateMachine::transitionWithNoTarget_data()
{
    QTest::addColumn<int>("restorePolicy");
    QTest::newRow("DontRestoreProperties") << int(QState::DontRestoreProperties);
    QTest::newRow("RestoreProperties") << int(QState::RestoreProperties);
}

void tst_QStateMachine::transitionWithNoTarget()
{
    QFETCH(int, restorePolicy);

    QStateMachine machine;
    machine.setGlobalRestorePolicy(static_cast<QState::RestorePolicy>(restorePolicy));

    QObject *object = new QObject;
    object->setProperty("a", 1);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    s1->assignProperty(object, "a", 2);
    EventTransition *t1 = new EventTransition(QEvent::User, /*target=*/0);
    s1->addTransition(t1);

    QSignalSpy s1EnteredSpy(s1, &QState::entered);
    QSignalSpy s1ExitedSpy(s1, &QState::exited);
    QSignalSpy t1TriggeredSpy(t1, &EventTransition::triggered);

    machine.start();
    QTRY_VERIFY(machine.configuration().contains(s1));
    QCOMPARE(s1EnteredSpy.size(), 1);
    QCOMPARE(s1ExitedSpy.size(), 0);
    QCOMPARE(t1TriggeredSpy.size(), 0);
    QCOMPARE(object->property("a").toInt(), 2);

    object->setProperty("a", 3);

    machine.postEvent(new QEvent(QEvent::User));
    QTRY_COMPARE(t1TriggeredSpy.size(), 1);
    QCOMPARE(s1EnteredSpy.size(), 1);
    QCOMPARE(s1ExitedSpy.size(), 0);
    // the assignProperty should not be re-executed, nor should the old value
    // be restored
    QCOMPARE(object->property("a").toInt(), 3);

    machine.postEvent(new QEvent(QEvent::User));
    QTRY_COMPARE(t1TriggeredSpy.size(), 2);
    QCOMPARE(s1EnteredSpy.size(), 1);
    QCOMPARE(s1ExitedSpy.size(), 0);
    QCOMPARE(object->property("a").toInt(), 3);

    delete object;
    TEST_ACTIVE_CHANGED(s1, 1);
    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::initialStateIsFinal()
{
    QStateMachine machine;
    QFinalState *f = new QFinalState(&machine);
    machine.setInitialState(f);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    machine.start();
    QTRY_VERIFY(machine.configuration().contains(f));
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
}

class PropertyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int prop READ prop WRITE setProp)
public:
    PropertyObject(QObject *parent = 0)
        : QObject(parent), m_propValue(0), m_propWriteCount(0)
    {}
    int prop() const { return m_propValue; }
    void setProp(int value) { m_propValue = value; ++m_propWriteCount; }
    int propWriteCount() const { return m_propWriteCount; }
private:
    int m_propValue;
    int m_propWriteCount;
};

void tst_QStateMachine::restorePropertiesSimple()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);

    PropertyObject *po = new PropertyObject;
    po->setProp(2);
    QCOMPARE(po->propWriteCount(), 1);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    s1->assignProperty(po, "prop", 4);
    machine.setInitialState(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s1->addTransition(new EventTransition(QEvent::User, s2));

    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);
    s3->assignProperty(po, "prop", 6);
    s2->addTransition(new EventTransition(QEvent::User, s3));

    QState *s4 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s4);
    s4->assignProperty(po, "prop", 8);
    s3->addTransition(new EventTransition(QEvent::User, s4));

    QState *s5 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s5);
    s4->addTransition(new EventTransition(QEvent::User, s5));

    QState *s6 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s6);
    s5->addTransition(new EventTransition(QEvent::User, s6));

    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    TEST_ACTIVE_CHANGED(s4, 0);
    TEST_ACTIVE_CHANGED(s5, 0);
    TEST_ACTIVE_CHANGED(s6, 0);
    QTRY_VERIFY(machine.configuration().contains(s1));
    QCOMPARE(po->propWriteCount(), 2);
    QCOMPARE(po->prop(), 4);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);
    TEST_ACTIVE_CHANGED(s4, 0);
    TEST_ACTIVE_CHANGED(s5, 0);
    TEST_ACTIVE_CHANGED(s6, 0);
    QTRY_VERIFY(machine.configuration().contains(s2));
    QCOMPARE(po->propWriteCount(), 3);
    QCOMPARE(po->prop(), 2); // restored

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    TEST_ACTIVE_CHANGED(s4, 0);
    TEST_ACTIVE_CHANGED(s5, 0);
    TEST_ACTIVE_CHANGED(s6, 0);
    QTRY_VERIFY(machine.configuration().contains(s3));
    QCOMPARE(po->propWriteCount(), 4);
    QCOMPARE(po->prop(), 6);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    TEST_ACTIVE_CHANGED(s4, 1);
    TEST_ACTIVE_CHANGED(s5, 0);
    TEST_ACTIVE_CHANGED(s6, 0);
    QTRY_VERIFY(machine.configuration().contains(s4));
    QCOMPARE(po->propWriteCount(), 5);
    QCOMPARE(po->prop(), 8);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    TEST_ACTIVE_CHANGED(s4, 2);
    TEST_ACTIVE_CHANGED(s5, 1);
    TEST_ACTIVE_CHANGED(s6, 0);
    QTRY_VERIFY(machine.configuration().contains(s5));
    QCOMPARE(po->propWriteCount(), 6);
    QCOMPARE(po->prop(), 2); // restored

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    TEST_ACTIVE_CHANGED(s4, 2);
    TEST_ACTIVE_CHANGED(s5, 2);
    TEST_ACTIVE_CHANGED(s6, 1);
    QVERIFY(machine.isRunning());
    QTRY_VERIFY(machine.configuration().contains(s6));
    QCOMPARE(po->propWriteCount(), 6);

    delete po;
}

void tst_QStateMachine::restoreProperties2()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);

    PropertyObject *po = new PropertyObject;
    po->setProp(2);
    QCOMPARE(po->propWriteCount(), 1);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    s1->assignProperty(po, "prop", 4);
    machine.setInitialState(s1);

    QState *s11 = new QState(s1);
    DEFINE_ACTIVE_SPY(s11);
    s1->setInitialState(s11);

    QState *s12 = new QState(s1);
    DEFINE_ACTIVE_SPY(s12);
    s11->addTransition(new EventTransition(QEvent::User, s12));

    QState *s13 = new QState(s1);
    DEFINE_ACTIVE_SPY(s13);
    s13->assignProperty(po, "prop", 6);
    s12->addTransition(new EventTransition(QEvent::User, s13));

    QState *s14 = new QState(s1);
    DEFINE_ACTIVE_SPY(s14);
    s14->assignProperty(po, "prop", 8);
    s13->addTransition(new EventTransition(QEvent::User, s14));

    QState *s15 = new QState(s1);
    DEFINE_ACTIVE_SPY(s15);
    s14->addTransition(new EventTransition(QEvent::User, s15));

    QState *s16 = new QState(s1);
    DEFINE_ACTIVE_SPY(s16);
    s15->addTransition(new EventTransition(QEvent::User, s16));

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(po, "prop", 10);
    s16->addTransition(new EventTransition(QEvent::User, s2));

    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);
    s2->addTransition(new EventTransition(QEvent::User, s3));

    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 1);
    TEST_ACTIVE_CHANGED(s12, 0);
    TEST_ACTIVE_CHANGED(s13, 0);
    TEST_ACTIVE_CHANGED(s14, 0);
    TEST_ACTIVE_CHANGED(s15, 0);
    TEST_ACTIVE_CHANGED(s16, 0);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    QTRY_VERIFY(machine.configuration().contains(s11));
    QCOMPARE(po->propWriteCount(), 2);
    QCOMPARE(po->prop(), 4);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s12, 1);
    TEST_ACTIVE_CHANGED(s13, 0);
    TEST_ACTIVE_CHANGED(s14, 0);
    TEST_ACTIVE_CHANGED(s15, 0);
    TEST_ACTIVE_CHANGED(s16, 0);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    QTRY_VERIFY(machine.configuration().contains(s12));
    QCOMPARE(po->propWriteCount(), 2);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s12, 2);
    TEST_ACTIVE_CHANGED(s13, 1);
    TEST_ACTIVE_CHANGED(s14, 0);
    TEST_ACTIVE_CHANGED(s15, 0);
    TEST_ACTIVE_CHANGED(s16, 0);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    QTRY_VERIFY(machine.configuration().contains(s13));
    QCOMPARE(po->propWriteCount(), 3);
    QCOMPARE(po->prop(), 6);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s12, 2);
    TEST_ACTIVE_CHANGED(s13, 2);
    TEST_ACTIVE_CHANGED(s14, 1);
    TEST_ACTIVE_CHANGED(s15, 0);
    TEST_ACTIVE_CHANGED(s16, 0);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    QTRY_VERIFY(machine.configuration().contains(s14));
    QCOMPARE(po->propWriteCount(), 4);
    QCOMPARE(po->prop(), 8);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s12, 2);
    TEST_ACTIVE_CHANGED(s13, 2);
    TEST_ACTIVE_CHANGED(s14, 2);
    TEST_ACTIVE_CHANGED(s15, 1);
    TEST_ACTIVE_CHANGED(s16, 0);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    QTRY_VERIFY(machine.configuration().contains(s15));
    QCOMPARE(po->propWriteCount(), 5);
    QCOMPARE(po->prop(), 4); // restored s1

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s12, 2);
    TEST_ACTIVE_CHANGED(s13, 2);
    TEST_ACTIVE_CHANGED(s14, 2);
    TEST_ACTIVE_CHANGED(s15, 2);
    TEST_ACTIVE_CHANGED(s16, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s3, 0);
    QTRY_VERIFY(machine.configuration().contains(s16));
    QCOMPARE(po->propWriteCount(), 5);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s12, 2);
    TEST_ACTIVE_CHANGED(s13, 2);
    TEST_ACTIVE_CHANGED(s14, 2);
    TEST_ACTIVE_CHANGED(s15, 2);
    TEST_ACTIVE_CHANGED(s16, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s3, 0);
    QTRY_VERIFY(machine.configuration().contains(s2));
    QCOMPARE(po->propWriteCount(), 6);
    QCOMPARE(po->prop(), 10);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s12, 2);
    TEST_ACTIVE_CHANGED(s13, 2);
    TEST_ACTIVE_CHANGED(s14, 2);
    TEST_ACTIVE_CHANGED(s15, 2);
    TEST_ACTIVE_CHANGED(s16, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QVERIFY(machine.isRunning());
    QTRY_VERIFY(machine.configuration().contains(s3));
    QCOMPARE(po->propWriteCount(), 7);
    QCOMPARE(po->prop(), 2); // restored original

    delete po;

}

void tst_QStateMachine::restoreProperties3()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);

    PropertyObject *po = new PropertyObject;
    po->setProp(2);
    QCOMPARE(po->propWriteCount(), 1);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    s1->assignProperty(po, "prop", 4);
    machine.setInitialState(s1);

    QState *s11 = new QState(s1);
    DEFINE_ACTIVE_SPY(s11);
    s11->assignProperty(po, "prop", 6);
    s1->setInitialState(s11);

    QState *s12 = new QState(s1);
    DEFINE_ACTIVE_SPY(s12);
    s11->addTransition(new EventTransition(QEvent::User, s12));

    QState *s13 = new QState(s1);
    DEFINE_ACTIVE_SPY(s13);
    s13->assignProperty(po, "prop", 8);
    s12->addTransition(new EventTransition(QEvent::User, s13));

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s13->addTransition(new EventTransition(QEvent::User, s2));

    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 1);
    TEST_ACTIVE_CHANGED(s12, 0);
    TEST_ACTIVE_CHANGED(s13, 0);
    TEST_ACTIVE_CHANGED(s2, 0);

    QTRY_VERIFY(machine.configuration().contains(s11));
    QCOMPARE(po->propWriteCount(), 3);
    QCOMPARE(po->prop(), 6); // s11

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s12, 1);
    TEST_ACTIVE_CHANGED(s13, 0);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_VERIFY(machine.configuration().contains(s12));
    QCOMPARE(po->propWriteCount(), 4);
    QCOMPARE(po->prop(), 4); // restored s1

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s12, 2);
    TEST_ACTIVE_CHANGED(s13, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_VERIFY(machine.configuration().contains(s13));
    QCOMPARE(po->propWriteCount(), 5);
    QCOMPARE(po->prop(), 8);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s12, 2);
    TEST_ACTIVE_CHANGED(s13, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    QVERIFY(machine.isRunning());
    QTRY_VERIFY(machine.configuration().contains(s2));
    QCOMPARE(po->propWriteCount(), 6);
    QCOMPARE(po->prop(), 2); // restored original

    delete po;
}

// QTBUG-20362
void tst_QStateMachine::restoreProperties4()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);

    PropertyObject *po1 = new PropertyObject;
    po1->setProp(2);
    QCOMPARE(po1->propWriteCount(), 1);
    PropertyObject *po2 = new PropertyObject;
    po2->setProp(4);
    QCOMPARE(po2->propWriteCount(), 1);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    s1->setChildMode(QState::ParallelStates);
    machine.setInitialState(s1);

    QState *s11 = new QState(s1);
    DEFINE_ACTIVE_SPY(s11);
    QState *s111 = new QState(s11);
    DEFINE_ACTIVE_SPY(s111);
    s111->assignProperty(po1, "prop", 6);
    s11->setInitialState(s111);

    QState *s112 = new QState(s11);
    DEFINE_ACTIVE_SPY(s112);
    s112->assignProperty(po1, "prop", 8);
    s111->addTransition(new EventTransition(QEvent::User, s112));

    QState *s12 = new QState(s1);
    DEFINE_ACTIVE_SPY(s12);
    QState *s121 = new QState(s12);
    DEFINE_ACTIVE_SPY(s121);
    s121->assignProperty(po2, "prop", 10);
    s12->setInitialState(s121);

    QState *s122 = new QState(s12);
    DEFINE_ACTIVE_SPY(s122);
    s122->assignProperty(po2, "prop", 12);
    s121->addTransition(new EventTransition(static_cast<QEvent::Type>(QEvent::User+1), s122));

    QState *s2 = new QState(&machine);
    s112->addTransition(new EventTransition(QEvent::User, s2));
    DEFINE_ACTIVE_SPY(s2);

    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 1);
    TEST_ACTIVE_CHANGED(s111, 1);
    TEST_ACTIVE_CHANGED(s112, 0);
    TEST_ACTIVE_CHANGED(s12, 1);
    TEST_ACTIVE_CHANGED(s121, 1);
    TEST_ACTIVE_CHANGED(s122, 0);
    TEST_ACTIVE_CHANGED(s2, 0);

    QTRY_VERIFY(machine.configuration().contains(s1));
    QVERIFY(machine.configuration().contains(s11));
    QVERIFY(machine.configuration().contains(s111));
    QVERIFY(machine.configuration().contains(s12));
    QVERIFY(machine.configuration().contains(s121));
    QCOMPARE(po1->propWriteCount(), 2);
    QCOMPARE(po1->prop(), 6);
    QCOMPARE(po2->propWriteCount(), 2);
    QCOMPARE(po2->prop(), 10);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 1);
    TEST_ACTIVE_CHANGED(s111, 2);
    TEST_ACTIVE_CHANGED(s112, 1);
    TEST_ACTIVE_CHANGED(s12, 1);
    TEST_ACTIVE_CHANGED(s121, 1);
    TEST_ACTIVE_CHANGED(s122, 0);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_VERIFY(machine.configuration().contains(s112));
    QCOMPARE(po1->propWriteCount(), 3);
    QCOMPARE(po1->prop(), 8);
    QCOMPARE(po2->propWriteCount(), 2);

    machine.postEvent(new QEvent(static_cast<QEvent::Type>(QEvent::User+1)));
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 1);
    TEST_ACTIVE_CHANGED(s111, 2);
    TEST_ACTIVE_CHANGED(s112, 1);
    TEST_ACTIVE_CHANGED(s12, 1);
    TEST_ACTIVE_CHANGED(s121, 2);
    TEST_ACTIVE_CHANGED(s122, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_VERIFY(machine.configuration().contains(s122));
    QCOMPARE(po1->propWriteCount(), 3);
    QCOMPARE(po2->propWriteCount(), 3);
    QCOMPARE(po2->prop(), 12);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s11, 2);
    TEST_ACTIVE_CHANGED(s111, 2);
    TEST_ACTIVE_CHANGED(s112, 2);
    TEST_ACTIVE_CHANGED(s12, 2);
    TEST_ACTIVE_CHANGED(s121, 2);
    TEST_ACTIVE_CHANGED(s122, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    QTRY_VERIFY(machine.configuration().contains(s2));
    QCOMPARE(po1->propWriteCount(), 4);
    QCOMPARE(po1->prop(), 2); // restored original
    QCOMPARE(po2->propWriteCount(), 4);
    QCOMPARE(po2->prop(), 4); // restored original

    delete po1;
    delete po2;
}

void tst_QStateMachine::restorePropertiesSelfTransition()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);

    PropertyObject *po = new PropertyObject;
    po->setProp(2);
    QCOMPARE(po->propWriteCount(), 1);

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    s1->assignProperty(po, "prop", 4);
    s1->addTransition(new EventTransition(QEvent::User, s1));
    machine.setInitialState(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s1->addTransition(new EventTransition(static_cast<QEvent::Type>(QEvent::User+1), s2));

    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_VERIFY(machine.configuration().contains(s1));
    QCOMPARE(po->propWriteCount(), 2);
    QCOMPARE(po->prop(), 4);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 3);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_COMPARE(po->propWriteCount(), 3);
    QCOMPARE(po->prop(), 4);

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 5);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_COMPARE(po->propWriteCount(), 4);
    QCOMPARE(po->prop(), 4);

    machine.postEvent(new QEvent(static_cast<QEvent::Type>(QEvent::User+1)));
    TEST_ACTIVE_CHANGED(s1, 6);
    TEST_ACTIVE_CHANGED(s2, 1);
    QTRY_VERIFY(machine.configuration().contains(s2));
    QCOMPARE(po->propWriteCount(), 5);
    QCOMPARE(po->prop(), 2); // restored

    delete po;
}

void tst_QStateMachine::changeStateWhileAnimatingProperty()
{
    QStateMachine machine;
    machine.setGlobalRestorePolicy(QState::RestoreProperties);

    QObject *o1 = new QObject;
    o1->setProperty("x", 10.);
    QObject *o2 = new QObject;
    o2->setProperty("y", 20.);

    QState *group = new QState(&machine);
    DEFINE_ACTIVE_SPY(group);
    machine.setInitialState(group);

    QState *s0 = new QState(group);
    DEFINE_ACTIVE_SPY(s0);
    group->setInitialState(s0);

    QState *s1 = new QState(group);
    DEFINE_ACTIVE_SPY(s1);
    s1->assignProperty(o1, "x", 15.);
    QPropertyAnimation *a1 = new QPropertyAnimation(o1, "x", s1);
    a1->setDuration(800);
    machine.addDefaultAnimation(a1);
    group->addTransition(new EventTransition(QEvent::User, s1));

    QState *s2 = new QState(group);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(o2, "y", 25.);
    QPropertyAnimation *a2 = new QPropertyAnimation(o2, "y", s2);
    a2->setDuration(800);
    machine.addDefaultAnimation(a2);
    group->addTransition(new EventTransition(static_cast<QEvent::Type>(QEvent::User+1), s2));

    machine.start();
    TEST_ACTIVE_CHANGED(group, 1);
    TEST_ACTIVE_CHANGED(s0, 1);
    TEST_ACTIVE_CHANGED(s1, 0);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_VERIFY(machine.configuration().contains(s0));

    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(group, 3);
    TEST_ACTIVE_CHANGED(s0, 2);
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_VERIFY(machine.configuration().contains(s1));
    QCOREAPPLICATION_EXEC(400);
    machine.postEvent(new QEvent(static_cast<QEvent::Type>(QEvent::User+1)));
    TEST_ACTIVE_CHANGED(group, 5);
    TEST_ACTIVE_CHANGED(s0, 2);
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    QTRY_VERIFY(machine.configuration().contains(s2));
    QCOREAPPLICATION_EXEC(300);
    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(group, 7);
    TEST_ACTIVE_CHANGED(s0, 2);
    TEST_ACTIVE_CHANGED(s1, 3);
    TEST_ACTIVE_CHANGED(s2, 2);
    QTRY_VERIFY(machine.configuration().contains(s1));
    QCOREAPPLICATION_EXEC(200);
    machine.postEvent(new QEvent(static_cast<QEvent::Type>(QEvent::User+1)));
    TEST_ACTIVE_CHANGED(group, 9);
    TEST_ACTIVE_CHANGED(s0, 2);
    TEST_ACTIVE_CHANGED(s1, 4);
    TEST_ACTIVE_CHANGED(s2, 3);
    QTRY_VERIFY(machine.configuration().contains(s2));
    QCOREAPPLICATION_EXEC(100);
    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(group, 11);
    TEST_ACTIVE_CHANGED(s0, 2);
    TEST_ACTIVE_CHANGED(s1, 5);
    TEST_ACTIVE_CHANGED(s2, 4);
    QTRY_VERIFY(machine.configuration().contains(s1));
    QTRY_COMPARE(o1->property("x").toDouble(), 15.);
    QTRY_COMPARE(o2->property("y").toDouble(), 20.);

    delete o1;
    delete o2;
}

class AssignPropertyTestState : public QState
{
    Q_OBJECT
public:
    AssignPropertyTestState(QState *parent = 0)
        : QState(parent), onEntryPassed(false), enteredPassed(false)
    { QObject::connect(this, SIGNAL(entered()), this, SLOT(onEntered())); }

    virtual void onEntry(QEvent *) override
    { onEntryPassed = property("wasAssigned").toBool(); }

    bool onEntryPassed;
    bool enteredPassed;

private Q_SLOTS:
    void onEntered()
    { enteredPassed = property("wasAssigned").toBool(); }
};

void tst_QStateMachine::propertiesAreAssignedBeforeEntryCallbacks_data()
{
    QTest::addColumn<int>("restorePolicy");
    QTest::newRow("DontRestoreProperties") << int(QState::DontRestoreProperties);
    QTest::newRow("RestoreProperties") << int(QState::RestoreProperties);
}

void tst_QStateMachine::propertiesAreAssignedBeforeEntryCallbacks()
{
    QFETCH(int, restorePolicy);

    QStateMachine machine;
    machine.setGlobalRestorePolicy(static_cast<QState::RestorePolicy>(restorePolicy));

    AssignPropertyTestState *s1 = new AssignPropertyTestState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    s1->assignProperty(s1, "wasAssigned", true);
    machine.setInitialState(s1);

    AssignPropertyTestState *s2 = new AssignPropertyTestState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s2->assignProperty(s2, "wasAssigned", true);
    s1->addTransition(new EventTransition(QEvent::User, s2));

    QVERIFY(!s1->property("wasAssigned").toBool());
    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_VERIFY(machine.configuration().contains(s1));

    QVERIFY(s1->onEntryPassed);
    QVERIFY(s1->enteredPassed);

    QVERIFY(!s2->property("wasAssigned").toBool());
    machine.postEvent(new QEvent(QEvent::User));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    QTRY_VERIFY(machine.configuration().contains(s2));

    QVERIFY(s2->onEntryPassed);
    QVERIFY(s2->enteredPassed);
}

// QTBUG-25958
void tst_QStateMachine::multiTargetTransitionInsideParallelStateGroup()
{
    // TODO QTBUG-25958 was reopened, see https://codereview.qt-project.org/89775
    return;

    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);

    QState *s2 = new QState(QState::ParallelStates, &machine);
    DEFINE_ACTIVE_SPY(s2);

    QState *s21 = new QState(s2);
    DEFINE_ACTIVE_SPY(s21);
    QState *s211 = new QState(s21);
    DEFINE_ACTIVE_SPY(s211);
    QState *s212 = new QState(s21);
    DEFINE_ACTIVE_SPY(s212);
    s21->setInitialState(s212);

    QState *s22 = new QState(s2);
    DEFINE_ACTIVE_SPY(s22);
    QState *s221 = new QState(s22);
    DEFINE_ACTIVE_SPY(s221);
    QState *s222 = new QState(s22);
    DEFINE_ACTIVE_SPY(s222);
    s22->setInitialState(s222);

    QAbstractTransition *t1 = new EventTransition(QEvent::User, QList<QAbstractState *>() << s211 << s221);
    s1->addTransition(t1);

    machine.start();
    QTRY_VERIFY(machine.configuration().contains(s1));
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    TEST_ACTIVE_CHANGED(s21, 0);
    TEST_ACTIVE_CHANGED(s211, 0);
    TEST_ACTIVE_CHANGED(s212, 0);
    TEST_ACTIVE_CHANGED(s22, 0);
    TEST_ACTIVE_CHANGED(s221, 0);
    TEST_ACTIVE_CHANGED(s222, 0);
    machine.postEvent(new QEvent(QEvent::User));
    QTRY_VERIFY(machine.configuration().contains(s2));
    QCOMPARE(machine.configuration().size(), 5);
    QVERIFY(machine.configuration().contains(s21));
    QVERIFY(machine.configuration().contains(s211));
    QVERIFY(machine.configuration().contains(s22));
    QVERIFY(machine.configuration().contains(s221));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    TEST_ACTIVE_CHANGED(s21, 1);
    TEST_ACTIVE_CHANGED(s211, 1);
    TEST_ACTIVE_CHANGED(s212, 0);
    TEST_ACTIVE_CHANGED(s22, 1);
    TEST_ACTIVE_CHANGED(s221, 1);
    TEST_ACTIVE_CHANGED(s222, 0);
}

void tst_QStateMachine::signalTransitionNormalizeSignature()
{
    QStateMachine machine;
    QState *s0 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s0);
    machine.setInitialState(s0);
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    SignalEmitter emitter;
    TestSignalTransition *t0 = new TestSignalTransition(&emitter, SIGNAL(signalWithNoArg()), s1);
    s0->addTransition(t0);

    machine.start();
    TEST_ACTIVE_CHANGED(s0, 1);
    TEST_ACTIVE_CHANGED(s1, 0);
    QTRY_VERIFY(machine.configuration().contains(s0));
    emitter.emitSignalWithNoArg();
    QTRY_VERIFY(machine.configuration().contains(s1));

    QCOMPARE(t0->eventTestSenderReceived(), (QObject*)&emitter);
    QCOMPARE(t0->eventTestSignalIndexReceived(), emitter.metaObject()->indexOfSignal("signalWithNoArg()"));
    QCOMPARE(t0->eventTestArgumentsReceived().size(), 0);
    QCOMPARE(t0->transitionSenderReceived(), (QObject*)&emitter);
    QCOMPARE(t0->transitionSignalIndexReceived(), emitter.metaObject()->indexOfSignal("signalWithNoArg()"));
    QCOMPARE(t0->transitionArgumentsReceived().size(), 0);
    TEST_ACTIVE_CHANGED(s0, 2);
    TEST_ACTIVE_CHANGED(s1, 1);
}

#ifdef Q_COMPILER_DELEGATING_CONSTRUCTORS
void tst_QStateMachine::createPointerToMemberSignalTransition()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    QTRY_VERIFY(machine.configuration().contains(s1));

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    SignalEmitter emitter;
    QSignalTransition *t1 = new QSignalTransition(&emitter, &SignalEmitter::signalWithNoArg, s1);
    QCOMPARE(t1->sourceState(), s1);
    t1->setTargetState(s2);
    s1->addTransition(t1);
    emitter.emitSignalWithNoArg();
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    QTRY_VERIFY(machine.configuration().contains(s2));
}
#endif

void tst_QStateMachine::createSignalTransitionWhenRunning()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    QTRY_VERIFY(machine.configuration().contains(s1));
    // Create by addTransition()
    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    SignalEmitter emitter;
    QAbstractTransition *t1 = s1->addTransition(&emitter, SIGNAL(signalWithNoArg()), s2);
    QCOMPARE(t1->sourceState(), s1);
    emitter.emitSignalWithNoArg();
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 1);
    QTRY_VERIFY(machine.configuration().contains(s2));

    // Create by constructor that takes sender, signal, source (parent) state
    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);
    QSignalTransition *t2 = new QSignalTransition(&emitter, SIGNAL(signalWithNoArg()), s2);
    QCOMPARE(t2->sourceState(), s2);
    t2->setTargetState(s3);
    emitter.emitSignalWithNoArg();
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 1);
    QTRY_VERIFY(machine.configuration().contains(s3));

    // Create by constructor that takes source (parent) state
    QState *s4 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s4);
    QSignalTransition *t3 = new QSignalTransition(s3);
    QCOMPARE(t3->sourceState(), s3);
    t3->setSenderObject(&emitter);
    t3->setSignal(SIGNAL(signalWithNoArg()));
    t3->setTargetState(s4);
    emitter.emitSignalWithNoArg();
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    TEST_ACTIVE_CHANGED(s4, 1);
    QTRY_VERIFY(machine.configuration().contains(s4));

    // Create by constructor without parent, then set the parent
    QState *s5 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s5);
    QSignalTransition *t4 = new QSignalTransition();
    t4->setSenderObject(&emitter);
    t4->setParent(s4);
    QCOMPARE(t4->sourceState(), s4);
    t4->setSignal(SIGNAL(signalWithNoArg()));
    t4->setTargetState(s5);
    emitter.emitSignalWithNoArg();
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    TEST_ACTIVE_CHANGED(s4, 2);
    TEST_ACTIVE_CHANGED(s5, 1);
    QTRY_VERIFY(machine.configuration().contains(s5));
}

void tst_QStateMachine::createEventTransitionWhenRunning()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    QTRY_VERIFY(machine.configuration().contains(s1));

    // Create by constructor that takes event source, type, source (parent) state
    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    QObject object;
    QEventTransition *t1 = new QEventTransition(&object, QEvent::Timer, s1);
    QCOMPARE(t1->sourceState(), s1);
    t1->setTargetState(s2);

    object.startTimer(10); // Will cause QEvent::Timer to fire every 10ms
    QTRY_VERIFY(machine.configuration().contains(s2));

    // Create by constructor that takes source (parent) state
    QState *s3 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s3);
    QEventTransition *t2 = new QEventTransition(s2);
    QCOMPARE(t2->sourceState(), s2);
    t2->setEventSource(&object);
    t2->setEventType(QEvent::Timer);
    t2->setTargetState(s3);
    QTRY_VERIFY(machine.configuration().contains(s3));

    // Create by constructor without parent, then set the parent
    QState *s4 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s4);
    QEventTransition *t3 = new QEventTransition();
    t3->setEventSource(&object);
    t3->setParent(s3);
    QCOMPARE(t3->sourceState(), s3);
    t3->setEventType(QEvent::Timer);
    t3->setTargetState(s4);
    QTRY_VERIFY(machine.configuration().contains(s4));
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    TEST_ACTIVE_CHANGED(s3, 2);
    TEST_ACTIVE_CHANGED(s4, 1);
}

class SignalEmitterThread : public QThread
{
    Q_OBJECT
public:
    SignalEmitterThread(QObject *parent = 0)
        : QThread(parent)
    {
        moveToThread(this);
    }

Q_SIGNALS:
    void signal1();
    void signal2();

public Q_SLOTS:
    void emitSignals()
    {
        emit signal1();
        emit signal2();
    }
};

// QTBUG-19789
void tst_QStateMachine::signalTransitionSenderInDifferentThread()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);

    SignalEmitterThread thread;
    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    s1->addTransition(&thread, SIGNAL(signal1()), s2);

    QFinalState *s3 = new QFinalState(&machine);
    s2->addTransition(&thread, SIGNAL(signal2()), s3);

    thread.start();
    QTRY_VERIFY(thread.isRunning());

    machine.start();
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s2, 0);
    QTRY_VERIFY(machine.configuration().contains(s1));

    QMetaObject::invokeMethod(&thread, "emitSignals");
    // thread emits both signal1() and signal2(), so we should end in s3
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
    QTRY_VERIFY(!machine.isRunning());
    QTRY_VERIFY(machine.configuration().contains(s3));

    // Run the machine again; transitions should still be registered
    machine.start();
    TEST_ACTIVE_CHANGED(s1, 3);
    TEST_ACTIVE_CHANGED(s2, 2);
    QTRY_VERIFY(machine.configuration().contains(s1));
    QMetaObject::invokeMethod(&thread, "emitSignals");
    QTRY_VERIFY(machine.configuration().contains(s3));

    thread.quit();
    QTRY_VERIFY(thread.wait());
    TEST_ACTIVE_CHANGED(s1, 4);
    TEST_ACTIVE_CHANGED(s2, 4);
    QVERIFY(!machine.isRunning());
}

void tst_QStateMachine::signalTransitionSenderInDifferentThread2()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);

    QState *s2 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s2);
    SignalEmitter emitter;
    // At the time of the transition creation, the machine and the emitter
    // are both in the same thread.
    s1->addTransition(&emitter, SIGNAL(signalWithNoArg()), s2);

    QFinalState *s3 = new QFinalState(&machine);
    s2->addTransition(&emitter, SIGNAL(signalWithDefaultArg()), s3);

    QThread thread;
    // Move the machine and its states to a secondary thread, but let the
    // SignalEmitter stay in the main thread.
    machine.moveToThread(&thread);

    thread.start();
    QTRY_VERIFY(thread.isRunning());

    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 1);
    TEST_RUNNING_CHANGED(true);

    emitter.emitSignalWithNoArg();
    // The second emission should not get "lost".
    emitter.emitSignalWithDefaultArg();
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED(false);

    thread.quit();
    QTRY_VERIFY(thread.wait());
    TEST_ACTIVE_CHANGED(s1, 2);
    TEST_ACTIVE_CHANGED(s2, 2);
}

class SignalTransitionMutatorThread : public QThread
{
public:
    SignalTransitionMutatorThread(QSignalTransition *transition)
        : m_transition(transition)
    {}
    void run() override
    {
        // Cause repeated registration and unregistration
        for (int i = 0; i < 50000; ++i) {
            m_transition->setSenderObject(this);
            m_transition->setSenderObject(0);
        }
    }
private:
    QSignalTransition *m_transition;
};

// Should not crash:
void tst_QStateMachine::signalTransitionRegistrationThreadSafety()
{
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    machine.setInitialState(s1);
    machine.start();
    QTRY_VERIFY(machine.configuration().contains(s1));

    QSignalTransition *t1 = new QSignalTransition();
    t1->setSignal(SIGNAL(objectNameChanged(QString)));
    s1->addTransition(t1);

    QSignalTransition *t2 = new QSignalTransition();
    t2->setSignal(SIGNAL(objectNameChanged(QString)));
    s1->addTransition(t2);

    SignalTransitionMutatorThread thread(t1);
    thread.start();
    QTRY_VERIFY(thread.isRunning());

    // Cause repeated registration and unregistration
    for (int i = 0; i < 50000; ++i) {
        t2->setSenderObject(this);
        t2->setSenderObject(0);
    }

    thread.quit();
    QTRY_VERIFY(thread.wait());
    TEST_ACTIVE_CHANGED(s1, 1);
    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::childModeConstructor()
{
    // Any child mode other than ExclusiveStates results in an invalid child mode
    // warning. So the three warnings here are expected. See more about this in
    // the QStateMachine constructor.
    {
        QStateMachine machine(QState::ExclusiveStates);
        QCOMPARE(machine.childMode(), QState::ExclusiveStates);
        QVERIFY(!machine.parent());
        QVERIFY(!machine.parentState());
    }
    {
        QStateMachine machine(QState::ParallelStates);
        QCOMPARE(machine.childMode(), QState::ParallelStates);
        QVERIFY(!machine.parent());
        QVERIFY(!machine.parentState());
    }
    {
        QStateMachine machine(QState::ExclusiveStates, this);
        QCOMPARE(machine.childMode(), QState::ExclusiveStates);
        QCOMPARE(machine.parent(), static_cast<QObject *>(this));
        QVERIFY(!machine.parentState());
    }
    {
        QStateMachine machine(QState::ParallelStates, this);
        QCOMPARE(machine.childMode(), QState::ParallelStates);
        QCOMPARE(machine.parent(), static_cast<QObject *>(this));
        QVERIFY(!machine.parentState());
    }
    QState state;
    {
        QStateMachine machine(QState::ExclusiveStates, &state);
        QCOMPARE(machine.childMode(), QState::ExclusiveStates);
        QCOMPARE(machine.parent(), static_cast<QObject *>(&state));
        QCOMPARE(machine.parentState(), &state);
    }
    {
        QStateMachine machine(QState::ParallelStates, &state);
        QCOMPARE(machine.childMode(), QState::ParallelStates);
        QCOMPARE(machine.parent(), static_cast<QObject *>(&state));
        QCOMPARE(machine.parentState(), &state);
    }
}

void tst_QStateMachine::qtbug_44963()
{
    SignalEmitter emitter;

    QStateMachine machine;
    QState a(QState::ParallelStates, &machine);
        QHistoryState ha(QHistoryState::DeepHistory, &a);
        QState b(QState::ParallelStates, &a);
            QState c(QState::ParallelStates, &b);
                QState d(QState::ParallelStates, &c);
                    QState e(QState::ParallelStates, &d);
                        QState i(&e);
                            QState i1(&i);
                            QState i2(&i);
                        QState j(&e);
                    QState h(&d);
                QState g(&c);
        QState k(&a);
    QState l(&machine);

    machine.setInitialState(&a);
    ha.setDefaultState(&b);
    i.setInitialState(&i1);
    i1.addTransition(&emitter, SIGNAL(signalWithIntArg(int)), &i2)->setObjectName("i1->i2");
    i2.addTransition(&emitter, SIGNAL(signalWithDefaultArg(int)), &l)->setObjectName("i2->l");
    l.addTransition(&emitter, SIGNAL(signalWithNoArg()), &ha)->setObjectName("l->ha");

    a.setObjectName("a");
    ha.setObjectName("ha");
    b.setObjectName("b");
    c.setObjectName("c");
    d.setObjectName("d");
    e.setObjectName("e");
    i.setObjectName("i");
    i1.setObjectName("i1");
    i2.setObjectName("i2");
    j.setObjectName("j");
    h.setObjectName("h");
    g.setObjectName("g");
    k.setObjectName("k");
    l.setObjectName("l");

    machine.start();

    QTRY_COMPARE(machine.configuration().contains(&i1), true);
    QTRY_COMPARE(machine.configuration().contains(&i2), false);
    QTRY_COMPARE(machine.configuration().contains(&j), true);
    QTRY_COMPARE(machine.configuration().contains(&h), true);
    QTRY_COMPARE(machine.configuration().contains(&g), true);
    QTRY_COMPARE(machine.configuration().contains(&k), true);
    QTRY_COMPARE(machine.configuration().contains(&l), false);

    emitter.emitSignalWithIntArg(0);

    QTRY_COMPARE(machine.configuration().contains(&i1), false);
    QTRY_COMPARE(machine.configuration().contains(&i2), true);
    QTRY_COMPARE(machine.configuration().contains(&j), true);
    QTRY_COMPARE(machine.configuration().contains(&h), true);
    QTRY_COMPARE(machine.configuration().contains(&g), true);
    QTRY_COMPARE(machine.configuration().contains(&k), true);
    QTRY_COMPARE(machine.configuration().contains(&l), false);

    emitter.emitSignalWithDefaultArg();

    QTRY_COMPARE(machine.configuration().contains(&i1), false);
    QTRY_COMPARE(machine.configuration().contains(&i2), false);
    QTRY_COMPARE(machine.configuration().contains(&j), false);
    QTRY_COMPARE(machine.configuration().contains(&h), false);
    QTRY_COMPARE(machine.configuration().contains(&g), false);
    QTRY_COMPARE(machine.configuration().contains(&k), false);
    QTRY_COMPARE(machine.configuration().contains(&l), true);

    emitter.emitSignalWithNoArg();

    QTRY_COMPARE(machine.configuration().contains(&i1), false);
    QTRY_COMPARE(machine.configuration().contains(&i2), true);
    QTRY_COMPARE(machine.configuration().contains(&j), true);
    QTRY_COMPARE(machine.configuration().contains(&h), true);
    QTRY_COMPARE(machine.configuration().contains(&g), true);
    QTRY_COMPARE(machine.configuration().contains(&k), true);
    QTRY_COMPARE(machine.configuration().contains(&l), false);

    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::qtbug_44783()
{
    SignalEmitter emitter;

    QStateMachine machine;
    QState s(&machine);
        QState p(QState::ParallelStates, &s);
            QState p1(&p);
                QState p1_1(&p1);
                QState p1_2(&p1);
            QState p2(&p);
    QState s1(&machine);

    machine.setInitialState(&s);
    s.setInitialState(&p);
    p1.setInitialState(&p1_1);
    p1_1.addTransition(&emitter, SIGNAL(signalWithNoArg()), &p1_2)->setObjectName("p1_1->p1_2");
    p2.addTransition(&emitter, SIGNAL(signalWithNoArg()), &s1)->setObjectName("p2->s1");

    s.setObjectName("s");
    p.setObjectName("p");
    p1.setObjectName("p1");
    p1_1.setObjectName("p1_1");
    p1_2.setObjectName("p1_2");
    p2.setObjectName("p2");
    s1.setObjectName("s1");

    machine.start();

    QTRY_COMPARE(machine.configuration().contains(&s), true);
    QTRY_COMPARE(machine.configuration().contains(&p), true);
    QTRY_COMPARE(machine.configuration().contains(&p1), true);
    QTRY_COMPARE(machine.configuration().contains(&p1_1), true);
    QTRY_COMPARE(machine.configuration().contains(&p1_2), false);
    QTRY_COMPARE(machine.configuration().contains(&p2), true);
    QTRY_COMPARE(machine.configuration().contains(&s1), false);

    emitter.emitSignalWithNoArg();

    // Only one of the following two can be true, because the two possible transitions conflict.
    if (machine.configuration().contains(&s1)) {
        // the transition p2 -> s1 was taken, not p1_1 -> p1_2, so:
        // the parallel state exited, so none of the states inside it are active
        QTRY_COMPARE(machine.configuration().contains(&s), false);
        QTRY_COMPARE(machine.configuration().contains(&p), false);
        QTRY_COMPARE(machine.configuration().contains(&p1), false);
        QTRY_COMPARE(machine.configuration().contains(&p1_1), false);
        QTRY_COMPARE(machine.configuration().contains(&p1_2), false);
        QTRY_COMPARE(machine.configuration().contains(&p2), false);
    } else {
        // the transition p1_1 -> p1_2 was taken, not p2 -> s1, so:
        // the parallel state was not exited and the state is the same as the start state with one
        // difference: p1_1 inactive and p1_2 active:
        QTRY_COMPARE(machine.configuration().contains(&s), true);
        QTRY_COMPARE(machine.configuration().contains(&p), true);
        QTRY_COMPARE(machine.configuration().contains(&p1), true);
        QTRY_COMPARE(machine.configuration().contains(&p1_1), false);
        QTRY_COMPARE(machine.configuration().contains(&p1_2), true);
        QTRY_COMPARE(machine.configuration().contains(&p2), true);
    }

    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::internalTransition()
{
    SignalEmitter emitter;

    QStateMachine machine;
    QState *s = new QState(&machine);
        QState *s1 = new QState(s);
            QState *s11 = new QState(s1);

    DEFINE_ACTIVE_SPY(s);
    DEFINE_ACTIVE_SPY(s1);
    DEFINE_ACTIVE_SPY(s11);

    machine.setInitialState(s);
    s->setInitialState(s1);
    s1->setInitialState(s11);
    QSignalTransition *t = s1->addTransition(&emitter, SIGNAL(signalWithNoArg()), s11);
    t->setObjectName("s1->s11");
    t->setTransitionType(QAbstractTransition::InternalTransition);

    s->setObjectName("s");
    s1->setObjectName("s1");
    s11->setObjectName("s11");

    machine.start();

    QTRY_COMPARE(machine.configuration().contains(s), true);
    QTRY_COMPARE(machine.configuration().contains(s1), true);
    QTRY_COMPARE(machine.configuration().contains(s11), true);
    TEST_ACTIVE_CHANGED(s, 1);
    TEST_ACTIVE_CHANGED(s1, 1);
    TEST_ACTIVE_CHANGED(s11, 1);

    emitter.emitSignalWithNoArg();

    QTRY_COMPARE(machine.configuration().contains(s), true);
    QTRY_COMPARE(machine.configuration().contains(s1), true);
    QTRY_COMPARE(machine.configuration().contains(s11), true);
    TEST_ACTIVE_CHANGED(s11, 3);
    TEST_ACTIVE_CHANGED(s1, 1); // external transitions will return 3, internal transitions should return 1.
    TEST_ACTIVE_CHANGED(s, 1);
}

void tst_QStateMachine::conflictingTransition()
{
    SignalEmitter emitter;

    QStateMachine machine;
    QState b(QState::ParallelStates, &machine);
        QState c(&b);
        QState d(QState::ParallelStates, &b);
            QState e(&d);
                QState e1(&e);
                QState e2(&e);
            QState f(&d);
                QState f1(&f);
                QState f2(&f);
    QState a1(&machine);

    machine.setInitialState(&b);
    e.setInitialState(&e1);
    f.setInitialState(&f1);
    c.addTransition(&emitter, SIGNAL(signalWithNoArg()), &a1)->setObjectName("c->a1");
    e1.addTransition(&emitter, SIGNAL(signalWithNoArg()), &e2)->setObjectName("e1->e2");
    f1.addTransition(&emitter, SIGNAL(signalWithNoArg()), &f2)->setObjectName("f1->f2");

    b.setObjectName("b");
    c.setObjectName("c");
    d.setObjectName("d");
    e.setObjectName("e");
    e1.setObjectName("e1");
    e2.setObjectName("e2");
    f.setObjectName("f");
    f1.setObjectName("f1");
    f2.setObjectName("f2");
    a1.setObjectName("a1");

    machine.start();

    QTRY_COMPARE(machine.configuration().contains(&b), true);
    QTRY_COMPARE(machine.configuration().contains(&c), true);
    QTRY_COMPARE(machine.configuration().contains(&d), true);
    QTRY_COMPARE(machine.configuration().contains(&e), true);
    QTRY_COMPARE(machine.configuration().contains(&e1), true);
    QTRY_COMPARE(machine.configuration().contains(&e2), false);
    QTRY_COMPARE(machine.configuration().contains(&f), true);
    QTRY_COMPARE(machine.configuration().contains(&f1), true);
    QTRY_COMPARE(machine.configuration().contains(&f2), false);
    QTRY_COMPARE(machine.configuration().contains(&a1), false);

    emitter.emitSignalWithNoArg();

    QTRY_COMPARE(machine.configuration().contains(&b), true);
    QTRY_COMPARE(machine.configuration().contains(&c), true);
    QTRY_COMPARE(machine.configuration().contains(&d), true);
    QTRY_COMPARE(machine.configuration().contains(&e), true);
    QTRY_COMPARE(machine.configuration().contains(&e1), false);
    QTRY_COMPARE(machine.configuration().contains(&e2), true);
    QTRY_COMPARE(machine.configuration().contains(&f), true);
    QTRY_COMPARE(machine.configuration().contains(&f1), false);
    QTRY_COMPARE(machine.configuration().contains(&f2), true);
    QTRY_COMPARE(machine.configuration().contains(&a1), false);

    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::conflictingTransition2()
{
    SignalEmitter emitter;

    QStateMachine machine;
    QState s0(&machine);
        QState p0(QState::ParallelStates, &s0);
            QState p0s1(&p0);
            QState p0s2(&p0);
            QState p0s3(&p0);
    QState s1(&machine);

    machine.setInitialState(&s0);
    s0.setInitialState(&p0);

    QSignalTransition *t1 = new QSignalTransition(&emitter, SIGNAL(signalWithNoArg()));
    p0s1.addTransition(t1);
    QSignalTransition *t2 = p0s2.addTransition(&emitter, SIGNAL(signalWithNoArg()), &p0s1);
    QSignalTransition *t3 = p0s3.addTransition(&emitter, SIGNAL(signalWithNoArg()), &s1);
    QSignalSpy t1Spy(t1, &QAbstractTransition::triggered);
    QSignalSpy t2Spy(t2, &QAbstractTransition::triggered);
    QSignalSpy t3Spy(t3, &QAbstractTransition::triggered);
    QVERIFY(t1Spy.isValid());
    QVERIFY(t2Spy.isValid());
    QVERIFY(t3Spy.isValid());

    s0.setObjectName("s0");
    p0.setObjectName("p0");
    p0s1.setObjectName("p0s1");
    p0s2.setObjectName("p0s2");
    p0s3.setObjectName("p0s3");
    s1.setObjectName("s1");
    t1->setObjectName("p0s1->p0s1");
    t2->setObjectName("p0s2->p0s1");
    t3->setObjectName("p0s3->s1");

    machine.start();

    QTRY_COMPARE(machine.configuration().contains(&s0), true);
    QTRY_COMPARE(machine.configuration().contains(&p0), true);
    QTRY_COMPARE(machine.configuration().contains(&p0s1), true);
    QTRY_COMPARE(machine.configuration().contains(&p0s2), true);
    QTRY_COMPARE(machine.configuration().contains(&p0s3), true);
    QTRY_COMPARE(machine.configuration().contains(&s1), false);

    QCOMPARE(t1Spy.size(), 0);
    QCOMPARE(t2Spy.size(), 0);
    QCOMPARE(t3Spy.size(), 0);

    emitter.emitSignalWithNoArg();

    QTRY_COMPARE(machine.configuration().contains(&s0), true);
    QTRY_COMPARE(machine.configuration().contains(&p0), true);
    QTRY_COMPARE(machine.configuration().contains(&p0s1), true);
    QTRY_COMPARE(machine.configuration().contains(&p0s2), true);
    QTRY_COMPARE(machine.configuration().contains(&p0s3), true);
    QTRY_COMPARE(machine.configuration().contains(&s1), false);

    QCOMPARE(t1Spy.size(), 1);
    QCOMPARE(t2Spy.size(), 1);
    QCOMPARE(t3Spy.size(), 0); // t3 got preempted by t2

    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::qtbug_46059()
{
    QStateMachine machine;
    QState a(&machine);
        QState b(&a);
        QState c(&a);
        QState success(&a);
    QState failure(&machine);

    machine.setInitialState(&a);
    a.setInitialState(&b);
    b.addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), &c));
    c.addTransition(new EventTransition(QEvent::Type(QEvent::User + 2), &success));
    b.addTransition(new EventTransition(QEvent::Type(QEvent::User + 2), &failure));

    machine.start();
    QCoreApplication::processEvents();

    QTRY_COMPARE(machine.configuration().contains(&a), true);
    QTRY_COMPARE(machine.configuration().contains(&b), true);
    QTRY_COMPARE(machine.configuration().contains(&c), false);
    QTRY_COMPARE(machine.configuration().contains(&failure), false);
    QTRY_COMPARE(machine.configuration().contains(&success), false);

    machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 0)), QStateMachine::HighPriority);
    machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1)), QStateMachine::HighPriority);
    machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 2)), QStateMachine::NormalPriority);
    QCoreApplication::processEvents();

    QTRY_COMPARE(machine.configuration().contains(&a), true);
    QTRY_COMPARE(machine.configuration().contains(&b), false);
    QTRY_COMPARE(machine.configuration().contains(&c), false);
    QTRY_COMPARE(machine.configuration().contains(&failure), false);
    QTRY_COMPARE(machine.configuration().contains(&success), true);

    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::qtbug_46703()
{
    QStateMachine machine;
    QState root(&machine);
        QHistoryState h(&root);
        QState p(QState::ParallelStates, &root);
            QState a(&p);
                QState a1(&a);
                QState a2(&a);
                QState a3(&a);
            QState b(&p);
                QState b1(&b);
                QState b2(&b);

    machine.setObjectName("machine");
    root.setObjectName("root");
    h.setObjectName("h");
    p.setObjectName("p");
    a.setObjectName("a");
    a1.setObjectName("a1");
    a2.setObjectName("a2");
    a3.setObjectName("a3");
    b.setObjectName("b");
    b1.setObjectName("b1");
    b2.setObjectName("b2");

    machine.setInitialState(&root);
    root.setInitialState(&h);
    a.setInitialState(&a3);
    b.setInitialState(&b1);
    struct : public QAbstractTransition {
        virtual bool eventTest(QEvent *) override { return false; }
        virtual void onTransition(QEvent *) override {}
    } defaultTransition;
    defaultTransition.setTargetStates(QList<QAbstractState*>() << &a2 << &b2);
    h.setDefaultTransition(&defaultTransition);

    machine.start();

    QTRY_COMPARE(machine.configuration().contains(&root), true);
    QTRY_COMPARE(machine.configuration().contains(&h), false);
    QTRY_COMPARE(machine.configuration().contains(&p), true);
    QTRY_COMPARE(machine.configuration().contains(&a), true);
    QTRY_COMPARE(machine.configuration().contains(&a1), false);
    QTRY_COMPARE(machine.configuration().contains(&a2), true);
    QTRY_COMPARE(machine.configuration().contains(&a3), false);
    QTRY_COMPARE(machine.configuration().contains(&b), true);
    QTRY_COMPARE(machine.configuration().contains(&b1), false);
    QTRY_COMPARE(machine.configuration().contains(&b2), true);

    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::postEventFromBeginSelectTransitions()
{
    class StateMachine : public QStateMachine {
    protected:
        void beginSelectTransitions(QEvent* e) override {
            if (e->type() == QEvent::Type(QEvent::User + 2))
                postEvent(new QEvent(QEvent::Type(QEvent::User + 1)), QStateMachine::HighPriority);
        }
    } machine;
    QState a(&machine);
    QState success(&machine);

    machine.setInitialState(&a);
    a.addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), &success));

    machine.start();

    QTRY_COMPARE(machine.configuration().contains(&a), true);
    QTRY_COMPARE(machine.configuration().contains(&success), false);

    machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 2)), QStateMachine::NormalPriority);

    QTRY_COMPARE(machine.configuration().contains(&a), false);
    QTRY_COMPARE(machine.configuration().contains(&success), true);

    QVERIFY(machine.isRunning());
}

void tst_QStateMachine::dontProcessSlotsWhenMachineIsNotRunning()
{
    QStateMachine machine;
    QState initialState;
    QFinalState finalState;

    struct Emitter : SignalEmitter
    {
        QThread thread;
        Emitter(QObject *parent = nullptr) : SignalEmitter(parent)
        {
            moveToThread(&thread);
            thread.start();
        }
    } emitter;

    initialState.addTransition(&emitter, &Emitter::signalWithNoArg, &finalState);
    machine.addState(&initialState);
    machine.addState(&finalState);
    machine.setInitialState(&initialState);
    connect(&machine, &QStateMachine::started, &emitter, [&]() {
        metaObject()->invokeMethod(&emitter, "emitSignalWithNoArg");
        metaObject()->invokeMethod(&emitter, "emitSignalWithNoArg");
    });
    connect(&machine, &QStateMachine::finished, &emitter.thread, &QThread::quit);
    machine.start();
    QSignalSpy emittedSpy(&emitter, &SignalEmitter::signalWithNoArg);
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QTRY_COMPARE_WITH_TIMEOUT(emittedSpy.size(), 2, 100);
    QTRY_COMPARE(finishedSpy.size(), 1);
    QTRY_VERIFY(emitter.thread.isFinished());
}

void tst_QStateMachine::cancelDelayedEventWithChrono()
{
#if __has_include(<chrono>)
    QStateMachine machine;
    QTest::ignoreMessage(QtWarningMsg,
                         "QStateMachine::cancelDelayedEvent: the machine is not running");
    QVERIFY(!machine.cancelDelayedEvent(-1));

    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    QFinalState *s2 = new QFinalState(&machine);
    s1->addTransition(new StringTransition("a", s2));
    machine.setInitialState(s1);

    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(startedSpy.isValid());
    QVERIFY(runningSpy.isValid());
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 1);
    TEST_RUNNING_CHANGED(true);
    TEST_ACTIVE_CHANGED(s1, 1);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));
    int id1 = machine.postDelayedEvent(new StringEvent("c"), std::chrono::seconds{50});
    QVERIFY(id1 != -1);
    int id2 = machine.postDelayedEvent(new StringEvent("b"), std::chrono::seconds{25});
    QVERIFY(id2 != -1);
    QVERIFY(id2 != id1);
    int id3 = machine.postDelayedEvent(new StringEvent("a"), std::chrono::milliseconds{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, &QStateMachine::finished);
    QVERIFY(finishedSpy.isValid());
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED(false);
    TEST_ACTIVE_CHANGED(s1, 2);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s2));
#endif
}

void tst_QStateMachine::postDelayedEventWithChronoAndStop()
{
#if __has_include(<chrono>)
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    QFinalState *s2 = new QFinalState(&machine);
    s1->addTransition(new StringTransition("a", s2));
    machine.setInitialState(s1);

    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy startedSpy(&machine, &QStateMachine::started);
    QVERIFY(startedSpy.isValid());
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 1);
    TEST_RUNNING_CHANGED(true);
    TEST_ACTIVE_CHANGED(s1, 1);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));

    int id1 = machine.postDelayedEvent(new StringEvent("a"), std::chrono::milliseconds{0});
    QVERIFY(id1 != -1);
    QSignalSpy stoppedSpy(&machine, &QStateMachine::stopped);
    QVERIFY(stoppedSpy.isValid());
    machine.stop();
    QTRY_COMPARE(stoppedSpy.size(), 1);
    TEST_RUNNING_CHANGED(false);
    TEST_ACTIVE_CHANGED(s1, 1);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));

    machine.start();
    QTRY_COMPARE(startedSpy.size(), 2);
    TEST_RUNNING_CHANGED(true);
    TEST_ACTIVE_CHANGED(s1, 3);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));

    int id2 = machine.postDelayedEvent(new StringEvent("a"), std::chrono::seconds{1});
    QVERIFY(id2 != -1);
    machine.stop();
    QTRY_COMPARE(stoppedSpy.size(), 2);
    TEST_RUNNING_CHANGED(false);
    TEST_ACTIVE_CHANGED(s1, 3);
    machine.start();
    QTRY_COMPARE(startedSpy.size(), 3);
    TEST_RUNNING_CHANGED(true);
    QTestEventLoop::instance().enterLoop(2);
    QCOMPARE(machine.configuration().size(), 1);
    QVERIFY(machine.configuration().contains(s1));
    TEST_ACTIVE_CHANGED(s1, 5);
    QVERIFY(machine.isRunning());
#endif
}

class DelayedEventWithChronoPosterThread : public QThread
{
    Q_OBJECT
public:
    DelayedEventWithChronoPosterThread(QStateMachine *machine, QObject *parent = 0)
        : QThread(parent), firstEventWasCancelled(false), m_machine(machine)
    {
        moveToThread(this);
        QObject::connect(m_machine, SIGNAL(started()), this, SLOT(postEvent()));
    }

    mutable bool firstEventWasCancelled;

private Q_SLOTS:
    void postEvent()
    {
#if __has_include(<chrono>)
        int id = m_machine->postDelayedEvent(new QEvent(QEvent::User), std::chrono::seconds{1});
        firstEventWasCancelled = m_machine->cancelDelayedEvent(id);

        m_machine->postDelayedEvent(new QEvent(QEvent::User), std::chrono::milliseconds{1});

        quit();
#endif
    }

private:
    QStateMachine *m_machine;
};

void tst_QStateMachine::postDelayedEventWithChronoFromThread()
{
#if __has_include(<chrono>)
    QStateMachine machine;
    QState *s1 = new QState(&machine);
    DEFINE_ACTIVE_SPY(s1);
    QFinalState *f = new QFinalState(&machine);
    s1->addTransition(new EventTransition(QEvent::User, f));
    machine.setInitialState(s1);

    DelayedEventWithChronoPosterThread poster(&machine);
    poster.start();

    QSignalSpy runningSpy(&machine, &QStateMachine::runningChanged);
    QVERIFY(runningSpy.isValid());
    QSignalSpy finishedSpy(&machine, &QStateMachine::finished);
    QVERIFY(finishedSpy.isValid());
    machine.start();
    QTRY_COMPARE(finishedSpy.size(), 1);
    TEST_RUNNING_CHANGED_STARTED_STOPPED;
    TEST_ACTIVE_CHANGED(s1, 2);
    QVERIFY(poster.firstEventWasCancelled);
#endif
}

void tst_QStateMachine::bindings()
{
    QStateMachine machine;
    TestState *state1 = new TestState(&machine);
    TestTransition *transition1 = new TestTransition(state1);
    TestTransition *transition2 = new TestTransition(state1);

    // -- QHistoryState::defaultTransition
    QHistoryState *historyState = new QHistoryState(&machine);
    QTestPrivate::testReadWritePropertyBasics<QHistoryState, QAbstractTransition*>(
                *historyState, transition1, transition2, "defaultTransition");
    if (QTest::currentTestFailed()) {
        qWarning() << "QHistoryState::defaultTransition bindable test failed.";
        return;
    }

    // -- QHistoryState::historyType
    QHistoryState::HistoryType type1 = QHistoryState::HistoryType::DeepHistory;
    QHistoryState::HistoryType type2 = QHistoryState::HistoryType::ShallowHistory;
    QTestPrivate::testReadWritePropertyBasics<QHistoryState, QHistoryState::HistoryType>(
                *historyState, type1, type2, "historyType");
    if (QTest::currentTestFailed()) {
        qWarning() << "QHistoryState::historyType bindable test failed.";
        return;
    }

    // -- QState::initialState
    QAbstractState *is1 = new QState(state1);
    QAbstractState *is2 = new QState(state1);
    QTestPrivate::testReadWritePropertyBasics<QState, QAbstractState*>(
                *state1, is1, is2, "initialState");
    if (QTest::currentTestFailed()) {
        qWarning() << "QState::initialState bindable test failed.";
        return;
    }

    // -- QState::errorState
    QTestPrivate::testReadWritePropertyBasics<QState, QAbstractState*>(
                *state1, is1, is2, "errorState");
    if (QTest::currentTestFailed()) {
        qWarning() << "QState::errorState bindable test failed.";
        return;
    }

    // -- QState::childMode
    // Make a new state as re-use of state1 would yield irrelevant warnings
    TestState *state2 = new TestState(&machine);
    QState::ChildMode mode1 = QState::ChildMode::ParallelStates;
    QState::ChildMode mode2 = QState::ChildMode::ExclusiveStates;
    QTestPrivate::testReadWritePropertyBasics<QState, QState::ChildMode>(
                *state2, mode1, mode2, "childMode");
    if (QTest::currentTestFailed()) {
        qWarning() << "QState::childMode bindable test failed.";
        return;
    }

    // -- QAbstractState::active
    QState *startState = new QState(&machine);
    QFinalState *endState = new QFinalState(&machine);
    DEFINE_ACTIVE_SPY(startState);
    DEFINE_ACTIVE_SPY(endState);
    machine.setInitialState(startState);
    startState->addTransition(endState);

    // Create a binding to the "active" property
    QProperty<bool> active([&](){ return endState->active(); });
    QVERIFY(!active);
    machine.start();
    // startState should get "enter/active == true" + "exit/active == false"
    QTRY_COMPARE(startState_activeSpy.size(), 2);
    // endState should get "enter/active == true"
    QTRY_COMPARE(endState_activeSpy.size(), 1);
    // Verify that the "active" value changed via binding
    QVERIFY(active);

    // -- QSignalTransition::senderObject
    QSignalTransition signalTransition;
    QObject sender1;
    QObject sender2;
    QTestPrivate::testReadWritePropertyBasics<QSignalTransition, const QObject*>(
                signalTransition, &sender1, &sender2, "senderObject");
    if (QTest::currentTestFailed()) {
        qWarning() << "QSignalTransition::senderObject bindable test failed.";
        return;
    }

    // -- QSignalTransition::signal
    QByteArray arr1("arr1");
    QByteArray arr2("arr2");
    QTestPrivate::testReadWritePropertyBasics<QSignalTransition, QByteArray>(
                signalTransition, arr1, arr2, "signal");
    if (QTest::currentTestFailed()) {
        qWarning() << "QSignalTransition::signal bindable test failed.";
        return;
    }

    // -- QAbstractTransition::transitionType
    auto transitionType1 = QAbstractTransition::InternalTransition;
    auto transitionType2 = QAbstractTransition::ExternalTransition;
    QTestPrivate::testReadWritePropertyBasics<QAbstractTransition, QAbstractTransition::TransitionType>(
                signalTransition, transitionType1, transitionType2, "transitionType");
    if (QTest::currentTestFailed()) {
        qWarning() << "QAbstractTransition::transitionType bindable test failed.";
        return;
    }

    // -- QStateMachine::errorString
    QStateMachine brokenMachine;
    brokenMachine.setErrorState(new QState(&machine)); // avoid warnings
    QState* brokenState = new QState(&brokenMachine);
    brokenMachine.setInitialState(brokenState);
    new QState(brokenState); // make broken state a compound state that lacks the initial state
    QTestPrivate::testReadOnlyPropertyBasics<QStateMachine, QString>(
                brokenMachine, QString(), QStringLiteral("Missing initial state in compound state ''"),
                "errorString",
                [&](){
                      brokenMachine.start();
                      QTRY_VERIFY(brokenMachine.isRunning());
                });
    if (QTest::currentTestFailed()) {
        qWarning() << "QStateMachine::errorString bindable test failed for (non-empty case).";
        return;
    }
    // test also clearing of the error above
    QTestPrivate::testReadOnlyPropertyBasics<QStateMachine, QString>(
                brokenMachine, QStringLiteral("Missing initial state in compound state ''"),  QString(),
                "errorString",
                [&](){ brokenMachine.clearError(); });
    if (QTest::currentTestFailed()) {
        qWarning() << "QStateMachine::errorString bindable test failed (empty case).";
        return;
    }

    // -- QStateMachine::globalRestorePolicy
    QTestPrivate::testReadWritePropertyBasics<QStateMachine, QState::RestorePolicy>(
                machine, QState::RestorePolicy::RestoreProperties,
                QState::RestorePolicy::DontRestoreProperties, "globalRestorePolicy");
    if (QTest::currentTestFailed()) {
        qWarning() << "QStateMachine::globalRestorePolicy bindable test failed.";
        return;
    }

#if QT_CONFIG(animation)
    // -- QStateMachine::animated
    QTestPrivate::testReadWritePropertyBasics<QStateMachine, bool>(
                machine, false, true, "animated");
    if (QTest::currentTestFailed()) {
        qWarning() << "QStateMachine::animated bindable test failed.";
        return;
    }
#endif

    // -- QEventTransition::eventSource
    QEventTransition eventTransition;
    QObject source1;
    QObject source2;
    QTestPrivate::testReadWritePropertyBasics<QEventTransition, QObject*>(
                eventTransition, &source1, &source2, "eventSource");
    if (QTest::currentTestFailed()) {
        qWarning() << "QEventTransition::eventSource bindable test failed.";
        return;
    }

    // -- QEventTransition::eventType
    auto eventType1 = QEvent::Enter;
    auto eventType2 = QEvent::Leave;
    QTestPrivate::testReadWritePropertyBasics<QEventTransition, QEvent::Type>(
                eventTransition, eventType1, eventType2, "eventType");
    if (QTest::currentTestFailed()) {
        qWarning() << "QEventTransition::eventType bindable test failed.";
        return;
    }

    // -- QKeyEventTransition::key
    QKeyEventTransition keyEventTransition;
    int key1{1};
    int key2{2};
    QTestPrivate::testReadWritePropertyBasics<QKeyEventTransition, int>(
                keyEventTransition, key1, key2, "key");
    if (QTest::currentTestFailed()) {
        qWarning() << "QKeyEventTransition::key bindable test failed.";
        return;
    }

    // -- QKeyEventTransition::modifierMask
    Qt::KeyboardModifiers mod1 = Qt::KeyboardModifier::ShiftModifier;
    Qt::KeyboardModifiers mod2 = Qt::KeyboardModifier::ControlModifier;
    QTestPrivate::testReadWritePropertyBasics<QKeyEventTransition>(
                keyEventTransition, mod1, mod2, "modifierMask");
    if (QTest::currentTestFailed()) {
        qWarning() << "QKeyEventTransition::modifierMask bindable test failed.";
        return;
    }

    // -- QMouseEventTransition::button
    QMouseEventTransition mouseEventTransition;
    Qt::MouseButton button1 = Qt::MouseButton::LeftButton;
    Qt::MouseButton button2 = Qt::MouseButton::RightButton;
    QTestPrivate::testReadWritePropertyBasics<QMouseEventTransition>(
                mouseEventTransition, button1, button2, "button");
    if (QTest::currentTestFailed()) {
        qWarning() << "QMouseEventTransition::button bindable test failed.";
        return;
    }

    // -- QMouseEventTransition::modifierMask
    QTestPrivate::testReadWritePropertyBasics<QMouseEventTransition>(
                mouseEventTransition, mod1, mod2, "modifierMask");
    if (QTest::currentTestFailed()) {
        qWarning() << "QMouseEventTransition::modifierMask bindable test failed.";
        return;
    }
}

QTEST_MAIN(tst_QStateMachine)
#include "tst_qstatemachine.moc"
