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 | |