QObject::deleteLater is not called as expected in my Qt Test

730 Views Asked by At

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.

1

There are 1 best solutions below

0
On BEST ANSWER

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");
}