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 | |
57 | class tst_QSocketNotifier : public QObject |
58 | { |
59 | Q_OBJECT |
60 | private 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 | |
71 | protected slots: |
72 | void async_readDatagramSlot(); |
73 | void async_writeDatagramSlot(); |
74 | |
75 | private: |
76 | QUdpSocket *m_asyncSender; |
77 | QUdpSocket *m_asyncReceiver; |
78 | }; |
79 | |
80 | static 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 | |
92 | class UnexpectedDisconnectTester : public QObject |
93 | { |
94 | Q_OBJECT |
95 | public: |
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 | |
110 | public 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 | |
127 | signals: |
128 | void finished(); |
129 | }; |
130 | |
131 | void 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 | |
205 | class MixingWithTimersHelper : public QObject |
206 | { |
207 | Q_OBJECT |
208 | |
209 | public: |
210 | MixingWithTimersHelper(QTimer *timer, QTcpServer *server); |
211 | |
212 | bool timerActivated; |
213 | bool socketActivated; |
214 | |
215 | private slots: |
216 | void timerFired(); |
217 | void socketFired(); |
218 | }; |
219 | |
220 | MixingWithTimersHelper::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 | |
229 | void MixingWithTimersHelper::timerFired() |
230 | { |
231 | timerActivated = true; |
232 | } |
233 | |
234 | void MixingWithTimersHelper::socketFired() |
235 | { |
236 | socketActivated = true; |
237 | } |
238 | |
239 | void 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 |
273 | void 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 | |
332 | void 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 | |
349 | void tst_QSocketNotifier::async_writeDatagramSlot() |
350 | { |
351 | m_asyncSender->writeDatagram(datagram: "3" , host: makeNonAny(address: m_asyncReceiver->localAddress()), |
352 | port: m_asyncReceiver->localPort()); |
353 | } |
354 | |
355 | void 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 | |
392 | void 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 | } |
399 | void 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: ¬ifier, event: activation); |
408 | |
409 | QSocketNotifier::Type notifierType; |
410 | connect(sender: ¬ifier, signal: &QSocketNotifier::activated, context: this, |
411 | slot: [¬ifierType, 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. |
424 | void 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: ¬ifier, event: activation); |
430 | |
431 | bool receivedQIntPtr = false; |
432 | connect(sender: ¬ifier, 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: ¬ifier, 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 | |
448 | QTEST_MAIN(tst_QSocketNotifier) |
449 | #include <tst_qsocketnotifier.moc> |
450 | |