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 <QObject>
31#include <QXmlStreamReader>
32#include <QtScxml/qscxmlcompiler.h>
33#include <QtScxml/qscxmlstatemachine.h>
34#include <QtScxml/qscxmlinvokableservice.h>
35#include <QtScxml/private/qscxmlstatemachine_p.h>
36
37enum { SpyWaitTime = 8000 };
38
39class tst_StateMachine: public QObject
40{
41 Q_OBJECT
42
43private Q_SLOTS:
44 void stateNames_data();
45 void stateNames();
46 void activeStateNames_data();
47 void activeStateNames();
48 void connections();
49 void historyState();
50 void onExit();
51 void eventOccurred();
52
53 void doneDotStateEvent();
54 void running();
55
56 void invokeStateMachine();
57
58 void multipleInvokableServices(); // QTBUG-61484
59 void logWithoutExpr();
60};
61
62void tst_StateMachine::stateNames_data()
63{
64 QTest::addColumn<QString>(name: "scxmlFileName");
65 QTest::addColumn<bool>(name: "compressed");
66 QTest::addColumn<QStringList>(name: "expectedStates");
67
68 QTest::newRow(dataTag: "stateNames-compressed") << QString(":/tst_statemachine/statenames.scxml")
69 << true
70 << (QStringList() << QString("a1") << QString("a2") << QString("final"));
71 QTest::newRow(dataTag: "stateNames-notCompressed") << QString(":/tst_statemachine/statenames.scxml")
72 << false
73 << (QStringList() << QString("top") << QString("a") << QString("a1") << QString("a2") << QString("b") << QString("final"));
74 QTest::newRow(dataTag: "stateNamesNested-compressed") << QString(":/tst_statemachine/statenamesnested.scxml")
75 << true
76 << (QStringList() << QString("a") << QString("b"));
77 QTest::newRow(dataTag: "stateNamesNested-notCompressed") << QString(":/tst_statemachine/statenamesnested.scxml")
78 << false
79 << (QStringList() << QString("super_top") << QString("a") << QString("b"));
80
81 QTest::newRow(dataTag: "ids1") << QString(":/tst_statemachine/ids1.scxml")
82 << false
83 << (QStringList() << QString("foo.bar") << QString("foo-bar")
84 << QString("foo_bar") << QString("_"));
85}
86
87void tst_StateMachine::stateNames()
88{
89 QFETCH(QString, scxmlFileName);
90 QFETCH(bool, compressed);
91 QFETCH(QStringList, expectedStates);
92
93 QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(fileName: scxmlFileName));
94 QVERIFY(!stateMachine.isNull());
95 QCOMPARE(stateMachine->parseErrors().count(), 0);
96
97 QCOMPARE(stateMachine->stateNames(compressed), expectedStates);
98}
99
100void tst_StateMachine::activeStateNames_data()
101{
102 QTest::addColumn<QString>(name: "scxmlFileName");
103 QTest::addColumn<bool>(name: "compressed");
104 QTest::addColumn<QStringList>(name: "expectedStates");
105
106 QTest::newRow(dataTag: "stateNames-compressed") << QString(":/tst_statemachine/statenames.scxml")
107 << true
108 << (QStringList() << QString("a1") << QString("final"));
109 QTest::newRow(dataTag: "stateNames-notCompressed") << QString(":/tst_statemachine/statenames.scxml")
110 << false
111 << (QStringList() << QString("top") << QString("a") << QString("a1") << QString("b") << QString("final"));
112 QTest::newRow(dataTag: "stateNamesNested-compressed") << QString(":/tst_statemachine/statenamesnested.scxml")
113 << true
114 << (QStringList() << QString("a") << QString("b"));
115 QTest::newRow(dataTag: "stateNamesNested-notCompressed") << QString(":/tst_statemachine/statenamesnested.scxml")
116 << false
117 << (QStringList() << QString("super_top") << QString("a") << QString("b"));
118}
119
120void tst_StateMachine::activeStateNames()
121{
122 QFETCH(QString, scxmlFileName);
123 QFETCH(bool, compressed);
124 QFETCH(QStringList, expectedStates);
125
126 QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(fileName: scxmlFileName));
127 QVERIFY(!stateMachine.isNull());
128
129 QSignalSpy stableStateSpy(stateMachine.data(), SIGNAL(reachedStableState()));
130
131 stateMachine->start();
132
133 stableStateSpy.wait(timeout: 5000);
134
135 QCOMPARE(stateMachine->activeStateNames(compressed), expectedStates);
136}
137
138class Receiver : public QObject {
139 Q_OBJECT
140public slots:
141 void a(bool enabled)
142 {
143 aReached = aReached || enabled;
144 }
145
146 void b(bool enabled)
147 {
148 bReached = bReached || enabled;
149 }
150
151 void aEnter()
152 {
153 aEntered = true;
154 }
155
156 void aExit()
157 {
158 aExited = true;
159 }
160
161public:
162 bool aReached = false;
163 bool bReached = false;
164 bool aEntered = false;
165 bool aExited = false;
166};
167
168void tst_StateMachine::connections()
169{
170 QScopedPointer<QScxmlStateMachine> stateMachine(
171 QScxmlStateMachine::fromFile(fileName: QString(":/tst_statemachine/statenames.scxml")));
172 QVERIFY(!stateMachine.isNull());
173 Receiver receiver;
174
175 bool a1Reached = false;
176 bool finalReached = false;
177 QMetaObject::Connection a = stateMachine->connectToState(scxmlStateName: "a", receiver: &receiver, method: &Receiver::a);
178 QVERIFY(a);
179 QMetaObject::Connection b = stateMachine->connectToState(scxmlStateName: "b", receiver: &receiver, SLOT(b(bool)));
180 QVERIFY(b);
181 QMetaObject::Connection a1 = stateMachine->connectToState(scxmlStateName: "a1", context: &receiver,
182 functor: [&a1Reached](bool enabled) {
183 a1Reached = a1Reached || enabled;
184 });
185 QVERIFY(a1);
186 QMetaObject::Connection final = stateMachine->connectToState(scxmlStateName: "final",
187 functor: [&finalReached](bool enabled) {
188 finalReached = finalReached || enabled;
189 });
190 QVERIFY(final);
191
192 bool a1Entered = false;
193 bool a1Exited = false;
194 bool finalEntered = false;
195 bool finalExited = false;
196 typedef QScxmlStateMachine QXSM;
197
198 QMetaObject::Connection aEntry = stateMachine->connectToState(
199 scxmlStateName: "a", functor: QXSM::onEntry(receiver: &receiver, method: &Receiver::aEnter));
200 QVERIFY(aEntry);
201 QMetaObject::Connection aExit = stateMachine->connectToState(
202 scxmlStateName: "a", functor: QXSM::onExit(receiver: &receiver, method: &Receiver::aExit));
203 QVERIFY(aExit);
204 QMetaObject::Connection a1Entry = stateMachine->connectToState(scxmlStateName: "a1", context: &receiver,
205 functor: QXSM::onEntry(functor: [&a1Entered]() {
206 a1Entered = true;
207 }));
208 QVERIFY(a1Entry);
209 QMetaObject::Connection a1Exit = stateMachine->connectToState(scxmlStateName: "a1", context: &receiver,
210 functor: QXSM::onExit(functor: [&a1Exited]() {
211 a1Exited = true;
212 }));
213 QVERIFY(a1Exit);
214
215 QMetaObject::Connection finalEntry = stateMachine->connectToState(
216 scxmlStateName: "final", functor: QXSM::onEntry(functor: [&finalEntered]() {
217 finalEntered = true;
218 }));
219 QVERIFY(finalEntry);
220
221 QMetaObject::Connection finalExit = stateMachine->connectToState(
222 scxmlStateName: "final", functor: QXSM::onExit(functor: [&finalExited]() {
223 finalExited = true;
224 }));
225 QVERIFY(finalExit);
226
227 stateMachine->start();
228
229 QTRY_VERIFY(a1Reached);
230 QTRY_VERIFY(finalReached);
231 QTRY_VERIFY(receiver.aReached);
232 QTRY_VERIFY(receiver.bReached);
233
234 QVERIFY(disconnect(a));
235 QVERIFY(disconnect(b));
236 QVERIFY(disconnect(a1));
237 QVERIFY(disconnect(final));
238
239#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304
240 QVERIFY(receiver.aEntered);
241 QVERIFY(!receiver.aExited);
242 QVERIFY(a1Entered);
243 QVERIFY(!a1Exited);
244 QVERIFY(finalEntered);
245 QVERIFY(!finalExited);
246
247 QVERIFY(disconnect(aEntry));
248 QVERIFY(disconnect(aExit));
249 QVERIFY(disconnect(a1Entry));
250 QVERIFY(disconnect(a1Exit));
251 QVERIFY(disconnect(finalEntry));
252 QVERIFY(disconnect(finalExit));
253#endif
254}
255
256void tst_StateMachine::historyState()
257{
258 QScopedPointer<QScxmlStateMachine> stateMachine(
259 QScxmlStateMachine::fromFile(fileName: QString(":/tst_statemachine/historystate.scxml")));
260 QVERIFY(!stateMachine.isNull());
261
262 bool state2Reached = false;
263 QMetaObject::Connection state2Connection = stateMachine->connectToState(scxmlStateName: "State2",
264 functor: [&state2Reached](bool enabled) {
265 state2Reached = state2Reached || enabled;
266 });
267 QVERIFY(state2Connection);
268
269 stateMachine->start();
270
271 QTRY_VERIFY(state2Reached);
272}
273
274void tst_StateMachine::onExit()
275{
276#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304
277 // Test onExit being actually called
278
279 typedef QScxmlStateMachine QXSM;
280 QScopedPointer<QXSM> stateMachine(QXSM::fromFile(fileName: QString(":/tst_statemachine/eventoccurred.scxml")));
281
282 Receiver receiver;
283 bool aExited1 = false;
284
285 stateMachine->connectToState(scxmlStateName: "a", functor: QXSM::onExit(functor: [&aExited1]() { aExited1 = true; }));
286 stateMachine->connectToState(scxmlStateName: "a", functor: QXSM::onExit(receiver: &receiver, method: &Receiver::aExit));
287 stateMachine->connectToState(scxmlStateName: "a", functor: QXSM::onExit(receiver: &receiver, method: "aEnter"));
288 {
289 // Should not crash
290 Receiver receiver2;
291 stateMachine->connectToState(scxmlStateName: "a", functor: QXSM::onEntry(receiver: &receiver2, method: &Receiver::aEnter));
292 stateMachine->connectToState(scxmlStateName: "a", functor: QXSM::onEntry(receiver: &receiver2, method: "aExit"));
293 stateMachine->connectToState(scxmlStateName: "a", functor: QXSM::onExit(receiver: &receiver2, method: &Receiver::aExit));
294 stateMachine->connectToState(scxmlStateName: "a", functor: QXSM::onExit(receiver: &receiver2, method: "aEnter"));
295 }
296
297 stateMachine->start();
298 QTRY_VERIFY(receiver.aEntered);
299 QTRY_VERIFY(receiver.aExited);
300 QTRY_VERIFY(aExited1);
301#endif
302}
303
304bool hasChildEventRouters(QScxmlStateMachine *stateMachine)
305{
306 // Cast to QObject, to avoid ambigous "children" member.
307 const QObject &parentRouter = QScxmlStateMachinePrivate::get(t: stateMachine)->m_router;
308 return !parentRouter.children().isEmpty();
309}
310
311void tst_StateMachine::eventOccurred()
312{
313 QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(fileName: QString(":/tst_statemachine/eventoccurred.scxml")));
314 QVERIFY(!stateMachine.isNull());
315
316 qRegisterMetaType<QScxmlEvent>();
317 QSignalSpy finishedSpy(stateMachine.data(), SIGNAL(finished()));
318
319 int events = 0;
320 auto con1 = stateMachine->connectToEvent(scxmlEventSpec: "internalEvent2", functor: [&events](const QScxmlEvent &event) {
321 QCOMPARE(++events, 1);
322 QCOMPARE(event.name(), QString("internalEvent2"));
323 QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
324 });
325 QVERIFY(con1);
326
327 auto con2 = stateMachine->connectToEvent(scxmlEventSpec: "externalEvent", functor: [&events](const QScxmlEvent &event) {
328 QCOMPARE(++events, 2);
329 QCOMPARE(event.name(), QString("externalEvent"));
330 QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
331 });
332 QVERIFY(con2);
333
334 auto con3 = stateMachine->connectToEvent(scxmlEventSpec: "timeout", functor: [&events](const QScxmlEvent &event) {
335 QCOMPARE(++events, 3);
336 QCOMPARE(event.name(), QString("timeout"));
337 QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
338 });
339 QVERIFY(con3);
340
341 auto con4 = stateMachine->connectToEvent(scxmlEventSpec: "done.*", functor: [&events](const QScxmlEvent &event) {
342 QCOMPARE(++events, 4);
343 QCOMPARE(event.name(), QString("done.state.top"));
344 QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
345 });
346 QVERIFY(con4);
347
348 auto con5 = stateMachine->connectToEvent(scxmlEventSpec: "done.state", functor: [&events](const QScxmlEvent &event) {
349 QCOMPARE(++events, 5);
350 QCOMPARE(event.name(), QString("done.state.top"));
351 QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
352 });
353 QVERIFY(con5);
354
355 auto con6 = stateMachine->connectToEvent(scxmlEventSpec: "done.state.top", functor: [&events](const QScxmlEvent &event) {
356 QCOMPARE(++events, 6);
357 QCOMPARE(event.name(), QString("done.state.top"));
358 QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
359 });
360 QVERIFY(con6);
361
362 stateMachine->start();
363
364 finishedSpy.wait(timeout: 5000);
365 QCOMPARE(events, 6);
366
367 QVERIFY(disconnect(con1));
368 QVERIFY(disconnect(con2));
369 QVERIFY(disconnect(con3));
370 QVERIFY(disconnect(con4));
371 QVERIFY(disconnect(con5));
372 QVERIFY(disconnect(con6));
373
374 QTRY_VERIFY(!hasChildEventRouters(stateMachine.data()));
375}
376
377void tst_StateMachine::doneDotStateEvent()
378{
379 QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(fileName: QString(":/tst_statemachine/stateDotDoneEvent.scxml")));
380 QVERIFY(!stateMachine.isNull());
381
382 QSignalSpy finishedSpy(stateMachine.data(), SIGNAL(finished()));
383
384 stateMachine->start();
385 finishedSpy.wait(timeout: 5000);
386 QCOMPARE(finishedSpy.count(), 1);
387 QCOMPARE(stateMachine->activeStateNames(true).size(), 1);
388 QVERIFY(stateMachine->activeStateNames(true).contains(QLatin1String("success")));
389}
390
391void tst_StateMachine::running()
392{
393 QScopedPointer<QScxmlStateMachine> stateMachine(
394 QScxmlStateMachine::fromFile(fileName: QString(":/tst_statemachine/statenames.scxml")));
395 QVERIFY(!stateMachine.isNull());
396
397 QSignalSpy runningChangedSpy(stateMachine.data(), SIGNAL(runningChanged(bool)));
398
399 QCOMPARE(stateMachine->isRunning(), false);
400
401 stateMachine->start();
402
403 QCOMPARE(runningChangedSpy.count(), 1);
404 QCOMPARE(stateMachine->isRunning(), true);
405
406 stateMachine->stop();
407
408 QCOMPARE(runningChangedSpy.count(), 2);
409 QCOMPARE(stateMachine->isRunning(), false);
410}
411
412void tst_StateMachine::invokeStateMachine()
413{
414 QScopedPointer<QScxmlStateMachine> stateMachine(
415 QScxmlStateMachine::fromFile(fileName: QString(":/tst_statemachine/invoke.scxml")));
416 QVERIFY(!stateMachine.isNull());
417
418 stateMachine->start();
419 QCOMPARE(stateMachine->isRunning(), true);
420 QTRY_VERIFY(stateMachine->activeStateNames().contains(QString("anyplace")));
421
422 QVector<QScxmlInvokableService *> services = stateMachine->invokedServices();
423 QCOMPARE(services.length(), 1);
424 QVariant subMachineVariant = services[0]->property(name: "stateMachine");
425 QVERIFY(subMachineVariant.isValid());
426 QScxmlStateMachine *subMachine = qvariant_cast<QScxmlStateMachine *>(v: subMachineVariant);
427 QVERIFY(subMachine);
428 QTRY_VERIFY(subMachine->activeStateNames().contains("here"));
429}
430
431void tst_StateMachine::multipleInvokableServices()
432{
433 QScopedPointer<QScxmlStateMachine> stateMachine(
434 QScxmlStateMachine::fromFile(fileName: QString(":/tst_statemachine/multipleinvokableservices.scxml")));
435 QVERIFY(!stateMachine.isNull());
436
437 QSignalSpy finishedSpy(stateMachine.data(), SIGNAL(finished()));
438 stateMachine->start();
439 QCOMPARE(stateMachine->isRunning(), true);
440
441 finishedSpy.wait(timeout: 5000);
442 QCOMPARE(finishedSpy.count(), 1);
443 QCOMPARE(stateMachine->activeStateNames(true).size(), 1);
444 QVERIFY(stateMachine->activeStateNames(true).contains(QLatin1String("success")));
445}
446
447void tst_StateMachine::logWithoutExpr()
448{
449 QScopedPointer<QScxmlStateMachine> stateMachine(
450 QScxmlStateMachine::fromFile(fileName: QString(":/tst_statemachine/emptylog.scxml")));
451 QVERIFY(!stateMachine.isNull());
452 QTest::ignoreMessage(type: QtDebugMsg, message: "\"Hi2\" : \"\"");
453 stateMachine->start();
454 QSignalSpy logSpy(stateMachine.data(), SIGNAL(log(QString,QString)));
455 QTRY_COMPARE(logSpy.count(), 1);
456}
457
458QTEST_MAIN(tst_StateMachine)
459
460#include "tst_statemachine.moc"
461
462
463

source code of qtscxml/tests/auto/statemachine/tst_statemachine.cpp