Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest>
#include <QtTest/private/qpropertytesthelper_p.h>
#include <QObject>
#include <QXmlStreamReader>
#include <QtScxml/qscxmlcompiler.h>
#include <QtScxml/qscxmlstatemachine.h>
#include <QtScxml/qscxmlinvokableservice.h>
#include <QtScxml/private/qscxmlstatemachine_p.h>
#include <QtScxml/QScxmlNullDataModel>
#include "topmachine.h"
enum { SpyWaitTime = 8000 };
class tst_StateMachine: public QObject
{
Q_OBJECT
private Q_SLOTS:
void stateNames_data();
void stateNames();
void activeStateNames_data();
void activeStateNames();
void connections();
void historyState();
void onExit();
void eventOccurred();
void doneDotStateEvent();
void running();
void restart();
void invokeStateMachine();
void multipleInvokableServices(); // QTBUG-61484
void logWithoutExpr();
void foreachLoop();
void bindings();
void setTableDataUpdatesObjectNames();
void finalizeRunsOnDoneInvokeEvents();
void errorsFromNestedStatemachine(); // QTBUG-135396
};
void tst_StateMachine::errorsFromNestedStatemachine()
{
QScopedPointer<QScxmlStateMachine> machine(QScxmlStateMachine::fromFile(":/tst_statemachine/nestedinvoker.scxml"));
auto error = machine->parseErrors().front();
QCOMPARE(error.fileName(), "nestederror.scxml");
}
void tst_StateMachine::stateNames_data()
{
QTest::addColumn<QString>("scxmlFileName");
QTest::addColumn<bool>("compressed");
QTest::addColumn<QStringList>("expectedStates");
QTest::newRow("stateNames-compressed") << QString(":/tst_statemachine/statenames.scxml")
<< true
<< (QStringList() << QString("a1") << QString("a2") << QString("final"));
QTest::newRow("stateNames-notCompressed") << QString(":/tst_statemachine/statenames.scxml")
<< false
<< (QStringList() << QString("top") << QString("a") << QString("a1") << QString("a2") << QString("b") << QString("final"));
QTest::newRow("stateNamesNested-compressed") << QString(":/tst_statemachine/statenamesnested.scxml")
<< true
<< (QStringList() << QString("a") << QString("b"));
QTest::newRow("stateNamesNested-notCompressed") << QString(":/tst_statemachine/statenamesnested.scxml")
<< false
<< (QStringList() << QString("super_top") << QString("a") << QString("b"));
QTest::newRow("ids1") << QString(":/tst_statemachine/ids1.scxml")
<< false
<< (QStringList() << QString("foo.bar") << QString("foo-bar")
<< QString("foo_bar") << QString("_"));
}
void tst_StateMachine::stateNames()
{
QFETCH(QString, scxmlFileName);
QFETCH(bool, compressed);
QFETCH(QStringList, expectedStates);
QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(scxmlFileName));
QVERIFY(!stateMachine.isNull());
QCOMPARE(stateMachine->parseErrors().size(), 0);
QCOMPARE(stateMachine->stateNames(compressed), expectedStates);
}
void tst_StateMachine::activeStateNames_data()
{
QTest::addColumn<QString>("scxmlFileName");
QTest::addColumn<bool>("compressed");
QTest::addColumn<QStringList>("expectedStates");
QTest::newRow("stateNames-compressed") << QString(":/tst_statemachine/statenames.scxml")
<< true
<< (QStringList() << QString("a1") << QString("final"));
QTest::newRow("stateNames-notCompressed") << QString(":/tst_statemachine/statenames.scxml")
<< false
<< (QStringList() << QString("top") << QString("a") << QString("a1") << QString("b") << QString("final"));
QTest::newRow("stateNamesNested-compressed") << QString(":/tst_statemachine/statenamesnested.scxml")
<< true
<< (QStringList() << QString("a") << QString("b"));
QTest::newRow("stateNamesNested-notCompressed") << QString(":/tst_statemachine/statenamesnested.scxml")
<< false
<< (QStringList() << QString("super_top") << QString("a") << QString("b"));
}
void tst_StateMachine::activeStateNames()
{
QFETCH(QString, scxmlFileName);
QFETCH(bool, compressed);
QFETCH(QStringList, expectedStates);
QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(scxmlFileName));
QVERIFY(!stateMachine.isNull());
QSignalSpy stableStateSpy(stateMachine.data(), SIGNAL(reachedStableState()));
stateMachine->start();
stableStateSpy.wait(5000);
QCOMPARE(stateMachine->activeStateNames(compressed), expectedStates);
}
class Receiver : public QObject {
Q_OBJECT
public slots:
void a(bool enabled)
{
aReached = aReached || enabled;
}
void b(bool enabled)
{
bReached = bReached || enabled;
}
void aEnter()
{
aEntered = true;
}
void aExit()
{
aExited = true;
}
public:
bool aReached = false;
bool bReached = false;
bool aEntered = false;
bool aExited = false;
};
void tst_StateMachine::connections()
{
QScopedPointer<QScxmlStateMachine> stateMachine(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/statenames.scxml")));
QVERIFY(!stateMachine.isNull());
Receiver receiver;
bool a1Reached = false;
bool finalReached = false;
QMetaObject::Connection a = stateMachine->connectToState("a", &receiver, &Receiver::a);
QVERIFY(a);
QMetaObject::Connection b = stateMachine->connectToState("b", &receiver, SLOT(b(bool)));
QVERIFY(b);
QMetaObject::Connection a1 = stateMachine->connectToState("a1", &receiver,
[&a1Reached](bool enabled) {
a1Reached = a1Reached || enabled;
});
QVERIFY(a1);
QMetaObject::Connection final = stateMachine->connectToState("final",
[&finalReached](bool enabled) {
finalReached = finalReached || enabled;
});
QVERIFY(final);
bool a1Entered = false;
bool a1Exited = false;
bool finalEntered = false;
bool finalExited = false;
typedef QScxmlStateMachine QXSM;
QMetaObject::Connection aEntry = stateMachine->connectToState(
"a", QXSM::onEntry(&receiver, &Receiver::aEnter));
QVERIFY(aEntry);
QMetaObject::Connection aExit = stateMachine->connectToState(
"a", QXSM::onExit(&receiver, &Receiver::aExit));
QVERIFY(aExit);
QMetaObject::Connection a1Entry = stateMachine->connectToState("a1", &receiver,
QXSM::onEntry([&a1Entered]() {
a1Entered = true;
}));
QVERIFY(a1Entry);
QMetaObject::Connection a1Exit = stateMachine->connectToState("a1", &receiver,
QXSM::onExit([&a1Exited]() {
a1Exited = true;
}));
QVERIFY(a1Exit);
QMetaObject::Connection finalEntry = stateMachine->connectToState(
"final", QXSM::onEntry([&finalEntered]() {
finalEntered = true;
}));
QVERIFY(finalEntry);
QMetaObject::Connection finalExit = stateMachine->connectToState(
"final", QXSM::onExit([&finalExited]() {
finalExited = true;
}));
QVERIFY(finalExit);
stateMachine->start();
QTRY_VERIFY(a1Reached);
QTRY_VERIFY(finalReached);
QTRY_VERIFY(receiver.aReached);
QTRY_VERIFY(receiver.bReached);
QVERIFY(disconnect(a));
QVERIFY(disconnect(b));
QVERIFY(disconnect(a1));
QVERIFY(disconnect(final));
#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304
QVERIFY(receiver.aEntered);
QVERIFY(!receiver.aExited);
QVERIFY(a1Entered);
QVERIFY(!a1Exited);
QVERIFY(finalEntered);
QVERIFY(!finalExited);
QVERIFY(disconnect(aEntry));
QVERIFY(disconnect(aExit));
QVERIFY(disconnect(a1Entry));
QVERIFY(disconnect(a1Exit));
QVERIFY(disconnect(finalEntry));
QVERIFY(disconnect(finalExit));
#endif
}
void tst_StateMachine::historyState()
{
QScopedPointer<QScxmlStateMachine> stateMachine(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/historystate.scxml")));
QVERIFY(!stateMachine.isNull());
bool state2Reached = false;
QMetaObject::Connection state2Connection = stateMachine->connectToState("State2",
[&state2Reached](bool enabled) {
state2Reached = state2Reached || enabled;
});
QVERIFY(state2Connection);
stateMachine->start();
QTRY_VERIFY(state2Reached);
}
void tst_StateMachine::onExit()
{
#if defined(__cpp_return_type_deduction) && __cpp_return_type_deduction == 201304
// Test onExit being actually called
typedef QScxmlStateMachine QXSM;
QScopedPointer<QXSM> stateMachine(QXSM::fromFile(QString(":/tst_statemachine/eventoccurred.scxml")));
Receiver receiver;
bool aExited1 = false;
stateMachine->connectToState("a", QXSM::onExit([&aExited1]() { aExited1 = true; }));
stateMachine->connectToState("a", QXSM::onExit(&receiver, &Receiver::aExit));
stateMachine->connectToState("a", QXSM::onExit(&receiver, "aEnter"));
{
// Should not crash
Receiver receiver2;
stateMachine->connectToState("a", QXSM::onEntry(&receiver2, &Receiver::aEnter));
stateMachine->connectToState("a", QXSM::onEntry(&receiver2, "aExit"));
stateMachine->connectToState("a", QXSM::onExit(&receiver2, &Receiver::aExit));
stateMachine->connectToState("a", QXSM::onExit(&receiver2, "aEnter"));
}
stateMachine->start();
QTRY_VERIFY(receiver.aEntered);
QTRY_VERIFY(receiver.aExited);
QTRY_VERIFY(aExited1);
#endif
}
bool hasChildEventRouters(QScxmlStateMachine *stateMachine)
{
// Cast to QObject, to avoid ambigous "children" member.
const QObject &parentRouter = QScxmlStateMachinePrivate::get(stateMachine)->m_router;
return !parentRouter.children().isEmpty();
}
void tst_StateMachine::eventOccurred()
{
QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(QString(":/tst_statemachine/eventoccurred.scxml")));
QVERIFY(!stateMachine.isNull());
qRegisterMetaType<QScxmlEvent>();
QSignalSpy finishedSpy(stateMachine.data(), SIGNAL(finished()));
int events = 0;
auto con1 = stateMachine->connectToEvent("internalEvent2", [&events](const QScxmlEvent &event) {
QCOMPARE(++events, 1);
QCOMPARE(event.name(), QString("internalEvent2"));
QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
});
QVERIFY(con1);
auto con2 = stateMachine->connectToEvent("externalEvent", [&events](const QScxmlEvent &event) {
QCOMPARE(++events, 2);
QCOMPARE(event.name(), QString("externalEvent"));
QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
});
QVERIFY(con2);
auto con3 = stateMachine->connectToEvent("timeout", [&events](const QScxmlEvent &event) {
QCOMPARE(++events, 3);
QCOMPARE(event.name(), QString("timeout"));
QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
});
QVERIFY(con3);
auto con4 = stateMachine->connectToEvent("done.*", [&events](const QScxmlEvent &event) {
QCOMPARE(++events, 4);
QCOMPARE(event.name(), QString("done.state.top"));
QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
});
QVERIFY(con4);
auto con5 = stateMachine->connectToEvent("done.state", [&events](const QScxmlEvent &event) {
QCOMPARE(++events, 5);
QCOMPARE(event.name(), QString("done.state.top"));
QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
});
QVERIFY(con5);
auto con6 = stateMachine->connectToEvent("done.state.top", [&events](const QScxmlEvent &event) {
QCOMPARE(++events, 6);
QCOMPARE(event.name(), QString("done.state.top"));
QCOMPARE(event.eventType(), QScxmlEvent::ExternalEvent);
});
QVERIFY(con6);
stateMachine->start();
finishedSpy.wait(5000);
QCOMPARE(events, 6);
QVERIFY(disconnect(con1));
QVERIFY(disconnect(con2));
QVERIFY(disconnect(con3));
QVERIFY(disconnect(con4));
QVERIFY(disconnect(con5));
QVERIFY(disconnect(con6));
QTRY_VERIFY(!hasChildEventRouters(stateMachine.data()));
}
void tst_StateMachine::doneDotStateEvent()
{
QScopedPointer<QScxmlStateMachine> stateMachine(QScxmlStateMachine::fromFile(QString(":/tst_statemachine/stateDotDoneEvent.scxml")));
QVERIFY(!stateMachine.isNull());
QSignalSpy finishedSpy(stateMachine.data(), SIGNAL(finished()));
stateMachine->start();
finishedSpy.wait(5000);
QCOMPARE(finishedSpy.size(), 1);
QCOMPARE(stateMachine->activeStateNames(true).size(), 1);
QVERIFY(stateMachine->activeStateNames(true).contains(QLatin1String("success")));
}
void tst_StateMachine::running()
{
QScopedPointer<QScxmlStateMachine> stateMachine(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/statenames.scxml")));
QVERIFY(!stateMachine.isNull());
QSignalSpy runningChangedSpy(stateMachine.data(), SIGNAL(runningChanged(bool)));
QCOMPARE(stateMachine->isRunning(), false);
stateMachine->start();
QCOMPARE(runningChangedSpy.size(), 1);
QCOMPARE(stateMachine->isRunning(), true);
stateMachine->stop();
QCOMPARE(runningChangedSpy.size(), 2);
QCOMPARE(stateMachine->isRunning(), false);
}
void tst_StateMachine::restart()
{
QScopedPointer<QScxmlStateMachine> stateMachine(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/stateDotDoneEvent.scxml")));
QVERIFY(!stateMachine.isNull());
QSignalSpy finishedSpy(stateMachine.data(), SIGNAL(finished()));
stateMachine->start();
finishedSpy.wait(5000);
QCOMPARE(finishedSpy.size(), 1);
QCOMPARE(stateMachine->activeStateNames(true).size(), 1);
QVERIFY(stateMachine->activeStateNames(true).contains(QLatin1String("success")));
QTest::ignoreMessage(QtWarningMsg, QRegularExpression("(.*)Can't start finished machine"));
stateMachine->start();
}
void tst_StateMachine::invokeStateMachine()
{
QScopedPointer<QScxmlStateMachine> stateMachine(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/invoke.scxml")));
QVERIFY(!stateMachine.isNull());
stateMachine->start();
QCOMPARE(stateMachine->isRunning(), true);
QTRY_VERIFY(stateMachine->activeStateNames().contains(QString("anyplace")));
QList<QScxmlInvokableService *> services = stateMachine->invokedServices();
QCOMPARE(services.size(), 1);
QVariant subMachineVariant = services[0]->property("stateMachine");
QVERIFY(subMachineVariant.isValid());
QScxmlStateMachine *subMachine = qvariant_cast<QScxmlStateMachine *>(subMachineVariant);
QVERIFY(subMachine);
QTRY_VERIFY(subMachine->activeStateNames().contains("here"));
}
void tst_StateMachine::multipleInvokableServices()
{
QScopedPointer<QScxmlStateMachine> stateMachine(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/multipleinvokableservices.scxml")));
QVERIFY(!stateMachine.isNull());
QSignalSpy finishedSpy(stateMachine.data(), SIGNAL(finished()));
stateMachine->start();
QCOMPARE(stateMachine->isRunning(), true);
finishedSpy.wait(5000);
QCOMPARE(finishedSpy.size(), 1);
QCOMPARE(stateMachine->activeStateNames(true).size(), 1);
QVERIFY(stateMachine->activeStateNames(true).contains(QLatin1String("success")));
}
void tst_StateMachine::logWithoutExpr()
{
QScopedPointer<QScxmlStateMachine> stateMachine(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/emptylog.scxml")));
QVERIFY(!stateMachine.isNull());
QTest::ignoreMessage(QtDebugMsg, "\"Hi2\" : \"\"");
stateMachine->start();
QSignalSpy logSpy(stateMachine.data(), SIGNAL(log(QString,QString)));
QTRY_COMPARE(logSpy.size(), 1);
}
void tst_StateMachine::foreachLoop()
{
QScopedPointer<QScxmlStateMachine> stateMachine(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/foreach.scxml")));
QVERIFY(!stateMachine.isNull());
qRegisterMetaType<QScxmlEvent>();
QSignalSpy finishedSpy(stateMachine.data(), SIGNAL(finished()));
int events1 = 0;
int events2 = 0;
int events3 = 0;
auto con1 = stateMachine->connectToEvent("internalEvent1", [&events1](const QScxmlEvent &event) {
++events1;
QCOMPARE(event.name(), QString("internalEvent1"));
});
QVERIFY(con1);
auto con2 = stateMachine->connectToEvent("internalEvent2", [&events2](const QScxmlEvent &event) {
++events2;
QCOMPARE(event.name(), QString("internalEvent2"));
});
QVERIFY(con2);
auto con3 = stateMachine->connectToEvent("internalEvent3", [&events3](const QScxmlEvent &event) {
++events3;
QCOMPARE(event.name(), QString("internalEvent3"));
});
QVERIFY(con3);
stateMachine->start();
finishedSpy.wait(5000);
QCOMPARE(events1, 3);
QCOMPARE(events2, 3);
QCOMPARE(events3, 3);
}
void tst_StateMachine::bindings()
{
// -- QScxmlStateMachine::initialized
std::unique_ptr<QScxmlStateMachine> stateMachine1(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/invoke.scxml")));
QVERIFY(stateMachine1.get());
QTestPrivate::testReadOnlyPropertyBasics<QScxmlStateMachine, bool>(
*stateMachine1, false, true, "initialized", [&](){ stateMachine1.get()->start(); });
if (QTest::currentTestFailed()) {
qWarning() << "QScxmlStateMachine::initialized bindable test failed.";
return;
}
using StateMachinePtr = std::unique_ptr<QScxmlStateMachine>;
// -- QScxmlStateMachine::initialValues
QVariantMap map1{{"map", 1}};
QVariantMap map2{{"map", 2}};
QTestPrivate::testReadWritePropertyBasics<QScxmlStateMachine, QVariantMap>(
*stateMachine1, map1, map2, "initialValues",
[]() {
return StateMachinePtr{QScxmlStateMachine::fromFile(QString("not_a_real_file"))};
});
if (QTest::currentTestFailed()) {
qWarning() << "QScxmlStateMachine::initialValues bindable test failed.";
return;
}
// -- QScxmlStateMachine::loader
class MockLoader: public QScxmlCompiler::Loader
{
public:
QByteArray load(const QString&, const QString&, QStringList*) override { return QByteArray(); }
};
MockLoader loader1;
MockLoader loader2;
QTestPrivate::testReadWritePropertyBasics<QScxmlStateMachine, QScxmlCompiler::Loader*>(
*stateMachine1, &loader1, &loader2, "loader",
[]() {
return StateMachinePtr{QScxmlStateMachine::fromFile(QString("not_a_real_file"))};
});
if (QTest::currentTestFailed()) {
qWarning() << "QScxmlStateMachine::loader bindable test failed.";
return;
}
// -- QScxmlStateMachine::dataModel
// Use non-existent file below, as valid file would initialize the model
std::unique_ptr<QScxmlStateMachine> stateMachine2(
QScxmlStateMachine::fromFile(QString("not_a_real_file")));
std::unique_ptr<QScxmlStateMachine> stateMachine3(
QScxmlStateMachine::fromFile(QString("not_a_real_file")));
QScxmlNullDataModel model1;
// data can only change once
QTestPrivate::testWriteOncePropertyBasics<QScxmlStateMachine, QScxmlDataModel*>(
*stateMachine2, nullptr, &model1, "dataModel", true,
[]() {
return StateMachinePtr{QScxmlStateMachine::fromFile(QString("not_a_real_file"))};
});
if (QTest::currentTestFailed()) {
qWarning() << "QScxmlStateMachine::dataModel bindable test failed.";
return;
}
// -- QScxmlStateMachine::tableData
// Use the statemachine to generate the tableData for testing
std::unique_ptr<QScxmlStateMachine> stateMachine4(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/invoke.scxml")));
QTestPrivate::testReadWritePropertyBasics<QScxmlStateMachine, QScxmlTableData*>(
*stateMachine2, stateMachine1.get()->tableData(), stateMachine4.get()->tableData(),
"tableData",
[]() {
return StateMachinePtr{QScxmlStateMachine::fromFile(QString("not_a_real_file"))};
});
if (QTest::currentTestFailed()) {
qWarning() << "QScxmlStateMachine::tableData bindable test failed.";
return;
}
// -- QScxmlStateMachine::invokedServices
// Test executes statemachine and observes as the invoked services change
TopMachine topSm;
QSignalSpy invokedSpy(&topSm, SIGNAL(invokedServicesChanged(QList<QScxmlInvokableService*>)));
QCOMPARE(topSm.invokedServices().size(), 0);
// at some point during the topSm execution there are 3 invoked services
topSm.start();
QTRY_COMPARE(topSm.invokedServices().size(), 3);
QCOMPARE(invokedSpy.size(), 1);
// after completion invoked services drop back to 0
QTRY_COMPARE(topSm.isRunning(), false);
QCOMPARE(topSm.invokedServices().size(), 0);
QCOMPARE(invokedSpy.size(), 2);
// bind *to* the invokedservices property and check that we observe same changes
// during the topSm execution
QProperty<qsizetype> invokedServicesObserver;
invokedServicesObserver.setBinding([&](){ return topSm.invokedServices().size(); });
QCOMPARE(invokedServicesObserver, 0);
topSm.start();
QTRY_COMPARE(invokedServicesObserver, 3);
QCOMPARE(topSm.invokedServices().size(), 3);
QCOMPARE(invokedSpy.size(), 3);
// -- QScxmlDataModel::stateMachine
QScxmlNullDataModel dataModel1;
std::unique_ptr<QScxmlStateMachine> stateMachine5(
QScxmlStateMachine::fromFile(QString("not_a_real_file")));
// data can only change once
QTestPrivate::testWriteOncePropertyBasics<QScxmlNullDataModel, QScxmlStateMachine*>(
dataModel1, nullptr, stateMachine5.get(), "stateMachine");
if (QTest::currentTestFailed()) {
qWarning() << "QScxmlDataModel::stateMachine bindable test failed.";
return;
}
}
void tst_StateMachine::setTableDataUpdatesObjectNames()
{
std::unique_ptr<QScxmlStateMachine> stateMachine1(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/emptylog.scxml")));
const QString sm1ObjectName = stateMachine1->objectName();
std::unique_ptr<QScxmlStateMachine> stateMachine2(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/eventoccurred.scxml")));
const QString sm2ObjectName = stateMachine2->objectName();
QCOMPARE_NE(sm1ObjectName, sm2ObjectName);
std::unique_ptr<QScxmlStateMachine> sm(
QScxmlStateMachine::fromFile(QString("not_a_real_file")));
QVERIFY(sm->objectName().isEmpty());
// no name set, so update object name
sm->setTableData(stateMachine1->tableData());
QCOMPARE_EQ(sm->objectName(), sm1ObjectName);
// object name already set, so do not update
sm->setTableData(stateMachine2->tableData());
QCOMPARE_EQ(sm->objectName(), sm1ObjectName); // did not change
}
void tst_StateMachine::finalizeRunsOnDoneInvokeEvents()
{
QScopedPointer<QScxmlStateMachine> stateMachine(
QScxmlStateMachine::fromFile(QString(":/tst_statemachine/finalizeOnDoneInvokeEvent.scxml")));
QVERIFY(!stateMachine.isNull());
QSignalSpy logSpy(stateMachine.data(), SIGNAL(log(QString,QString)));
stateMachine->start();
QTRY_COMPARE(logSpy.size(), 1);
QList<QVariant> arguments = logSpy.takeFirst();
QCOMPARE(arguments.at(0).toString(), QString(u"finalize"));
QVERIFY(arguments.at(1).toString().startsWith("done.invoke.s0"));
}
QTEST_MAIN(tst_StateMachine)
#include "tst_statemachine.moc"