From bc0d3869e409b40faa5e152a3e38e8ed97938efd Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Mon, 19 Sep 2016 12:33:25 +0200 Subject: State machine interrogation interface This interface can be used to get all states and transitions from an SCXML state machine. It will also signal which states are entered and exited for each microstep, and which transitions are taken by the microstep. Change-Id: I4bb936add6b3fd87a322093b8aee66521bb294a4 Reviewed-by: Ulf Hermann --- tests/auto/auto.pro | 3 +- tests/auto/statemachineinfo/statemachine.scxml | 45 +++++ tests/auto/statemachineinfo/statemachineinfo.pro | 13 ++ .../auto/statemachineinfo/tst_statemachineinfo.cpp | 205 +++++++++++++++++++++ .../auto/statemachineinfo/tst_statemachineinfo.qrc | 5 + 5 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 tests/auto/statemachineinfo/statemachine.scxml create mode 100644 tests/auto/statemachineinfo/statemachineinfo.pro create mode 100644 tests/auto/statemachineinfo/tst_statemachineinfo.cpp create mode 100644 tests/auto/statemachineinfo/tst_statemachineinfo.qrc (limited to 'tests') diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 776a54a..60576d5 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -4,4 +4,5 @@ SUBDIRS = cmake\ dynamicmetaobject\ parser\ scion\ - statemachine + statemachine \ + statemachineinfo diff --git a/tests/auto/statemachineinfo/statemachine.scxml b/tests/auto/statemachineinfo/statemachine.scxml new file mode 100644 index 0000000..94f06b1 --- /dev/null +++ b/tests/auto/statemachineinfo/statemachine.scxml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + diff --git a/tests/auto/statemachineinfo/statemachineinfo.pro b/tests/auto/statemachineinfo/statemachineinfo.pro new file mode 100644 index 0000000..f8d5b5d --- /dev/null +++ b/tests/auto/statemachineinfo/statemachineinfo.pro @@ -0,0 +1,13 @@ +QT = core gui qml testlib scxml-private +CONFIG += testcase + +TARGET = tst_statemachineinfo +CONFIG += console +CONFIG -= app_bundle + +TEMPLATE = app + +RESOURCES += tst_statemachineinfo.qrc + +SOURCES += \ + tst_statemachineinfo.cpp diff --git a/tests/auto/statemachineinfo/tst_statemachineinfo.cpp b/tests/auto/statemachineinfo/tst_statemachineinfo.cpp new file mode 100644 index 0000000..1c33847 --- /dev/null +++ b/tests/auto/statemachineinfo/tst_statemachineinfo.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtScxml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include + +Q_DECLARE_METATYPE(QScxmlError); + +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 QVector &states) + { entered = states; ++enterCount; } + + void statesExited(const QVector &states) + { exited = states; ++exitCount; } + + void transitionsTriggered(const QVector &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; + QVector entered; + int exitCount = 0; + QVector exited; + int transitionTriggerCount = 0; + QVector transitions; + bool macroStepDone = false; +}; + +void tst_StateMachineInfo::checkInfo() +{ + QScopedPointer stateMachine( + QScxmlStateMachine::fromFile(QString(":/tst_statemachineinfo/statemachine.scxml"))); + QVERIFY(!stateMachine.isNull()); + QVERIFY(stateMachine->parseErrors().isEmpty()); + auto info = new QScxmlStateMachineInfo(stateMachine.data()); + + const QString machineName = QLatin1String("InfoTest"); + QCOMPARE(info->stateName(QScxmlStateMachineInfo::StateMachineRootState), 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->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); + + 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::StateMachineRootState); + QCOMPARE(info->transitionTargets(transitions.at(0)).size(), 0); + + // ->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)); + + // 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)); + + // 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)); + + // 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::StateMachineRootState); + QCOMPARE(info->transitionTargets(transitions.at(4)).size(), 1); + QCOMPARE(info->transitionTargets(transitions.at(4)).at(0), states.at(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)); + + Recorder recorder; + QObject::connect(info, &QScxmlStateMachineInfo::statesEntered, + &recorder, &Recorder::statesEntered); + QObject::connect(info, &QScxmlStateMachineInfo::statesExited, + &recorder, &Recorder::statesExited); + QObject::connect(info, &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, QVector() << 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, QVector() << 1 << 2 << 3); + QCOMPARE(recorder.exited, QVector() << 0); + QCOMPARE(recorder.transitionTriggerCount, 1); + QCOMPARE(recorder.transitions, QVector() << 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, QVector() << 4); + QCOMPARE(recorder.exited, QVector() << 3 << 2 << 1); + QCOMPARE(recorder.transitionTriggerCount, 1); + QCOMPARE(recorder.transitions, QVector() << 2); +} + + +QTEST_MAIN(tst_StateMachineInfo) + +#include "tst_statemachineinfo.moc" + + diff --git a/tests/auto/statemachineinfo/tst_statemachineinfo.qrc b/tests/auto/statemachineinfo/tst_statemachineinfo.qrc new file mode 100644 index 0000000..0e464d4 --- /dev/null +++ b/tests/auto/statemachineinfo/tst_statemachineinfo.qrc @@ -0,0 +1,5 @@ + + + statemachine.scxml + + -- cgit v1.2.3