Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtTest>
++ #include <QtScxml/qscxmlstatemachine.h>
++ #include <QtScxml/private/qscxmlstatemachineinfo_p.h>
++
++ #include <memory>
++
++ class tst_StateMachineInfo: public QObject
++ {
++ Q_OBJECT
++
++ private Q_SLOTS:
++ void checkInfo();
++ };
++
++ class Recorder: public QObject
++ {
++ Q_OBJECT
++
++ public:
++ void clear()
++ {
++ enterCount = 0;
++ entered.clear();
++ exitCount = 0;
++ exited.clear();
++ transitionTriggerCount = 0;
++ transitions.clear();
++ macroStepDone = false;
++ }
++
++ public slots:
++ void statesEntered(const QList<QScxmlStateMachineInfo::StateId> &states)
++ { entered = states; ++enterCount; }
++
++ void statesExited(const QList<QScxmlStateMachineInfo::StateId> &states)
++ { exited = states; ++exitCount; }
++
++ void transitionsTriggered(const QList<QScxmlStateMachineInfo::TransitionId> &transitions)
++ { this->transitions = transitions; ++transitionTriggerCount; }
++
++ void reachedStableState()
++ { macroStepDone = true; }
++
++ bool finishMacroStep() const
++ {
++ for (int i = 0; i < 100; ++i) {
++ if (!macroStepDone)
++ QCoreApplication::processEvents();
++ }
++
++ return macroStepDone;
++ }
++
++ public:
++ int enterCount = 0;
++ QList<QScxmlStateMachineInfo::StateId> entered;
++ int exitCount = 0;
++ QList<QScxmlStateMachineInfo::StateId> exited;
++ int transitionTriggerCount = 0;
++ QList<QScxmlStateMachineInfo::TransitionId> transitions;
++ bool macroStepDone = false;
++ };
++
++ void tst_StateMachineInfo::checkInfo()
++ {
++ QScopedPointer<QScxmlStateMachine> stateMachine(
++ QScxmlStateMachine::fromFile(QString(":/tst_statemachineinfo/statemachine.scxml")));
++ QVERIFY(!stateMachine.isNull());
++ QVERIFY(stateMachine->parseErrors().isEmpty());
++ auto info = std::make_unique<QScxmlStateMachineInfo>(stateMachine.get());
++
++ const QString machineName = QLatin1String("InfoTest");
++ QCOMPARE(stateMachine->name(), machineName);
++
++ auto states = info->allStates();
++ QCOMPARE(states.size(), 5);
++ QCOMPARE(info->stateName(states.at(0)), QLatin1String(""));
++ QCOMPARE(info->stateName(states.at(1)), QLatin1String("next"));
++ QCOMPARE(info->stateName(states.at(2)), QLatin1String("a"));
++ QCOMPARE(info->stateName(states.at(3)), QLatin1String("b"));
++ QCOMPARE(info->stateName(states.at(4)), QLatin1String("theEnd"));
++
++ QCOMPARE(info->stateParent(QScxmlStateMachineInfo::InvalidState),
++ static_cast<int>(QScxmlStateMachineInfo::InvalidStateId));
++ QCOMPARE(info->stateParent(states.at(0)), static_cast<int>(QScxmlStateMachineInfo::InvalidStateId));
++ QCOMPARE(info->stateParent(states.at(1)), static_cast<int>(QScxmlStateMachineInfo::InvalidStateId));
++ QCOMPARE(info->stateParent(states.at(2)), 1);
++ QCOMPARE(info->stateParent(states.at(3)), 1);
++ QCOMPARE(info->stateParent(states.at(4)), static_cast<int>(QScxmlStateMachineInfo::InvalidStateId));
++
++ QCOMPARE(info->stateType(states.at(0)), QScxmlStateMachineInfo::NormalState);
++ QCOMPARE(info->stateType(states.at(1)), QScxmlStateMachineInfo::ParallelState);
++ QCOMPARE(info->stateType(states.at(2)), QScxmlStateMachineInfo::NormalState);
++ QCOMPARE(info->stateType(states.at(3)), QScxmlStateMachineInfo::NormalState);
++ QCOMPARE(info->stateType(states.at(4)), QScxmlStateMachineInfo::FinalState);
++
++ QCOMPARE(info->stateChildren(QScxmlStateMachineInfo::InvalidStateId),
++ QList<int>() << 0 << 1 << 4);
++ QCOMPARE(info->stateChildren(states.at(0)), QList<int>());
++ QCOMPARE(info->stateChildren(states.at(1)), QList<int>() << 2 << 3);
++ QCOMPARE(info->stateChildren(states.at(2)), QList<int>());
++ QCOMPARE(info->stateChildren(states.at(3)), QList<int>());
++ QCOMPARE(info->stateChildren(states.at(4)), QList<int>());
++
++ QCOMPARE(info->initialTransition(QScxmlStateMachineInfo::InvalidStateId), 4);
++ QCOMPARE(info->initialTransition(states.at(0)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId));
++ QCOMPARE(info->initialTransition(states.at(1)), 5);
++ QCOMPARE(info->initialTransition(states.at(2)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId));
++ QCOMPARE(info->initialTransition(states.at(3)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId));
++ QCOMPARE(info->initialTransition(states.at(4)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId));
++
++ auto transitions = info->allTransitions();
++ QCOMPARE(transitions.size(), 6);
++
++ // targetless transition on top level
++ QCOMPARE(info->transitionType(transitions.at(0)), QScxmlStateMachineInfo::ExternalTransition);
++ QCOMPARE(info->stateType(info->transitionSource(transitions.at(0))),
++ QScxmlStateMachineInfo::InvalidState);
++ QCOMPARE(info->transitionTargets(transitions.at(0)).size(), 0);
++ QCOMPARE(info->transitionEvents(transitions.at(0)).size(), 0);
++
++ // <anon>->next
++ QCOMPARE(info->transitionType(transitions.at(1)), QScxmlStateMachineInfo::ExternalTransition);
++ QCOMPARE(info->transitionSource(transitions.at(1)), states.at(0));
++ QCOMPARE(info->transitionTargets(transitions.at(1)).size(), 1);
++ QCOMPARE(info->transitionTargets(transitions.at(1)).at(0), states.at(1));
++ QCOMPARE(info->transitionEvents(transitions.at(1)).size(), 1);
++ QCOMPARE(info->transitionEvents(transitions.at(1)).at(0), QStringLiteral("step"));
++
++ // a->theEnd
++ QCOMPARE(info->transitionType(transitions.at(2)), QScxmlStateMachineInfo::ExternalTransition);
++ QCOMPARE(info->transitionSource(transitions.at(2)), states.at(2));
++ QCOMPARE(info->transitionTargets(transitions.at(2)).size(), 1);
++ QCOMPARE(info->transitionTargets(transitions.at(2)).at(0), states.at(4));
++ QCOMPARE(info->transitionEvents(transitions.at(2)).size(), 1);
++ QCOMPARE(info->transitionEvents(transitions.at(2)).at(0), QStringLiteral("step"));
++
++ // b->theEnd
++ QCOMPARE(info->transitionType(transitions.at(3)), QScxmlStateMachineInfo::InternalTransition);
++ QCOMPARE(info->transitionSource(transitions.at(3)), states.at(3));
++ QCOMPARE(info->transitionTargets(transitions.at(3)).size(), 1);
++ QCOMPARE(info->transitionTargets(transitions.at(3)).at(0), states.at(4));
++ QCOMPARE(info->transitionEvents(transitions.at(3)).size(), 1);
++ QCOMPARE(info->transitionEvents(transitions.at(3)).at(0), QStringLiteral("step"));
++
++ // initial transition that activates the first (anonymous) state
++ QCOMPARE(info->transitionType(transitions.at(4)), QScxmlStateMachineInfo::SyntheticTransition);
++ QCOMPARE(info->stateType(info->transitionSource(transitions.at(4))),
++ QScxmlStateMachineInfo::InvalidState);
++ QCOMPARE(info->transitionTargets(transitions.at(4)).size(), 1);
++ QCOMPARE(info->transitionTargets(transitions.at(4)).at(0), states.at(0));
++ QCOMPARE(info->transitionEvents(transitions.at(4)).size(), 0);
++
++ // "initial" transition in the next state that activates all sub-states
++ QCOMPARE(info->transitionType(transitions.at(5)), QScxmlStateMachineInfo::SyntheticTransition);
++ QCOMPARE(info->transitionSource(transitions.at(5)), states.at(1));
++ QCOMPARE(info->transitionTargets(transitions.at(5)).size(), 2);
++ QCOMPARE(info->transitionTargets(transitions.at(5)).at(0), states.at(2));
++ QCOMPARE(info->transitionTargets(transitions.at(5)).at(1), states.at(3));
++ QCOMPARE(info->transitionEvents(transitions.at(5)).size(), 0);
++
++ Recorder recorder;
++ QObject::connect(info.get(), &QScxmlStateMachineInfo::statesEntered,
++ &recorder, &Recorder::statesEntered);
++ QObject::connect(info.get(), &QScxmlStateMachineInfo::statesExited,
++ &recorder, &Recorder::statesExited);
++ QObject::connect(info.get(), &QScxmlStateMachineInfo::transitionsTriggered,
++ &recorder, &Recorder::transitionsTriggered);
++ QObject::connect(stateMachine.data(), &QScxmlStateMachine::reachedStableState,
++ &recorder, &Recorder::reachedStableState);
++
++ // initial step into first anonymous state
++ stateMachine->start();
++ QVERIFY(recorder.finishMacroStep());
++ QCOMPARE(recorder.enterCount, 1);
++ QCOMPARE(recorder.entered, QList<QScxmlStateMachineInfo::StateId>() << 0);
++ QVERIFY(recorder.exited.isEmpty());
++
++ recorder.clear();
++
++ // step from anonymous state into the parallel state, which activates "a" and "b" (in THAT
++ // order!)
++ stateMachine->submitEvent("step");
++ QVERIFY(recorder.finishMacroStep());
++ QCOMPARE(recorder.enterCount, 1);
++ QCOMPARE(recorder.entered, QList<QScxmlStateMachineInfo::StateId>() << 1 << 2 << 3);
++ QCOMPARE(recorder.exited, QList<QScxmlStateMachineInfo::StateId>() << 0);
++ QCOMPARE(recorder.transitionTriggerCount, 1);
++ QCOMPARE(recorder.transitions, QList<QScxmlStateMachineInfo::TransitionId>() << 1);
++
++ recorder.clear();
++
++ // step from the state "b" into "theEnd", which exits "b", "a", and "next" in exactly that
++ // order
++ stateMachine->submitEvent("step");
++ QVERIFY(recorder.finishMacroStep());
++ QCOMPARE(recorder.enterCount, 1);
++ QCOMPARE(recorder.entered, QList<QScxmlStateMachineInfo::StateId>() << 4);
++ QCOMPARE(recorder.exited, QList<QScxmlStateMachineInfo::StateId>() << 3 << 2 << 1);
++ QCOMPARE(recorder.transitionTriggerCount, 1);
++ QCOMPARE(recorder.transitions, QList<QScxmlStateMachineInfo::TransitionId>() << 2);
++ }
++
++
++ QTEST_MAIN(tst_StateMachineInfo)
++
++ #include "tst_statemachineinfo.moc"
++
++