qt 6.5.1 original

This commit is contained in:
kleuter
2023-10-29 23:33:08 +01:00
parent 71d22ab6b0
commit 85d238dfda
21202 changed files with 5499099 additions and 0 deletions

View File

@ -0,0 +1,10 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(qfile_vs_qnetworkaccessmanager)
add_subdirectory(qnetworkreply)
add_subdirectory(qnetworkreply_from_cache)
add_subdirectory(qnetworkdiskcache)
if(QT_FEATURE_private_tests)
add_subdirectory(qdecompresshelper)
endif()

View File

@ -0,0 +1,16 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## qdecompresshelper Binary:
#####################################################################
qt_internal_add_benchmark(qdecompresshelper
SOURCES
main.cpp
DEFINES
SRC_DIR=${CMAKE_CURRENT_SOURCE_DIR}
LIBRARIES
Qt::NetworkPrivate
Qt::Test
)

View File

@ -0,0 +1,71 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtNetwork/private/qdecompresshelper_p.h>
#include <QtTest/QTest>
class tst_QDecompressHelper : public QObject
{
Q_OBJECT
private slots:
void decompress_data();
void decompress();
};
void tst_QDecompressHelper::decompress_data()
{
QTest::addColumn<QByteArray>("encoding");
QTest::addColumn<QString>("fileName");
QString srcDir = QStringLiteral(QT_STRINGIFY(SRC_DIR));
srcDir = QDir::fromNativeSeparators(srcDir);
if (!srcDir.endsWith("/"))
srcDir += "/";
bool dataAdded = false;
#ifndef QT_NO_COMPRESS
QTest::addRow("gzip") << QByteArray("gzip") << srcDir + QString("50mb.txt.gz");
dataAdded = true;
#endif
#if QT_CONFIG(brotli)
QTest::addRow("brotli") << QByteArray("br") << srcDir + QString("50mb.txt.br");
dataAdded = true;
#endif
#if QT_CONFIG(zstd)
QTest::addRow("zstandard") << QByteArray("zstd") << srcDir + QString("50mb.txt.zst");
dataAdded = true;
#endif
if (!dataAdded)
QSKIP("There's no decompression support");
}
void tst_QDecompressHelper::decompress()
{
QFETCH(QByteArray, encoding);
QFETCH(QString, fileName);
QFile file { fileName };
QVERIFY(file.open(QIODevice::ReadOnly));
QBENCHMARK {
file.seek(0);
QDecompressHelper helper;
helper.setEncoding(encoding);
QVERIFY(helper.isValid());
helper.feed(file.readAll());
qsizetype bytes = 0;
while (helper.hasData()) {
QByteArray out(64 * 1024, Qt::Uninitialized);
qsizetype bytesRead = helper.read(out.data(), out.size());
bytes += bytesRead;
}
QCOMPARE(bytes, 50 * 1024 * 1024);
}
}
QTEST_MAIN(tst_QDecompressHelper)
#include "main.moc"

View File

@ -0,0 +1,14 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_bench_qfile_vs_qnetworkaccessmanager Binary:
#####################################################################
qt_internal_add_benchmark(tst_bench_qfile_vs_qnetworkaccessmanager
SOURCES
main.cpp
LIBRARIES
Qt::Network
Qt::Test
)

View File

@ -0,0 +1,154 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QDebug>
#include <qtest.h>
#include <QTest>
#include <QTestEventLoop>
#include <QtNetwork/qnetworkreply.h>
#include <QtNetwork/qnetworkrequest.h>
#include <QtNetwork/qnetworkaccessmanager.h>
#include <QtCore/QTemporaryFile>
#include <QtCore/QElapsedTimer>
#include <QtCore/QFile>
class qfile_vs_qnetworkaccessmanager : public QObject
{
Q_OBJECT
protected:
void qnamFileRead_iteration(QNetworkAccessManager &manager, QNetworkRequest &request);
void qnamImmediateFileRead_iteration(QNetworkAccessManager &manager, QNetworkRequest &request);
void qfileFileRead_iteration();
static const int iterations = 10;
private slots:
void qnamFileRead();
void qnamImmediateFileRead();
void qfileFileRead();
void initTestCase();
void cleanupTestCase();
public:
qint64 size;
QTemporaryFile testFile;
qfile_vs_qnetworkaccessmanager() : QObject(), size(0) {};
};
void qfile_vs_qnetworkaccessmanager::initTestCase()
{
testFile.open();
QByteArray qba(1*1024*1024, 'x'); // 1 MB
for (int i = 0; i < 100; i++) {
testFile.write(qba);
testFile.flush();
size += qba.size();
} // 100 MB or 10 MB
testFile.reset();
}
void qfile_vs_qnetworkaccessmanager::cleanupTestCase()
{
}
void qfile_vs_qnetworkaccessmanager::qnamFileRead_iteration(QNetworkAccessManager &manager, QNetworkRequest &request)
{
QNetworkReply* reply = manager.get(request);
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection);
QTestEventLoop::instance().enterLoop(10);
QVERIFY(!QTestEventLoop::instance().timeout());
QByteArray qba = reply->readAll();
delete reply;
}
void qfile_vs_qnetworkaccessmanager::qnamFileRead()
{
QNetworkAccessManager manager;
QElapsedTimer t;
QNetworkRequest request(QUrl::fromLocalFile(testFile.fileName()));
// do 3 dry runs for cache warmup
qnamFileRead_iteration(manager, request);
qnamFileRead_iteration(manager, request);
qnamFileRead_iteration(manager, request);
t.start();
// 10 real runs
QBENCHMARK_ONCE {
for (int i = 0; i < iterations; i++) {
qnamFileRead_iteration(manager, request);
}
}
qint64 elapsed = t.elapsed();
qDebug() << Qt::endl << "Finished!";
qDebug() << "Bytes:" << size;
qDebug() << "Speed:" << (qreal(size*iterations) / 1024.0) / (qreal(elapsed) / 1000.0) << "KB/sec";
}
void qfile_vs_qnetworkaccessmanager::qnamImmediateFileRead_iteration(QNetworkAccessManager &manager, QNetworkRequest &request)
{
QNetworkReply* reply = manager.get(request);
QVERIFY(reply->isFinished()); // should be like that!
QByteArray qba = reply->readAll();
delete reply;
}
void qfile_vs_qnetworkaccessmanager::qnamImmediateFileRead()
{
QNetworkAccessManager manager;
QElapsedTimer t;
QNetworkRequest request(QUrl::fromLocalFile(testFile.fileName()));
// do 3 dry runs for cache warmup
qnamImmediateFileRead_iteration(manager, request);
qnamImmediateFileRead_iteration(manager, request);
qnamImmediateFileRead_iteration(manager, request);
t.start();
// 10 real runs
QBENCHMARK_ONCE {
for (int i = 0; i < iterations; i++) {
qnamImmediateFileRead_iteration(manager, request);
}
}
qint64 elapsed = t.elapsed();
qDebug() << Qt::endl << "Finished!";
qDebug() << "Bytes:" << size;
qDebug() << "Speed:" << (qreal(size*iterations) / 1024.0) / (qreal(elapsed) / 1000.0) << "KB/sec";
}
void qfile_vs_qnetworkaccessmanager::qfileFileRead_iteration()
{
testFile.reset();
QByteArray qba = testFile.readAll();
}
void qfile_vs_qnetworkaccessmanager::qfileFileRead()
{
QElapsedTimer t;
// do 3 dry runs for cache warmup
qfileFileRead_iteration();
qfileFileRead_iteration();
qfileFileRead_iteration();
t.start();
// 10 real runs
QBENCHMARK_ONCE {
for (int i = 0; i < iterations; i++) {
qfileFileRead_iteration();
}
}
qint64 elapsed = t.elapsed();
qDebug() << Qt::endl << "Finished!";
qDebug() << "Bytes:" << size;
qDebug() << "Speed:" << (qreal(size*iterations) / 1024.0) / (qreal(elapsed) / 1000.0) << "KB/sec";
}
QTEST_MAIN(qfile_vs_qnetworkaccessmanager)
#include "main.moc"

View File

@ -0,0 +1,14 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_bench_qnetworkdiskcache Binary:
#####################################################################
qt_internal_add_benchmark(tst_bench_qnetworkdiskcache
SOURCES
tst_qnetworkdiskcache.cpp
LIBRARIES
Qt::Network
Qt::Test
)

View File

@ -0,0 +1,379 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QNetworkDiskCache>
#include <QNetworkCacheMetaData>
#include <QDir>
#include <QBuffer>
#include <QTextStream>
#include <QDebug>
#include <QTest>
#include <QIODevice>
#include <QStandardPaths>
#include <QDirIterator>
enum Numbers { NumFakeCacheObjects = 200, //entries in pre-populated cache
NumInsertions = 100, //insertions to be timed
NumRemovals = 100, //removals to be timed
NumReadContent = 100, //meta requests to be timed
HugeCacheLimit = 50*1024*1024, // max size for a big cache
TinyCacheLimit = 1*512*1024}; // max size for a tiny cache
const QString fakeURLbase = "http://127.0.0.1/fake/";
//fake HTTP body aka payload
const QByteArray payload("Qt rocks!");
class tst_qnetworkdiskcache : public QObject
{
Q_OBJECT
private:
void injectFakeData();
void insertOneItem();
bool isUrlCached(quint32 id);
void cleanRecursive(QString &path);
void cleanupCacheObject();
void initCacheObject();
QString cacheDir;
QNetworkDiskCache *cache;
public slots:
void initTestCase();
void cleanupTestCase();
private slots:
void timeInsertion_data();
void timeInsertion();
void timeRead_data();
void timeRead();
void timeRemoval_data();
void timeRemoval();
void timeExpiration_data();
void timeExpiration();
};
void tst_qnetworkdiskcache::initTestCase()
{
cache = 0;
}
void tst_qnetworkdiskcache::cleanupTestCase()
{
cleanupCacheObject();
cleanRecursive(cacheDir);
}
void tst_qnetworkdiskcache::timeInsertion_data()
{
QTest::addColumn<QString>("cacheRootDirectory");
QString cacheLoc = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
QTest::newRow("QStandardPaths Cache Location") << cacheLoc;
}
//This functions times an insert() operation.
//You can run it after populating the cache with
//fake data so that more realistic performance
//estimates are obtained.
void tst_qnetworkdiskcache::timeInsertion()
{
QFETCH(QString, cacheRootDirectory);
cacheDir = QString( cacheRootDirectory + QDir::separator() + "man_qndc");
QDir d;
qDebug() << "Setting cache directory to = " << d.absoluteFilePath(cacheDir);
//Housekeeping
cleanRecursive(cacheDir); // slow op.
initCacheObject();
cache->setCacheDirectory(cacheDir);
cache->setMaximumCacheSize(qint64(HugeCacheLimit));
cache->clear();
//populate some fake data to simulate partially full cache
injectFakeData(); // SLOW
//Sanity-check that the first URL that we insert below isn't already in there.
QVERIFY(isUrlCached(NumFakeCacheObjects) == false);
// IMPORTANT: max cache size should be HugeCacheLimit, to avoid evictions below
//time insertion of previously-uncached URLs.
QBENCHMARK_ONCE {
for (quint32 i = NumFakeCacheObjects; i < (NumFakeCacheObjects + NumInsertions); i++) {
//prepare metata for url
QNetworkCacheMetaData meta;
QString fakeURL;
QTextStream stream(&fakeURL);
stream << fakeURLbase << i;
QUrl url(fakeURL);
meta.setUrl(url);
meta.setSaveToDisk(true);
//commit payload and metadata to disk
QIODevice *device = cache->prepare(meta);
device->write(payload);
cache->insert(device);
}
}
//SLOW cleanup
cleanupCacheObject();
cleanRecursive(cacheDir);
}
void tst_qnetworkdiskcache::timeRead_data()
{
QTest::addColumn<QString>("cacheRootDirectory");
QString cacheLoc = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
QTest::newRow("QStandardPaths Cache Location") << cacheLoc;
}
//Times metadata as well payload lookup
// i.e metaData(), rawHeaders() and data()
void tst_qnetworkdiskcache::timeRead()
{
QFETCH(QString, cacheRootDirectory);
cacheDir = QString( cacheRootDirectory + QDir::separator() + "man_qndc");
QDir d;
qDebug() << "Setting cache directory to = " << d.absoluteFilePath(cacheDir);
//Housekeeping
cleanRecursive(cacheDir); // slow op.
initCacheObject();
cache->setCacheDirectory(cacheDir);
cache->setMaximumCacheSize(qint64(HugeCacheLimit));
cache->clear();
//populate some fake data to simulate partially full cache
injectFakeData();
//Entries in the cache should be > what we try to remove
QVERIFY(NumFakeCacheObjects > NumReadContent);
//time metadata lookup of previously inserted URL.
QBENCHMARK_ONCE {
for (quint32 i = 0; i < NumReadContent; i++) {
QString fakeURL;
QTextStream stream(&fakeURL);
stream << fakeURLbase << i;
QUrl url(fakeURL);
QNetworkCacheMetaData qndc = cache->metaData(url);
QVERIFY(qndc.isValid()); // we must have read the metadata
QNetworkCacheMetaData::RawHeaderList raw(qndc.rawHeaders());
QVERIFY(raw.size()); // we must have parsed the headers from the meta
QIODevice *iodevice(cache->data(url));
QVERIFY(iodevice); //must not be NULL
iodevice->close();
delete iodevice;
}
}
//Cleanup (slow)
cleanupCacheObject();
cleanRecursive(cacheDir);
}
void tst_qnetworkdiskcache::timeRemoval_data()
{
QTest::addColumn<QString>("cacheRootDirectory");
QString cacheLoc = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
QTest::newRow("QStandardPaths Cache Location") << cacheLoc;
}
void tst_qnetworkdiskcache::timeRemoval()
{
QFETCH(QString, cacheRootDirectory);
cacheDir = QString( cacheRootDirectory + QDir::separator() + "man_qndc");
QDir d;
qDebug() << "Setting cache directory to = " << d.absoluteFilePath(cacheDir);
//Housekeeping
initCacheObject();
cleanRecursive(cacheDir); // slow op.
cache->setCacheDirectory(cacheDir);
// Make max cache size HUGE, so that evictions don't happen below
cache->setMaximumCacheSize(qint64(HugeCacheLimit));
cache->clear();
//populate some fake data to simulate partially full cache
injectFakeData();
//Sanity-check that the URL is already in there somewhere
QVERIFY(isUrlCached(NumRemovals-1) == true);
//Entries in the cache should be > what we try to remove
QVERIFY(NumFakeCacheObjects > NumRemovals);
//time removal of previously-inserted URL.
QBENCHMARK_ONCE {
for (quint32 i = 0; i < NumRemovals; i++) {
QString fakeURL;
QTextStream stream(&fakeURL);
stream << fakeURLbase << i;
QUrl url(fakeURL);
cache->remove(url);
}
}
//Cleanup (slow)
cleanupCacheObject();
cleanRecursive(cacheDir);
}
void tst_qnetworkdiskcache::timeExpiration_data()
{
QTest::addColumn<QString>("cacheRootDirectory");
QString cacheLoc = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
QTest::newRow("QStandardPaths Cache Location") << cacheLoc;
}
void tst_qnetworkdiskcache::timeExpiration()
{
QFETCH(QString, cacheRootDirectory);
cacheDir = QString( cacheRootDirectory + QDir::separator() + "man_qndc");
QDir d;
qDebug() << "Setting cache directory to = " << d.absoluteFilePath(cacheDir);
//Housekeeping
initCacheObject();
cleanRecursive(cacheDir); // slow op.
cache->setCacheDirectory(cacheDir);
// Make max cache size HUGE, so that evictions don't happen below
cache->setMaximumCacheSize(qint64(HugeCacheLimit));
cache->clear();
//populate some fake data to simulate partially full cache
injectFakeData();
//Sanity-check that the URL is already in there somewhere
QVERIFY(isUrlCached(NumRemovals-1) == true);
//Entries in the cache should be > what we try to remove
QVERIFY(NumFakeCacheObjects > NumRemovals);
//Set cache limit lower, so this force 1 round of eviction
cache->setMaximumCacheSize(qint64(TinyCacheLimit));
//time insertions of additional content, which is likely to internally cause evictions
QBENCHMARK_ONCE {
for (quint32 i = NumFakeCacheObjects; i < (NumFakeCacheObjects + NumInsertions); i++) {
//prepare metata for url
QNetworkCacheMetaData meta;
QString fakeURL;
QTextStream stream(&fakeURL);
stream << fakeURLbase << i;//codescanner::leave
QUrl url(fakeURL);
meta.setUrl(url);
meta.setSaveToDisk(true);
//commit payload and metadata to disk
QIODevice *device = cache->prepare(meta);
device->write(payload);
cache->insert(device); // this should trigger evictions, if TinyCacheLimit is small enough
}
}
//Cleanup (slow)
cleanupCacheObject();
cleanRecursive(cacheDir);
}
// This function simulates a partially or fully occupied disk cache
// like a normal user of a cache might encounter is real-life browsing.
// The point of this is to trigger degradation in file-system and media performance
// that occur due to the quantity and layout of data.
void tst_qnetworkdiskcache::injectFakeData()
{
QNetworkCacheMetaData::RawHeaderList headers;
headers.append(qMakePair(QByteArray("X-TestHeader"),QByteArray("HeaderValue")));
//Prep cache dir with fake data using QNetworkDiskCache APIs
for (quint32 i = 0; i < NumFakeCacheObjects; i++) {
//prepare metata for url
QNetworkCacheMetaData meta;
QString fakeURL;
QTextStream stream(&fakeURL);
stream << fakeURLbase << i;
QUrl url(fakeURL);
meta.setUrl(url);
meta.setRawHeaders(headers);
meta.setSaveToDisk(true);
//commit payload and metadata to disk
QIODevice *device = cache->prepare(meta);
device->write(payload);
cache->insert(device);
}
}
// Checks if the fake URL #id is already cached or not.
bool tst_qnetworkdiskcache::isUrlCached(quint32 id)
{
QString str;
QTextStream stream(&str);
stream << fakeURLbase << id;
QUrl url(str);
QIODevice *iod = cache->data(url);
return ((iod == 0) ? false : true) ;
}
// Utility function for recursive directory cleanup.
void tst_qnetworkdiskcache::cleanRecursive(QString &path)
{
QDirIterator it(path, QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (it.hasNext()) {
QFile f(it.next());
bool err = f.remove();
Q_UNUSED(err);
}
QDirIterator it2(path, QDir::AllDirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (it2.hasNext()) {
QString s(it2.next());
QDir dir(s);
dir.rmdir(s);
}
}
void tst_qnetworkdiskcache::cleanupCacheObject()
{
delete cache;
cache = 0;
}
void tst_qnetworkdiskcache::initCacheObject()
{
cache = new QNetworkDiskCache();
}
QTEST_MAIN(tst_qnetworkdiskcache)
#include "tst_qnetworkdiskcache.moc"

View File

@ -0,0 +1,16 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_bench_qnetworkreply Binary:
#####################################################################
qt_internal_add_benchmark(tst_bench_qnetworkreply
SOURCES
tst_qnetworkreply.cpp
LIBRARIES
Qt::CorePrivate
Qt::Network
Qt::NetworkPrivate
Qt::Test
)

View File

@ -0,0 +1,935 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// This file contains benchmarks for QNetworkReply functions.
#include <QDebug>
#include <qtest.h>
#include <QTest>
#include <QTestEventLoop>
#include <QSemaphore>
#include <QTimer>
#include <QtCore/qrandom.h>
#include <QtCore/QElapsedTimer>
#include <QtNetwork/qnetworkreply.h>
#include <QtNetwork/qnetworkrequest.h>
#include <QtNetwork/qnetworkaccessmanager.h>
#include <QtNetwork/qtcpsocket.h>
#include <QtNetwork/qtcpserver.h>
#include "../../../../auto/network-settings.h"
#ifdef QT_BUILD_INTERNAL
#include <QtNetwork/private/qhostinfo_p.h>
#endif
Q_DECLARE_METATYPE(QSharedPointer<char>)
class TimedSender: public QThread
{
Q_OBJECT
qint64 totalBytes;
QSemaphore ready;
QByteArray dataToSend;
QTcpSocket *client;
int timeout;
int port;
public:
int transferRate;
TimedSender(int ms)
: totalBytes(0), timeout(ms), port(-1), transferRate(-1)
{
dataToSend = QByteArray(16*1024, '@');
start();
ready.acquire();
}
inline int serverPort() const { return port; }
private slots:
void writeMore()
{
while (client->bytesToWrite() < 128 * 1024) {
writePacket(dataToSend);
}
}
protected:
void run() override
{
QTcpServer server;
server.listen();
port = server.serverPort();
ready.release();
server.waitForNewConnection(-1);
client = server.nextPendingConnection();
writeMore();
connect(client, SIGNAL(bytesWritten(qint64)), SLOT(writeMore()), Qt::DirectConnection);
QEventLoop eventLoop;
QTimer::singleShot(timeout, &eventLoop, SLOT(quit()));
QElapsedTimer timer;
timer.start();
eventLoop.exec();
disconnect(client, SIGNAL(bytesWritten(qint64)), this, 0);
// wait for the connection to shut down
client->disconnectFromHost();
if (!client->waitForDisconnected(10000))
return;
transferRate = totalBytes * 1000 / timer.elapsed();
qDebug() << "TimedSender::run" << "receive rate:" << (transferRate / 1024) << "kB/s in"
<< timer.elapsed() << "ms";
}
void writePacket(const QByteArray &array)
{
client->write(array);
totalBytes += array.size();
}
};
typedef QSharedPointer<QNetworkReply> QNetworkReplyPtr;
class DataReader: public QObject
{
Q_OBJECT
public:
qint64 totalBytes;
QByteArray data;
QIODevice *device;
bool accumulate;
DataReader(const QNetworkReplyPtr &dev, bool acc = true) : totalBytes(0), device(dev.data()), accumulate(acc)
{
connect(device, SIGNAL(readyRead()), SLOT(doRead()));
}
DataReader(QIODevice *dev, bool acc = true) : totalBytes(0), device(dev), accumulate(acc)
{
connect(device, SIGNAL(readyRead()), SLOT(doRead()));
}
public slots:
void doRead()
{
QByteArray buffer;
buffer.resize(device->bytesAvailable());
qint64 bytesRead = device->read(buffer.data(), device->bytesAvailable());
if (bytesRead == -1) {
QTestEventLoop::instance().exitLoop();
return;
}
buffer.truncate(bytesRead);
totalBytes += bytesRead;
if (accumulate)
data += buffer;
}
};
class ThreadedDataReader: public QThread
{
Q_OBJECT
// used to make the constructor only return after the tcp server started listening
QSemaphore ready;
QTcpSocket *client;
int port;
public:
qint64 transferRate;
ThreadedDataReader()
: port(-1), transferRate(-1)
{
start();
ready.acquire();
}
inline int serverPort() const { return port; }
protected:
void run() override
{
QTcpServer server;
server.listen();
port = server.serverPort();
ready.release();
server.waitForNewConnection(-1);
client = server.nextPendingConnection();
QEventLoop eventLoop;
DataReader reader(client, false);
QObject::connect(client, SIGNAL(disconnected()), &eventLoop, SLOT(quit()));
QElapsedTimer timer;
timer.start();
eventLoop.exec();
qint64 elapsed = timer.elapsed();
transferRate = reader.totalBytes * 1000 / elapsed;
qDebug() << "ThreadedDataReader::run" << "send rate:" << (transferRate / 1024) << "kB/s in" << elapsed << "msec";
}
};
class DataGenerator: public QIODevice
{
Q_OBJECT
enum { Idle, Started, Stopped } state;
public:
DataGenerator() : state(Idle)
{ open(ReadOnly); }
bool isSequential() const override { return true; }
qint64 bytesAvailable() const override { return state == Started ? 1024*1024 : 0; }
public slots:
void start() { state = Started; emit readyRead(); }
void stop() { state = Stopped; emit readyRead(); }
protected:
qint64 readData(char *data, qint64 maxlen) override
{
if (state == Stopped)
return -1; // EOF
// return as many bytes as are wanted
memset(data, '@', maxlen);
return maxlen;
}
qint64 writeData(const char *, qint64) override { return -1; }
};
class ThreadedDataReaderHttpServer: public QThread
{
Q_OBJECT
// used to make the constructor only return after the tcp server started listening
QSemaphore ready;
QTcpSocket *client;
int port;
public:
qint64 transferRate;
ThreadedDataReaderHttpServer()
: port(-1), transferRate(-1)
{
start();
ready.acquire();
}
inline int serverPort() const { return port; }
protected:
void run() override
{
QTcpServer server;
server.listen();
port = server.serverPort();
ready.release();
QVERIFY(server.waitForNewConnection(10*1000));
client = server.nextPendingConnection();
// read lines until we read the empty line seperating HTTP request from HTTP request body
do {
if (client->canReadLine()) {
QString line = client->readLine();
if (line == "\n" || line == "\r\n")
break; // empty line
}
if (!client->waitForReadyRead(10*1000)) {
client->close();
return;
}
} while (client->state() == QAbstractSocket::ConnectedState);
client->write("HTTP/1.0 200 OK\r\n");
client->write("Content-length: 0\r\n");
client->write("\r\n");
client->flush();
QCoreApplication::processEvents();
QEventLoop eventLoop;
DataReader reader(client, false);
QObject::connect(client, SIGNAL(disconnected()), &eventLoop, SLOT(quit()));
QElapsedTimer timer;
timer.start();
eventLoop.exec();
qint64 elapsed = timer.elapsed();
transferRate = reader.totalBytes * 1000 / elapsed;
qDebug() << "ThreadedDataReaderHttpServer::run" << "send rate:" << (transferRate / 1024) << "kB/s in" << elapsed << "msec";
}
};
class FixedSizeDataGenerator : public QIODevice
{
Q_OBJECT
enum { Idle, Started, Stopped } state;
public:
FixedSizeDataGenerator(qint64 size) : state(Idle)
{ open(ReadOnly | Unbuffered);
toBeGeneratedTotalCount = toBeGeneratedCount = size;
}
qint64 bytesAvailable() const override
{
return state == Started ? toBeGeneratedCount + QIODevice::bytesAvailable() : 0;
}
bool isSequential() const override { return false; }
bool reset() override { return false; }
qint64 size() const override { return toBeGeneratedTotalCount; }
public slots:
void start() { state = Started; emit readyRead(); }
protected:
qint64 readData(char *data, qint64 maxlen) override
{
memset(data, '@', maxlen);
if (toBeGeneratedCount <= 0) {
return -1;
}
qint64 n = qMin(maxlen, toBeGeneratedCount);
toBeGeneratedCount -= n;
if (toBeGeneratedCount <= 0) {
// make sure this is a queued connection!
emit readChannelFinished();
}
return n;
}
qint64 writeData(const char *, qint64) override { return -1; }
qint64 toBeGeneratedCount;
qint64 toBeGeneratedTotalCount;
};
class HttpDownloadPerformanceServer : QObject {
Q_OBJECT;
qint64 dataSize;
qint64 dataSent;
QTcpServer server;
QTcpSocket *client;
bool serverSendsContentLength;
bool chunkedEncoding;
public:
HttpDownloadPerformanceServer (qint64 ds, bool sscl, bool ce) : dataSize(ds), dataSent(0),
client(0), serverSendsContentLength(sscl), chunkedEncoding(ce) {
server.listen();
connect(&server, SIGNAL(newConnection()), this, SLOT(newConnectionSlot()));
}
int serverPort() {
return server.serverPort();
}
public slots:
void newConnectionSlot() {
client = server.nextPendingConnection();
client->setParent(this);
connect(client, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
connect(client, SIGNAL(bytesWritten(qint64)), this, SLOT(bytesWrittenSlot(qint64)));
}
void readyReadSlot() {
client->readAll();
client->write("HTTP/1.0 200 OK\n");
if (serverSendsContentLength)
client->write(QString("Content-Length: " + QString::number(dataSize) + "\n").toLatin1());
if (chunkedEncoding)
client->write(QString("Transfer-Encoding: chunked\n").toLatin1());
client->write("Connection: close\n\n");
}
void bytesWrittenSlot(qint64 amount) {
Q_UNUSED(amount);
if (dataSent == dataSize && client) {
// close eventually
// chunked encoding: we have to send a last "empty" chunk
if (chunkedEncoding)
client->write(QString("0\r\n\r\n").toLatin1());
client->disconnectFromHost();
server.close();
client = 0;
return;
}
// send data
if (client && client->bytesToWrite() < 100*1024 && dataSent < dataSize) {
qint64 amount = qMin(qint64(16*1024), dataSize - dataSent);
QByteArray data(amount, '@');
if (chunkedEncoding) {
client->write(QString(QString("%1").arg(amount,0,16).toUpper() + "\r\n").toLatin1());
client->write(data.constData(), amount);
client->write(QString("\r\n").toLatin1());
} else {
client->write(data.constData(), amount);
}
dataSent += amount;
}
}
};
class HttpDownloadPerformanceClient : QObject {
Q_OBJECT;
QIODevice *device;
public:
HttpDownloadPerformanceClient (QIODevice *dev) : device(dev){
connect(dev, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
}
public slots:
void readyReadSlot() {
device->readAll();
}
};
class tst_qnetworkreply : public QObject
{
Q_OBJECT
QNetworkAccessManager manager;
public:
using QObject::connect;
bool connect(const QNetworkReplyPtr &sender, const char *signal, const QObject *receiver, const char *slot, Qt::ConnectionType ct = Qt::AutoConnection)
{ return connect(sender.data(), signal, receiver, slot, ct); }
private slots:
void initTestCase();
void httpLatency();
#ifndef QT_NO_SSL
void echoPerformance_data();
void echoPerformance();
void preConnectEncrypted();
#endif // !QT_NO_SSL
void preConnectEncrypted_data();
void downloadPerformance();
void uploadPerformance();
void performanceControlRate();
void httpUploadPerformance();
void httpDownloadPerformance_data();
void httpDownloadPerformance();
void httpDownloadPerformanceDownloadBuffer_data();
void httpDownloadPerformanceDownloadBuffer();
void httpsRequestChain();
void httpsUpload();
void preConnect_data();
void preConnect();
private:
void runHttpsUploadRequest(const QByteArray &data, const QNetworkRequest &request);
QPair<QNetworkReply *, qint64> runGetRequest(QNetworkAccessManager *manager,
const QNetworkRequest &request);
};
void tst_qnetworkreply::initTestCase()
{
if (!QtNetworkSettings::verifyTestNetworkSettings())
QSKIP("No network test server available");
}
void tst_qnetworkreply::httpLatency()
{
QNetworkAccessManager manager;
QBENCHMARK{
QNetworkRequest request(QUrl("http://" + QtNetworkSettings::serverName() + "/qtest/"));
QNetworkReply* reply = manager.get(request);
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection);
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
delete reply;
}
}
QPair<QNetworkReply *, qint64> tst_qnetworkreply::runGetRequest(
QNetworkAccessManager *manager, const QNetworkRequest &request)
{
QElapsedTimer timer;
timer.start();
QNetworkReply *reply = manager->get(request);
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply, SLOT(ignoreSslErrors()));
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection);
QTestEventLoop::instance().enterLoop(20);
qint64 elapsed = timer.elapsed();
return qMakePair(reply, elapsed);
}
#ifndef QT_NO_SSL
void tst_qnetworkreply::echoPerformance_data()
{
QTest::addColumn<bool>("ssl");
QTest::newRow("no_ssl") << false;
QTest::newRow("ssl") << true;
}
void tst_qnetworkreply::echoPerformance()
{
QFETCH(bool, ssl);
QNetworkAccessManager manager;
QNetworkRequest request(QUrl((ssl ? "https://" : "http://") + QtNetworkSettings::serverName() + "/qtest/cgi-bin/echo.cgi"));
QByteArray data;
data.resize(1024*1024*10); // 10 MB
// init with garbage. needed so ssl cannot compress it in an efficient way.
for (size_t i = 0; i < data.size() / sizeof(int); i++) {
char r = char(QRandomGenerator::global()->generate());
data.data()[i*sizeof(int)] = r;
}
QBENCHMARK{
QNetworkReply* reply = manager.post(request, data);
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply, SLOT(ignoreSslErrors()));
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection);
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(reply->error() == QNetworkReply::NoError);
delete reply;
}
}
void tst_qnetworkreply::preConnectEncrypted()
{
QFETCH(int, sleepTime);
QString hostName = QLatin1String("www.google.com");
QNetworkAccessManager manager;
QNetworkRequest request(QUrl("https://" + hostName));
// make sure we have a full request including
// DNS lookup, TCP and SSL handshakes
#ifdef QT_BUILD_INTERNAL
qt_qhostinfo_clear_cache();
#else
qWarning("no internal build, could not clear DNS cache. Results may not be representative.");
#endif
// first, benchmark a normal request
QPair<QNetworkReply *, qint64> normalResult = runGetRequest(&manager, request);
QNetworkReply *normalReply = normalResult.first;
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(normalReply->error() == QNetworkReply::NoError);
qint64 normalElapsed = normalResult.second;
// clear all caches again
#ifdef QT_BUILD_INTERNAL
qt_qhostinfo_clear_cache();
#else
qWarning("no internal build, could not clear DNS cache. Results may not be representative.");
#endif
manager.clearAccessCache();
// now try to make the connection beforehand
manager.connectToHostEncrypted(hostName);
QTestEventLoop::instance().enterLoopMSecs(sleepTime);
// now make another request and hopefully use the existing connection
QPair<QNetworkReply *, qint64> preConnectResult = runGetRequest(&manager, request);
QNetworkReply *preConnectReply = normalResult.first;
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(preConnectReply->error() == QNetworkReply::NoError);
qint64 preConnectElapsed = preConnectResult.second;
qDebug() << request.url().toString() << "full request:" << normalElapsed
<< "ms, pre-connect request:" << preConnectElapsed << "ms, difference:"
<< (normalElapsed - preConnectElapsed) << "ms";
}
#endif // !QT_NO_SSL
void tst_qnetworkreply::preConnectEncrypted_data()
{
#ifndef QT_NO_OPENSSL
QTest::addColumn<int>("sleepTime");
// start a new normal request after preconnecting is done
QTest::newRow("HTTPS-2secs") << 2000;
// start a new normal request while preconnecting is in-flight
QTest::newRow("HTTPS-100ms") << 100;
#endif // QT_NO_OPENSSL
}
void tst_qnetworkreply::downloadPerformance()
{
// unlike the above function, this one tries to send as fast as possible
// and measures how fast it was.
TimedSender sender(5000);
QNetworkRequest request(QUrl(QStringLiteral("debugpipe://127.0.0.1:") + QString::number(sender.serverPort()) + QStringLiteral("/?bare=1")));
QNetworkReplyPtr reply(manager.get(request));
DataReader reader(reply, false);
QElapsedTimer loopTime;
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
loopTime.start();
QTestEventLoop::instance().enterLoop(40);
int elapsedTime = loopTime.elapsed();
sender.wait();
qint64 receivedBytes = reader.totalBytes;
qDebug() << "tst_QNetworkReply::downloadPerformance" << "receive rate:" << (receivedBytes * 1000 / elapsedTime / 1024) << "kB/s and"
<< elapsedTime << "ms";
}
void tst_qnetworkreply::uploadPerformance()
{
ThreadedDataReader reader;
DataGenerator generator;
QNetworkRequest request(QUrl(QStringLiteral("debugpipe://127.0.0.1:") + QString::number(reader.serverPort()) + QStringLiteral("/?bare=1")));
QNetworkReplyPtr reply(manager.put(request, &generator));
generator.start();
connect(&reader, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QTimer::singleShot(5000, &generator, SLOT(stop()));
QTestEventLoop::instance().enterLoop(30);
QCOMPARE(reply->error(), QNetworkReply::NoError);
QVERIFY(!QTestEventLoop::instance().timeout());
}
constexpr qint64 MiB = 1024 * 1024;
void tst_qnetworkreply::httpUploadPerformance()
{
constexpr qint64 UploadSize = 128 * MiB;
ThreadedDataReaderHttpServer reader;
FixedSizeDataGenerator generator(UploadSize);
QNetworkRequest request(QUrl("http://127.0.0.1:" + QString::number(reader.serverPort()) + "/?bare=1"));
request.setHeader(QNetworkRequest::ContentLengthHeader,UploadSize);
QNetworkReplyPtr reply(manager.put(request, &generator));
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QElapsedTimer time;
generator.start();
time.start();
QTestEventLoop::instance().enterLoop(40);
qint64 elapsed = time.elapsed();
reader.exit();
reader.wait();
QVERIFY(reply->isFinished());
QCOMPARE(reply->error(), QNetworkReply::NoError);
QVERIFY(!QTestEventLoop::instance().timeout());
qDebug() << "tst_QNetworkReply::httpUploadPerformance" << elapsed << "msec, "
<< ((UploadSize/1024.0)/(elapsed/1000.0)) << " kB/sec";
}
void tst_qnetworkreply::performanceControlRate()
{
// this is a control comparison for the other two above
// it does the same thing, but instead bypasses the QNetworkAccess system
qDebug() << "The following are the maximum transfer rates that we can get in this system"
" (bypassing QNetworkAccess)";
TimedSender sender(5000);
QTcpSocket sink;
sink.connectToHost("127.0.0.1", sender.serverPort());
DataReader reader(&sink, false);
QElapsedTimer loopTime;
connect(&sink, SIGNAL(disconnected()), &QTestEventLoop::instance(), SLOT(exitLoop()));
loopTime.start();
QTestEventLoop::instance().enterLoop(40);
int elapsedTime = loopTime.elapsed();
sender.wait();
qint64 receivedBytes = reader.totalBytes;
qDebug() << "tst_QNetworkReply::performanceControlRate" << "receive rate:" << (receivedBytes * 1000 / elapsedTime / 1024) << "kB/s and"
<< elapsedTime << "ms";
}
void tst_qnetworkreply::httpDownloadPerformance_data()
{
QTest::addColumn<bool>("serverSendsContentLength");
QTest::addColumn<bool>("chunkedEncoding");
QTest::newRow("Server sends no Content-Length") << false << false;
QTest::newRow("Server sends Content-Length") << true << false;
QTest::newRow("Server uses chunked encoding") << false << true;
}
void tst_qnetworkreply::httpDownloadPerformance()
{
QFETCH(bool, serverSendsContentLength);
QFETCH(bool, chunkedEncoding);
constexpr qint64 UploadSize = 128 * MiB;
HttpDownloadPerformanceServer server(UploadSize, serverSendsContentLength, chunkedEncoding);
QNetworkRequest request(QUrl("http://127.0.0.1:" + QString::number(server.serverPort()) + "/?bare=1"));
QNetworkReplyPtr reply(manager.get(request));
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection);
HttpDownloadPerformanceClient client(reply.data());
QElapsedTimer time;
time.start();
QTestEventLoop::instance().enterLoop(40);
QCOMPARE(reply->error(), QNetworkReply::NoError);
QVERIFY(!QTestEventLoop::instance().timeout());
qint64 elapsed = time.elapsed();
qDebug() << "tst_QNetworkReply::httpDownloadPerformance" << elapsed << "msec, "
<< ((UploadSize/1024.0)/(elapsed/1000.0)) << " kB/sec";
};
enum HttpDownloadPerformanceDownloadBufferTestType {
JustDownloadBuffer,
DownloadBufferButUseRead,
NoDownloadBuffer
};
Q_DECLARE_METATYPE(HttpDownloadPerformanceDownloadBufferTestType)
class HttpDownloadPerformanceClientDownloadBuffer : QObject {
Q_OBJECT
private:
HttpDownloadPerformanceDownloadBufferTestType testType;
QNetworkReply *reply;
qint64 uploadSize;
QList<qint64> bytesAvailableList;
public:
HttpDownloadPerformanceClientDownloadBuffer (QNetworkReply *reply, HttpDownloadPerformanceDownloadBufferTestType testType, qint64 uploadSize)
: testType(testType), reply(reply), uploadSize(uploadSize)
{
connect(reply, SIGNAL(finished()), this, SLOT(finishedSlot()));
}
public slots:
void finishedSlot() {
if (testType == JustDownloadBuffer) {
// We have a download buffer and use it. This should be the fastest benchmark result.
QVariant downloadBufferAttribute = reply->attribute(QNetworkRequest::DownloadBufferAttribute);
QSharedPointer<char> data = downloadBufferAttribute.value<QSharedPointer<char> >();
} else if (testType == DownloadBufferButUseRead) {
// We had a download buffer but we benchmark here the "legacy" read() way to access it
char* replyData = (char*) malloc(uploadSize);
QVERIFY(reply->read(replyData, uploadSize) == uploadSize);
free(replyData);
} else if (testType == NoDownloadBuffer) {
// We did not have a download buffer but we still need to benchmark having the data, e.g. reading it all.
// This should be the slowest benchmark result.
char* replyData = (char*) malloc(uploadSize);
QVERIFY(reply->read(replyData, uploadSize) == uploadSize);
free(replyData);
}
QMetaObject::invokeMethod(&QTestEventLoop::instance(), "exitLoop", Qt::QueuedConnection);
}
};
void tst_qnetworkreply::httpDownloadPerformanceDownloadBuffer_data()
{
QTest::addColumn<HttpDownloadPerformanceDownloadBufferTestType>("testType");
QTest::newRow("use-download-buffer") << JustDownloadBuffer;
QTest::newRow("use-download-buffer-but-use-read") << DownloadBufferButUseRead;
QTest::newRow("do-not-use-download-buffer") << NoDownloadBuffer;
}
// Please note that the whole "zero copy" download buffer API is private right now. Do not use it.
void tst_qnetworkreply::httpDownloadPerformanceDownloadBuffer()
{
QFETCH(HttpDownloadPerformanceDownloadBufferTestType, testType);
// On my Linux Desktop the results are already visible with 128 kB, however we use this to have good results.
enum {UploadSize = 32*1024*1024}; // 32 MB
HttpDownloadPerformanceServer server(UploadSize, true, false);
QNetworkRequest request(QUrl("http://127.0.0.1:" + QString::number(server.serverPort()) + "/?bare=1"));
if (testType == JustDownloadBuffer || testType == DownloadBufferButUseRead)
request.setAttribute(QNetworkRequest::MaximumDownloadBufferSizeAttribute, 1024*1024*128); // 128 MB is max allowed
QNetworkAccessManager manager;
QNetworkReplyPtr reply(manager.get(request));
HttpDownloadPerformanceClientDownloadBuffer client(reply.data(), testType, UploadSize);
QBENCHMARK_ONCE {
QTestEventLoop::instance().enterLoop(40);
QCOMPARE(reply->error(), QNetworkReply::NoError);
QVERIFY(reply->isFinished());
QVERIFY(!QTestEventLoop::instance().timeout());
}
}
class HttpsRequestChainHelper : public QObject {
Q_OBJECT
public:
QList<QNetworkRequest> requestList;
QElapsedTimer timeOneRequest;
QList<qint64> timeList;
QElapsedTimer globalTime;
QNetworkAccessManager manager;
HttpsRequestChainHelper() {
}
public slots:
void doNextRequest() {
// all requests done
if (requestList.isEmpty()) {
QTestEventLoop::instance().exitLoop();
return;
}
if (qobject_cast<QNetworkReply*>(sender()) == 0) {
// first start after DNS lookup, start timer
globalTime.start();
}
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (reply) {
QVERIFY(reply->error() == QNetworkReply::NoError);
qDebug() << "time =" << timeOneRequest.elapsed() << "ms";
timeList.append(timeOneRequest.elapsed());
}
QNetworkRequest request = requestList.takeFirst();
timeOneRequest.restart();
reply = manager.get(request);
QObject::connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply, SLOT(ignoreSslErrors()));
QObject::connect(reply, SIGNAL(finished()), this, SLOT(doNextRequest()));
}
};
void tst_qnetworkreply::httpsRequestChain()
{
int count = 10;
QNetworkRequest request(QUrl("https://" + QtNetworkSettings::serverName() + "/fluke.gif"));
// Disable keep-alive so we have the full re-connecting of TCP.
request.setRawHeader("Connection", "close");
HttpsRequestChainHelper helper;
for (int i = 0; i < count; i++)
helper.requestList.append(request);
// Warm up DNS cache and then immediately start HTTP
QHostInfo::lookupHost(QtNetworkSettings::serverName(), &helper, SLOT(doNextRequest()));
// we can use QBENCHMARK_ONCE when we find out how to make it really run once.
// there is still a warmup-run :(
//QBENCHMARK_ONCE {
QTestEventLoop::instance().enterLoop(40);
QVERIFY(!QTestEventLoop::instance().timeout());
//}
qint64 elapsed = helper.globalTime.elapsed();
qint64 average = (elapsed / count);
std::sort(helper.timeList.begin(), helper.timeList.end());
qint64 median = helper.timeList.at(5);
qDebug() << "Total:" << elapsed << " Average:" << average << " Median:" << median;
}
void tst_qnetworkreply::runHttpsUploadRequest(const QByteArray &data, const QNetworkRequest &request)
{
QNetworkReply* reply = manager.post(request, data);
reply->ignoreSslErrors();
connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(15);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(reply->error(), QNetworkReply::NoError);
reply->deleteLater();
}
void tst_qnetworkreply::httpsUpload()
{
QByteArray data = QByteArray(2*1024*1024+1, '\177');
QNetworkRequest request(QUrl("https://" + QtNetworkSettings::serverName() + "/qtest/cgi-bin/md5sum.cgi"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream");
// for (int a = 0; a < 10; ++a)
// runHttpsUploadRequest(data, request); // to warmup all TCP connections
QBENCHMARK {
runHttpsUploadRequest(data, request);
}
}
void tst_qnetworkreply::preConnect_data()
{
preConnectEncrypted_data();
}
void tst_qnetworkreply::preConnect()
{
QString hostName = QLatin1String("www.google.com");
QNetworkAccessManager manager;
QNetworkRequest request(QUrl("http://" + hostName));
// make sure we have a full request including
// DNS lookup and TCP handshake
#ifdef QT_BUILD_INTERNAL
qt_qhostinfo_clear_cache();
#else
qWarning("no internal build, could not clear DNS cache. Results may not be representative.");
#endif
// first, benchmark a normal request
QPair<QNetworkReply *, qint64> normalResult = runGetRequest(&manager, request);
QNetworkReply *normalReply = normalResult.first;
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(normalReply->error() == QNetworkReply::NoError);
qint64 normalElapsed = normalResult.second;
// clear all caches again
#ifdef QT_BUILD_INTERNAL
qt_qhostinfo_clear_cache();
#else
qWarning("no internal build, could not clear DNS cache. Results may not be representative.");
#endif
manager.clearAccessCache();
// now try to make the connection beforehand
QFETCH(int, sleepTime);
manager.connectToHost(hostName);
QTestEventLoop::instance().enterLoopMSecs(sleepTime);
// now make another request and hopefully use the existing connection
QPair<QNetworkReply *, qint64> preConnectResult = runGetRequest(&manager, request);
QNetworkReply *preConnectReply = normalResult.first;
QVERIFY(!QTestEventLoop::instance().timeout());
QVERIFY(preConnectReply->error() == QNetworkReply::NoError);
qint64 preConnectElapsed = preConnectResult.second;
qDebug() << request.url().toString() << "full request:" << normalElapsed
<< "ms, pre-connect request:" << preConnectElapsed << "ms, difference:"
<< (normalElapsed - preConnectElapsed) << "ms";
}
QTEST_MAIN(tst_qnetworkreply)
#include "tst_qnetworkreply.moc"

View File

@ -0,0 +1,14 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_bench_qnetworkreply_from_cache Binary:
#####################################################################
qt_internal_add_benchmark(tst_bench_qnetworkreply_from_cache
SOURCES
tst_qnetworkreply_from_cache.cpp
LIBRARIES
Qt::Network
Qt::Test
)

View File

@ -0,0 +1,194 @@
// Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <QBuffer>
#include <QTestEventLoop>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkDiskCache>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QTcpServer>
#include <QtNetwork/QTcpSocket>
#define TEST_CASE_TIMEOUT 30
class NetworkDiskCache : public QNetworkDiskCache
{
public:
NetworkDiskCache(QObject *parent = nullptr)
: QNetworkDiskCache(parent)
{
}
QByteArray cachedData;
QNetworkCacheMetaData metaData(const QUrl &url) override
{
QNetworkCacheMetaData metaData;
if (!cachedData.isEmpty()) {
metaData.setUrl(url);
QDateTime now = QDateTime::currentDateTime();
metaData.setLastModified(now.addDays(-1));
metaData.setExpirationDate(now.addDays(1));
metaData.setSaveToDisk(true);
}
return metaData;
}
QIODevice *data(const QUrl &/*url*/) override
{
if (cachedData.isEmpty())
return 0;
QBuffer *buffer = new QBuffer;
buffer->setData(cachedData);
buffer->open(QIODevice::ReadOnly);
return buffer;
}
};
class HttpServer : public QTcpServer
{
Q_OBJECT
public:
HttpServer(const QByteArray &reply)
: m_reply(reply), m_writePos(), m_client()
{
listen(QHostAddress::AnyIPv4);
connect(this, SIGNAL(newConnection()), this, SLOT(accept()));
}
private Q_SLOTS:
void accept()
{
m_client = nextPendingConnection();
m_client->setParent(this);
connect(m_client, SIGNAL(readyRead()), this, SLOT(reply()));
}
void reply()
{
disconnect(m_client, SIGNAL(readyRead()));
m_client->readAll();
connect(m_client, SIGNAL(bytesWritten(qint64)), this, SLOT(write()));
write();
}
void write()
{
qint64 pos = m_client->write(m_reply.mid(m_writePos));
if (pos > 0)
m_writePos += pos;
if (m_writePos >= m_reply.size())
m_client->disconnect();
}
private:
QByteArray m_reply;
qint64 m_writePos;
QTcpSocket *m_client;
};
class tst_qnetworkreply_from_cache : public QObject
{
Q_OBJECT
public:
tst_qnetworkreply_from_cache();
void timeReadAll(const QString &headers, const QByteArray &data = QByteArray());
private Q_SLOTS:
void initTestCase();
void cleanup();
void readAll_data();
void readAll();
void readAllFromCache_data();
void readAllFromCache();
protected Q_SLOTS:
void replyReadAll() { m_replyData += m_reply->readAll(); }
private:
QTemporaryDir m_tempDir;
QNetworkAccessManager *m_networkAccessManager;
NetworkDiskCache *m_networkDiskCache;
QNetworkReply *m_reply;
QByteArray m_replyData;
};
tst_qnetworkreply_from_cache::tst_qnetworkreply_from_cache()
: m_tempDir(QDir::tempPath() + "/tst_qnetworkreply_from_cache.XXXXXX")
{
}
void tst_qnetworkreply_from_cache::timeReadAll(const QString &headers, const QByteArray &data)
{
QByteArray reply;
reply.append(headers.toUtf8());
reply.append(data);
m_replyData.reserve(data.size());
HttpServer server(reply);
QBENCHMARK_ONCE {
QNetworkRequest request(QUrl(QString("http://127.0.0.1:%1").arg(server.serverPort())));
m_reply = m_networkAccessManager->get(request);
connect(m_reply, SIGNAL(readyRead()), this, SLOT(replyReadAll()), Qt::QueuedConnection);
connect(m_reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()), Qt::QueuedConnection);
QTestEventLoop::instance().enterLoop(TEST_CASE_TIMEOUT);
QVERIFY(!QTestEventLoop::instance().timeout());
delete m_reply;
}
QCOMPARE(data.size(), m_replyData.size());
QCOMPARE(data, m_replyData);
}
void tst_qnetworkreply_from_cache::initTestCase()
{
m_networkAccessManager = new QNetworkAccessManager(this);
m_networkDiskCache = new NetworkDiskCache(m_networkAccessManager);
m_networkDiskCache->setCacheDirectory(m_tempDir.path());
m_networkAccessManager->setCache(m_networkDiskCache);
}
void tst_qnetworkreply_from_cache::cleanup()
{
m_replyData.clear();
}
void tst_qnetworkreply_from_cache::readAll_data()
{
QTest::addColumn<int>("dataSize");
QTest::newRow("1MB") << (int)1e6;
QTest::newRow("5MB") << (int)5e6;
QTest::newRow("10MB") << (int)10e6;
}
void tst_qnetworkreply_from_cache::readAll()
{
QFETCH(int, dataSize);
QString headers = QString("HTTP/1.0 200 OK\r\nContent-Length: %1\r\n\r\n").arg(dataSize);
QByteArray data(QByteArray(dataSize, (char)42));
m_networkDiskCache->cachedData.clear();
timeReadAll(headers, data);
}
void tst_qnetworkreply_from_cache::readAllFromCache_data()
{
readAll_data();
}
void tst_qnetworkreply_from_cache::readAllFromCache()
{
QFETCH(int, dataSize);
QByteArray headers("HTTP/1.0 304 Use Cache\r\n\r\n");
QByteArray data(QByteArray(dataSize, (char)42));
m_networkDiskCache->cachedData = data;
timeReadAll(headers, data);
}
QTEST_MAIN(tst_qnetworkreply_from_cache)
#include "tst_qnetworkreply_from_cache.moc"