// 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 <QtNetwork/QtNetwork>
#include <QTest>
#include <QTestEventLoop>
#include <qnetworkdiskcache.h>
#include <qrandom.h>

#include <algorithm>

#define EXAMPLE_URL "http://user:pass@localhost:4/#foo"
#define EXAMPLE_URL2 "http://user:pass@localhost:4/bar"
//cached objects are organized into these many subdirs
#define NUM_SUBDIRECTORIES 15

class tst_QNetworkDiskCache : public QObject
{
    Q_OBJECT

public:
    tst_QNetworkDiskCache();

public slots:
    void accessAfterRemoveReadyReadSlot();
    void setCookieHeaderMetaDataChangedSlot();

private slots:
    void initTestCase();
    void cleanupTestCase();
    void qnetworkdiskcache_data();
    void qnetworkdiskcache();

    void prepare();
    void cacheSize();
    void clear();
    void data_data();
    void data();
    void metaData();
    void remove();
    void accessAfterRemove(); // QTBUG-17400
    void setCookieHeader(); // QTBUG-41514
    void setCacheDirectory_data();
    void setCacheDirectory();
    void updateMetaData();
    void fileMetaData();
    void expire();

    void oldCacheVersionFile_data();
    void oldCacheVersionFile();

    void streamVersion_data();
    void streamVersion();

    void sync();

    void crashWhenParentingCache();

private:
    QTemporaryDir tempDir;
    QUrl url; // used by accessAfterRemove(), setCookieHeader()
    QNetworkDiskCache *diskCache; // used by accessAfterRemove()
    QNetworkAccessManager *manager; // used by setCookieHeader()
};

// FIXME same as in tst_qnetworkreply.cpp .. could be unified
// Does not work for POST/PUT!
class MiniHttpServer: public QTcpServer
{
    Q_OBJECT
public:
    QTcpSocket *client; // always the last one that was received
    QByteArray dataToTransmit;
    QByteArray receivedData;
    bool doClose;
    bool multiple;
    int totalConnections;

    MiniHttpServer(const QByteArray &data) : client(0), dataToTransmit(data), doClose(true), multiple(false), totalConnections(0)
    {
        listen();
        connect(this, SIGNAL(newConnection()), this, SLOT(doAccept()));
    }

public slots:
    void doAccept()
    {
        client = nextPendingConnection();
        client->setParent(this);
        ++totalConnections;
        connect(client, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
    }

    void readyReadSlot()
    {
        receivedData += client->readAll();
        int doubleEndlPos = receivedData.indexOf("\r\n\r\n");

        if (doubleEndlPos != -1) {
            // multiple requests incoming. remove the bytes of the current one
            if (multiple)
                receivedData.remove(0, doubleEndlPos+4);

            client->write(dataToTransmit);
            if (doClose) {
                client->disconnectFromHost();
                disconnect(client, 0, this, 0);
                client = nullptr;
            }
        }
    }
};

// Subclass that exposes the protected functions.
class SubQNetworkDiskCache : public QNetworkDiskCache
{
public:
    ~SubQNetworkDiskCache()
    {
        if (!cacheDirectory().isEmpty() && clearOnDestruction)
            clear();
    }

    QNetworkCacheMetaData call_fileMetaData(QString const &fileName)
        { return SubQNetworkDiskCache::fileMetaData(fileName); }

    qint64 call_expire()
        { return SubQNetworkDiskCache::expire(); }

    void setupWithOne(const QString &path, const QUrl &url, const QNetworkCacheMetaData &metaData = QNetworkCacheMetaData())
    {
        setCacheDirectory(path);

        QIODevice *d = nullptr;
        if (metaData.isValid()) {
            d = prepare(metaData);
        } else {
            QNetworkCacheMetaData m;
            m.setUrl(url);
            QNetworkCacheMetaData::RawHeader header("content-type", "text/html");
            QNetworkCacheMetaData::RawHeaderList list;
            list.append(header);
            m.setRawHeaders(list);
            d = prepare(m);
        }
        d->write("Hello World!");
        insert(d);
    }

    void setClearCacheOnDestruction(bool value) { clearOnDestruction = value; }

private:
    bool clearOnDestruction = true;
};

tst_QNetworkDiskCache::tst_QNetworkDiskCache()
    : tempDir(QDir::tempPath() + "/tst_qnetworkdiskcache.XXXXXX")
{
}

// This will be called before the first test function is executed.
// It is only called once.
void tst_QNetworkDiskCache::initTestCase()
{
    QVERIFY(tempDir.isValid());

    SubQNetworkDiskCache cache;
    cache.setCacheDirectory(tempDir.path());
}

// This will be called after the last test function is executed.
// It is only called once.
void tst_QNetworkDiskCache::cleanupTestCase()
{
    QDir workingDir("foo");
    if (workingDir.exists())
        workingDir.removeRecursively();
}

void tst_QNetworkDiskCache::qnetworkdiskcache_data()
{
}

void tst_QNetworkDiskCache::qnetworkdiskcache()
{
    QUrl url(EXAMPLE_URL);
    SubQNetworkDiskCache cache;
    QCOMPARE(cache.cacheDirectory(), QString());
    QCOMPARE(cache.cacheSize(), qint64(0));
    cache.clear();
    QCOMPARE(cache.metaData(QUrl()), QNetworkCacheMetaData());
    QCOMPARE(cache.remove(QUrl()), false);
    QCOMPARE(cache.remove(url), false);
    cache.insert((QIODevice*)0);
    cache.setCacheDirectory(QString());
    cache.updateMetaData(QNetworkCacheMetaData());
    cache.prepare(QNetworkCacheMetaData());
    QCOMPARE(cache.call_fileMetaData(QString()), QNetworkCacheMetaData());

    // leave one hanging around...
    QNetworkDiskCache badCache;
    QNetworkCacheMetaData metaData;
    metaData.setUrl(url);
    badCache.prepare(metaData);
    badCache.setCacheDirectory(tempDir.path());
    badCache.prepare(metaData);
}

void tst_QNetworkDiskCache::prepare()
{
    SubQNetworkDiskCache cache;
    cache.setCacheDirectory(tempDir.path());

    QUrl url(EXAMPLE_URL);
    QNetworkCacheMetaData metaData;
    metaData.setUrl(url);

    cache.prepare(metaData);
    cache.remove(url);
}

// public qint64 cacheSize() const
void tst_QNetworkDiskCache::cacheSize()
{
    qint64 cacheSize = 0;
    {
        SubQNetworkDiskCache cache;
        cache.setCacheDirectory(tempDir.path());
        QCOMPARE(cache.cacheSize(), qint64(0));

        {
            QUrl url(EXAMPLE_URL);
            QNetworkCacheMetaData metaData;
            metaData.setUrl(url);
            QIODevice *d = cache.prepare(metaData);
            cache.insert(d);
            cacheSize = cache.cacheSize();
            QVERIFY(cacheSize > qint64(0));
        }
        // Add a second item, some difference in behavior when the cache is not empty
        {
            QUrl url(EXAMPLE_URL2);
            QNetworkCacheMetaData metaData;
            metaData.setUrl(url);
            QIODevice *d = cache.prepare(metaData);
            cache.insert(d);
            QVERIFY(cache.cacheSize() > cacheSize);
            cacheSize = cache.cacheSize();
        }

        // Don't clear the cache on destruction so we can re-open the cache and test its size.
        cache.setClearCacheOnDestruction(false);
    }

    SubQNetworkDiskCache cache;
    cache.setCacheDirectory(tempDir.path());
    QCOMPARE(cache.cacheSize(), cacheSize);
    cache.clear();
    QCOMPARE(cache.cacheSize(), qint64(0));
}

static QStringList countFiles(const QString dir)
{
    QStringList list;
    QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot);
    QDirIterator it(dir, filter, QDirIterator::Subdirectories);
    while (it.hasNext())
        list.append(it.next());
    return list;
}

// public void clear()
void tst_QNetworkDiskCache::clear()
{
    SubQNetworkDiskCache cache;
    QUrl url(EXAMPLE_URL);
    cache.setupWithOne(tempDir.path(), url);
    QVERIFY(cache.cacheSize() > qint64(0));

    QString cacheDirectory = cache.cacheDirectory();
    QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3);
    cache.clear();
    QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 2);

    // don't delete files that it didn't create
    QTemporaryFile file(cacheDirectory + "/XXXXXX");
    if (file.open()) {
        file.fileName();    // make sure it exists with a name
        QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3);
        cache.clear();
        QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3);
    }
}

Q_DECLARE_METATYPE(QNetworkCacheMetaData)
void tst_QNetworkDiskCache::data_data()
{
    QTest::addColumn<QNetworkCacheMetaData>("data");

    QTest::newRow("null") << QNetworkCacheMetaData();

    QUrl url(EXAMPLE_URL);
    QNetworkCacheMetaData metaData;
    metaData.setUrl(url);
    QNetworkCacheMetaData::RawHeaderList headers;
    headers.append(QNetworkCacheMetaData::RawHeader("type", "bin"));
    metaData.setRawHeaders(headers);
    QTest::newRow("non-null") << metaData;
}

// public QIODevice* data(QUrl const& url)
void tst_QNetworkDiskCache::data()
{
    QFETCH(QNetworkCacheMetaData, data);
    SubQNetworkDiskCache cache;
    QUrl url(EXAMPLE_URL);
    cache.setupWithOne(tempDir.path(), url, data);

    for (int i = 0; i < 3; ++i) {
        QIODevice *d = cache.data(url);
        QVERIFY(d);
        QCOMPARE(d->readAll(), QByteArray("Hello World!"));
        delete d;
    }
}

// public QNetworkCacheMetaData metaData(QUrl const& url)
void tst_QNetworkDiskCache::metaData()
{
    SubQNetworkDiskCache cache;

    QUrl url(EXAMPLE_URL);
    QNetworkCacheMetaData metaData;
    metaData.setUrl(url);
    QNetworkCacheMetaData::RawHeaderList headers;
    headers.append(QNetworkCacheMetaData::RawHeader("type", "bin"));
    metaData.setRawHeaders(headers);
    metaData.setLastModified(QDateTime::currentDateTime());
    metaData.setExpirationDate(QDateTime::currentDateTime());
    metaData.setSaveToDisk(true);

    cache.setupWithOne(tempDir.path(), url, metaData);

    for (int i = 0; i < 3; ++i) {
        QNetworkCacheMetaData cacheMetaData = cache.metaData(url);
        QVERIFY(cacheMetaData.isValid());
        QCOMPARE(metaData, cacheMetaData);
    }
}

// public bool remove(QUrl const& url)
void tst_QNetworkDiskCache::remove()
{
    SubQNetworkDiskCache cache;
    QUrl url(EXAMPLE_URL);
    cache.setupWithOne(tempDir.path(), url);
    QString cacheDirectory = cache.cacheDirectory();
    QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 3);
    cache.remove(url);
    QCOMPARE(countFiles(cacheDirectory).size(), NUM_SUBDIRECTORIES + 2);
}

void tst_QNetworkDiskCache::accessAfterRemove() // QTBUG-17400
{
    QByteArray data("HTTP/1.1 200 OK\r\n"
                    "Content-Length: 1\r\n"
                    "\r\n"
                    "a");

    MiniHttpServer server(data);

    QNetworkAccessManager *manager = new QNetworkAccessManager();
    SubQNetworkDiskCache subCache;
    subCache.setCacheDirectory(QLatin1String("cacheDir"));
    diskCache = &subCache;
    manager->setCache(&subCache);

    url = QUrl("http://127.0.0.1:" + QString::number(server.serverPort()));
    QNetworkRequest request(url);

    QNetworkReply *reply = manager->get(request);
    connect(reply, SIGNAL(readyRead()), this, SLOT(accessAfterRemoveReadyReadSlot()));
    connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));

    QTestEventLoop::instance().enterLoop(5);
    QVERIFY(!QTestEventLoop::instance().timeout());

    reply->deleteLater();
    manager->deleteLater();
}

void tst_QNetworkDiskCache::accessAfterRemoveReadyReadSlot()
{
    diskCache->remove(url); // this used to cause a crash later on
}

void tst_QNetworkDiskCache::setCookieHeader() // QTBUG-41514
{
    SubQNetworkDiskCache *cache = new SubQNetworkDiskCache();
    url = QUrl("http://localhost:4/cookieTest.html");   // hopefully no one is running an HTTP server on port 4
    QNetworkCacheMetaData metaData;
    metaData.setUrl(url);

    QNetworkCacheMetaData::RawHeaderList headers;
    headers.append(QNetworkCacheMetaData::RawHeader("Set-Cookie", "aaa=bbb"));
    metaData.setRawHeaders(headers);
    metaData.setSaveToDisk(true);
    QDateTime expirationDate = QDateTime::currentDateTime().addSecs(500);
    metaData.setExpirationDate(expirationDate);
    cache->setupWithOne(tempDir.path(), url, metaData);

    manager = new QNetworkAccessManager();
    manager->setCache(cache);

    QNetworkRequest request(url);
    request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
    QNetworkReply  *reply = manager->get(request);
    connect(reply, SIGNAL(metaDataChanged()), this, SLOT(setCookieHeaderMetaDataChangedSlot()));
    connect(reply, SIGNAL(finished()), &QTestEventLoop::instance(), SLOT(exitLoop()));

    QTestEventLoop::instance().enterLoop(5);
    QVERIFY(!QTestEventLoop::instance().timeout());

    QCOMPARE(reply->error(), QNetworkReply::NoError);

    reply->deleteLater();
    manager->deleteLater();
}

void tst_QNetworkDiskCache::setCookieHeaderMetaDataChangedSlot()
{
    QList<QNetworkCookie> actualCookieJar = manager->cookieJar()->cookiesForUrl(url);
    QVERIFY(!actualCookieJar.empty());
}

void tst_QNetworkDiskCache::setCacheDirectory_data()
{
    QTest::addColumn<QString>("cacheDir");
    QTest::newRow("null") << QString();
    QDir dir("foo");
    QTest::newRow("foo") << dir.absolutePath() + QString("/");
}

// public void setCacheDirectory(QString const& cacheDir)
void tst_QNetworkDiskCache::setCacheDirectory()
{
    QFETCH(QString, cacheDir);

    SubQNetworkDiskCache cache;
    cache.setCacheDirectory(cacheDir);
    QCOMPARE(cache.cacheDirectory(), cacheDir);
}

// public void updateMetaData(QNetworkCacheMetaData const& metaData)
void tst_QNetworkDiskCache::updateMetaData()
{
    QUrl url(EXAMPLE_URL);
    SubQNetworkDiskCache cache;
    cache.setupWithOne(tempDir.path(), url);

    QNetworkCacheMetaData metaData = cache.metaData(url);
    metaData.setLastModified(QDateTime::currentDateTime());
    cache.updateMetaData(metaData);
    QNetworkCacheMetaData newMetaData = cache.metaData(url);
    QCOMPARE(newMetaData, metaData);
}

// protected QNetworkCacheMetaData fileMetaData(QString const& fileName)
void tst_QNetworkDiskCache::fileMetaData()
{
    SubQNetworkDiskCache cache;
    QUrl url(EXAMPLE_URL);
    cache.setupWithOne(tempDir.path(), url);

    url.setPassword(QString());
    url.setFragment(QString());

    QString cacheDirectory = cache.cacheDirectory();
    QStringList list = countFiles(cacheDirectory);
    QCOMPARE(list.size(), NUM_SUBDIRECTORIES + 3);
    foreach(QString fileName, list) {
        QFileInfo info(fileName);
        if (info.isFile()) {
            QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName);
            QCOMPARE(metaData.url(), url);
        }
    }

    QTemporaryFile file(cacheDirectory + "/qt_temp.XXXXXX");
    if (file.open()) {
        QNetworkCacheMetaData metaData = cache.call_fileMetaData(file.fileName());
        QVERIFY(!metaData.isValid());
    }
}

// protected qint64 expire()
void tst_QNetworkDiskCache::expire()
{
    SubQNetworkDiskCache cache;
    cache.setCacheDirectory(tempDir.path());
    QCOMPARE(cache.call_expire(), (qint64)0);
    QUrl url(EXAMPLE_URL);
    cache.setupWithOne(tempDir.path(), url);
    QVERIFY(cache.call_expire() > (qint64)0);
    qint64 limit = (1024 * 1024 / 4) * 5;
    cache.setMaximumCacheSize(limit);

    qint64 max = cache.maximumCacheSize();
    QCOMPARE(max, limit);
    for (int i = 0; i < 10; ++i) {
        if (i % 3 == 0)
            QTest::qWait(2000);
        QNetworkCacheMetaData m;
        m.setUrl(QUrl("http://localhost:4/" + QString::number(i)));
        QIODevice *d = cache.prepare(m);
        QString bigString;
        bigString.fill(QLatin1Char('Z'), (1024 * 1024 / 4));
        d->write(bigString.toLatin1().data());
        cache.insert(d);
        QVERIFY(cache.call_expire() < max);
    }

    QString cacheDirectory = cache.cacheDirectory();
    QStringList list = countFiles(cacheDirectory);
    QStringList cacheList;
    foreach(QString fileName, list) {
        QFileInfo info(fileName);
        if (info.isFile()) {
            QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName);
            cacheList.append(metaData.url().toString());
        }
    }
    std::sort(cacheList.begin(), cacheList.end());
    for (int i = 0; i < cacheList.size(); ++i) {
        QString fileName = cacheList[i];
        QCOMPARE(fileName, QLatin1String("http://localhost:4/") + QString::number(i + 6));
    }
}

void tst_QNetworkDiskCache::oldCacheVersionFile_data()
{
    QTest::addColumn<int>("pass");
    QTest::newRow("0") << 0;
    QTest::newRow("1") << 1;
}

void tst_QNetworkDiskCache::oldCacheVersionFile()
{
    QFETCH(int, pass);
    SubQNetworkDiskCache cache;
    QUrl url(EXAMPLE_URL);
    cache.setupWithOne(tempDir.path(), url);

    if (pass == 0) {
        QString name;
        {
        QTemporaryFile file(cache.cacheDirectory() + "/XXXXXX.d");
        file.setAutoRemove(false);
        QVERIFY2(file.open(), qPrintable(file.errorString()));
        QDataStream out(&file);
        out << qint32(0xe8);
        out << qint32(2);
        name = file.fileName();
        file.close();
        }

        QVERIFY(QFile::exists(name));
        QNetworkCacheMetaData metaData = cache.call_fileMetaData(name);
        QVERIFY(!metaData.isValid());
        QVERIFY(!QFile::exists(name));
    } else {
        QStringList files = countFiles(cache.cacheDirectory());
        QCOMPARE(files.size(), NUM_SUBDIRECTORIES + 3);
        // find the file
        QString cacheFile;
        foreach (QString file, files) {
            QFileInfo info(file);
            if (info.isFile())
                cacheFile = file;
        }
        QVERIFY(QFile::exists(cacheFile));

        QFile file(cacheFile);
        QVERIFY(file.open(QFile::ReadWrite));
        QDataStream out(&file);
        out << qint32(0xe8);
        out << qint32(2);
        file.close();

        QIODevice *device = cache.data(url);
        QVERIFY(!device);
        QVERIFY(!QFile::exists(cacheFile));
    }
}

void tst_QNetworkDiskCache::streamVersion_data()
{
    QTest::addColumn<int>("version");
    QTest::newRow("Qt 5.1") << int(QDataStream::Qt_5_1);
    QDataStream ds;
    QTest::newRow("current") << ds.version();
    QTest::newRow("higher than current") << ds.version() + 1;
}

void tst_QNetworkDiskCache::streamVersion()
{
    SubQNetworkDiskCache cache;
    QUrl url(EXAMPLE_URL);
    cache.setupWithOne(tempDir.path(), url);

    QString cacheFile;
    // find the file
    QStringList files = countFiles(cache.cacheDirectory());
    foreach (const QString &file, files) {
        QFileInfo info(file);
        if (info.isFile()) {
            cacheFile = file;
            break;
        }
    }

    QFile file(cacheFile);
    QVERIFY(file.open(QFile::ReadWrite|QIODevice::Truncate));
    QDataStream out(&file);
    QFETCH(int, version);
    if (version < out.version())
        out.setVersion(version);
    out << qint32(0xe8);    // cache magic
    // Following code works only for cache file version 8 and should be updated on version change
    out << qint32(8);
    out << qint32(version);

    QNetworkCacheMetaData md;
    md.setUrl(url);
    QNetworkCacheMetaData::RawHeader header("content-type", "text/html");
    QNetworkCacheMetaData::RawHeaderList list;
    list.append(header);
    md.setRawHeaders(list);
    md.setLastModified(QDateTime::currentDateTimeUtc().toOffsetFromUtc(3600));
    out << md;

    bool compressed = true;
    out << compressed;

    QByteArray data("Hello World!");
    out << qCompress(data);

    file.close();

    QNetworkCacheMetaData cachedMetaData = cache.call_fileMetaData(cacheFile);
    if (version > out.version()) {
        QVERIFY(!cachedMetaData.isValid());
        QVERIFY(!QFile::exists(cacheFile));
    } else {
        QVERIFY(cachedMetaData.isValid());
        QVERIFY(QFile::exists(cacheFile));
        QIODevice *dataDevice = cache.data(url);
        QVERIFY(dataDevice != 0);
        QByteArray cachedData = dataDevice->readAll();
        delete dataDevice;
        QCOMPARE(cachedData, data);
    }
}

class Runner : public QThread
{

public:
    Runner(const QString& cachePath)
        : QThread()
        , other(0)
        , cachePath(cachePath)
    {}

    void run() override
    {
        QByteArray longString = "Hello World, this is some long string, well not really that long";
        for (int j = 0; j < 10; ++j)
            longString += longString;
        QByteArray longString2 = "Help, I am stuck in an autotest!";
        QUrl url(EXAMPLE_URL);

        QNetworkCacheMetaData metaData;
        metaData.setUrl(url);
        QNetworkCacheMetaData::RawHeaderList headers;
        headers.append(QNetworkCacheMetaData::RawHeader("type", "bin"));
        metaData.setRawHeaders(headers);
        metaData.setLastModified(dt);
        metaData.setSaveToDisk(true);

        QNetworkCacheMetaData metaData2 = metaData;
        metaData2.setExpirationDate(dt);

        QNetworkDiskCache cache;
        cache.setCacheDirectory(cachePath);

        int i = 0;
        for (; i < 5000; ++i) {
            if (other && other->isFinished())
                break;

            if (write) {
                QNetworkCacheMetaData m;
                if (QRandomGenerator::global()->bounded(2) == 0)
                    m = metaData;
                else
                    m = metaData2;

                if (QRandomGenerator::global()->bounded(20) == 1) {
                    //qDebug() << "write update";
                    cache.updateMetaData(m);
                    continue;
                }

                QIODevice *device = cache.prepare(m);
                if (QRandomGenerator::global()->bounded(20) == 1) {
                    //qDebug() << "write remove";
                    cache.remove(url);
                    continue;
                }
                QVERIFY(device);
                if (QRandomGenerator::global()->bounded(2) == 0)
                    device->write(longString);
                else
                    device->write(longString2);
                //qDebug() << "write write" << device->size();
                cache.insert(device);
                continue;
            }

            QNetworkCacheMetaData gotMetaData = cache.metaData(url);
            if (gotMetaData.isValid()) {
                QVERIFY(gotMetaData == metaData || gotMetaData == metaData2);
                QIODevice *d = cache.data(url);
                if (d) {
                    QByteArray x = d->readAll();
                    if (x != longString && x != longString2) {
                        qDebug() << x.size() << QString(x);
                        gotMetaData = cache.metaData(url);
                        qDebug() << (gotMetaData.url().toString())
                         << gotMetaData.lastModified()
                         << gotMetaData.expirationDate()
                         << gotMetaData.saveToDisk();
                    }
                    if (gotMetaData.isValid())
                        QVERIFY(x == longString || x == longString2);
                    delete d;
                }
            }
            if (QRandomGenerator::global()->bounded(5) == 1)
                cache.remove(url);
            if (QRandomGenerator::global()->bounded(5) == 1)
                cache.clear();
            sleep(0);
        }
    }

    QDateTime dt;
    bool write;
    Runner *other;
    QString cachePath;
};

void tst_QNetworkDiskCache::crashWhenParentingCache()
{
    // the trick here is to not send the complete response
    // but some data. So we get a readyRead() and it gets tried
    // to be saved to the cache
    QByteArray data("HTTP/1.0 200 OK\r\nCache-Control: max-age=300\r\nAge: 1\r\nContent-Length: 5\r\n\r\n123");
    MiniHttpServer server(data);

    QNetworkAccessManager *manager = new QNetworkAccessManager();
    QNetworkDiskCache *diskCache = new QNetworkDiskCache(manager); // parent to qnam!
    // we expect the temp dir to be cleaned at some point anyway

    const QString diskCachePath = QDir::tempPath() + QLatin1String("/cacheDir_")
        + QString::number(QCoreApplication::applicationPid());
    diskCache->setCacheDirectory(diskCachePath);
    manager->setCache(diskCache);

    QUrl url("http://127.0.0.1:" + QString::number(server.serverPort()));
    QNetworkRequest request(url);
   // request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
    QNetworkReply *reply = manager->get(request); // new reply is parented to qnam

    // wait for readyRead of reply!
    connect(reply, SIGNAL(readyRead()), &QTestEventLoop::instance(), SLOT(exitLoop()));
    QTestEventLoop::instance().enterLoop(5);
    QVERIFY(!QTestEventLoop::instance().timeout());

    delete manager; // crashed before..
}

void tst_QNetworkDiskCache::sync()
{
    // This tests would be a nice to have, but is currently not supported.
    return;

    QTime midnight(0, 0, 0);
    Runner reader(tempDir.path());
    reader.dt = QDateTime::currentDateTime();
    reader.write = false;

    Runner writer(tempDir.path());
    writer.dt = reader.dt;
    writer.write = true;

    writer.other = &reader;
    reader.other = &writer;

    writer.start();
    reader.start();
    writer.wait();
    reader.wait();
}

QTEST_MAIN(tst_QNetworkDiskCache)
#include "tst_qnetworkdiskcache.moc"

