File indexing completed on 2025-01-12 12:29:33
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "ktcpsockettest.h" 0009 #include "ktcpsocket.h" 0010 #include <QDebug> 0011 #include <QTcpServer> 0012 #include <QThread> 0013 0014 /* TODO items: 0015 - test errors including error strings 0016 - test overriding errors 0017 - test the most important SSL operations (full coverage is very hard) 0018 - test readLine() 0019 - test nonblocking, signal based usage 0020 - test that waitForDisconnected() writes out all buffered data 0021 - (test local and peer address and port getters) 0022 - test isValid(). Its documentation is less than clear :( 0023 */ 0024 0025 static const quint16 testPort = 22342; 0026 0027 KTcpSocketTest::KTcpSocketTest() 0028 { 0029 server = nullptr; 0030 } 0031 0032 KTcpSocketTest::~KTcpSocketTest() 0033 { 0034 } 0035 0036 void KTcpSocketTest::invokeOnServer(const char *method) 0037 { 0038 QMetaObject::invokeMethod(server, method, Qt::QueuedConnection); 0039 QTest::qWait(1); // Enter the event loop 0040 } 0041 0042 Server::Server(quint16 _port) 0043 : listener(new QTcpServer(this)) 0044 , socket(nullptr) 0045 , port(_port) 0046 { 0047 listener->listen(QHostAddress(QStringLiteral("127.0.0.1")), testPort); 0048 } 0049 0050 Server::~Server() 0051 { 0052 } 0053 0054 void Server::cleanupSocket() 0055 { 0056 Q_ASSERT(socket); 0057 socket->close(); 0058 socket->deleteLater(); 0059 socket = nullptr; 0060 } 0061 0062 void KTcpSocketTest::initTestCase() 0063 { 0064 m_thread = new QThread(); 0065 server = new Server(testPort); 0066 server->moveToThread(m_thread); 0067 connect(m_thread, &QThread::finished, server, &QObject::deleteLater); 0068 m_thread->start(); 0069 } 0070 0071 void KTcpSocketTest::cleanupTestCase() 0072 { 0073 m_thread->quit(); 0074 m_thread->wait(); 0075 delete m_thread; 0076 } 0077 0078 void KTcpSocketTest::connectDisconnect() 0079 { 0080 invokeOnServer("connectDisconnect"); 0081 0082 KTcpSocket *s = new KTcpSocket(this); 0083 QCOMPARE(s->openMode(), QIODevice::NotOpen); 0084 QCOMPARE(s->error(), KTcpSocket::UnknownError); 0085 0086 s->connectToHost(QStringLiteral("127.0.0.1"), testPort); 0087 QCOMPARE(s->state(), KTcpSocket::ConnectingState); 0088 QVERIFY(s->openMode() & QIODevice::ReadWrite); 0089 const bool connected = s->waitForConnected(150); 0090 QVERIFY(connected); 0091 QCOMPARE(s->state(), KTcpSocket::ConnectedState); 0092 0093 s->waitForDisconnected(150); 0094 // ClosingState occurs only when there is buffered data 0095 QCOMPARE(s->state(), KTcpSocket::UnconnectedState); 0096 0097 s->deleteLater(); 0098 } 0099 0100 void Server::connectDisconnect() 0101 { 0102 listener->waitForNewConnection(10000, nullptr); 0103 socket = listener->nextPendingConnection(); 0104 0105 cleanupSocket(); 0106 } 0107 0108 #define TESTDATA QByteArray("things and stuff and a bag of chips") 0109 0110 void KTcpSocketTest::read() 0111 { 0112 invokeOnServer("read"); 0113 0114 KTcpSocket *s = new KTcpSocket(this); 0115 s->connectToHost(QStringLiteral("127.0.0.1"), testPort); 0116 s->waitForConnected(40); 0117 s->waitForReadyRead(40); 0118 QCOMPARE((int)s->bytesAvailable(), TESTDATA.size()); 0119 QCOMPARE(s->readAll(), TESTDATA); 0120 s->deleteLater(); 0121 } 0122 0123 void Server::read() 0124 { 0125 listener->waitForNewConnection(10000, nullptr); 0126 socket = listener->nextPendingConnection(); 0127 0128 socket->write(TESTDATA); 0129 socket->waitForBytesWritten(150); 0130 cleanupSocket(); 0131 } 0132 0133 void KTcpSocketTest::write() 0134 { 0135 invokeOnServer("write"); 0136 0137 KTcpSocket *s = new KTcpSocket(this); 0138 s->connectToHost(QStringLiteral("127.0.0.1"), testPort); 0139 s->waitForConnected(40); 0140 s->write(TESTDATA); 0141 QCOMPARE((int)s->bytesToWrite(), TESTDATA.size()); 0142 s->waitForReadyRead(150); 0143 QCOMPARE((int)s->bytesAvailable(), TESTDATA.size()); 0144 QCOMPARE(s->readAll(), TESTDATA); 0145 0146 s->write(TESTDATA); 0147 QCOMPARE((int)s->bytesToWrite(), TESTDATA.size()); 0148 s->disconnectFromHost(); 0149 // Test closing with pending data to transmit (pending rx data comes later) 0150 QCOMPARE(s->state(), KTcpSocket::ClosingState); 0151 s->waitForDisconnected(150); 0152 QCOMPARE(s->state(), KTcpSocket::UnconnectedState); 0153 0154 s->deleteLater(); 0155 } 0156 0157 void Server::write() 0158 { 0159 listener->waitForNewConnection(10000, nullptr); 0160 socket = listener->nextPendingConnection(); 0161 0162 socket->waitForReadyRead(40); 0163 socket->write(socket->readAll()); // echo 0164 socket->waitForBytesWritten(150); 0165 0166 socket->waitForReadyRead(40); 0167 socket->write(socket->readAll()); 0168 cleanupSocket(); 0169 } 0170 0171 static QString stateToString(KTcpSocket::State state) 0172 { 0173 switch (state) { 0174 case KTcpSocket::UnconnectedState: 0175 return QStringLiteral("UnconnectedState"); 0176 case KTcpSocket::HostLookupState: 0177 return QStringLiteral("HostLookupState"); 0178 case KTcpSocket::ConnectingState: 0179 return QStringLiteral("ConnectingState"); 0180 case KTcpSocket::ConnectedState: 0181 return QStringLiteral("ConnectedState"); 0182 case KTcpSocket::BoundState: 0183 return QStringLiteral("BoundState"); 0184 case KTcpSocket::ListeningState: 0185 return QStringLiteral("ListeningState"); 0186 case KTcpSocket::ClosingState: 0187 return QStringLiteral("ClosingState"); 0188 } 0189 return QStringLiteral("ERROR"); 0190 } 0191 0192 #define HTTPREQUEST QByteArray("GET / HTTP/1.1\nHost: www.example.com\n\n") 0193 // I assume that example.com, hosted by the IANA, will exist indefinitely. 0194 // It is a nice test site because it serves a very small HTML page that should 0195 // fit into a TCP packet or two. 0196 0197 void KTcpSocketTest::statesIana() 0198 { 0199 QSKIP("Too unreliable"); 0200 // A connection to a real internet host 0201 KTcpSocket *s = new KTcpSocket(this); 0202 connect(s, &KTcpSocket::hostFound, this, &KTcpSocketTest::states_hostFound); 0203 QCOMPARE(s->state(), KTcpSocket::UnconnectedState); 0204 s->connectToHost(QStringLiteral("www.iana.org"), 80); 0205 QCOMPARE(s->state(), KTcpSocket::HostLookupState); 0206 s->write(HTTPREQUEST); 0207 QCOMPARE(s->state(), KTcpSocket::HostLookupState); 0208 s->waitForBytesWritten(2500); 0209 QCOMPARE(s->state(), KTcpSocket::ConnectedState); 0210 0211 // Try to ensure that inbound data in the next part of the test is really from the second request; 0212 // it is not *guaranteed* that this reads all data, e.g. if the connection is very slow (so too many 0213 // of the waitForReadyRead() time out), or if the reply packets are extremely fragmented (so 50 reads 0214 // are not enough to receive all of them). I don't know the details of fragmentation so the latter 0215 // problem could be nonexistent. 0216 QByteArray received; 0217 for (int i = 0; i < 50; i++) { 0218 s->waitForReadyRead(50); 0219 received.append(s->readAll()); 0220 } 0221 QVERIFY(received.size() > 200); 0222 0223 // Here, the connection should neither have data in its write buffer nor inbound packets in flight 0224 0225 // Now reuse the connection for another request / reply pair 0226 0227 s->write(HTTPREQUEST); 0228 s->waitForReadyRead(); 0229 // After waitForReadyRead(), the write buffer should be empty because the server has to wait for the 0230 // end of the request before sending a reply. 0231 // The socket can then shut down without having to wait for draining the write buffer. 0232 // Incoming data cannot delay the transition to UnconnectedState, as documented in 0233 // QAbstractSocket::disconnectFromHost(). close() just wraps disconnectFromHost(). 0234 s->close(); 0235 QCOMPARE((int)s->state(), (int)KTcpSocket::UnconnectedState); 0236 0237 delete s; 0238 } 0239 0240 void KTcpSocketTest::statesLocalHost() 0241 { 0242 // Now again an internal connection 0243 invokeOnServer("states"); 0244 0245 KTcpSocket *s = new KTcpSocket(this); 0246 connect(s, &KTcpSocket::hostFound, this, &KTcpSocketTest::states_hostFound); 0247 s->connectToHost(QStringLiteral("127.0.0.1"), testPort); 0248 QCOMPARE(s->state(), KTcpSocket::ConnectingState); 0249 s->waitForConnected(40); 0250 QCOMPARE(s->state(), KTcpSocket::ConnectedState); 0251 0252 s->write(HTTPREQUEST); 0253 s->waitForReadyRead(); 0254 QCOMPARE((int)s->bytesAvailable(), HTTPREQUEST.size()); // for good measure... 0255 QCOMPARE(s->state(), KTcpSocket::ConnectedState); 0256 0257 s->waitForDisconnected(40); 0258 QCOMPARE(s->state(), KTcpSocket::UnconnectedState); 0259 0260 disconnect(s, SIGNAL(hostFound())); 0261 delete s; 0262 } 0263 0264 void KTcpSocketTest::statesManyHosts() 0265 { 0266 KTcpSocket *s = new KTcpSocket(this); 0267 QByteArray requestProlog( 0268 "GET / HTTP/1.1\r\n" // exact copy of a real HTTP query 0269 "Connection: Keep-Alive\r\n" // not really... 0270 "User-Agent: Mozilla/5.0 (compatible; Konqueror/3.96; Linux) " 0271 "KHTML/3.96.0 (like Gecko)\r\n" 0272 "Pragma: no-cache\r\n" 0273 "Cache-control: no-cache\r\n" 0274 "Accept: text/html, image/jpeg, image/png, text/*, image/*, */*\r\n" 0275 "Accept-Encoding: x-gzip, x-deflate, gzip, deflate\r\n" 0276 "Accept-Charset: utf-8, utf-8;q=0.5, *;q=0.5\r\n" 0277 "Accept-Language: en-US, en\r\n" 0278 "Host: "); 0279 QByteArray requestEpilog("\r\n\r\n"); 0280 // Test rapid connection and disconnection to different hosts 0281 static const char *hosts[] = {"www.google.de", "www.spiegel.de", "www.stern.de", "www.google.com"}; 0282 static const int numHosts = 4; 0283 for (int i = 0; i < numHosts * 5; i++) { 0284 qDebug("\nNow trying %s...", hosts[i % numHosts]); 0285 QCOMPARE(s->state(), KTcpSocket::UnconnectedState); 0286 s->connectToHost(hosts[i % numHosts], 80); 0287 bool skip = false; 0288 KTcpSocket::State expectedState = KTcpSocket::ConnectingState; 0289 0290 if (i < numHosts) { 0291 expectedState = KTcpSocket::HostLookupState; 0292 } else { 0293 expectedState = KTcpSocket::ConnectingState; 0294 } 0295 0296 if (!skip) { 0297 QCOMPARE(stateToString(s->state()), stateToString(expectedState)); 0298 } else { // let's make sure it's at least one of the two expected states 0299 QVERIFY(stateToString(s->state()) == stateToString(KTcpSocket::HostLookupState) 0300 || stateToString(s->state()) == stateToString(KTcpSocket::ConnectingState)); 0301 } 0302 0303 // weave the host address into the HTTP request 0304 QByteArray request(requestProlog); 0305 request.append(hosts[i % numHosts]); 0306 request.append(requestEpilog); 0307 s->write(request); 0308 0309 if (!skip) { 0310 QCOMPARE(stateToString(s->state()), stateToString(expectedState)); 0311 } 0312 0313 s->waitForBytesWritten(-1); 0314 QCOMPARE(s->state(), KTcpSocket::ConnectedState); 0315 int tries = 0; 0316 while (s->bytesAvailable() <= 100 && ++tries < 10) { 0317 s->waitForReadyRead(-1); 0318 } 0319 QVERIFY(s->bytesAvailable() > 100); 0320 if (i % (numHosts + 1)) { 0321 s->readAll(); 0322 QVERIFY(s->bytesAvailable() == 0); 0323 } else { 0324 char dummy[4]; 0325 s->read(dummy, 1); 0326 QVERIFY(s->bytesAvailable() > 100 - 1); 0327 } 0328 s->disconnectFromHost(); 0329 if (s->state() != KTcpSocket::UnconnectedState) { 0330 s->waitForDisconnected(-1); 0331 } 0332 if (i % 2) { 0333 s->close(); // close() is not very well defined for sockets so just check that it 0334 // does no harm 0335 } 0336 } 0337 0338 s->deleteLater(); 0339 } 0340 0341 void KTcpSocketTest::states_hostFound() 0342 { 0343 QCOMPARE(static_cast<KTcpSocket *>(sender())->state(), KTcpSocket::ConnectingState); 0344 } 0345 0346 void Server::states() 0347 { 0348 listener->waitForNewConnection(10000, nullptr); 0349 socket = listener->nextPendingConnection(); 0350 0351 socket->waitForReadyRead(40); 0352 socket->write(socket->readAll()); // echo 0353 socket->waitForBytesWritten(150); 0354 0355 cleanupSocket(); 0356 } 0357 0358 void KTcpSocketTest::errors() 0359 { 0360 // invokeOnServer("errors"); 0361 } 0362 0363 void Server::errors() 0364 { 0365 listener->waitForNewConnection(10000, nullptr); 0366 socket = listener->nextPendingConnection(); 0367 0368 cleanupSocket(); 0369 } 0370 0371 QTEST_MAIN(KTcpSocketTest) 0372 0373 #include "moc_ktcpsockettest.cpp"