I am testing a shared lib that contains a call to deleteLater
internally.
There is no event loop running inside the library so a requirement for the application is to have a event loop running, so that all memory is properly released.
But in the test, the object dtor is not called as expected.
For example :
void test1()
{
Foo foo;
QSignalSpy spy(&foo, SIGNAL(mySignal(Status)));
foo.do(); // should trigger mySignal
QVERIFY(spy.wait(10000)); // event loop started for 10 s max
QCOMPARE(spy.count(), 1);
QList<QVariant> sig = spy.takeFirst();
Foo::Status status = qvariant_cast<Foo::Status>(sig.at(0));
QVERIFY2(status == Foo:Ok, "Failed");
}
The class Foo
looks like this :
class Foo : public QObject
{
Q_OBJECT
// ... methods, signals, slots..
private slots:
// this call is asynchronous (depends on a network reply)
void myslot() {
//..
m_obj->deleteLater();
emit mySignal(Foo:Ok);
}
};
I have added some debug print in the dtor of m_obj and it is not called when the test1 is executed.
However, if i execute the test twice (by adding a test2 slot that is copy of test1), then it is called once.
My understanding is that when the signal is emitted it stops the spy event loop and then deleteLater
is never called.
And after that the second event loop starts in test2, it process the pending deletion from the previous test1.
Is it correct ? Thank you.
Yes, you're correct. Since the QSignalSpy
and Foo
live in the same thread, the signal mySignal
is not delivered through the event loop but via a direct connection. Therefore, the event loop stops immediately after the signal is emitted in myslot
. However, since myslot
was called by the same event loop, control only returns to it when myslot
returns. So by the time the event loop could potentially perform the cleanup requested by deleteLater
it has already been stopped.
If you want to test that m_obj
is cleaned up correctly you could instead create an additional QSignalSpy
and connect it to the QObject::destroyed
signal that every QObject
emits when it gets destroyed.
You would, however, need to pass in m_obj
as a dependency to Foo
either in the constructor or through a setter instead of constructing it in Foo
itself.
The test could then look something like this:
void test1()
{
auto obj = new Obj{}; // will be assigned to `Foo::m_obj`
Foo foo{obj};
QSignalSpy deletion_spy(obj, &QObject::destroyed);
QSignalSpy mysignal_spy(&Foo, &Foo::mySignal);
QVERIFY(deletion_spy.wait(10000));
QCOMPARE(deletion_spy.count(), 1); // verify that `obj` got deleted
QCOMPARE(mysignal_spy.count(), 1);
QList<QVariant> sig = spy.takeFirst();
Foo::Status status = qvariant_cast<Foo::Status>(sig.at(0));
QVERIFY2(status == Foo:Ok, "Failed");
}