diff options
author | Simon Hausmann <simon.hausmann@qt.io> | 2016-07-28 17:40:36 +0200 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@qt.io> | 2016-08-04 14:32:08 +0000 |
commit | d77e544a568dbfff698c4d37c669bc991383fe9b (patch) | |
tree | fc48e7dae9ba9e53a6de1018ff3436182e7a8f58 /tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp | |
parent | 1534dd6d97c49f5c7e0392df9c95198311b5b817 (diff) |
Safeguard disk cache loading with checksum verification
When loading a QML component from the disk cache, compare the checksum
of the dependent types against the checksum when the cache was created.
Any change in the meta-object of a dependent type should trigger a
re-creation discard of the cache and consequent re-creation (in the
test-case).
Unfortunately this also requires extending the existing hack in the unit
test to deal with the low second precision on HFS+ in order to pass the
tests.
Change-Id: Ib8e899347680f7be676788388e9c23a09b0277e3
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp')
-rw-r--r-- | tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp | 122 |
1 files changed, 117 insertions, 5 deletions
diff --git a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp index 1fe63bb99a..a4e10205bd 100644 --- a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp +++ b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp @@ -48,8 +48,40 @@ private slots: void regenerateAfterChange(); void registerImportForImplicitComponent(); void basicVersionChecks(); + void recompileAfterChange(); }; +// A wrapper around QQmlComponent to ensure the temporary reference counts +// on the type data as a result of the main thread <> loader thread communication +// are dropped. Regular Synchronous loading will leave us with an event posted +// to the gui thread and an extra refcount that will only be dropped after the +// event delivery. A plain sendPostedEvents() however is insufficient because +// we can't be sure that the event is posted after the constructor finished. +class CleanlyLoadingComponent : public QQmlComponent +{ +public: + CleanlyLoadingComponent(QQmlEngine *engine, const QUrl &url) + : QQmlComponent(engine, url, QQmlComponent::Asynchronous) + { waitForLoad(); } + CleanlyLoadingComponent(QQmlEngine *engine, const QString &fileName) + : QQmlComponent(engine, fileName, QQmlComponent::Asynchronous) + { waitForLoad(); } + + void waitForLoad() + { + QTRY_VERIFY(status() == QQmlComponent::Ready || status() == QQmlComponent::Error); + } +}; + +static void waitForFileSystem() +{ + // On macOS with HFS+ the precision of file times is measured in seconds, so to ensure that + // the newly written file has a modification date newer than an existing cache file, we must + // wait. + // Similar effects of lacking precision have been observed on some Linux systems. + QThread::sleep(1); +} + struct TestCompiler { TestCompiler(QQmlEngine *engine) @@ -67,10 +99,8 @@ struct TestCompiler closeMapping(); engine->clearComponentCache(); - // Qt API limits the precision of QFileInfo::modificationTime() to seconds, so to ensure that - // the newly written file has a modification date newer than an existing cache file, we must - // wait. - QThread::sleep(1); + waitForFileSystem(); + { QFile f(testFilePath); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { @@ -83,7 +113,7 @@ struct TestCompiler } } - QQmlComponent component(engine, testFilePath); + CleanlyLoadingComponent component(engine, testFilePath); if (!component.isReady()) { lastErrorString = component.errorString(); return false; @@ -321,6 +351,88 @@ void tst_qmldiskcache::basicVersionChecks() } } +class TypeVersion1 : public QObject +{ + Q_OBJECT + Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged); +public: + + + int m_value = 0; + int value() const { return m_value; } + void setValue(int v) { m_value = v; emit valueChanged(); } + +signals: + void valueChanged(); +}; + +// Same as TypeVersion1 except the property type changed! +class TypeVersion2 : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged); +public: + + + QString m_value; + QString value() const { return m_value; } + void setValue(QString v) { m_value = v; emit valueChanged(); } + +signals: + void valueChanged(); +}; + +void tst_qmldiskcache::recompileAfterChange() +{ + QQmlEngine engine; + + TestCompiler testCompiler(&engine); + QVERIFY(testCompiler.tempDir.isValid()); + + const QByteArray contents = QByteArrayLiteral("import TypeTest 1.0\n" + "TypeThatWillChange {\n" + "}"); + + qmlRegisterType<TypeVersion1>("TypeTest", 1, 0, "TypeThatWillChange"); + + { + testCompiler.clearCache(); + QVERIFY2(testCompiler.compile(contents), qPrintable(testCompiler.lastErrorString)); + QVERIFY2(testCompiler.verify(), qPrintable(testCompiler.lastErrorString)); + } + + QDateTime initialCacheTimeStamp = QFileInfo(testCompiler.cacheFilePath).lastModified(); + + { + CleanlyLoadingComponent component(&engine, testCompiler.testFilePath); + QScopedPointer<TypeVersion1> obj(qobject_cast<TypeVersion1*>(component.create())); + QVERIFY(!obj.isNull()); + QCOMPARE(QFileInfo(testCompiler.cacheFilePath).lastModified(), initialCacheTimeStamp); + } + + engine.clearComponentCache(); + + { + CleanlyLoadingComponent component(&engine, testCompiler.testFilePath); + QScopedPointer<TypeVersion1> obj(qobject_cast<TypeVersion1*>(component.create())); + QVERIFY(!obj.isNull()); + QCOMPARE(QFileInfo(testCompiler.cacheFilePath).lastModified(), initialCacheTimeStamp); + } + + engine.clearComponentCache(); + qmlClearTypeRegistrations(); + qmlRegisterType<TypeVersion2>("TypeTest", 1, 0, "TypeThatWillChange"); + + waitForFileSystem(); + + { + CleanlyLoadingComponent component(&engine, testCompiler.testFilePath); + QScopedPointer<TypeVersion2> obj(qobject_cast<TypeVersion2*>(component.create())); + QVERIFY(!obj.isNull()); + QVERIFY(QFileInfo(testCompiler.cacheFilePath).lastModified() > initialCacheTimeStamp); + } +} + QTEST_MAIN(tst_qmldiskcache) #include "tst_qmldiskcache.moc" |