/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include "../../shared/util.h" #include "testhttpserver.h" #include #if QT_CONFIG(concurrent) #include #include #endif #define PIXMAP_DATA_LEAK_TEST 0 class tst_qquickpixmapcache : public QQmlDataTest { Q_OBJECT public: tst_qquickpixmapcache() {} private slots: void initTestCase(); void single(); void single_data(); void parallel(); void parallel_data(); void massive(); void cancelcrash(); void shrinkcache(); #if QT_CONFIG(concurrent) void networkCrash(); #endif void lockingCrash(); void uncached(); void asynchronousNoCache(); #if PIXMAP_DATA_LEAK_TEST void dataLeak(); #endif private: QQmlEngine engine; TestHTTPServer server; }; static int slotters=0; class Slotter : public QObject { Q_OBJECT public: Slotter() { gotslot = false; slotters++; } bool gotslot; public slots: void got() { gotslot = true; --slotters; if (slotters==0) QTestEventLoop::instance().exitLoop(); } }; #ifndef QT_NO_LOCALFILE_OPTIMIZED_QML static const bool localfile_optimized = true; #else static const bool localfile_optimized = false; #endif void tst_qquickpixmapcache::initTestCase() { QQmlDataTest::initTestCase(); QVERIFY2(server.listen(), qPrintable(server.errorString())); #if QT_CONFIG(bearermanagement) // This avoids a race condition/deadlock bug in network config // manager when it is accessed by the HTTP server thread before // anything else. Bug report can be found at: // QTBUG-26355 QNetworkConfigurationManager cm; cm.updateConfigurations(); #endif server.serveDirectory(testFile("http")); } void tst_qquickpixmapcache::single_data() { // Note, since QQuickPixmapCache is shared, tests affect each other! // so use different files fore all test functions. QTest::addColumn("target"); QTest::addColumn("incache"); QTest::addColumn("exists"); QTest::addColumn("neterror"); // File URLs are optimized QTest::newRow("local") << testFileUrl("exists.png") << localfile_optimized << true << false; QTest::newRow("local") << testFileUrl("notexists.png") << localfile_optimized << false << false; QTest::newRow("remote") << server.url("/exists.png") << false << true << false; QTest::newRow("remote") << server.url("/notexists.png") << false << false << true; } void tst_qquickpixmapcache::single() { QFETCH(QUrl, target); QFETCH(bool, incache); QFETCH(bool, exists); QFETCH(bool, neterror); QString expectedError; if (neterror) { expectedError = "Error transferring " + target.toString() + " - server replied: Not found"; } else if (!exists) { expectedError = "Cannot open: " + target.toString(); } QQuickPixmap pixmap; QVERIFY(pixmap.width() <= 0); // Check Qt assumption pixmap.load(&engine, target); if (incache) { QCOMPARE(pixmap.error(), expectedError); if (exists) { QCOMPARE(pixmap.status(), QQuickPixmap::Ready); QVERIFY(pixmap.width() > 0); } else { QCOMPARE(pixmap.status(), QQuickPixmap::Error); QVERIFY(pixmap.width() <= 0); } } else { QVERIFY(pixmap.width() <= 0); Slotter getter; pixmap.connectFinished(&getter, SLOT(got())); QTestEventLoop::instance().enterLoop(10); QVERIFY(!QTestEventLoop::instance().timeout()); QVERIFY(getter.gotslot); if (exists) { QCOMPARE(pixmap.status(), QQuickPixmap::Ready); QVERIFY(pixmap.width() > 0); } else { QCOMPARE(pixmap.status(), QQuickPixmap::Error); QVERIFY(pixmap.width() <= 0); } QCOMPARE(pixmap.error(), expectedError); } } void tst_qquickpixmapcache::parallel_data() { // Note, since QQuickPixmapCache is shared, tests affect each other! // so use different files fore all test functions. QTest::addColumn("target1"); QTest::addColumn("target2"); QTest::addColumn("incache"); QTest::addColumn("cancel"); // which one to cancel QTest::newRow("local") << testFileUrl("exists1.png") << testFileUrl("exists2.png") << (localfile_optimized ? 2 : 0) << -1; QTest::newRow("remote") << server.url("/exists2.png") << server.url("/exists3.png") << 0 << -1; QTest::newRow("remoteagain") << server.url("/exists2.png") << server.url("/exists3.png") << 2 << -1; QTest::newRow("remotecopy") << server.url("/exists4.png") << server.url("/exists4.png") << 0 << -1; QTest::newRow("remotecopycancel") << server.url("/exists5.png") << server.url("/exists5.png") << 0 << 0; } void tst_qquickpixmapcache::parallel() { QFETCH(QUrl, target1); QFETCH(QUrl, target2); QFETCH(int, incache); QFETCH(int, cancel); QList targets; targets << target1 << target2; QList pixmaps; QList pending; QList getters; for (int i=0; iload(&engine, target); QVERIFY(pixmap->status() != QQuickPixmap::Error); pixmaps.append(pixmap); if (pixmap->isReady()) { QVERIFY(pixmap->width() > 0); getters.append(0); pending.append(false); } else { QVERIFY(pixmap->width() <= 0); getters.append(new Slotter); pixmap->connectFinished(getters[i], SLOT(got())); pending.append(true); } } if (incache + slotters != targets.count()) QFAIL(QString::fromLatin1("pixmap counts don't add up: %1 incache, %2 slotters, %3 total") .arg(incache).arg(slotters).arg(targets.count()).toLatin1().constData()); if (cancel >= 0) { pixmaps.at(cancel)->clear(getters[cancel]); slotters--; } if (slotters) { QTestEventLoop::instance().enterLoop(10); QVERIFY(!QTestEventLoop::instance().timeout()); } for (int i=0; igotslot); } else { if (pending[i]) QVERIFY(getters[i]->gotslot); if (!pixmap->isReady()) { QFAIL(QString::fromLatin1("pixmap %1 not ready, status %2: %3") .arg(pixmap->url().toString()).arg(pixmap->status()) .arg(pixmap->error()).toLatin1().constData()); } QVERIFY(pixmap->width() > 0); delete getters[i]; } } qDeleteAll(pixmaps); } void tst_qquickpixmapcache::massive() { QQmlEngine engine; QUrl url = testFileUrl("massive.png"); // Confirm that massive images remain in the cache while they are // in use by the application. { qint64 cachekey = 0; QQuickPixmap p(&engine, url); QVERIFY(p.isReady()); QVERIFY(p.image().size() == QSize(10000, 1000)); cachekey = p.image().cacheKey(); QQuickPixmap p2(&engine, url); QVERIFY(p2.isReady()); QVERIFY(p2.image().size() == QSize(10000, 1000)); QCOMPARE(p2.image().cacheKey(), cachekey); } // Confirm that massive images are removed from the cache when // they become unused { qint64 cachekey = 0; { QQuickPixmap p(&engine, url); QVERIFY(p.isReady()); QVERIFY(p.image().size() == QSize(10000, 1000)); cachekey = p.image().cacheKey(); } QQuickPixmap p2(&engine, url); QVERIFY(p2.isReady()); QVERIFY(p2.image().size() == QSize(10000, 1000)); QVERIFY(p2.image().cacheKey() != cachekey); } } // QTBUG-12729 void tst_qquickpixmapcache::cancelcrash() { QUrl url = server.url("/cancelcrash_notexist.png"); for (int ii = 0; ii < 1000; ++ii) { QQuickPixmap pix(&engine, url); } } class MyPixmapProvider : public QQuickImageProvider { public: MyPixmapProvider() : QQuickImageProvider(Pixmap) {} virtual QPixmap requestPixmap(const QString &d, QSize *, const QSize &) { Q_UNUSED(d) QPixmap pix(800, 600); pix.fill(fillColor); return pix; } static QRgb fillColor; }; QRgb MyPixmapProvider::fillColor = qRgb(255, 0, 0); // QTBUG-13345 void tst_qquickpixmapcache::shrinkcache() { QQmlEngine engine; engine.addImageProvider(QLatin1String("mypixmaps"), new MyPixmapProvider); for (int ii = 0; ii < 4000; ++ii) { QUrl url("image://mypixmaps/" + QString::number(ii)); QQuickPixmap p(&engine, url); } } #if QT_CONFIG(concurrent) void createNetworkServer(TestHTTPServer *server) { QEventLoop eventLoop; server->serveDirectory(QQmlDataTest::instance()->testFile("http")); QTimer::singleShot(100, &eventLoop, SLOT(quit())); eventLoop.exec(); } #if QT_CONFIG(concurrent) // QT-3957 void tst_qquickpixmapcache::networkCrash() { TestHTTPServer server; QVERIFY2(server.listen(), qPrintable(server.errorString())); QFuture future = QtConcurrent::run(createNetworkServer, &server); QQmlEngine engine; for (int ii = 0; ii < 100 ; ++ii) { QQuickPixmap* pixmap = new QQuickPixmap; pixmap->load(&engine, server.url("/exists.png")); QTest::qSleep(1); pixmap->clear(); delete pixmap; } future.cancel(); } #endif #endif // QTBUG-22125 void tst_qquickpixmapcache::lockingCrash() { TestHTTPServer server; QVERIFY2(server.listen(), qPrintable(server.errorString())); server.serveDirectory(testFile("http"), TestHTTPServer::Delay); { QQuickPixmap* p = new QQuickPixmap; { QQmlEngine e; p->load(&e, server.url("/exists6.png")); } p->clear(); QVERIFY(p->isNull()); delete p; server.sendDelayedItem(); } } void tst_qquickpixmapcache::uncached() { QQmlEngine engine; engine.addImageProvider(QLatin1String("mypixmaps"), new MyPixmapProvider); QUrl url("image://mypixmaps/mypix"); { QQuickPixmap p; p.load(&engine, url, nullptr); QImage img = p.image(); QCOMPARE(img.pixel(0,0), qRgb(255, 0, 0)); } // uncached, so we will get a different colored image MyPixmapProvider::fillColor = qRgb(0, 255, 0); { QQuickPixmap p; p.load(&engine, url, nullptr); QImage img = p.image(); QCOMPARE(img.pixel(0,0), qRgb(0, 255, 0)); } // Load the image with cache enabled MyPixmapProvider::fillColor = qRgb(0, 0, 255); { QQuickPixmap p; p.load(&engine, url, QQuickPixmap::Cache); QImage img = p.image(); QCOMPARE(img.pixel(0,0), qRgb(0, 0, 255)); } // We should not get the cached version if we request uncached MyPixmapProvider::fillColor = qRgb(255, 0, 255); { QQuickPixmap p; p.load(&engine, url, nullptr); QImage img = p.image(); QCOMPARE(img.pixel(0,0), qRgb(255, 0, 255)); } // If we again load the image with cache enabled, we should get the previously cached version MyPixmapProvider::fillColor = qRgb(0, 255, 255); { QQuickPixmap p; p.load(&engine, url, QQuickPixmap::Cache); QImage img = p.image(); QCOMPARE(img.pixel(0,0), qRgb(0, 0, 255)); } } void tst_qquickpixmapcache::asynchronousNoCache() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("asynchronousNoCache.qml")); QScopedPointer root {component.create()}; // should not crash } #if PIXMAP_DATA_LEAK_TEST // This test should not be enabled by default as it // produces spurious output in the expected case. #include class DataLeakView : public QQuickView { Q_OBJECT public: explicit DataLeakView() : QQuickView() { setSource(testFileUrl("dataLeak.qml")); } void showFor2Seconds() { showFullScreen(); QTimer::singleShot(2000, this, SIGNAL(ready())); } signals: void ready(); }; // QTBUG-22742 Q_GLOBAL_STATIC(QQuickPixmap, dataLeakPixmap) void tst_qquickpixmapcache::dataLeak() { // Should not leak cached QQuickPixmapData. // Unfortunately, since the QQuickPixmapStore // is a global static, and it releases the cache // entries on dtor (application exit), we must use // valgrind to determine whether it leaks or not. QQuickPixmap *p1 = new QQuickPixmap; QQuickPixmap *p2 = new QQuickPixmap; { QScopedPointer test(new DataLeakView); test->showFor2Seconds(); dataLeakPixmap()->load(test->engine(), testFileUrl("exists.png")); p1->load(test->engine(), testFileUrl("exists.png")); p2->load(test->engine(), testFileUrl("exists2.png")); QTest::qWait(2005); // 2 seconds + a few more millis. } // When the (global static) dataLeakPixmap is deleted, it // shouldn't attempt to dereference a QQuickPixmapData // which has been deleted by the QQuickPixmapStore // destructor. } #endif #undef PIXMAP_DATA_LEAK_TEST QTEST_MAIN(tst_qquickpixmapcache) #include "tst_qquickpixmapcache.moc"