1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtTest/QtTest>
31#include <QtNetwork/QtNetwork>
32#include <qnetworkdiskcache.h>
33#include <qrandom.h>
34
35#include <algorithm>
36
37#define EXAMPLE_URL "http://user:pass@localhost:4/#foo"
38#define EXAMPLE_URL2 "http://user:pass@localhost:4/bar"
39//cached objects are organized into these many subdirs
40#define NUM_SUBDIRECTORIES 16
41
42class tst_QNetworkDiskCache : public QObject
43{
44 Q_OBJECT
45
46public:
47 tst_QNetworkDiskCache();
48
49public slots:
50 void accessAfterRemoveReadyReadSlot();
51 void setCookieHeaderMetaDataChangedSlot();
52
53private slots:
54 void initTestCase();
55 void cleanupTestCase();
56 void qnetworkdiskcache_data();
57 void qnetworkdiskcache();
58
59 void prepare();
60 void cacheSize();
61 void clear();
62 void data_data();
63 void data();
64 void metaData();
65 void remove();
66 void accessAfterRemove(); // QTBUG-17400
67 void setCookieHeader(); // QTBUG-41514
68 void setCacheDirectory_data();
69 void setCacheDirectory();
70 void updateMetaData();
71 void fileMetaData();
72 void expire();
73
74 void oldCacheVersionFile_data();
75 void oldCacheVersionFile();
76
77 void streamVersion_data();
78 void streamVersion();
79
80 void sync();
81
82 void crashWhenParentingCache();
83
84private:
85 QTemporaryDir tempDir;
86 QUrl url; // used by accessAfterRemove(), setCookieHeader()
87 QNetworkDiskCache *diskCache; // used by accessAfterRemove()
88 QNetworkAccessManager *manager; // used by setCookieHeader()
89};
90
91// FIXME same as in tst_qnetworkreply.cpp .. could be unified
92// Does not work for POST/PUT!
93class MiniHttpServer: public QTcpServer
94{
95 Q_OBJECT
96public:
97 QTcpSocket *client; // always the last one that was received
98 QByteArray dataToTransmit;
99 QByteArray receivedData;
100 bool doClose;
101 bool multiple;
102 int totalConnections;
103
104 MiniHttpServer(const QByteArray &data) : client(0), dataToTransmit(data), doClose(true), multiple(false), totalConnections(0)
105 {
106 listen();
107 connect(sender: this, SIGNAL(newConnection()), receiver: this, SLOT(doAccept()));
108 }
109
110public slots:
111 void doAccept()
112 {
113 client = nextPendingConnection();
114 client->setParent(this);
115 ++totalConnections;
116 connect(sender: client, SIGNAL(readyRead()), receiver: this, SLOT(readyReadSlot()));
117 }
118
119 void readyReadSlot()
120 {
121 receivedData += client->readAll();
122 int doubleEndlPos = receivedData.indexOf(c: "\r\n\r\n");
123
124 if (doubleEndlPos != -1) {
125 // multiple requests incoming. remove the bytes of the current one
126 if (multiple)
127 receivedData.remove(index: 0, len: doubleEndlPos+4);
128
129 client->write(data: dataToTransmit);
130 if (doClose) {
131 client->disconnectFromHost();
132 disconnect(sender: client, signal: 0, receiver: this, member: 0);
133 client = 0;
134 }
135 }
136 }
137};
138
139// Subclass that exposes the protected functions.
140class SubQNetworkDiskCache : public QNetworkDiskCache
141{
142public:
143 ~SubQNetworkDiskCache()
144 {
145 if (!cacheDirectory().isEmpty() && clearOnDestruction)
146 clear();
147 }
148
149 QNetworkCacheMetaData call_fileMetaData(QString const &fileName)
150 { return SubQNetworkDiskCache::fileMetaData(fileName); }
151
152 qint64 call_expire()
153 { return SubQNetworkDiskCache::expire(); }
154
155 void setupWithOne(const QString &path, const QUrl &url, const QNetworkCacheMetaData &metaData = QNetworkCacheMetaData())
156 {
157 setCacheDirectory(path);
158
159 QIODevice *d = 0;
160 if (metaData.isValid()) {
161 d = prepare(metaData);
162 } else {
163 QNetworkCacheMetaData m;
164 m.setUrl(url);
165 QNetworkCacheMetaData::RawHeader header("content-type", "text/html");
166 QNetworkCacheMetaData::RawHeaderList list;
167 list.append(t: header);
168 m.setRawHeaders(list);
169 d = prepare(metaData: m);
170 }
171 d->write(data: "Hello World!");
172 insert(device: d);
173 }
174
175 void setClearCacheOnDestruction(bool value) { clearOnDestruction = value; }
176
177private:
178 bool clearOnDestruction = true;
179};
180
181tst_QNetworkDiskCache::tst_QNetworkDiskCache()
182 : tempDir(QDir::tempPath() + "/tst_qnetworkdiskcache.XXXXXX")
183{
184}
185
186// This will be called before the first test function is executed.
187// It is only called once.
188void tst_QNetworkDiskCache::initTestCase()
189{
190 QVERIFY(tempDir.isValid());
191
192 SubQNetworkDiskCache cache;
193 cache.setCacheDirectory(tempDir.path());
194}
195
196// This will be called after the last test function is executed.
197// It is only called once.
198void tst_QNetworkDiskCache::cleanupTestCase()
199{
200 QDir workingDir("foo");
201 if (workingDir.exists())
202 workingDir.removeRecursively();
203}
204
205void tst_QNetworkDiskCache::qnetworkdiskcache_data()
206{
207}
208
209void tst_QNetworkDiskCache::qnetworkdiskcache()
210{
211 QUrl url(EXAMPLE_URL);
212 SubQNetworkDiskCache cache;
213 QCOMPARE(cache.cacheDirectory(), QString());
214 QCOMPARE(cache.cacheSize(), qint64(0));
215 cache.clear();
216 QCOMPARE(cache.metaData(QUrl()), QNetworkCacheMetaData());
217 QCOMPARE(cache.remove(QUrl()), false);
218 QCOMPARE(cache.remove(url), false);
219 cache.insert(device: (QIODevice*)0);
220 cache.setCacheDirectory(QString());
221 cache.updateMetaData(metaData: QNetworkCacheMetaData());
222 cache.prepare(metaData: QNetworkCacheMetaData());
223 QCOMPARE(cache.call_fileMetaData(QString()), QNetworkCacheMetaData());
224
225 // leave one hanging around...
226 QNetworkDiskCache badCache;
227 QNetworkCacheMetaData metaData;
228 metaData.setUrl(url);
229 badCache.prepare(metaData);
230 badCache.setCacheDirectory(tempDir.path());
231 badCache.prepare(metaData);
232}
233
234void tst_QNetworkDiskCache::prepare()
235{
236 SubQNetworkDiskCache cache;
237 cache.setCacheDirectory(tempDir.path());
238
239 QUrl url(EXAMPLE_URL);
240 QNetworkCacheMetaData metaData;
241 metaData.setUrl(url);
242
243 cache.prepare(metaData);
244 cache.remove(url);
245}
246
247// public qint64 cacheSize() const
248void tst_QNetworkDiskCache::cacheSize()
249{
250 qint64 cacheSize = 0;
251 {
252 SubQNetworkDiskCache cache;
253 cache.setCacheDirectory(tempDir.path());
254 QCOMPARE(cache.cacheSize(), qint64(0));
255
256 {
257 QUrl url(EXAMPLE_URL);
258 QNetworkCacheMetaData metaData;
259 metaData.setUrl(url);
260 QIODevice *d = cache.prepare(metaData);
261 cache.insert(device: d);
262 cacheSize = cache.cacheSize();
263 QVERIFY(cacheSize > qint64(0));
264 }
265 // Add a second item, some difference in behavior when the cache is not empty
266 {
267 QUrl url(EXAMPLE_URL2);
268 QNetworkCacheMetaData metaData;
269 metaData.setUrl(url);
270 QIODevice *d = cache.prepare(metaData);
271 cache.insert(device: d);
272 QVERIFY(cache.cacheSize() > cacheSize);
273 cacheSize = cache.cacheSize();
274 }
275
276 // Don't clear the cache on destruction so we can re-open the cache and test its size.
277 cache.setClearCacheOnDestruction(false);
278 }
279
280 SubQNetworkDiskCache cache;
281 cache.setCacheDirectory(tempDir.path());
282 QCOMPARE(cache.cacheSize(), cacheSize);
283 cache.clear();
284 QCOMPARE(cache.cacheSize(), qint64(0));
285}
286
287static QStringList countFiles(const QString dir)
288{
289 QStringList list;
290 QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot);
291 QDirIterator it(dir, filter, QDirIterator::Subdirectories);
292 while (it.hasNext())
293 list.append(t: it.next());
294 return list;
295}
296
297// public void clear()
298void tst_QNetworkDiskCache::clear()
299{
300 SubQNetworkDiskCache cache;
301 QUrl url(EXAMPLE_URL);
302 cache.setupWithOne(path: tempDir.path(), url);
303 QVERIFY(cache.cacheSize() > qint64(0));
304
305 QString cacheDirectory = cache.cacheDirectory();
306 QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 3);
307 cache.clear();
308 QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 2);
309
310 // don't delete files that it didn't create
311 QTemporaryFile file(cacheDirectory + "/XXXXXX");
312 if (file.open()) {
313 file.fileName(); // make sure it exists with a name
314 QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 3);
315 cache.clear();
316 QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 3);
317 }
318}
319
320Q_DECLARE_METATYPE(QNetworkCacheMetaData)
321void tst_QNetworkDiskCache::data_data()
322{
323 QTest::addColumn<QNetworkCacheMetaData>(name: "data");
324
325 QTest::newRow(dataTag: "null") << QNetworkCacheMetaData();
326
327 QUrl url(EXAMPLE_URL);
328 QNetworkCacheMetaData metaData;
329 metaData.setUrl(url);
330 QNetworkCacheMetaData::RawHeaderList headers;
331 headers.append(t: QNetworkCacheMetaData::RawHeader("type", "bin"));
332 metaData.setRawHeaders(headers);
333 QTest::newRow(dataTag: "non-null") << metaData;
334}
335
336// public QIODevice* data(QUrl const& url)
337void tst_QNetworkDiskCache::data()
338{
339 QFETCH(QNetworkCacheMetaData, data);
340 SubQNetworkDiskCache cache;
341 QUrl url(EXAMPLE_URL);
342 cache.setupWithOne(path: tempDir.path(), url, metaData: data);
343
344 for (int i = 0; i < 3; ++i) {
345 QIODevice *d = cache.data(url);
346 QVERIFY(d);
347 QCOMPARE(d->readAll(), QByteArray("Hello World!"));
348 delete d;
349 }
350}
351
352// public QNetworkCacheMetaData metaData(QUrl const& url)
353void tst_QNetworkDiskCache::metaData()
354{
355 SubQNetworkDiskCache cache;
356
357 QUrl url(EXAMPLE_URL);
358 QNetworkCacheMetaData metaData;
359 metaData.setUrl(url);
360 QNetworkCacheMetaData::RawHeaderList headers;
361 headers.append(t: QNetworkCacheMetaData::RawHeader("type", "bin"));
362 metaData.setRawHeaders(headers);
363 metaData.setLastModified(QDateTime::currentDateTime());
364 metaData.setExpirationDate(QDateTime::currentDateTime());
365 metaData.setSaveToDisk(true);
366
367 cache.setupWithOne(path: tempDir.path(), url, metaData);
368
369 for (int i = 0; i < 3; ++i) {
370 QNetworkCacheMetaData cacheMetaData = cache.metaData(url);
371 QVERIFY(cacheMetaData.isValid());
372 QCOMPARE(metaData, cacheMetaData);
373 }
374}
375
376// public bool remove(QUrl const& url)
377void tst_QNetworkDiskCache::remove()
378{
379 SubQNetworkDiskCache cache;
380 QUrl url(EXAMPLE_URL);
381 cache.setupWithOne(path: tempDir.path(), url);
382 QString cacheDirectory = cache.cacheDirectory();
383 QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 3);
384 cache.remove(url);
385 QCOMPARE(countFiles(cacheDirectory).count(), NUM_SUBDIRECTORIES + 2);
386}
387
388void tst_QNetworkDiskCache::accessAfterRemove() // QTBUG-17400
389{
390 QByteArray data("HTTP/1.1 200 OK\r\n"
391 "Content-Length: 1\r\n"
392 "\r\n"
393 "a");
394
395 MiniHttpServer server(data);
396
397 QNetworkAccessManager *manager = new QNetworkAccessManager();
398 SubQNetworkDiskCache subCache;
399 subCache.setCacheDirectory(QLatin1String("cacheDir"));
400 diskCache = &subCache;
401 manager->setCache(&subCache);
402
403 url = QUrl("http://127.0.0.1:" + QString::number(server.serverPort()));
404 QNetworkRequest request(url);
405
406 QNetworkReply *reply = manager->get(request);
407 connect(sender: reply, SIGNAL(readyRead()), receiver: this, SLOT(accessAfterRemoveReadyReadSlot()));
408 connect(sender: reply, SIGNAL(finished()), receiver: &QTestEventLoop::instance(), SLOT(exitLoop()));
409
410 QTestEventLoop::instance().enterLoop(secs: 5);
411 QVERIFY(!QTestEventLoop::instance().timeout());
412
413 reply->deleteLater();
414 manager->deleteLater();
415}
416
417void tst_QNetworkDiskCache::accessAfterRemoveReadyReadSlot()
418{
419 diskCache->remove(url); // this used to cause a crash later on
420}
421
422void tst_QNetworkDiskCache::setCookieHeader() // QTBUG-41514
423{
424 SubQNetworkDiskCache *cache = new SubQNetworkDiskCache();
425 url = QUrl("http://localhost:4/cookieTest.html"); // hopefully no one is running an HTTP server on port 4
426 QNetworkCacheMetaData metaData;
427 metaData.setUrl(url);
428
429 QNetworkCacheMetaData::RawHeaderList headers;
430 headers.append(t: QNetworkCacheMetaData::RawHeader("Set-Cookie", "aaa=bbb"));
431 metaData.setRawHeaders(headers);
432 metaData.setSaveToDisk(true);
433 cache->setupWithOne(path: tempDir.path(), url, metaData);
434
435 manager = new QNetworkAccessManager();
436 manager->setCache(cache);
437
438 QNetworkRequest request(url);
439 QNetworkReply *reply = manager->get(request);
440 connect(sender: reply, SIGNAL(metaDataChanged()), receiver: this, SLOT(setCookieHeaderMetaDataChangedSlot()));
441 connect(sender: reply, SIGNAL(finished()), receiver: &QTestEventLoop::instance(), SLOT(exitLoop()));
442
443 QTestEventLoop::instance().enterLoop(secs: 5);
444 QVERIFY(!QTestEventLoop::instance().timeout());
445
446 reply->deleteLater();
447 manager->deleteLater();
448}
449
450void tst_QNetworkDiskCache::setCookieHeaderMetaDataChangedSlot()
451{
452 QList<QNetworkCookie> actualCookieJar = manager->cookieJar()->cookiesForUrl(url);
453 QVERIFY(!actualCookieJar.empty());
454}
455
456void tst_QNetworkDiskCache::setCacheDirectory_data()
457{
458 QTest::addColumn<QString>(name: "cacheDir");
459 QTest::newRow(dataTag: "null") << QString();
460 QDir dir("foo");
461 QTest::newRow(dataTag: "foo") << dir.absolutePath() + QString("/");
462}
463
464// public void setCacheDirectory(QString const& cacheDir)
465void tst_QNetworkDiskCache::setCacheDirectory()
466{
467 QFETCH(QString, cacheDir);
468
469 SubQNetworkDiskCache cache;
470 cache.setCacheDirectory(cacheDir);
471 QCOMPARE(cache.cacheDirectory(), cacheDir);
472}
473
474// public void updateMetaData(QNetworkCacheMetaData const& metaData)
475void tst_QNetworkDiskCache::updateMetaData()
476{
477 QUrl url(EXAMPLE_URL);
478 SubQNetworkDiskCache cache;
479 cache.setupWithOne(path: tempDir.path(), url);
480
481 QNetworkCacheMetaData metaData = cache.metaData(url);
482 metaData.setLastModified(QDateTime::currentDateTime());
483 cache.updateMetaData(metaData);
484 QNetworkCacheMetaData newMetaData = cache.metaData(url);
485 QCOMPARE(newMetaData, metaData);
486}
487
488// protected QNetworkCacheMetaData fileMetaData(QString const& fileName)
489void tst_QNetworkDiskCache::fileMetaData()
490{
491 SubQNetworkDiskCache cache;
492 QUrl url(EXAMPLE_URL);
493 cache.setupWithOne(path: tempDir.path(), url);
494
495 url.setPassword(password: QString());
496 url.setFragment(fragment: QString());
497
498 QString cacheDirectory = cache.cacheDirectory();
499 QStringList list = countFiles(dir: cacheDirectory);
500 QCOMPARE(list.count(), NUM_SUBDIRECTORIES + 3);
501 foreach(QString fileName, list) {
502 QFileInfo info(fileName);
503 if (info.isFile()) {
504 QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName);
505 QCOMPARE(metaData.url(), url);
506 }
507 }
508
509 QTemporaryFile file(cacheDirectory + "/qt_temp.XXXXXX");
510 if (file.open()) {
511 QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName: file.fileName());
512 QVERIFY(!metaData.isValid());
513 }
514}
515
516// protected qint64 expire()
517void tst_QNetworkDiskCache::expire()
518{
519 SubQNetworkDiskCache cache;
520 cache.setCacheDirectory(tempDir.path());
521 QCOMPARE(cache.call_expire(), (qint64)0);
522 QUrl url(EXAMPLE_URL);
523 cache.setupWithOne(path: tempDir.path(), url);
524 QVERIFY(cache.call_expire() > (qint64)0);
525 qint64 limit = (1024 * 1024 / 4) * 5;
526 cache.setMaximumCacheSize(limit);
527
528 qint64 max = cache.maximumCacheSize();
529 QCOMPARE(max, limit);
530 for (int i = 0; i < 10; ++i) {
531 if (i % 3 == 0)
532 QTest::qWait(ms: 2000);
533 QNetworkCacheMetaData m;
534 m.setUrl(QUrl("http://localhost:4/" + QString::number(i)));
535 QIODevice *d = cache.prepare(metaData: m);
536 QString bigString;
537 bigString.fill(c: QLatin1Char('Z'), size: (1024 * 1024 / 4));
538 d->write(data: bigString.toLatin1().data());
539 cache.insert(device: d);
540 QVERIFY(cache.call_expire() < max);
541 }
542
543 QString cacheDirectory = cache.cacheDirectory();
544 QStringList list = countFiles(dir: cacheDirectory);
545 QStringList cacheList;
546 foreach(QString fileName, list) {
547 QFileInfo info(fileName);
548 if (info.isFile()) {
549 QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName);
550 cacheList.append(t: metaData.url().toString());
551 }
552 }
553 std::sort(first: cacheList.begin(), last: cacheList.end());
554 for (int i = 0; i < cacheList.count(); ++i) {
555 QString fileName = cacheList[i];
556 QCOMPARE(fileName, QLatin1String("http://localhost:4/") + QString::number(i + 6));
557 }
558}
559
560void tst_QNetworkDiskCache::oldCacheVersionFile_data()
561{
562 QTest::addColumn<int>(name: "pass");
563 QTest::newRow(dataTag: "0") << 0;
564 QTest::newRow(dataTag: "1") << 1;
565}
566
567void tst_QNetworkDiskCache::oldCacheVersionFile()
568{
569 QFETCH(int, pass);
570 SubQNetworkDiskCache cache;
571 QUrl url(EXAMPLE_URL);
572 cache.setupWithOne(path: tempDir.path(), url);
573
574 if (pass == 0) {
575 QString name;
576 {
577 QTemporaryFile file(cache.cacheDirectory() + "/XXXXXX.d");
578 file.setAutoRemove(false);
579 QVERIFY2(file.open(), qPrintable(file.errorString()));
580 QDataStream out(&file);
581 out << qint32(0xe8);
582 out << qint32(2);
583 name = file.fileName();
584 file.close();
585 }
586
587 QVERIFY(QFile::exists(name));
588 QNetworkCacheMetaData metaData = cache.call_fileMetaData(fileName: name);
589 QVERIFY(!metaData.isValid());
590 QVERIFY(!QFile::exists(name));
591 } else {
592 QStringList files = countFiles(dir: cache.cacheDirectory());
593 QCOMPARE(files.count(), NUM_SUBDIRECTORIES + 3);
594 // find the file
595 QString cacheFile;
596 foreach (QString file, files) {
597 QFileInfo info(file);
598 if (info.isFile())
599 cacheFile = file;
600 }
601 QVERIFY(QFile::exists(cacheFile));
602
603 QFile file(cacheFile);
604 QVERIFY(file.open(QFile::ReadWrite));
605 QDataStream out(&file);
606 out << qint32(0xe8);
607 out << qint32(2);
608 file.close();
609
610 QIODevice *device = cache.data(url);
611 QVERIFY(!device);
612 QVERIFY(!QFile::exists(cacheFile));
613 }
614}
615
616void tst_QNetworkDiskCache::streamVersion_data()
617{
618 QTest::addColumn<int>(name: "version");
619 QTest::newRow(dataTag: "Qt 5.1") << int(QDataStream::Qt_5_1);
620 QDataStream ds;
621 QTest::newRow(dataTag: "current") << ds.version();
622 QTest::newRow(dataTag: "higher than current") << ds.version() + 1;
623}
624
625void tst_QNetworkDiskCache::streamVersion()
626{
627 SubQNetworkDiskCache cache;
628 QUrl url(EXAMPLE_URL);
629 cache.setupWithOne(path: tempDir.path(), url);
630
631 QString cacheFile;
632 // find the file
633 QStringList files = countFiles(dir: cache.cacheDirectory());
634 foreach (const QString &file, files) {
635 QFileInfo info(file);
636 if (info.isFile()) {
637 cacheFile = file;
638 break;
639 }
640 }
641
642 QFile file(cacheFile);
643 QVERIFY(file.open(QFile::ReadWrite|QIODevice::Truncate));
644 QDataStream out(&file);
645 QFETCH(int, version);
646 if (version < out.version())
647 out.setVersion(version);
648 out << qint32(0xe8); // cache magic
649 // Following code works only for cache file version 8 and should be updated on version change
650 out << qint32(8);
651 out << qint32(version);
652
653 QNetworkCacheMetaData md;
654 md.setUrl(url);
655 QNetworkCacheMetaData::RawHeader header("content-type", "text/html");
656 QNetworkCacheMetaData::RawHeaderList list;
657 list.append(t: header);
658 md.setRawHeaders(list);
659 md.setLastModified(QDateTime::currentDateTimeUtc().toOffsetFromUtc(offsetSeconds: 3600));
660 out << md;
661
662 bool compressed = true;
663 out << compressed;
664
665 QByteArray data("Hello World!");
666 out << qCompress(data);
667
668 file.close();
669
670 QNetworkCacheMetaData cachedMetaData = cache.call_fileMetaData(fileName: cacheFile);
671 if (version > out.version()) {
672 QVERIFY(!cachedMetaData.isValid());
673 QVERIFY(!QFile::exists(cacheFile));
674 } else {
675 QVERIFY(cachedMetaData.isValid());
676 QVERIFY(QFile::exists(cacheFile));
677 QIODevice *dataDevice = cache.data(url);
678 QVERIFY(dataDevice != 0);
679 QByteArray cachedData = dataDevice->readAll();
680 QCOMPARE(cachedData, data);
681 }
682}
683
684class Runner : public QThread
685{
686
687public:
688 Runner(const QString& cachePath)
689 : QThread()
690 , other(0)
691 , cachePath(cachePath)
692 {}
693
694 void run()
695 {
696 QByteArray longString = "Hello World, this is some long string, well not really that long";
697 for (int j = 0; j < 10; ++j)
698 longString += longString;
699 QByteArray longString2 = "Help, I am stuck in an autotest!";
700 QUrl url(EXAMPLE_URL);
701
702 QNetworkCacheMetaData metaData;
703 metaData.setUrl(url);
704 QNetworkCacheMetaData::RawHeaderList headers;
705 headers.append(t: QNetworkCacheMetaData::RawHeader("type", "bin"));
706 metaData.setRawHeaders(headers);
707 metaData.setLastModified(dt);
708 metaData.setSaveToDisk(true);
709
710 QNetworkCacheMetaData metaData2 = metaData;
711 metaData2.setExpirationDate(dt);
712
713 QNetworkDiskCache cache;
714 cache.setCacheDirectory(cachePath);
715
716 int read = 0;
717
718 int i = 0;
719 for (; i < 5000; ++i) {
720 if (other && other->isFinished())
721 break;
722
723 if (write) {
724 QNetworkCacheMetaData m;
725 if (QRandomGenerator::global()->bounded(highest: 2) == 0)
726 m = metaData;
727 else
728 m = metaData2;
729
730 if (QRandomGenerator::global()->bounded(highest: 20) == 1) {
731 //qDebug() << "write update";
732 cache.updateMetaData(metaData: m);
733 continue;
734 }
735
736 QIODevice *device = cache.prepare(metaData: m);
737 if (QRandomGenerator::global()->bounded(highest: 20) == 1) {
738 //qDebug() << "write remove";
739 cache.remove(url);
740 continue;
741 }
742 QVERIFY(device);
743 if (QRandomGenerator::global()->bounded(highest: 2) == 0)
744 device->write(data: longString);
745 else
746 device->write(data: longString2);
747 //qDebug() << "write write" << device->size();
748 cache.insert(device);
749 continue;
750 }
751
752 QNetworkCacheMetaData gotMetaData = cache.metaData(url);
753 if (gotMetaData.isValid()) {
754 QVERIFY(gotMetaData == metaData || gotMetaData == metaData2);
755 QIODevice *d = cache.data(url);
756 if (d) {
757 QByteArray x = d->readAll();
758 if (x != longString && x != longString2) {
759 qDebug() << x.length() << QString(x);
760 gotMetaData = cache.metaData(url);
761 qDebug() << (gotMetaData.url().toString())
762 << gotMetaData.lastModified()
763 << gotMetaData.expirationDate()
764 << gotMetaData.saveToDisk();
765 }
766 if (gotMetaData.isValid())
767 QVERIFY(x == longString || x == longString2);
768 read++;
769 delete d;
770 }
771 }
772 if (QRandomGenerator::global()->bounded(highest: 5) == 1)
773 cache.remove(url);
774 if (QRandomGenerator::global()->bounded(highest: 5) == 1)
775 cache.clear();
776 sleep(0);
777 }
778 //qDebug() << "read!" << read << i;
779 }
780
781 QDateTime dt;
782 bool write;
783 Runner *other;
784 QString cachePath;
785};
786
787void tst_QNetworkDiskCache::crashWhenParentingCache()
788{
789 // the trick here is to not send the complete response
790 // but some data. So we get a readyRead() and it gets tried
791 // to be saved to the cache
792 QByteArray data("HTTP/1.0 200 OK\r\nCache-Control: max-age=300\r\nAge: 1\r\nContent-Length: 5\r\n\r\n123");
793 MiniHttpServer server(data);
794
795 QNetworkAccessManager *manager = new QNetworkAccessManager();
796 QNetworkDiskCache *diskCache = new QNetworkDiskCache(manager); // parent to qnam!
797 // we expect the temp dir to be cleaned at some point anyway
798
799 const QString diskCachePath = QDir::tempPath() + QLatin1String("/cacheDir_")
800 + QString::number(QCoreApplication::applicationPid());
801 diskCache->setCacheDirectory(diskCachePath);
802 manager->setCache(diskCache);
803
804 QUrl url("http://127.0.0.1:" + QString::number(server.serverPort()));
805 QNetworkRequest request(url);
806 // request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
807 QNetworkReply *reply = manager->get(request); // new reply is parented to qnam
808
809 // wait for readyRead of reply!
810 connect(sender: reply, SIGNAL(readyRead()), receiver: &QTestEventLoop::instance(), SLOT(exitLoop()));
811 QTestEventLoop::instance().enterLoop(secs: 5);
812 QVERIFY(!QTestEventLoop::instance().timeout());
813
814 delete manager; // crashed before..
815}
816
817void tst_QNetworkDiskCache::sync()
818{
819 // This tests would be a nice to have, but is currently not supported.
820 return;
821
822 QTime midnight(0, 0, 0);
823 Runner reader(tempDir.path());
824 reader.dt = QDateTime::currentDateTime();
825 reader.write = false;
826
827 Runner writer(tempDir.path());
828 writer.dt = reader.dt;
829 writer.write = true;
830
831 writer.other = &reader;
832 reader.other = &writer;
833
834 writer.start();
835 reader.start();
836 writer.wait();
837 reader.wait();
838}
839
840QTEST_MAIN(tst_QNetworkDiskCache)
841#include "tst_qnetworkdiskcache.moc"
842
843

source code of qtbase/tests/auto/network/access/qnetworkdiskcache/tst_qnetworkdiskcache.cpp