Release pixmap cache data to avoid leaking memory
[profile/ivi/qtdeclarative.git] / tests / auto / declarative / qdeclarativepixmapcache / tst_qdeclarativepixmapcache.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the test suite of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 #include <qtest.h>
42 #include <QtTest/QtTest>
43 #include <private/qdeclarativepixmapcache_p.h>
44 #include <QtDeclarative/qdeclarativeengine.h>
45 #include <QtDeclarative/qdeclarativeimageprovider.h>
46 #include <QNetworkReply>
47 #include "../shared/util.h"
48 #include "testhttpserver.h"
49
50 #ifndef QT_NO_CONCURRENT
51 #include <qtconcurrentrun.h>
52 #include <qfuture.h>
53 #endif
54
55 inline QUrl TEST_FILE(const QString &filename)
56 {
57     return QUrl::fromLocalFile(TESTDATA(filename));
58 }
59
60 class tst_qdeclarativepixmapcache : public QObject
61 {
62     Q_OBJECT
63 public:
64     tst_qdeclarativepixmapcache() :
65         server(14452)
66     {
67         server.serveDirectory(TESTDATA("http"));
68     }
69
70 private slots:
71     void single();
72     void single_data();
73     void parallel();
74     void parallel_data();
75     void massive();
76     void cancelcrash();
77     void shrinkcache();
78 #ifndef QT_NO_CONCURRENT
79     void networkCrash();
80 #endif
81     void lockingCrash();
82     void dataLeak();
83 private:
84     QDeclarativeEngine engine;
85     TestHTTPServer server;
86 };
87
88 static int slotters=0;
89
90 class Slotter : public QObject
91 {
92     Q_OBJECT
93 public:
94     Slotter()
95     {
96         gotslot = false;
97         slotters++;
98     }
99     bool gotslot;
100
101 public slots:
102     void got()
103     {
104         gotslot = true;
105         --slotters;
106         if (slotters==0)
107             QTestEventLoop::instance().exitLoop();
108     }
109 };
110
111 #ifndef QT_NO_LOCALFILE_OPTIMIZED_QML
112 static const bool localfile_optimized = true;
113 #else
114 static const bool localfile_optimized = false;
115 #endif
116
117 void tst_qdeclarativepixmapcache::single_data()
118 {
119     // Note, since QDeclarativePixmapCache is shared, tests affect each other!
120     // so use different files fore all test functions.
121
122     QTest::addColumn<QUrl>("target");
123     QTest::addColumn<bool>("incache");
124     QTest::addColumn<bool>("exists");
125     QTest::addColumn<bool>("neterror");
126
127     // File URLs are optimized
128     QTest::newRow("local") << TEST_FILE("exists.png") << localfile_optimized << true << false;
129     QTest::newRow("local") << TEST_FILE("notexists.png") << localfile_optimized << false << false;
130     QTest::newRow("remote") << QUrl("http://127.0.0.1:14452/exists.png") << false << true << false;
131     QTest::newRow("remote") << QUrl("http://127.0.0.1:14452/notexists.png") << false << false << true;
132 }
133
134 void tst_qdeclarativepixmapcache::single()
135 {
136     QFETCH(QUrl, target);
137     QFETCH(bool, incache);
138     QFETCH(bool, exists);
139     QFETCH(bool, neterror);
140
141     QString expectedError;
142     if (neterror) {
143         expectedError = "Error downloading " + target.toString() + " - server replied: Not found";
144     } else if (!exists) {
145         expectedError = "Cannot open: " + target.toString();
146     }
147
148     QDeclarativePixmap pixmap;
149     QVERIFY(pixmap.width() <= 0); // Check Qt assumption
150
151     pixmap.load(&engine, target);
152
153     if (incache) {
154         QCOMPARE(pixmap.error(), expectedError);
155         if (exists) {
156             QVERIFY(pixmap.status() == QDeclarativePixmap::Ready);
157             QVERIFY(pixmap.width() > 0);
158         } else {
159             QVERIFY(pixmap.status() == QDeclarativePixmap::Error);
160             QVERIFY(pixmap.width() <= 0);
161         }
162     } else {
163         QVERIFY(pixmap.width() <= 0);
164
165         Slotter getter;
166         pixmap.connectFinished(&getter, SLOT(got()));
167         QTestEventLoop::instance().enterLoop(10);
168         QVERIFY(!QTestEventLoop::instance().timeout());
169         QVERIFY(getter.gotslot);
170         if (exists) {
171             QVERIFY(pixmap.status() == QDeclarativePixmap::Ready);
172             QVERIFY(pixmap.width() > 0);
173         } else {
174             QVERIFY(pixmap.status() == QDeclarativePixmap::Error);
175             QVERIFY(pixmap.width() <= 0);
176         }
177         QCOMPARE(pixmap.error(), expectedError);
178     }
179 }
180
181 void tst_qdeclarativepixmapcache::parallel_data()
182 {
183     // Note, since QDeclarativePixmapCache is shared, tests affect each other!
184     // so use different files fore all test functions.
185
186     QTest::addColumn<QUrl>("target1");
187     QTest::addColumn<QUrl>("target2");
188     QTest::addColumn<int>("incache");
189     QTest::addColumn<int>("cancel"); // which one to cancel
190
191     QTest::newRow("local")
192             << TEST_FILE("exists1.png")
193             << TEST_FILE("exists2.png")
194             << (localfile_optimized ? 2 : 0)
195             << -1;
196
197     QTest::newRow("remote")
198             << QUrl("http://127.0.0.1:14452/exists2.png")
199             << QUrl("http://127.0.0.1:14452/exists3.png")
200             << 0
201             << -1;
202
203     QTest::newRow("remoteagain")
204             << QUrl("http://127.0.0.1:14452/exists2.png")
205             << QUrl("http://127.0.0.1:14452/exists3.png")
206             << 2
207             << -1;
208
209     QTest::newRow("remotecopy")
210             << QUrl("http://127.0.0.1:14452/exists4.png")
211             << QUrl("http://127.0.0.1:14452/exists4.png")
212             << 0
213             << -1;
214
215     QTest::newRow("remotecopycancel")
216             << QUrl("http://127.0.0.1:14452/exists5.png")
217             << QUrl("http://127.0.0.1:14452/exists5.png")
218             << 0
219             << 0;
220 }
221
222 void tst_qdeclarativepixmapcache::parallel()
223 {
224     QFETCH(QUrl, target1);
225     QFETCH(QUrl, target2);
226     QFETCH(int, incache);
227     QFETCH(int, cancel);
228
229     QList<QUrl> targets;
230     targets << target1 << target2;
231
232     QList<QDeclarativePixmap *> pixmaps;
233     QList<bool> pending;
234     QList<Slotter*> getters;
235
236     for (int i=0; i<targets.count(); ++i) {
237         QUrl target = targets.at(i);
238         QDeclarativePixmap *pixmap = new QDeclarativePixmap;
239
240         pixmap->load(&engine, target);
241
242         QVERIFY(pixmap->status() != QDeclarativePixmap::Error);
243         pixmaps.append(pixmap);
244         if (pixmap->isReady()) {
245             QVERIFY(pixmap->width() >  0);
246             getters.append(0);
247             pending.append(false);
248         } else {
249             QVERIFY(pixmap->width() <= 0);
250             getters.append(new Slotter);
251             pixmap->connectFinished(getters[i], SLOT(got()));
252             pending.append(true);
253         }
254     }
255
256     QCOMPARE(incache+slotters, targets.count());
257
258     if (cancel >= 0) {
259         pixmaps.at(cancel)->clear(getters[cancel]);
260         slotters--;
261     }
262
263     if (slotters) {
264         QTestEventLoop::instance().enterLoop(10);
265         QVERIFY(!QTestEventLoop::instance().timeout());
266     }
267
268     for (int i=0; i<targets.count(); ++i) {
269         QDeclarativePixmap *pixmap = pixmaps[i];
270
271         if (i == cancel) {
272             QVERIFY(!getters[i]->gotslot);
273         } else {
274             if (pending[i]) 
275                 QVERIFY(getters[i]->gotslot);
276
277             QVERIFY(pixmap->isReady());
278             QVERIFY(pixmap->width() > 0);
279             delete getters[i];
280         }
281     }
282
283     qDeleteAll(pixmaps);
284 }
285
286 void tst_qdeclarativepixmapcache::massive()
287 {
288     QDeclarativeEngine engine;
289     QUrl url = TEST_FILE("massive.png");
290
291     // Confirm that massive images remain in the cache while they are
292     // in use by the application.
293     {
294     qint64 cachekey = 0;
295     QDeclarativePixmap p(&engine, url);
296     QVERIFY(p.isReady());
297     QVERIFY(p.pixmap().size() == QSize(10000, 1000));
298     cachekey = p.pixmap().cacheKey();
299
300     QDeclarativePixmap p2(&engine, url);
301     QVERIFY(p2.isReady());
302     QVERIFY(p2.pixmap().size() == QSize(10000, 1000));
303
304     QVERIFY(p2.pixmap().cacheKey() == cachekey);
305     }
306
307     // Confirm that massive images are removed from the cache when
308     // they become unused
309     {
310     qint64 cachekey = 0;
311     {
312         QDeclarativePixmap p(&engine, url);
313         QVERIFY(p.isReady());
314         QVERIFY(p.pixmap().size() == QSize(10000, 1000));
315         cachekey = p.pixmap().cacheKey();
316     }
317
318     QDeclarativePixmap p2(&engine, url);
319     QVERIFY(p2.isReady());
320     QVERIFY(p2.pixmap().size() == QSize(10000, 1000));
321
322     QVERIFY(p2.pixmap().cacheKey() != cachekey);
323     }
324 }
325
326 // QTBUG-12729
327 void tst_qdeclarativepixmapcache::cancelcrash()
328 {
329     QUrl url("http://127.0.0.1:14452/cancelcrash_notexist.png");
330     for (int ii = 0; ii < 1000; ++ii) {
331         QDeclarativePixmap pix(&engine, url);
332     }
333 }
334
335 class MyPixmapProvider : public QDeclarativeImageProvider
336 {
337 public:
338     MyPixmapProvider()
339     : QDeclarativeImageProvider(Pixmap) {}
340
341     virtual QPixmap requestPixmap(const QString &d, QSize *, const QSize &) {
342         Q_UNUSED(d)
343         QPixmap pix(800, 600);
344         pix.fill(Qt::red);
345         return pix;
346     }
347 };
348
349 // QTBUG-13345
350 void tst_qdeclarativepixmapcache::shrinkcache()
351 {
352     QDeclarativeEngine engine;
353     engine.addImageProvider(QLatin1String("mypixmaps"), new MyPixmapProvider);
354
355     for (int ii = 0; ii < 4000; ++ii) {
356         QUrl url("image://mypixmaps/" + QString::number(ii));
357         QDeclarativePixmap p(&engine, url);
358     }
359 }
360
361 #ifndef QT_NO_CONCURRENT
362
363 void createNetworkServer()
364 {
365    QEventLoop eventLoop;
366    TestHTTPServer server(14453);
367    server.serveDirectory(TESTDATA("http"));
368    QTimer::singleShot(100, &eventLoop, SLOT(quit()));
369    eventLoop.exec();
370 }
371
372 #ifndef QT_NO_CONCURRENT
373 // QT-3957
374 void tst_qdeclarativepixmapcache::networkCrash()
375 {
376     QFuture<void> future = QtConcurrent::run(createNetworkServer);
377     QDeclarativeEngine engine;
378     for (int ii = 0; ii < 100 ; ++ii) {
379         QDeclarativePixmap* pixmap = new QDeclarativePixmap;
380         pixmap->load(&engine,  QUrl(QString("http://127.0.0.1:14453/exists.png")));
381         QTest::qSleep(1);
382         pixmap->clear();
383         delete pixmap;
384     }
385     future.cancel();
386 }
387 #endif
388
389 #endif
390
391 // QTBUG-22125
392 void tst_qdeclarativepixmapcache::lockingCrash()
393 {
394     TestHTTPServer server(14453);
395     server.serveDirectory(TESTDATA("http"), TestHTTPServer::Delay);
396
397     {
398         QDeclarativePixmap* p = new QDeclarativePixmap;
399         {
400             QDeclarativeEngine e;
401             p->load(&e,  QUrl(QString("http://127.0.0.1:14453/exists6.png")));
402         }
403         p->clear();
404         QVERIFY(p->isNull());
405         delete p;
406     }
407 }
408
409 #include <QQuickView>
410 class DataLeakView : public QQuickView
411 {
412     Q_OBJECT
413
414 public:
415     explicit DataLeakView() : QQuickView()
416     {
417         setSource(TEST_FILE("dataLeak.qml"));
418     }
419
420     void showFor2Seconds()
421     {
422         showFullScreen();
423         QTimer::singleShot(2000, this, SIGNAL(ready()));
424     }
425
426 signals:
427     void ready();
428 };
429
430 // QTBUG-22742
431 Q_GLOBAL_STATIC(QDeclarativePixmap, dataLeakPixmap)
432 void tst_qdeclarativepixmapcache::dataLeak()
433 {
434     // Should not leak cached QDeclarativePixmapData.
435     // Unfortunately, since the QDeclarativePixmapStore
436     // is a global static, and it releases the cache
437     // entries on dtor (application exit), we must use
438     // valgrind to determine whether it leaks or not.
439     QDeclarativePixmap *p1 = new QDeclarativePixmap;
440     QDeclarativePixmap *p2 = new QDeclarativePixmap;
441     {
442         QScopedPointer<DataLeakView> test(new DataLeakView);
443         test->showFor2Seconds();
444         dataLeakPixmap()->load(test->engine(), TEST_FILE("exists.png"));
445         p1->load(test->engine(), TEST_FILE("exists.png"));
446         p2->load(test->engine(), TEST_FILE("exists2.png"));
447         QTest::qWait(2005); // 2 seconds + a few more millis.
448     }
449
450     // When the (global static) dataLeakPixmap is deleted, it
451     // shouldn't attempt to dereference a QDeclarativePixmapData
452     // which has been deleted by the QDeclarativePixmapStore
453     // destructor.
454 }
455
456 QTEST_MAIN(tst_qdeclarativepixmapcache)
457
458 #include "tst_qdeclarativepixmapcache.moc"