| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. |
| 4 | ** Contact: https://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the QtScxml module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at https://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| 21 | ** included in the packaging of this file. Please review the following |
| 22 | ** information to ensure the GNU General Public License requirements will |
| 23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| 24 | ** |
| 25 | ** $QT_END_LICENSE$ |
| 26 | ** |
| 27 | ****************************************************************************/ |
| 28 | |
| 29 | #include <QtTest> |
| 30 | #include <QtScxml/qscxmlstatemachine.h> |
| 31 | #include <QtScxml/private/qscxmlstatemachineinfo_p.h> |
| 32 | |
| 33 | class tst_StateMachineInfo: public QObject |
| 34 | { |
| 35 | Q_OBJECT |
| 36 | |
| 37 | private Q_SLOTS: |
| 38 | void checkInfo(); |
| 39 | }; |
| 40 | |
| 41 | class Recorder: public QObject |
| 42 | { |
| 43 | Q_OBJECT |
| 44 | |
| 45 | public: |
| 46 | void clear() |
| 47 | { |
| 48 | enterCount = 0; |
| 49 | entered.clear(); |
| 50 | exitCount = 0; |
| 51 | exited.clear(); |
| 52 | transitionTriggerCount = 0; |
| 53 | transitions.clear(); |
| 54 | macroStepDone = false; |
| 55 | } |
| 56 | |
| 57 | public slots: |
| 58 | void statesEntered(const QVector<QScxmlStateMachineInfo::StateId> &states) |
| 59 | { entered = states; ++enterCount; } |
| 60 | |
| 61 | void statesExited(const QVector<QScxmlStateMachineInfo::StateId> &states) |
| 62 | { exited = states; ++exitCount; } |
| 63 | |
| 64 | void transitionsTriggered(const QVector<QScxmlStateMachineInfo::TransitionId> &transitions) |
| 65 | { this->transitions = transitions; ++transitionTriggerCount; } |
| 66 | |
| 67 | void reachedStableState() |
| 68 | { macroStepDone = true; } |
| 69 | |
| 70 | bool finishMacroStep() const |
| 71 | { |
| 72 | for (int i = 0; i < 100; ++i) { |
| 73 | if (!macroStepDone) |
| 74 | QCoreApplication::processEvents(); |
| 75 | } |
| 76 | |
| 77 | return macroStepDone; |
| 78 | } |
| 79 | |
| 80 | public: |
| 81 | int enterCount = 0; |
| 82 | QVector<QScxmlStateMachineInfo::StateId> entered; |
| 83 | int exitCount = 0; |
| 84 | QVector<QScxmlStateMachineInfo::StateId> exited; |
| 85 | int transitionTriggerCount = 0; |
| 86 | QVector<QScxmlStateMachineInfo::TransitionId> transitions; |
| 87 | bool macroStepDone = false; |
| 88 | }; |
| 89 | |
| 90 | void tst_StateMachineInfo::checkInfo() |
| 91 | { |
| 92 | QScopedPointer<QScxmlStateMachine> stateMachine( |
| 93 | QScxmlStateMachine::fromFile(fileName: QString(":/tst_statemachineinfo/statemachine.scxml" ))); |
| 94 | QVERIFY(!stateMachine.isNull()); |
| 95 | QVERIFY(stateMachine->parseErrors().isEmpty()); |
| 96 | auto info = new QScxmlStateMachineInfo(stateMachine.data()); |
| 97 | |
| 98 | const QString machineName = QLatin1String("InfoTest" ); |
| 99 | QCOMPARE(stateMachine->name(), machineName); |
| 100 | |
| 101 | auto states = info->allStates(); |
| 102 | QCOMPARE(states.size(), 5); |
| 103 | QCOMPARE(info->stateName(states.at(0)), QLatin1String("" )); |
| 104 | QCOMPARE(info->stateName(states.at(1)), QLatin1String("next" )); |
| 105 | QCOMPARE(info->stateName(states.at(2)), QLatin1String("a" )); |
| 106 | QCOMPARE(info->stateName(states.at(3)), QLatin1String("b" )); |
| 107 | QCOMPARE(info->stateName(states.at(4)), QLatin1String("theEnd" )); |
| 108 | |
| 109 | QCOMPARE(info->stateParent(QScxmlStateMachineInfo::InvalidState), |
| 110 | static_cast<int>(QScxmlStateMachineInfo::InvalidStateId)); |
| 111 | QCOMPARE(info->stateParent(states.at(0)), static_cast<int>(QScxmlStateMachineInfo::InvalidStateId)); |
| 112 | QCOMPARE(info->stateParent(states.at(1)), static_cast<int>(QScxmlStateMachineInfo::InvalidStateId)); |
| 113 | QCOMPARE(info->stateParent(states.at(2)), 1); |
| 114 | QCOMPARE(info->stateParent(states.at(3)), 1); |
| 115 | QCOMPARE(info->stateParent(states.at(4)), static_cast<int>(QScxmlStateMachineInfo::InvalidStateId)); |
| 116 | |
| 117 | QCOMPARE(info->stateType(states.at(0)), QScxmlStateMachineInfo::NormalState); |
| 118 | QCOMPARE(info->stateType(states.at(1)), QScxmlStateMachineInfo::ParallelState); |
| 119 | QCOMPARE(info->stateType(states.at(2)), QScxmlStateMachineInfo::NormalState); |
| 120 | QCOMPARE(info->stateType(states.at(3)), QScxmlStateMachineInfo::NormalState); |
| 121 | QCOMPARE(info->stateType(states.at(4)), QScxmlStateMachineInfo::FinalState); |
| 122 | |
| 123 | QCOMPARE(info->stateChildren(QScxmlStateMachineInfo::InvalidStateId), |
| 124 | QVector<int>() << 0 << 1 << 4); |
| 125 | QCOMPARE(info->stateChildren(states.at(0)), QVector<int>()); |
| 126 | QCOMPARE(info->stateChildren(states.at(1)), QVector<int>() << 2 << 3); |
| 127 | QCOMPARE(info->stateChildren(states.at(2)), QVector<int>()); |
| 128 | QCOMPARE(info->stateChildren(states.at(3)), QVector<int>()); |
| 129 | QCOMPARE(info->stateChildren(states.at(4)), QVector<int>()); |
| 130 | |
| 131 | QCOMPARE(info->initialTransition(QScxmlStateMachineInfo::InvalidStateId), 4); |
| 132 | QCOMPARE(info->initialTransition(states.at(0)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId)); |
| 133 | QCOMPARE(info->initialTransition(states.at(1)), 5); |
| 134 | QCOMPARE(info->initialTransition(states.at(2)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId)); |
| 135 | QCOMPARE(info->initialTransition(states.at(3)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId)); |
| 136 | QCOMPARE(info->initialTransition(states.at(4)), static_cast<int>(QScxmlStateMachineInfo::InvalidTransitionId)); |
| 137 | |
| 138 | auto transitions = info->allTransitions(); |
| 139 | QCOMPARE(transitions.size(), 6); |
| 140 | |
| 141 | // targetless transition on top level |
| 142 | QCOMPARE(info->transitionType(transitions.at(0)), QScxmlStateMachineInfo::ExternalTransition); |
| 143 | QCOMPARE(info->stateType(info->transitionSource(transitions.at(0))), |
| 144 | QScxmlStateMachineInfo::InvalidState); |
| 145 | QCOMPARE(info->transitionTargets(transitions.at(0)).size(), 0); |
| 146 | QCOMPARE(info->transitionEvents(transitions.at(0)).size(), 0); |
| 147 | |
| 148 | // <anon>->next |
| 149 | QCOMPARE(info->transitionType(transitions.at(1)), QScxmlStateMachineInfo::ExternalTransition); |
| 150 | QCOMPARE(info->transitionSource(transitions.at(1)), states.at(0)); |
| 151 | QCOMPARE(info->transitionTargets(transitions.at(1)).size(), 1); |
| 152 | QCOMPARE(info->transitionTargets(transitions.at(1)).at(0), states.at(1)); |
| 153 | QCOMPARE(info->transitionEvents(transitions.at(1)).size(), 1); |
| 154 | QCOMPARE(info->transitionEvents(transitions.at(1)).at(0), QStringLiteral("step" )); |
| 155 | |
| 156 | // a->theEnd |
| 157 | QCOMPARE(info->transitionType(transitions.at(2)), QScxmlStateMachineInfo::ExternalTransition); |
| 158 | QCOMPARE(info->transitionSource(transitions.at(2)), states.at(2)); |
| 159 | QCOMPARE(info->transitionTargets(transitions.at(2)).size(), 1); |
| 160 | QCOMPARE(info->transitionTargets(transitions.at(2)).at(0), states.at(4)); |
| 161 | QCOMPARE(info->transitionEvents(transitions.at(2)).size(), 1); |
| 162 | QCOMPARE(info->transitionEvents(transitions.at(2)).at(0), QStringLiteral("step" )); |
| 163 | |
| 164 | // b->theEnd |
| 165 | QCOMPARE(info->transitionType(transitions.at(3)), QScxmlStateMachineInfo::InternalTransition); |
| 166 | QCOMPARE(info->transitionSource(transitions.at(3)), states.at(3)); |
| 167 | QCOMPARE(info->transitionTargets(transitions.at(3)).size(), 1); |
| 168 | QCOMPARE(info->transitionTargets(transitions.at(3)).at(0), states.at(4)); |
| 169 | QCOMPARE(info->transitionEvents(transitions.at(3)).size(), 1); |
| 170 | QCOMPARE(info->transitionEvents(transitions.at(3)).at(0), QStringLiteral("step" )); |
| 171 | |
| 172 | // initial transition that activates the first (anonymous) state |
| 173 | QCOMPARE(info->transitionType(transitions.at(4)), QScxmlStateMachineInfo::SyntheticTransition); |
| 174 | QCOMPARE(info->stateType(info->transitionSource(transitions.at(4))), |
| 175 | QScxmlStateMachineInfo::InvalidState); |
| 176 | QCOMPARE(info->transitionTargets(transitions.at(4)).size(), 1); |
| 177 | QCOMPARE(info->transitionTargets(transitions.at(4)).at(0), states.at(0)); |
| 178 | QCOMPARE(info->transitionEvents(transitions.at(4)).size(), 0); |
| 179 | |
| 180 | // "initial" transition in the next state that activates all sub-states |
| 181 | QCOMPARE(info->transitionType(transitions.at(5)), QScxmlStateMachineInfo::SyntheticTransition); |
| 182 | QCOMPARE(info->transitionSource(transitions.at(5)), states.at(1)); |
| 183 | QCOMPARE(info->transitionTargets(transitions.at(5)).size(), 2); |
| 184 | QCOMPARE(info->transitionTargets(transitions.at(5)).at(0), states.at(2)); |
| 185 | QCOMPARE(info->transitionTargets(transitions.at(5)).at(1), states.at(3)); |
| 186 | QCOMPARE(info->transitionEvents(transitions.at(5)).size(), 0); |
| 187 | |
| 188 | Recorder recorder; |
| 189 | QObject::connect(sender: info, signal: &QScxmlStateMachineInfo::statesEntered, |
| 190 | receiver: &recorder, slot: &Recorder::statesEntered); |
| 191 | QObject::connect(sender: info, signal: &QScxmlStateMachineInfo::statesExited, |
| 192 | receiver: &recorder, slot: &Recorder::statesExited); |
| 193 | QObject::connect(sender: info, signal: &QScxmlStateMachineInfo::transitionsTriggered, |
| 194 | receiver: &recorder, slot: &Recorder::transitionsTriggered); |
| 195 | QObject::connect(sender: stateMachine.data(), signal: &QScxmlStateMachine::reachedStableState, |
| 196 | receiver: &recorder, slot: &Recorder::reachedStableState); |
| 197 | |
| 198 | // initial step into first anonymous state |
| 199 | stateMachine->start(); |
| 200 | QVERIFY(recorder.finishMacroStep()); |
| 201 | QCOMPARE(recorder.enterCount, 1); |
| 202 | QCOMPARE(recorder.entered, QVector<QScxmlStateMachineInfo::StateId>() << 0); |
| 203 | QVERIFY(recorder.exited.isEmpty()); |
| 204 | |
| 205 | recorder.clear(); |
| 206 | |
| 207 | // step from anonymous state into the parallel state, which activates "a" and "b" (in THAT |
| 208 | // order!) |
| 209 | stateMachine->submitEvent(eventName: "step" ); |
| 210 | QVERIFY(recorder.finishMacroStep()); |
| 211 | QCOMPARE(recorder.enterCount, 1); |
| 212 | QCOMPARE(recorder.entered, QVector<QScxmlStateMachineInfo::StateId>() << 1 << 2 << 3); |
| 213 | QCOMPARE(recorder.exited, QVector<QScxmlStateMachineInfo::StateId>() << 0); |
| 214 | QCOMPARE(recorder.transitionTriggerCount, 1); |
| 215 | QCOMPARE(recorder.transitions, QVector<QScxmlStateMachineInfo::TransitionId>() << 1); |
| 216 | |
| 217 | recorder.clear(); |
| 218 | |
| 219 | // step from the state "b" into "theEnd", which exits "b", "a", and "next" in exactly that |
| 220 | // order |
| 221 | stateMachine->submitEvent(eventName: "step" ); |
| 222 | QVERIFY(recorder.finishMacroStep()); |
| 223 | QCOMPARE(recorder.enterCount, 1); |
| 224 | QCOMPARE(recorder.entered, QVector<QScxmlStateMachineInfo::StateId>() << 4); |
| 225 | QCOMPARE(recorder.exited, QVector<QScxmlStateMachineInfo::StateId>() << 3 << 2 << 1); |
| 226 | QCOMPARE(recorder.transitionTriggerCount, 1); |
| 227 | QCOMPARE(recorder.transitions, QVector<QScxmlStateMachineInfo::TransitionId>() << 2); |
| 228 | } |
| 229 | |
| 230 | |
| 231 | QTEST_MAIN(tst_StateMachineInfo) |
| 232 | |
| 233 | #include "tst_statemachineinfo.moc" |
| 234 | |
| 235 | |
| 236 | |