File indexing completed on 2024-04-21 14:59:44

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"