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 | |
42 | class tst_QNetworkDiskCache : public QObject |
43 | { |
44 | Q_OBJECT |
45 | |
46 | public: |
47 | tst_QNetworkDiskCache(); |
48 | |
49 | public slots: |
50 | void accessAfterRemoveReadyReadSlot(); |
51 | void setCookieHeaderMetaDataChangedSlot(); |
52 | |
53 | private 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 | |
84 | private: |
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! |
93 | class MiniHttpServer: public QTcpServer |
94 | { |
95 | Q_OBJECT |
96 | public: |
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 | |
110 | public 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. |
140 | class SubQNetworkDiskCache : public QNetworkDiskCache |
141 | { |
142 | public: |
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 ("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 | |
177 | private: |
178 | bool clearOnDestruction = true; |
179 | }; |
180 | |
181 | tst_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. |
188 | void 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. |
198 | void tst_QNetworkDiskCache::cleanupTestCase() |
199 | { |
200 | QDir workingDir("foo" ); |
201 | if (workingDir.exists()) |
202 | workingDir.removeRecursively(); |
203 | } |
204 | |
205 | void tst_QNetworkDiskCache::qnetworkdiskcache_data() |
206 | { |
207 | } |
208 | |
209 | void 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 | |
234 | void 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 |
248 | void 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 | |
287 | static 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() |
298 | void 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 | |
320 | Q_DECLARE_METATYPE(QNetworkCacheMetaData) |
321 | void 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 ; |
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) |
337 | void 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) |
353 | void tst_QNetworkDiskCache::metaData() |
354 | { |
355 | SubQNetworkDiskCache cache; |
356 | |
357 | QUrl url(EXAMPLE_URL); |
358 | QNetworkCacheMetaData metaData; |
359 | metaData.setUrl(url); |
360 | QNetworkCacheMetaData::RawHeaderList ; |
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) |
377 | void 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 | |
388 | void 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 | |
417 | void tst_QNetworkDiskCache::accessAfterRemoveReadyReadSlot() |
418 | { |
419 | diskCache->remove(url); // this used to cause a crash later on |
420 | } |
421 | |
422 | void tst_QNetworkDiskCache::() // 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 ; |
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 | |
450 | void tst_QNetworkDiskCache::() |
451 | { |
452 | QList<QNetworkCookie> actualCookieJar = manager->cookieJar()->cookiesForUrl(url); |
453 | QVERIFY(!actualCookieJar.empty()); |
454 | } |
455 | |
456 | void 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) |
465 | void 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) |
475 | void 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) |
489 | void 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() |
517 | void 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 | |
560 | void 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 | |
567 | void 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 | |
616 | void 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 | |
625 | void 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 ("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 | |
684 | class Runner : public QThread |
685 | { |
686 | |
687 | public: |
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 ; |
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 | |
787 | void 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 | |
817 | void 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 | |
840 | QTEST_MAIN(tst_QNetworkDiskCache) |
841 | #include "tst_qnetworkdiskcache.moc" |
842 | |
843 | |