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#define Q_TEST_QPIXMAPCACHE
30
31#include <QtTest/QtTest>
32
33
34#include <qpixmapcache.h>
35#include "private/qpixmapcache_p.h"
36
37class tst_QPixmapCache : public QObject
38{
39 Q_OBJECT
40
41public:
42 tst_QPixmapCache();
43 virtual ~tst_QPixmapCache();
44
45
46public slots:
47 void init();
48private slots:
49 void cacheLimit();
50 void setCacheLimit();
51 void find();
52 void insert();
53 void replace();
54 void remove();
55 void clear();
56 void pixmapKey();
57 void noLeak();
58 void strictCacheLimit();
59 void noCrashOnLargeInsert();
60};
61
62static QPixmapCache::KeyData* getPrivate(QPixmapCache::Key &key)
63{
64 return (*reinterpret_cast<QPixmapCache::KeyData**>(&key));
65}
66
67static QPixmapCache::KeyData** getPrivateRef(QPixmapCache::Key &key)
68{
69 return (reinterpret_cast<QPixmapCache::KeyData**>(&key));
70}
71
72static int originalCacheLimit;
73
74tst_QPixmapCache::tst_QPixmapCache()
75{
76 originalCacheLimit = QPixmapCache::cacheLimit();
77}
78
79tst_QPixmapCache::~tst_QPixmapCache()
80{
81}
82
83void tst_QPixmapCache::init()
84{
85 QPixmapCache::setCacheLimit(originalCacheLimit);
86 QPixmapCache::clear();
87}
88
89void tst_QPixmapCache::cacheLimit()
90{
91 // make sure the default is reasonable;
92 // it was between 2048 and 10240 last time I looked at it
93 QVERIFY(originalCacheLimit >= 1024 && originalCacheLimit <= 20480);
94
95 QPixmapCache::setCacheLimit(std::numeric_limits<int>::max());
96 QCOMPARE(QPixmapCache::cacheLimit(), std::numeric_limits<int>::max());
97
98 QPixmapCache::setCacheLimit(100);
99 QCOMPARE(QPixmapCache::cacheLimit(), 100);
100
101 QPixmapCache::setCacheLimit(-50);
102 QCOMPARE(QPixmapCache::cacheLimit(), -50);
103}
104
105void tst_QPixmapCache::setCacheLimit()
106{
107 QPixmap res;
108 QPixmap *p1 = new QPixmap(2, 3);
109 QPixmapCache::insert("P1", *p1);
110#if QT_DEPRECATED_SINCE(5, 13)
111 QVERIFY(QPixmapCache::find("P1") != 0);
112#endif
113 QVERIFY(QPixmapCache::find("P1", &res));
114 delete p1;
115
116 QPixmapCache::setCacheLimit(0);
117#if QT_DEPRECATED_SINCE(5, 13)
118 QVERIFY(!QPixmapCache::find("P1"));
119#endif
120 QVERIFY(!QPixmapCache::find("P1", &res));
121
122 p1 = new QPixmap(2, 3);
123 QPixmapCache::setCacheLimit(1000);
124 QPixmapCache::insert("P1", *p1);
125#if QT_DEPRECATED_SINCE(5, 13)
126 QVERIFY(QPixmapCache::find("P1") != 0);
127#endif
128 QVERIFY(QPixmapCache::find("P1", &res));
129
130 delete p1;
131
132 //The int part of the API
133 p1 = new QPixmap(2, 3);
134 QPixmapCache::Key key = QPixmapCache::insert(*p1);
135 QVERIFY(QPixmapCache::find(key, p1) != 0);
136 delete p1;
137
138 QPixmapCache::setCacheLimit(0);
139 QVERIFY(QPixmapCache::find(key, p1) == 0);
140
141 p1 = new QPixmap(2, 3);
142 QPixmapCache::setCacheLimit(1000);
143 QPixmapCache::replace(key, *p1);
144 QVERIFY(QPixmapCache::find(key, p1) == 0);
145
146 delete p1;
147
148 //Let check if keys are released when the pixmap cache is
149 //full or has been flushed.
150 QPixmapCache::clear();
151 p1 = new QPixmap(2, 3);
152 key = QPixmapCache::insert(*p1);
153 QVERIFY(QPixmapCache::find(key, p1) != 0);
154 p1->detach(); // dectach so that the cache thinks no-one is using it.
155 QPixmapCache::setCacheLimit(0);
156 QVERIFY(QPixmapCache::find(key, p1) == 0);
157 QPixmapCache::setCacheLimit(1000);
158 key = QPixmapCache::insert(*p1);
159 QVERIFY(key.isValid());
160 QCOMPARE(getPrivate(key)->key, 1);
161
162 delete p1;
163
164 //Let check if removing old entries doesn't let you get
165 // wrong pixmaps
166 QPixmapCache::clear();
167 QPixmap p2;
168 p1 = new QPixmap(2, 3);
169 key = QPixmapCache::insert(*p1);
170 QVERIFY(QPixmapCache::find(key, &p2) != 0);
171 //we flush the cache
172 p1->detach();
173 p2.detach();
174 QPixmapCache::setCacheLimit(0);
175 QPixmapCache::setCacheLimit(1000);
176 QPixmapCache::Key key2 = QPixmapCache::insert(*p1);
177 QCOMPARE(getPrivate(key2)->key, 1);
178 QVERIFY(QPixmapCache::find(key, &p2) == 0);
179 QVERIFY(QPixmapCache::find(key2, &p2) != 0);
180 QCOMPARE(p2, *p1);
181
182 delete p1;
183
184 //Here we simulate the flushing when the app is idle
185 QPixmapCache::clear();
186 QPixmapCache::setCacheLimit(originalCacheLimit);
187 p1 = new QPixmap(300, 300);
188 key = QPixmapCache::insert(*p1);
189 p1->detach();
190 QCOMPARE(getPrivate(key)->key, 1);
191 key2 = QPixmapCache::insert(*p1);
192 p1->detach();
193 key2 = QPixmapCache::insert(*p1);
194 p1->detach();
195 QPixmapCache::Key key3 = QPixmapCache::insert(*p1);
196 p1->detach();
197 QPixmapCache::flushDetachedPixmaps();
198 key2 = QPixmapCache::insert(*p1);
199 QCOMPARE(getPrivate(key2)->key, 1);
200 //This old key is not valid anymore after the flush
201 QVERIFY(!key.isValid());
202 QVERIFY(QPixmapCache::find(key, &p2) == 0);
203 delete p1;
204}
205
206void tst_QPixmapCache::find()
207{
208 QPixmap p1(10, 10);
209 p1.fill(Qt::red);
210 QVERIFY(QPixmapCache::insert("P1", p1));
211
212 QPixmap p2;
213#if QT_DEPRECATED_SINCE(5, 13)
214 QVERIFY(QPixmapCache::find("P1", p2));
215 QCOMPARE(p2.width(), 10);
216 QCOMPARE(p2.height(), 10);
217 QCOMPARE(p1, p2);
218
219 // obsolete
220 QPixmap *p3 = QPixmapCache::find("P1");
221 QVERIFY(p3);
222 QCOMPARE(p1, *p3);
223#endif
224
225 QVERIFY(QPixmapCache::find("P1", &p2));
226 QCOMPARE(p2.width(), 10);
227 QCOMPARE(p2.height(), 10);
228 QCOMPARE(p1, p2);
229
230 //The int part of the API
231 QPixmapCache::Key key = QPixmapCache::insert(p1);
232
233 QVERIFY(QPixmapCache::find(key, &p2));
234 QCOMPARE(p2.width(), 10);
235 QCOMPARE(p2.height(), 10);
236 QCOMPARE(p1, p2);
237
238 QPixmapCache::clear();
239 QPixmapCache::setCacheLimit(128);
240
241 QPixmap p4(10,10);
242 key = QPixmapCache::insert(p4);
243 p4.detach();
244
245 QPixmap p5(10,10);
246 QList<QPixmapCache::Key> keys;
247 for (int i = 0; i < 4000; ++i)
248 QPixmapCache::insert(p5);
249
250 //at that time the first key has been erase because no more place in the cache
251 QVERIFY(QPixmapCache::find(key, &p1) == 0);
252 QVERIFY(!key.isValid());
253}
254
255void tst_QPixmapCache::insert()
256{
257 QPixmap p1(10, 10);
258 p1.fill(Qt::red);
259
260 QPixmap p2(10, 10);
261 p2.fill(Qt::yellow);
262
263 // Calcuate estimated num of items what fits to cache
264 int estimatedNum = (1024 * QPixmapCache::cacheLimit())
265 / ((p1.width() * p1.height() * p1.depth()) / 8);
266
267 // Mare sure we will put enough items to reach the cache limit
268 const int numberOfKeys = estimatedNum + 1000;
269
270 // make sure it doesn't explode
271 for (int i = 0; i < numberOfKeys; ++i)
272 QPixmapCache::insert("0", p1);
273
274 // ditto
275 for (int j = 0; j < numberOfKeys; ++j) {
276 QPixmap p3(10, 10);
277 QPixmapCache::insert(QString::number(j), p3);
278 }
279
280 int num = 0;
281#if QT_DEPRECATED_SINCE(5, 13)
282 for (int k = 0; k < numberOfKeys; ++k) {
283 if (QPixmapCache::find(QString::number(k)))
284 ++num;
285 }
286
287 if (QPixmapCache::find("0"))
288 ++num;
289 num = 0;
290#endif
291 QPixmap res;
292 for (int k = 0; k < numberOfKeys; ++k) {
293 if (QPixmapCache::find(QString::number(k), &res))
294 ++num;
295 }
296
297 if (QPixmapCache::find("0", &res))
298 ++num;
299
300 QVERIFY(num <= estimatedNum);
301 QPixmap p3;
302 QPixmapCache::insert("null", p3);
303
304 QPixmap c1(10, 10);
305 c1.fill(Qt::yellow);
306 QPixmapCache::insert("custom", c1);
307 QVERIFY(!c1.isDetached());
308 QPixmap c2(10, 10);
309 c2.fill(Qt::red);
310 QPixmapCache::insert("custom", c2);
311 //We have deleted the old pixmap in the cache for the same key
312 QVERIFY(c1.isDetached());
313
314 //The int part of the API
315 // make sure it doesn't explode
316 QList<QPixmapCache::Key> keys;
317 for (int i = 0; i < numberOfKeys; ++i) {
318 QPixmap p3(10,10);
319 keys.append(QPixmapCache::insert(p3));
320 }
321
322 num = 0;
323 for (int k = 0; k < numberOfKeys; ++k) {
324 if (QPixmapCache::find(keys.at(k), &p2))
325 ++num;
326 }
327
328 estimatedNum = (1024 * QPixmapCache::cacheLimit())
329 / ((p1.width() * p1.height() * p1.depth()) / 8);
330 QVERIFY(num <= estimatedNum);
331}
332
333void tst_QPixmapCache::replace()
334{
335 //The int part of the API
336 QPixmap p1(10, 10);
337 p1.fill(Qt::red);
338
339 QPixmap p2(10, 10);
340 p2.fill(Qt::yellow);
341
342 QPixmapCache::Key key = QPixmapCache::insert(p1);
343 QVERIFY(key.isValid());
344
345 QPixmap p3;
346 QVERIFY(QPixmapCache::find(key, &p3) == 1);
347
348 QPixmapCache::replace(key, p2);
349
350 QVERIFY(QPixmapCache::find(key, &p3) == 1);
351 QVERIFY(key.isValid());
352 QCOMPARE(getPrivate(key)->key, 1);
353
354 QCOMPARE(p3.width(), 10);
355 QCOMPARE(p3.height(), 10);
356 QCOMPARE(p3, p2);
357
358 //Broken keys
359 QCOMPARE(QPixmapCache::replace(QPixmapCache::Key(), p2), false);
360}
361
362void tst_QPixmapCache::remove()
363{
364 QPixmap p1(10, 10);
365 p1.fill(Qt::red);
366
367 QPixmapCache::insert("red", p1);
368 p1.fill(Qt::yellow);
369
370 QPixmap p2;
371 QVERIFY(QPixmapCache::find("red", &p2));
372 QVERIFY(p1.toImage() != p2.toImage());
373 QVERIFY(p1.toImage() == p1.toImage()); // sanity check
374
375 QPixmapCache::remove("red");
376 QVERIFY(!QPixmapCache::find("red", &p2));
377 QPixmapCache::remove("red");
378 QVERIFY(!QPixmapCache::find("red", &p2));
379
380 QPixmapCache::remove("green");
381 QVERIFY(!QPixmapCache::find("green", &p2));
382
383 //The int part of the API
384 QPixmapCache::clear();
385 p1.fill(Qt::red);
386 QPixmapCache::Key key = QPixmapCache::insert(p1);
387 p1.fill(Qt::yellow);
388
389 QVERIFY(QPixmapCache::find(key, &p2));
390 QVERIFY(p1.toImage() != p2.toImage());
391 QVERIFY(p1.toImage() == p1.toImage()); // sanity check
392
393 QPixmapCache::remove(key);
394 QVERIFY(QPixmapCache::find(key, &p1) == 0);
395
396 //Broken key
397 QPixmapCache::remove(QPixmapCache::Key());
398 QVERIFY(QPixmapCache::find(QPixmapCache::Key(), &p1) == 0);
399
400 //Test if keys are release
401 QPixmapCache::clear();
402 key = QPixmapCache::insert(p1);
403 QCOMPARE(getPrivate(key)->key, 1);
404 QPixmapCache::remove(key);
405 key = QPixmapCache::insert(p1);
406 QCOMPARE(getPrivate(key)->key, 1);
407
408 //Test if pixmaps are correctly deleted
409 QPixmapCache::clear();
410 key = QPixmapCache::insert(p1);
411 QCOMPARE(getPrivate(key)->key, 1);
412 QVERIFY(QPixmapCache::find(key, &p1) != 0);
413 QPixmapCache::remove(key);
414 QCOMPARE(p1.isDetached(), true);
415
416 //We mix both part of the API
417 QPixmapCache::clear();
418 p1.fill(Qt::red);
419 QPixmapCache::insert("red", p1);
420 key = QPixmapCache::insert(p1);
421 QPixmapCache::remove(key);
422 QVERIFY(QPixmapCache::find(key, &p1) == 0);
423 QVERIFY(QPixmapCache::find("red", &p1) != 0);
424}
425
426void tst_QPixmapCache::clear()
427{
428 QPixmap p1(10, 10);
429 p1.fill(Qt::red);
430
431 // Calcuate estimated num of items what fits to cache
432 int estimatedNum = (1024 * QPixmapCache::cacheLimit())
433 / ((p1.width() * p1.height() * p1.depth()) / 8);
434
435 // Mare sure we will put enough items to reach the cache limit
436 const int numberOfKeys = estimatedNum + 1000;
437
438 for (int i = 0; i < numberOfKeys; ++i)
439 QVERIFY(!QPixmapCache::find("x" + QString::number(i), &p1));
440
441 for (int j = 0; j < numberOfKeys; ++j)
442 QPixmapCache::insert(QString::number(j), p1);
443
444 int num = 0;
445 for (int k = 0; k < numberOfKeys; ++k) {
446 if (QPixmapCache::find(QString::number(k), &p1))
447 ++num;
448 }
449 QVERIFY(num > 0);
450
451 QPixmapCache::clear();
452
453 for (int k = 0; k < numberOfKeys; ++k)
454 QVERIFY(!QPixmapCache::find(QString::number(k), &p1));
455
456 //The int part of the API
457 QPixmap p2(10, 10);
458 p2.fill(Qt::red);
459
460 QList<QPixmapCache::Key> keys;
461 for (int k = 0; k < numberOfKeys; ++k)
462 keys.append(QPixmapCache::insert(p2));
463
464 QPixmapCache::clear();
465
466 for (int k = 0; k < numberOfKeys; ++k) {
467 QVERIFY(QPixmapCache::find(keys.at(k), &p1) == 0);
468 QVERIFY(!keys[k].isValid());
469 }
470}
471
472void tst_QPixmapCache::pixmapKey()
473{
474 QPixmapCache::Key key;
475 //Default constructed keys have no d pointer unless
476 //we use them
477 QVERIFY(!getPrivate(key));
478 //Let's put a d pointer
479 QPixmapCache::KeyData** keyd = getPrivateRef(key);
480 *keyd = new QPixmapCache::KeyData;
481 QCOMPARE(getPrivate(key)->ref, 1);
482 QPixmapCache::Key key2;
483 //Let's put a d pointer
484 QPixmapCache::KeyData** key2d = getPrivateRef(key2);
485 *key2d = new QPixmapCache::KeyData;
486 QCOMPARE(getPrivate(key2)->ref, 1);
487 key = key2;
488 QCOMPARE(getPrivate(key2)->ref, 2);
489 QCOMPARE(getPrivate(key)->ref, 2);
490 QPixmapCache::Key key3;
491 //Let's put a d pointer
492 QPixmapCache::KeyData** key3d = getPrivateRef(key3);
493 *key3d = new QPixmapCache::KeyData;
494 QPixmapCache::Key key4 = key3;
495 QCOMPARE(getPrivate(key3)->ref, 2);
496 QCOMPARE(getPrivate(key4)->ref, 2);
497 key4 = key;
498 QCOMPARE(getPrivate(key4)->ref, 3);
499 QCOMPARE(getPrivate(key3)->ref, 1);
500 QPixmapCache::Key key5(key3);
501 QCOMPARE(getPrivate(key3)->ref, 2);
502 QCOMPARE(getPrivate(key5)->ref, 2);
503
504 //let test default constructed keys
505 QPixmapCache::Key key6;
506 QVERIFY(!getPrivate(key6));
507 QPixmapCache::Key key7;
508 QVERIFY(!getPrivate(key7));
509 key6 = key7;
510 QVERIFY(!getPrivate(key6));
511 QVERIFY(!getPrivate(key7));
512 QPixmapCache::Key key8(key7);
513 QVERIFY(!getPrivate(key8));
514}
515
516QT_BEGIN_NAMESPACE
517extern int q_QPixmapCache_keyHashSize();
518QT_END_NAMESPACE
519
520void tst_QPixmapCache::noLeak()
521{
522 QPixmapCache::Key key;
523
524 int oldSize = q_QPixmapCache_keyHashSize();
525 for (int i = 0; i < 100; ++i) {
526 QPixmap pm(128, 128);
527 pm.fill(Qt::transparent);
528 key = QPixmapCache::insert(pm);
529 QPixmapCache::remove(key);
530 }
531 int newSize = q_QPixmapCache_keyHashSize();
532
533 QCOMPARE(oldSize, newSize);
534}
535
536
537void tst_QPixmapCache::strictCacheLimit()
538{
539 const int limit = 1024; // 1024 KB
540
541 QPixmapCache::clear();
542 QPixmapCache::setCacheLimit(limit);
543
544 // insert 200 64x64 pixmaps
545 // 3200 KB for 32-bit depths
546 // 1600 KB for 16-bit depths
547 // not counting the duplicate entries
548 for (int i = 0; i < 200; ++i) {
549 QPixmap pixmap(64, 64);
550 pixmap.fill(Qt::transparent);
551
552 QString id = QString::number(i);
553 QPixmapCache::insert(id + "-a", pixmap);
554 QPixmapCache::insert(id + "-b", pixmap);
555 }
556
557 QVERIFY(QPixmapCache::totalUsed() <= limit);
558}
559
560void tst_QPixmapCache::noCrashOnLargeInsert()
561{
562 QPixmapCache::clear();
563 QPixmapCache::setCacheLimit(100);
564 QPixmap pixmap(500, 500);
565 pixmap.fill(Qt::transparent);
566 QPixmapCache::insert("test", pixmap);
567 QVERIFY(true); // no crash
568}
569
570QTEST_MAIN(tst_QPixmapCache)
571#include "tst_qpixmapcache.moc"
572