1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QTest>
30#include <QtTest/QSignalSpy>
31#include <QtTest/QTestEventLoop>
32
33#include <QtCore/QCoreApplication>
34#include <QtCore/QTimer>
35#include <QtCore/QSocketNotifier>
36#include <QtNetwork/QTcpServer>
37#include <QtNetwork/QTcpSocket>
38#include <QtNetwork/QUdpSocket>
39#ifndef Q_OS_WINRT
40#include <private/qnativesocketengine_p.h>
41#else
42#include <private/qnativesocketengine_winrt_p.h>
43#endif
44#define NATIVESOCKETENGINE QNativeSocketEngine
45#ifdef Q_OS_UNIX
46#include <private/qnet_unix_p.h>
47#include <sys/select.h>
48#endif
49#include <limits>
50
51#if defined (Q_CC_MSVC) && defined(max)
52# undef max
53# undef min
54#endif // Q_CC_MSVC
55
56
57class tst_QSocketNotifier : public QObject
58{
59 Q_OBJECT
60private slots:
61 void unexpectedDisconnection();
62 void mixingWithTimers();
63#ifdef Q_OS_UNIX
64 void posixSockets();
65#endif
66 void asyncMultipleDatagram();
67 void activationReason_data();
68 void activationReason();
69 void legacyConnect();
70
71protected slots:
72 void async_readDatagramSlot();
73 void async_writeDatagramSlot();
74
75private:
76 QUdpSocket *m_asyncSender;
77 QUdpSocket *m_asyncReceiver;
78};
79
80static QHostAddress makeNonAny(const QHostAddress &address,
81 QHostAddress::SpecialAddress preferForAny = QHostAddress::LocalHost)
82{
83 if (address == QHostAddress::Any)
84 return preferForAny;
85 if (address == QHostAddress::AnyIPv4)
86 return QHostAddress::LocalHost;
87 if (address == QHostAddress::AnyIPv6)
88 return QHostAddress::LocalHostIPv6;
89 return address;
90}
91
92class UnexpectedDisconnectTester : public QObject
93{
94 Q_OBJECT
95public:
96 NATIVESOCKETENGINE *readEnd1, *readEnd2;
97 int sequence;
98
99 UnexpectedDisconnectTester(NATIVESOCKETENGINE *s1, NATIVESOCKETENGINE *s2)
100 : readEnd1(s1), readEnd2(s2), sequence(0)
101 {
102 QSocketNotifier *notifier1 =
103 new QSocketNotifier(readEnd1->socketDescriptor(), QSocketNotifier::Read, this);
104 connect(asender: notifier1, SIGNAL(activated(QSocketDescriptor)), SLOT(handleActivated()));
105 QSocketNotifier *notifier2 =
106 new QSocketNotifier(readEnd2->socketDescriptor(), QSocketNotifier::Read, this);
107 connect(asender: notifier2, SIGNAL(activated(QSocketDescriptor)), SLOT(handleActivated()));
108 }
109
110public slots:
111 void handleActivated()
112 {
113 char data1[1], data2[1];
114 ++sequence;
115 if (sequence == 1) {
116 // read from both ends
117 (void) readEnd1->read(data: data1, maxlen: sizeof(data1));
118 (void) readEnd2->read(data: data2, maxlen: sizeof(data2));
119 emit finished();
120 } else if (sequence == 2) {
121 // we should never get here
122 QCOMPARE(readEnd2->read(data2, sizeof(data2)), qint64(-2));
123 QVERIFY(readEnd2->isValid());
124 }
125 }
126
127signals:
128 void finished();
129};
130
131void tst_QSocketNotifier::unexpectedDisconnection()
132{
133#ifdef Q_OS_WINRT
134 // WinRT does not allow a connection to the localhost
135 QSKIP("Local connection not allowed", SkipAll);
136#else
137 /*
138 Given two sockets and two QSocketNotifiers registered on each
139 their socket. If both sockets receive data, and the first slot
140 invoked by one of the socket notifiers empties both sockets, the
141 other notifier will also emit activated(). This results in
142 unexpected disconnection in QAbstractSocket.
143
144 The use case is that somebody calls one of the
145 waitFor... functions in a QSocketNotifier activated slot, and
146 the waitFor... functions do local selects that can empty both
147 stdin and stderr while waiting for fex bytes to be written.
148 */
149
150 QTcpServer server;
151 QVERIFY(server.listen(QHostAddress::LocalHost, 0));
152
153 NATIVESOCKETENGINE readEnd1;
154 readEnd1.initialize(type: QAbstractSocket::TcpSocket);
155 readEnd1.connectToHost(address: server.serverAddress(), port: server.serverPort());
156 QVERIFY(readEnd1.waitForWrite());
157 QCOMPARE(readEnd1.state(), QAbstractSocket::ConnectedState);
158 QVERIFY(server.waitForNewConnection());
159 QTcpSocket *writeEnd1 = server.nextPendingConnection();
160 QVERIFY(writeEnd1 != 0);
161
162 NATIVESOCKETENGINE readEnd2;
163 readEnd2.initialize(type: QAbstractSocket::TcpSocket);
164 readEnd2.connectToHost(address: server.serverAddress(), port: server.serverPort());
165 QVERIFY(readEnd2.waitForWrite());
166 QCOMPARE(readEnd2.state(), QAbstractSocket::ConnectedState);
167 QVERIFY(server.waitForNewConnection());
168 QTcpSocket *writeEnd2 = server.nextPendingConnection();
169 QVERIFY(writeEnd2 != 0);
170
171 writeEnd1->write(data: "1", len: 1);
172 writeEnd2->write(data: "2", len: 1);
173
174 writeEnd1->waitForBytesWritten();
175 writeEnd2->waitForBytesWritten();
176
177 writeEnd1->flush();
178 writeEnd2->flush();
179
180 UnexpectedDisconnectTester tester(&readEnd1, &readEnd2);
181
182 QTimer timer;
183 timer.setSingleShot(true);
184 timer.start(msec: 30000);
185 do {
186 // we have to wait until sequence value changes
187 // as any event can make us jump out processing
188 QCoreApplication::processEvents(flags: QEventLoop::WaitForMoreEvents);
189 QVERIFY(timer.isActive()); //escape if test would hang
190 } while(tester.sequence <= 0);
191
192 QCOMPARE(readEnd1.state(), QAbstractSocket::ConnectedState);
193 QCOMPARE(readEnd2.state(), QAbstractSocket::ConnectedState);
194
195 QCOMPARE(tester.sequence, 2);
196
197 readEnd1.close();
198 readEnd2.close();
199 writeEnd1->close();
200 writeEnd2->close();
201 server.close();
202#endif // !Q_OS_WINRT
203}
204
205class MixingWithTimersHelper : public QObject
206{
207 Q_OBJECT
208
209public:
210 MixingWithTimersHelper(QTimer *timer, QTcpServer *server);
211
212 bool timerActivated;
213 bool socketActivated;
214
215private slots:
216 void timerFired();
217 void socketFired();
218};
219
220MixingWithTimersHelper::MixingWithTimersHelper(QTimer *timer, QTcpServer *server)
221{
222 timerActivated = false;
223 socketActivated = false;
224
225 connect(asender: timer, SIGNAL(timeout()), SLOT(timerFired()));
226 connect(asender: server, SIGNAL(newConnection()), SLOT(socketFired()));
227}
228
229void MixingWithTimersHelper::timerFired()
230{
231 timerActivated = true;
232}
233
234void MixingWithTimersHelper::socketFired()
235{
236 socketActivated = true;
237}
238
239void tst_QSocketNotifier::mixingWithTimers()
240{
241#ifdef Q_OS_WINRT
242 QSKIP("WinRT does not allow connection to localhost", SkipAll);
243#else
244 QTimer timer;
245 timer.setInterval(0);
246 timer.start();
247
248 QTcpServer server;
249 QVERIFY(server.listen(QHostAddress::LocalHost, 0));
250
251 MixingWithTimersHelper helper(&timer, &server);
252
253 QCoreApplication::processEvents();
254
255 QCOMPARE(helper.timerActivated, true);
256 QCOMPARE(helper.socketActivated, false);
257
258 helper.timerActivated = false;
259 helper.socketActivated = false;
260
261 QTcpSocket socket;
262 socket.connectToHost(address: server.serverAddress(), port: server.serverPort());
263
264 QCoreApplication::processEvents();
265
266 QCOMPARE(helper.timerActivated, true);
267 QTRY_COMPARE(helper.socketActivated, true);
268#endif // !Q_OS_WINRT
269}
270
271#ifdef Q_OS_UNIX
272// test only for posix
273void tst_QSocketNotifier::posixSockets()
274{
275 QTcpServer server;
276 QVERIFY(server.listen(QHostAddress::LocalHost, 0));
277
278 int posixSocket = qt_safe_socket(AF_INET, SOCK_STREAM, protocol: 0);
279 sockaddr_in addr;
280 addr.sin_addr.s_addr = htonl(hostlong: 0x7f000001);
281 addr.sin_family = AF_INET;
282 addr.sin_port = htons(hostshort: server.serverPort());
283 qt_safe_connect(sockfd: posixSocket, addr: (const struct sockaddr*)&addr, addrlen: sizeof(sockaddr_in));
284 QVERIFY(server.waitForNewConnection(5000));
285 QScopedPointer<QTcpSocket> passive(server.nextPendingConnection());
286
287 ::fcntl(fd: posixSocket, F_SETFL, ::fcntl(fd: posixSocket, F_GETFL) | O_NONBLOCK);
288
289 {
290 QSocketNotifier rn(posixSocket, QSocketNotifier::Read);
291 connect(sender: &rn, SIGNAL(activated(QSocketDescriptor)), receiver: &QTestEventLoop::instance(), SLOT(exitLoop()));
292 QSignalSpy readSpy(&rn, &QSocketNotifier::activated);
293 QVERIFY(readSpy.isValid());
294 // No write notifier, some systems trigger write notification on socket creation, but not all
295 QSocketNotifier en(posixSocket, QSocketNotifier::Exception);
296 connect(sender: &en, SIGNAL(activated(QSocketDescriptor)), receiver: &QTestEventLoop::instance(), SLOT(exitLoop()));
297 QSignalSpy errorSpy(&en, &QSocketNotifier::activated);
298 QVERIFY(errorSpy.isValid());
299
300 passive->write(data: "hello",len: 6);
301 passive->waitForBytesWritten(msecs: 5000);
302
303 QTestEventLoop::instance().enterLoop(secs: 3);
304 QCOMPARE(readSpy.count(), 1);
305 QCOMPARE(errorSpy.count(), 0);
306
307 char buffer[100];
308 int r = qt_safe_read(fd: posixSocket, data: buffer, maxlen: 100);
309 QCOMPARE(r, 6);
310 QCOMPARE(buffer, "hello");
311
312 QSocketNotifier wn(posixSocket, QSocketNotifier::Write);
313 connect(sender: &wn, SIGNAL(activated(QSocketDescriptor)), receiver: &QTestEventLoop::instance(), SLOT(exitLoop()));
314 QSignalSpy writeSpy(&wn, &QSocketNotifier::activated);
315 QVERIFY(writeSpy.isValid());
316 qt_safe_write(fd: posixSocket, data: "goodbye", len: 8);
317
318 QTestEventLoop::instance().enterLoop(secs: 3);
319 QCOMPARE(readSpy.count(), 1);
320 QCOMPARE(writeSpy.count(), 1);
321 QCOMPARE(errorSpy.count(), 0);
322
323 // Write notifier may have fired before the read notifier inside
324 // QTcpSocket, give QTcpSocket a chance to see the incoming data
325 passive->waitForReadyRead(msecs: 100);
326 QCOMPARE(passive->readAll(), QByteArray("goodbye",8));
327 }
328 qt_safe_close(fd: posixSocket);
329}
330#endif
331
332void tst_QSocketNotifier::async_readDatagramSlot()
333{
334 char buf[1];
335 QVERIFY(m_asyncReceiver->hasPendingDatagrams());
336 do {
337 QCOMPARE(m_asyncReceiver->pendingDatagramSize(), qint64(1));
338 QCOMPARE(m_asyncReceiver->readDatagram(buf, sizeof(buf)), qint64(1));
339 if (buf[0] == '1') {
340 // wait for the second datagram message.
341 QTest::qSleep(ms: 100);
342 }
343 } while (m_asyncReceiver->hasPendingDatagrams());
344
345 if (buf[0] == '3')
346 QTestEventLoop::instance().exitLoop();
347}
348
349void tst_QSocketNotifier::async_writeDatagramSlot()
350{
351 m_asyncSender->writeDatagram(datagram: "3", host: makeNonAny(address: m_asyncReceiver->localAddress()),
352 port: m_asyncReceiver->localPort());
353}
354
355void tst_QSocketNotifier::asyncMultipleDatagram()
356{
357#ifdef Q_OS_WINRT
358 QSKIP("WinRT does not allow connection to localhost", SkipAll);
359#else
360 m_asyncSender = new QUdpSocket;
361 m_asyncReceiver = new QUdpSocket;
362
363 QVERIFY(m_asyncReceiver->bind(QHostAddress(QHostAddress::AnyIPv4), 0));
364 quint16 port = m_asyncReceiver->localPort();
365 QVERIFY(port != 0);
366
367 QSignalSpy spy(m_asyncReceiver, &QIODevice::readyRead);
368 connect(sender: m_asyncReceiver, signal: &QIODevice::readyRead, receiver: this,
369 slot: &tst_QSocketNotifier::async_readDatagramSlot);
370
371 // activate socket notifiers
372 QTestEventLoop::instance().enterLoopMSecs(ms: 100);
373
374 m_asyncSender->writeDatagram(datagram: "1", host: makeNonAny(address: m_asyncReceiver->localAddress()), port);
375 m_asyncSender->writeDatagram(datagram: "2", host: makeNonAny(address: m_asyncReceiver->localAddress()), port);
376 // wait a little to ensure that the datagrams we've just sent
377 // will be delivered on receiver side.
378 QTest::qSleep(ms: 100);
379 QVERIFY(m_asyncReceiver->hasPendingDatagrams());
380
381 QTimer::singleShot(interval: 500, receiver: this, slot: &tst_QSocketNotifier::async_writeDatagramSlot);
382
383 QTestEventLoop::instance().enterLoop(secs: 1);
384 QVERIFY(!QTestEventLoop::instance().timeout());
385 QCOMPARE(spy.count(), 2);
386
387 delete m_asyncSender;
388 delete m_asyncReceiver;
389 #endif // !Q_OS_WINRT
390}
391
392void tst_QSocketNotifier::activationReason_data()
393{
394 QTest::addColumn<QSocketNotifier::Type>(name: "type");
395 QTest::addRow(format: "read") << QSocketNotifier::Read;
396 QTest::addRow(format: "write") << QSocketNotifier::Write;
397 QTest::addRow(format: "exception") << QSocketNotifier::Exception;
398}
399void tst_QSocketNotifier::activationReason()
400{
401 QSocketDescriptor fd = 15;
402
403 QFETCH(QSocketNotifier::Type, type);
404
405 QSocketNotifier notifier(fd, type);
406 auto activation = new QEvent(QEvent::SockAct);
407 QCoreApplication::postEvent(receiver: &notifier, event: activation);
408
409 QSocketNotifier::Type notifierType;
410 connect(sender: &notifier, signal: &QSocketNotifier::activated, context: this,
411 slot: [&notifierType, fd](QSocketDescriptor sockfd, QSocketNotifier::Type sntype) {
412 if (sockfd == fd)
413 notifierType = sntype;
414 else
415 qWarning() << "Got an unexpected socket file descriptor:" << qintptr(sockfd);
416 });
417
418 QCoreApplication::processEvents();
419 QCOMPARE(notifierType, type);
420}
421
422// This test ensures that we can connect QSocketNotifier::activated to a slot taking an integer
423// or qintptr.
424void tst_QSocketNotifier::legacyConnect()
425{
426 qintptr fd = 15;
427 QSocketNotifier notifier(fd, QSocketNotifier::Read);
428 auto activation = new QEvent(QEvent::SockAct);
429 QCoreApplication::postEvent(receiver: &notifier, event: activation);
430
431 bool receivedQIntPtr = false;
432 connect(sender: &notifier, signal: &QSocketNotifier::activated, context: this, slot: [&receivedQIntPtr, fd](qintptr q){
433 if (q == fd)
434 receivedQIntPtr = true;
435 });
436 bool receivedInt = false;
437 connect(sender: &notifier, signal: &QSocketNotifier::activated, context: this, slot: [&receivedInt, fd](int q){
438 if (q == fd)
439 receivedInt = true;
440 });
441
442 QCoreApplication::processEvents();
443 QVERIFY(receivedQIntPtr);
444 QVERIFY(receivedInt);
445}
446
447
448QTEST_MAIN(tst_QSocketNotifier)
449#include <tst_qsocketnotifier.moc>
450

source code of qtbase/tests/auto/corelib/kernel/qsocketnotifier/tst_qsocketnotifier.cpp