blob: f5c3bfde34c2d591b4e4c3dba7a1294924a068da [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtCore/QThread>
#include <QtCore/QSemaphore>
#include <QtCore/QElapsedTimer>
#include <QtCore/QSharedPointer>
#include <QtCore/QVector>
#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QSslSocket>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkAccessManager>
#ifdef QT_BUILD_INTERNAL
# include <private/qnetworkaccessmanager_p.h>
#endif
#include <qplatformdefs.h>
#ifdef Q_OS_UNIX
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/select.h>
# include <netinet/in.h>
# include <errno.h>
# include <netdb.h>
# include <signal.h>
# include <unistd.h>
# include <fcntl.h>
typedef int SOCKET;
# define INVALID_SOCKET -1
# define SOCKET_ERROR -1
#elif defined(Q_OS_WIN)
# include <winsock2.h>
#endif
class tst_NetworkRemoteStressTest : public QObject
{
Q_OBJECT
public:
enum { AttemptCount = 100 };
tst_NetworkRemoteStressTest();
qint64 byteCounter;
QNetworkAccessManager manager;
QVector<QUrl> httpUrls, httpsUrls, mixedUrls;
bool intermediateDebug;
private:
void clearManager();
public slots:
void initTestCase_data();
void init();
void slotReadAll() { byteCounter += static_cast<QIODevice *>(sender())->readAll().size(); }
private Q_SLOTS:
void blockingSequentialRemoteHosts();
void sequentialRemoteHosts();
void parallelRemoteHosts_data();
void parallelRemoteHosts();
void namRemoteGet_data();
void namRemoteGet();
};
tst_NetworkRemoteStressTest::tst_NetworkRemoteStressTest()
: intermediateDebug(qgetenv("STRESSDEBUG").toInt() > 0)
{
#ifdef Q_OS_WIN
WSAData wsadata;
// IPv6 requires Winsock v2.0 or better.
WSAStartup(MAKEWORD(2,0), &wsadata);
#elif defined(Q_OS_UNIX)
::signal(SIGALRM, SIG_IGN);
#endif
QFile urlList(":/url-list.txt");
if (urlList.open(QIODevice::ReadOnly)) {
while (!urlList.atEnd()) {
QByteArray line = urlList.readLine().trimmed();
QUrl url = QUrl::fromEncoded(line);
if (url.scheme() == "http") {
httpUrls << url;
mixedUrls << url;
} else if (url.scheme() == "https") {
httpsUrls << url;
mixedUrls << url;
}
}
}
httpUrls << httpUrls;
httpsUrls << httpsUrls;
}
void tst_NetworkRemoteStressTest::initTestCase_data()
{
QTest::addColumn<QVector<QUrl> >("urlList");
QTest::addColumn<bool>("useSslSocket");
QTest::newRow("no-ssl") << httpUrls << false;
// QTest::newRow("no-ssl-in-sslsocket") << httpUrls << true;
QTest::newRow("ssl") << httpsUrls << true;
QTest::newRow("mixed") << mixedUrls << false;
// QTest::newRow("mixed-in-sslsocket") << mixedUrls << true;
}
void tst_NetworkRemoteStressTest::init()
{
// clear the internal cache
#ifndef QT_BUILD_INTERNAL
if (strncmp(QTest::currentTestFunction(), "nam", 3) == 0)
QSKIP("QNetworkAccessManager tests disabled");
#endif
}
void tst_NetworkRemoteStressTest::clearManager()
{
#ifdef QT_BUILD_INTERNAL
QNetworkAccessManagerPrivate::clearAuthenticationCache(&manager);
QNetworkAccessManagerPrivate::clearConnectionCache(&manager);
manager.setProxy(QNetworkProxy());
manager.setCache(0);
#endif
}
bool nativeLookup(const char *hostname, int port, QByteArray &buf)
{
#if 0
addrinfo *res = 0;
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
int result = getaddrinfo(QUrl::toAce(hostname).constData(), QByteArray::number(port).constData(), &hints, &res);
if (!result)
return false;
for (addrinfo *node = res; node; node = node->ai_next) {
if (node->ai_family == AF_INET) {
buf = QByteArray((char *)node->ai_addr, node->ai_addrlen);
break;
}
}
freeaddrinfo(res);
#else
hostent *result = gethostbyname(hostname);
if (!result || result->h_addrtype != AF_INET)
return false;
struct sockaddr_in s;
s.sin_family = AF_INET;
s.sin_port = htons(port);
s.sin_addr = *(struct in_addr *) result->h_addr_list[0];
buf = QByteArray((char *)&s, sizeof s);
#endif
return !buf.isEmpty();
}
bool nativeSelect(int fd, int timeout, bool selectForWrite)
{
if (timeout < 0)
return false;
// wait for connected
fd_set fds, fde;
FD_ZERO(&fds);
FD_ZERO(&fde);
FD_SET(fd, &fds);
FD_SET(fd, &fde);
int ret;
do {
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = timeout % 1000;
if (selectForWrite)
ret = ::select(fd + 1, 0, &fds, &fde, &tv);
else
ret = ::select(fd + 1, &fds, 0, &fde, &tv);
} while (ret == -1 && errno == EINTR);
return ret != 0;
}
void tst_NetworkRemoteStressTest::blockingSequentialRemoteHosts()
{
QFETCH_GLOBAL(QVector<QUrl>, urlList);
QFETCH_GLOBAL(bool, useSslSocket);
qint64 totalBytes = 0;
QElapsedTimer outerTimer;
outerTimer.start();
#ifdef QT_NO_SSL
QVERIFY(!useSslSocket);
#endif // QT_NO_SSL
for (int i = 0; i < urlList.size(); ++i) {
const QUrl &url = urlList.at(i);
bool isHttps = url.scheme() == "https";
QElapsedTimer timeout;
byteCounter = 0;
timeout.start();
QSharedPointer<QTcpSocket> socket;
#ifndef QT_NO_SSL
if (useSslSocket || isHttps)
socket = QSharedPointer<QTcpSocket>(new QSslSocket);
#endif // QT_NO_SSL
if (socket.isNull())
socket = QSharedPointer<QTcpSocket>(new QTcpSocket);
socket->connectToHost(url.host(), url.port(isHttps ? 443 : 80));
const QByteArray encodedHost = url.host(QUrl::FullyEncoded).toLatin1();
QVERIFY2(socket->waitForConnected(10000), "Timeout connecting to " + encodedHost);
#ifndef QT_NO_SSL
if (isHttps) {
static_cast<QSslSocket *>(socket.data())->setProtocol(QSsl::TlsV1_0);
static_cast<QSslSocket *>(socket.data())->startClientEncryption();
static_cast<QSslSocket *>(socket.data())->ignoreSslErrors();
QVERIFY2(static_cast<QSslSocket *>(socket.data())->waitForEncrypted(10000), "Timeout starting TLS with " + encodedHost);
}
#endif // QT_NO_SSL
socket->write("GET " + url.toEncoded(QUrl::RemoveScheme | QUrl::RemoveAuthority | QUrl::RemoveFragment) + " HTTP/1.0\r\n"
"Connection: close\r\n"
"User-Agent: tst_QTcpSocket_stresstest/1.0\r\n"
"Host: " + encodedHost + "\r\n"
"\r\n");
while (socket->bytesToWrite())
QVERIFY2(socket->waitForBytesWritten(10000), "Timeout writing to " + encodedHost);
while (socket->state() == QAbstractSocket::ConnectedState && !timeout.hasExpired(10000)) {
socket->waitForReadyRead(10000);
byteCounter += socket->readAll().size(); // discard
}
QVERIFY2(!timeout.hasExpired(10000), "Timeout reading from " + encodedHost);
totalBytes += byteCounter;
if (intermediateDebug) {
double rate = (byteCounter * 1.0 / timeout.elapsed());
qDebug() << i << url << byteCounter << "bytes in" << timeout.elapsed() << "ms:"
<< (rate / 1024.0 * 1000) << "kB/s";
}
}
qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 * 1000 / outerTimer.elapsed()) << "kB/s";
}
void tst_NetworkRemoteStressTest::sequentialRemoteHosts()
{
QFETCH_GLOBAL(QVector<QUrl>, urlList);
QFETCH_GLOBAL(bool, useSslSocket);
#ifdef QT_NO_SSL
QVERIFY(!useSslSocket);
#endif // QT_NO_SSL
qint64 totalBytes = 0;
QElapsedTimer outerTimer;
outerTimer.start();
for (int i = 0; i < urlList.size(); ++i) {
const QUrl &url = urlList.at(i);
bool isHttps = url.scheme() == "https";
QElapsedTimer timeout;
byteCounter = 0;
timeout.start();
QSharedPointer<QTcpSocket> socket;
#ifndef QT_NO_SSL
if (useSslSocket || isHttps)
socket = QSharedPointer<QTcpSocket>(new QSslSocket);
#endif // QT_NO_SSL
if (socket.isNull())
socket = QSharedPointer<QTcpSocket>(new QTcpSocket);
if (isHttps) {
#ifndef QT_NO_SSL
static_cast<QSslSocket *>(socket.data())->setProtocol(QSsl::TlsV1_0);
static_cast<QSslSocket *>(socket.data())->connectToHostEncrypted(url.host(), url.port(443));
static_cast<QSslSocket *>(socket.data())->ignoreSslErrors();
#endif // QT_NO_SSL
} else {
socket->connectToHost(url.host(), url.port(80));
}
const QByteArray encodedHost = url.host(QUrl::FullyEncoded).toLatin1();
socket->write("GET " + url.toEncoded(QUrl::RemoveScheme | QUrl::RemoveAuthority | QUrl::RemoveFragment) + " HTTP/1.0\r\n"
"Connection: close\r\n"
"User-Agent: tst_QTcpSocket_stresstest/1.0\r\n"
"Host: " + encodedHost + "\r\n"
"\r\n");
connect(socket.data(), SIGNAL(readyRead()), SLOT(slotReadAll()));
QTestEventLoop::instance().connect(socket.data(), SIGNAL(disconnected()), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(30);
QVERIFY2(!QTestEventLoop::instance().timeout(), "Timeout with " + encodedHost + "; "
+ QByteArray::number(socket->bytesToWrite()) + " bytes to write");
totalBytes += byteCounter;
if (intermediateDebug) {
double rate = (byteCounter * 1.0 / timeout.elapsed());
qDebug() << i << url << byteCounter << "bytes in" << timeout.elapsed() << "ms:"
<< (rate / 1024.0 * 1000) << "kB/s";
}
}
qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 * 1000 / outerTimer.elapsed()) << "kB/s";
}
void tst_NetworkRemoteStressTest::parallelRemoteHosts_data()
{
QTest::addColumn<int>("parallelAttempts");
QTest::newRow("1") << 1;
QTest::newRow("2") << 2;
QTest::newRow("4") << 4;
QTest::newRow("5") << 5;
QTest::newRow("6") << 6;
QTest::newRow("8") << 8;
QTest::newRow("10") << 10;
QTest::newRow("25") << 25;
QTest::newRow("500") << 500;
}
void tst_NetworkRemoteStressTest::parallelRemoteHosts()
{
QFETCH_GLOBAL(QVector<QUrl>, urlList);
QFETCH_GLOBAL(bool, useSslSocket);
QFETCH(int, parallelAttempts);
#ifdef QT_NO_SSL
QVERIFY(!useSslSocket);
#endif // QT_NO_SSL
qint64 totalBytes = 0;
QElapsedTimer outerTimer;
outerTimer.start();
QVector<QUrl>::ConstIterator it = urlList.constBegin();
while (it != urlList.constEnd()) {
QElapsedTimer timeout;
byteCounter = 0;
timeout.start();
QVector<QSharedPointer<QTcpSocket> > sockets;
sockets.reserve(parallelAttempts);
for (int j = 0; j < parallelAttempts && it != urlList.constEnd(); ++j, ++it) {
const QUrl &url = *it;
bool isHttps = url.scheme() == "https";
QTcpSocket *socket = 0;
#ifndef QT_NO_SSL
if (useSslSocket || isHttps)
socket = new QSslSocket;
#endif // QT_NO_SSL
if (!socket)
socket = new QTcpSocket;
if (isHttps) {
#ifndef QT_NO_SSL
static_cast<QSslSocket *>(socket)->setProtocol(QSsl::TlsV1_0);
static_cast<QSslSocket *>(socket)->connectToHostEncrypted(url.host(), url.port(443));
static_cast<QSslSocket *>(socket)->ignoreSslErrors();
#endif // QT_NO_SSL
} else {
socket->connectToHost(url.host(), url.port(isHttps ? 443 : 80));
}
const QByteArray encodedHost = url.host(QUrl::FullyEncoded).toLatin1();
socket->write("GET " + url.toEncoded(QUrl::RemoveScheme | QUrl::RemoveAuthority | QUrl::RemoveFragment) + " HTTP/1.0\r\n"
"Connection: close\r\n"
"User-Agent: tst_QTcpSocket_stresstest/1.0\r\n"
"Host: " + encodedHost + "\r\n"
"\r\n");
connect(socket, SIGNAL(readyRead()), SLOT(slotReadAll()));
QTestEventLoop::instance().connect(socket, SIGNAL(disconnected()), SLOT(exitLoop()));
socket->setProperty("remoteUrl", url);
sockets.append(QSharedPointer<QTcpSocket>(socket));
}
while (!timeout.hasExpired(10000)) {
QTestEventLoop::instance().enterLoop(10);
int done = 0;
for (int j = 0; j < sockets.size(); ++j)
done += sockets[j]->state() == QAbstractSocket::UnconnectedState ? 1 : 0;
if (done == sockets.size())
break;
}
for (int j = 0; j < sockets.size(); ++j)
if (sockets[j]->state() != QAbstractSocket::UnconnectedState) {
qDebug() << "Socket to" << sockets[j]->property("remoteUrl").toUrl() << "still open with"
<< sockets[j]->bytesToWrite() << "bytes to write";
QFAIL("Timed out");
}
totalBytes += byteCounter;
if (intermediateDebug) {
double rate = (byteCounter * 1.0 / timeout.elapsed());
qDebug() << byteCounter << "bytes in" << timeout.elapsed() << "ms:"
<< (rate / 1024.0 * 1000) << "kB/s";
}
}
qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 * 1000 / outerTimer.elapsed()) << "kB/s";
}
void tst_NetworkRemoteStressTest::namRemoteGet_data()
{
QTest::addColumn<int>("parallelAttempts");
QTest::newRow("1") << 1;
QTest::newRow("2") << 2;
QTest::newRow("4") << 4;
QTest::newRow("5") << 5;
QTest::newRow("6") << 6;
QTest::newRow("8") << 8;
QTest::newRow("10") << 10;
QTest::newRow("25") << 25;
QTest::newRow("500") << 500;
}
void tst_NetworkRemoteStressTest::namRemoteGet()
{
QFETCH_GLOBAL(QVector<QUrl>, urlList);
QFETCH(int, parallelAttempts);
bool pipelineAllowed = false;// QFETCH(bool, pipelineAllowed);
qint64 totalBytes = 0;
QElapsedTimer outerTimer;
outerTimer.start();
QVector<QUrl>::ConstIterator it = urlList.constBegin();
while (it != urlList.constEnd()) {
QElapsedTimer timeout;
byteCounter = 0;
timeout.start();
QNetworkRequest req;
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, pipelineAllowed);
QVector<QSharedPointer<QNetworkReply> > replies;
replies.reserve(parallelAttempts);
for (int j = 0; j < parallelAttempts && it != urlList.constEnd(); ++j) {
req.setUrl(*it++);
QNetworkReply *r = manager.get(req);
r->ignoreSslErrors();
connect(r, SIGNAL(readyRead()), SLOT(slotReadAll()));
QTestEventLoop::instance().connect(r, SIGNAL(finished()), SLOT(exitLoop()));
replies.append(QSharedPointer<QNetworkReply>(r));
}
while (!timeout.hasExpired(30000)) {
QTestEventLoop::instance().enterLoop(30 - timeout.elapsed() / 1000);
int done = 0;
for (int j = 0; j < replies.size(); ++j)
done += replies[j]->isFinished() ? 1 : 0;
if (done == replies.size())
break;
}
if (timeout.hasExpired(30000)) {
for (int j = 0; j < replies.size(); ++j)
if (!replies[j]->isFinished())
qDebug() << "Request" << replies[j]->url() << "not finished";
QFAIL("Timed out");
}
replies.clear();
totalBytes += byteCounter;
if (intermediateDebug) {
double rate = (byteCounter * 1.0 / timeout.elapsed());
qDebug() << byteCounter << "bytes in" << timeout.elapsed() << "ms:"
<< (rate / 1024.0 * 1000) << "kB/s";
}
}
qDebug() << "Average transfer rate was" << (totalBytes / 1024.0 * 1000 / outerTimer.elapsed()) << "kB/s";
}
QTEST_MAIN(tst_NetworkRemoteStressTest);
#include "tst_network_remote_stresstest.moc"