mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-07-03 15:55:27 +08:00
qt 6.5.1 original
This commit is contained in:
10
tests/benchmarks/network/access/CMakeLists.txt
Normal file
10
tests/benchmarks/network/access/CMakeLists.txt
Normal 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()
|
BIN
tests/benchmarks/network/access/qdecompresshelper/50mb.txt.br
Normal file
BIN
tests/benchmarks/network/access/qdecompresshelper/50mb.txt.br
Normal file
Binary file not shown.
BIN
tests/benchmarks/network/access/qdecompresshelper/50mb.txt.gz
Normal file
BIN
tests/benchmarks/network/access/qdecompresshelper/50mb.txt.gz
Normal file
Binary file not shown.
BIN
tests/benchmarks/network/access/qdecompresshelper/50mb.txt.zst
Normal file
BIN
tests/benchmarks/network/access/qdecompresshelper/50mb.txt.zst
Normal file
Binary file not shown.
@ -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
|
||||
)
|
71
tests/benchmarks/network/access/qdecompresshelper/main.cpp
Normal file
71
tests/benchmarks/network/access/qdecompresshelper/main.cpp
Normal 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"
|
@ -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
|
||||
)
|
@ -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"
|
@ -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
|
||||
)
|
@ -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"
|
16
tests/benchmarks/network/access/qnetworkreply/CMakeLists.txt
Normal file
16
tests/benchmarks/network/access/qnetworkreply/CMakeLists.txt
Normal 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
|
||||
)
|
@ -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"
|
@ -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
|
||||
)
|
@ -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"
|
Reference in New Issue
Block a user