Signal/Slot problems in QTest
-
I'm writing an application which sends commands (UDP) to hardware, which returns data answers (UDP).
I have looked at a couple of posts that I thought might apply to this, but they weren't exactly what I was searching for.
The problem I'm having is that during a normal run of the application, I get the data back using a slot, but in my attempt at unit testing, the slot never gets called.
The connect code looks like this:
// Tie readyRead Signal to readPendingDatagrams Slot connect(udpSocket, &QUdpSocket::readyRead, this, &Sender::readPendingDatagrams );
The Sender class looks like this:
class Sender : public QObject { Q_OBJECT public: explicit Sender(Ui::MainWindow* mainwindow); bool createPCSocket(QHostAddress ipAddress, quint16 port); qint64 sendPacket(QNetworkDatagram datagram); private slots: void readPendingDatagrams(); private: Ui::MainWindow *ui; QByteArray buffer; QUdpSocket *udpSocket; // for unit test friend class tst_udp_connection; };
Definition of readPendingDatagrams() is:
void Sender::readPendingDatagrams() { static quint16 currentPacketNumber(0); // when data comes inMiscRegisterGroup uint byteCount = udpSocket->pendingDatagramSize(); buffer.resize(byteCount); QHostAddress senderAddress; quint16 senderPort; if(udpSocket->readDatagram(buffer.data(), buffer.size(), &senderAddress, &senderPort) > 0) { formatUtilities::endianAdjust(buffer); }
...and so forth.
Using the debugger, I verified that the connect call is being made from the unit test when I execute a simulated button press, but the signal is not issued when I issued the read command. I worked around this by explicitly calling readPendingDatagrams() after the send command button click:
ui->sendCommandButton->click(); sender->readPendingDatagrams();
This doesn't seem to me like something I should need to do, so my conclusion is that I am missing something important -- or just not understanding how to properly set up my unit tests.
Summary: In unit test code, I'm not getting the callback to readPendingDatagrams(). In regular (non unit test) code, the signals/slots behave as expected. So, what am I doing wrong?
-
You have to wait until the data arrived. Simply reading data without the notification that data arrived will not work. See QTest::qWaitFor() and/or QSignalSpy
-
@Christian-Ehrlicher said in Signal/Slot problems in QTest:
You have to wait until the data arrived.
I don't understand that at all. The function readPendingDatagrams() is basically a callback. It should get called as soon as the input packet is ready -- which is what it does in the application.
In the unit test, unless I explicitly call it (the explicit call to readPendingDatagrams() shown in my original post works without any explicit wait, indicating that the return packet is coming back very quickly), it never gets called. Using WireShark, I see the packet coming in, but the slot is not called.
As an additional verification, I added a flag to the Sender class, and had readPendingDatagrams() set it so I can detect when readPendingDatagrams() was called.
ui->sendCommandButton->click(); while(!sender->isPacketReady()) { QTest::qWait(1); }
The unit test hangs forever on the while-loop. The slot is never called.
-
@HowardHarkness said in Signal/Slot problems in QTest:
In the unit test, unless I explicitly call it (the explicit call to readPendingDatagrams() shown in my original post works without any explicit wait, indicating that the return packet is coming back very quickly), it never gets called.
Sorry but re-read this sentence and then tell me again what your problem is.
Also what for instance does sendCommandButton() do? Please show us the full but simplified code.
-
@Christian-Ehrlicher said in Signal/Slot problems in QTest:
Also what for instance does sendCommandButton() do?
I think it's a
QPushbutton
on which aclick()
is simulated during theQTest
.
[Edit: You probably know that... :) Misunderstood your question :) ]@HowardHarkness
But @Christian-Ehrlicher is right, nobody can see from your example when, for instance,isPacketReady()
becomestrue
since it's not shown in your header or your code file snippet :)
Just looked upqWait(int ms)
... this, at least is not the problem, since the documentation states:While waiting, events will be processed and your test will stay responsive to user interface events or network communication.
-
@Christian-Ehrlicher said in Signal/Slot problems in QTest:
Sorry but re-read this sentence and then tell me again what your problem is.
. In the normal application, the slot handler gets called normally.
. In the unit test, the slot handler never gets called unless I make an explicit call to it.Also what for instance does sendCommandButton() do? Please show us the full but simplified code.
Ok, here's the handler for the send command button, with most of the fluff elided.void MainWindow::on_sendCommandButton_clicked() { uint commandLineCount(ui->wordCountLineEdit->text().toUInt(nullptr, baseHex)); ... error condition checks for initial setup else // setup was completed normally { // actual number of lines may differ from the value in wordCountLineEdit QStringList lines(udpPayload.split('\n')); ... pad out the payload to 20 bytes for the eval board QString updData(ui->updOutputPlainTextEdit->toPlainText().replace("\n", "")); QByteArray transmitData(QByteArray::fromHex(updData.toUtf8())); QNetworkDatagram datagram(transmitData, evalBoardIp, evalBoardPort); quint64 bytesTransmitted = sender->sendPacket(datagram); ... compose & display confirmation or failure message in status bar } ui->updInputPlainTextEdit->clear(); }
The "setup was completed normally" section does get executed, as verified with WireShark, the debugger, and the status bar confirmation message.
-
@Pl45m4 said in Signal/Slot problems in QTest:
nobody can see from your example when, for instance, isPacketReady() becomes true
I defined a bool packetReady; in the Sender class, and set it false in the ctor.
The last line of readPendingDatagrams() is "packetReady = true;" to indicate that it has been called.
Function isPacketReady() returns the value of packetReady -- which is never true, since the slot handler readPendingDatagrams() is never called. -
This works fine for me
class tst_Udp : public QObject { Q_OBJECT private Q_SLOTS: void testSendReceiveUdp(); }; void tst_Udp::testSendReceiveUdp() { QUdpSocket sender; QUdpSocket receiver; bool packetReceived = false; QByteArray buf; connect(&receiver, &QUdpSocket::readyRead, [&]() { if (receiver.hasPendingDatagrams()) { buf.resize(receiver.pendingDatagramSize()); receiver.readDatagram(buf.data(), buf.size()); qDebug() << "Received: " << buf; packetReceived = true; } }); receiver.bind(QHostAddress("127.0.0.1"), 12345); QTimer::singleShot(0, this, [&]() { sender.writeDatagram(QByteArray("Hello!"), QHostAddress("127.0.0.1"), 12345); }); while (!packetReceived) { QTest::qWait(100); qDebug() << "Waiting..."; } qDebug() << "Finished"; } QTEST_MAIN(tst_Udp)
-
@Christian-Ehrlicher Does that mean that I have to do the connect() call in the unit test case?
I'm still a bit confused. The connect() does happen in the unit test setup, as confirmed using the debugger.
I'll be examining this in more depth after I figure out how to solve my other problem with Ui::MainWindow access...
-
@HowardHarkness said in Signal/Slot problems in QTest:
Does that mean that I have to do the connect() call in the unit test case?
Somewhere in your code you have to call the connect, yes. How else should the slot be called?
-
@Christian-Ehrlicher The problem is that the slot is not called at all in the unit test.
-
@HowardHarkness You have a unittest which works - I don't know what you're doing in your code. A slot can only be called when a signal is connected to it.
-
@Christian-Ehrlicher said in Signal/Slot problems in QTest:
A slot can only be called when a signal is connected to it.
The slot is connected, but does not get called unless I insert an explicit call to the read function. I suspect this is a red herring, and the real problem is elsewhere. Inserting the explicit call is the workaround that makes the unit test work. Meanwhile, I'm working on another problem (a poor design decision, which might actually impact this).
-
Well, I'm back from COVID recovery, and still have this problem.
I tried the "poor man's" unit test framework -- I created a dialog with an output report and some buttons to run tests on the mainwindow. I was surprised to run into exactly the same problem, in exactly the same place, for what appears to be the same reason.In my test, I click on a button that should send a command to the hardware, which will respond with a reply packet.
// send read command btnSendCommand->click();
It appears that the signal for the read never gets sent.
However, if I actually click on that button with my mouse, it works, and I get the expected input.
I have to conclude one of the following:
- The btnSendCommand->click(); does not actually do the same thing as using the mouse to click the button.
- There is some environmental issue I am getting wrong in my test.
- There is a timing issue I don't understand.
-
@HowardHarkness said in Signal/Slot problems in QTest:
The btnSendCommand->click(); does not actually do the same thing as using the mouse to click the button.
What if you "focus" the button first, before you virtually click it? (for example with
setFocus
).
What if you click it twice by code?
Just a guess...you could try it.Have you tried
animateClick ()
? -
@Pl45m4 Ok, I tried focus(). No difference. I tried multiple calls to click(). No difference. As for animateClick(), I didn't see any difference, but I'm wondering if I need to add some sort of delay to wait for completion.
I put some tracing code in the slot handler, and discovered that the first time I invoke the test function, the slot handler is not called. The second time I invoke the test function, the slot IS called, but the results aren't available in time for my checks. So, the THIRD time I invoke the test function, the test results are as expected.
So, I think I have narrowed it down to a timing problem. Not sure how to address that yet.
-
Ah, I have narrowed it down to some sort of timing or thread conflict. Invoking the test function by hand 3 times has the effect noted above (1. No slot call, 2. Slot call, but updates don't happen in time, 3. Success).
However if I just invoke the test function three times in code, the result is no slot call. -