From 4e3e2e69aaefa4cd46f678e89e93db15ff36dd46 Mon Sep 17 00:00:00 2001 From: Peter Petrik Date: Tue, 3 Oct 2023 14:26:46 +0200 Subject: [PATCH] add tests --- app/CMakeLists.txt | 2 + app/test/inputtests.cpp | 6 ++ app/test/testmerginapi.cpp | 26 ++++- app/test/testprojectchecksumcache.cpp | 138 ++++++++++++++++++++++++++ app/test/testprojectchecksumcache.h | 29 ++++++ core/coreutils.cpp | 5 +- core/coreutils.h | 2 +- core/merginapi.cpp | 2 +- core/projectchecksumcache.cpp | 15 ++- core/projectchecksumcache.h | 11 ++ test/CMakeLists.txt | 1 + 11 files changed, 225 insertions(+), 12 deletions(-) create mode 100644 app/test/testprojectchecksumcache.cpp create mode 100644 app/test/testprojectchecksumcache.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index fc9a1a0fe..b13513dbd 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -195,6 +195,7 @@ if (ENABLE_TESTS) test/testutilsfunctions.cpp test/testvariablesmanager.cpp test/testactiveproject.cpp + test/testprojectchecksumcache.cpp ) set(MM_HDRS @@ -220,6 +221,7 @@ if (ENABLE_TESTS) test/testutilsfunctions.h test/testvariablesmanager.h test/testactiveproject.h + test/testprojectchecksumcache.h ) endif () diff --git a/app/test/inputtests.cpp b/app/test/inputtests.cpp index 4093f8a69..16bad90aa 100644 --- a/app/test/inputtests.cpp +++ b/app/test/inputtests.cpp @@ -30,6 +30,7 @@ #include "test/testmaptools.h" #include "test/testlayertree.h" #include "test/testactiveproject.h" +#include "test/testprojectchecksumcache.h" #if not defined APPLE_PURCHASING #include "test/testpurchasing.h" @@ -181,6 +182,11 @@ int InputTests::runTest() const TestActiveProject activeProjectTest( mApi ); nFailed = QTest::qExec( &activeProjectTest, mTestArgs ); } + else if ( mTestRequested == "--testProjectChecksumCache" ) + { + TestProjectChecksumCache projectChecksumTest; + nFailed = QTest::qExec( &projectChecksumTest, mTestArgs ); + } #if not defined APPLE_PURCHASING else if ( mTestRequested == "--testPurchasing" ) { diff --git a/app/test/testmerginapi.cpp b/app/test/testmerginapi.cpp index 1627fc268..232ee27c6 100644 --- a/app/test/testmerginapi.cpp +++ b/app/test/testmerginapi.cpp @@ -534,7 +534,7 @@ void TestMerginApi::testMultiChunkUploadDownload() bigFile.write( QByteArray( 1024 * 1024, static_cast( 'A' + i ) ) ); // AAAA.....BBBB.....CCCC..... bigFile.close(); - QByteArray checksum = CoreUtils::calculate( bigFilePath ); + QByteArray checksum = CoreUtils::calculateChecksum( bigFilePath ); QVERIFY( !checksum.isEmpty() ); // upload @@ -546,7 +546,7 @@ void TestMerginApi::testMultiChunkUploadDownload() downloadRemoteProject( mApi, mUsername, projectName ); // verify it's there and with correct content - QByteArray checksum2 = CoreUtils::calculate( bigFilePath ); + QByteArray checksum2 = CoreUtils::calculateChecksum( bigFilePath ); QVERIFY( QFileInfo::exists( bigFilePath ) ); QCOMPARE( checksum, checksum2 ); } @@ -567,7 +567,7 @@ void TestMerginApi::testEmptyFileUploadDownload() QFile::copy( mTestDataPath + "/" + TEST_EMPTY_FILE_NAME, emptyFileDestinationPath ); QVERIFY( QFileInfo::exists( emptyFileDestinationPath ) ); - QByteArray checksum = CoreUtils::calculate( emptyFileDestinationPath ); + QByteArray checksum = CoreUtils::calculateChecksum( emptyFileDestinationPath ); QVERIFY( !checksum.isEmpty() ); //upload @@ -579,7 +579,7 @@ void TestMerginApi::testEmptyFileUploadDownload() downloadRemoteProject( mApi, mUsername, projectName ); // verify it's there and with correct content - QByteArray checksum2 = CoreUtils::calculate( emptyFileDestinationPath ); + QByteArray checksum2 = CoreUtils::calculateChecksum( emptyFileDestinationPath ); QVERIFY( QFileInfo::exists( emptyFileDestinationPath ) ); QCOMPARE( checksum, checksum2 ); } @@ -785,6 +785,7 @@ void TestMerginApi::testPushNoChanges() QCOMPARE( project2.mergin.status, ProjectStatus::UpToDate ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); } void TestMerginApi::testUpdateAddedFile() @@ -1123,6 +1124,7 @@ void TestMerginApi::testDiffUpload() QVERIFY( QFileInfo::exists( projectDir + "/.mergin/base.gpkg" ) ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); // replace gpkg with a new version with a modified geometry QFile::remove( projectDir + "/base.gpkg" ); @@ -1132,6 +1134,7 @@ void TestMerginApi::testDiffUpload() ProjectDiff expectedDiff; expectedDiff.localUpdated = QSet() << "base.gpkg"; QCOMPARE( diff, expectedDiff ); + QVERIFY( MerginApi::hasLocalProjectChanges( projectDir ) ); GeodiffUtils::ChangesetSummary expectedSummary; expectedSummary["simple"] = GeodiffUtils::TableSummary( 0, 1, 0 ); @@ -1142,6 +1145,7 @@ void TestMerginApi::testDiffUpload() uploadRemoteProject( mApi, mUsername, projectName ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); } void TestMerginApi::testDiffSubdirsUpload() @@ -1157,7 +1161,7 @@ void TestMerginApi::testDiffSubdirsUpload() QVERIFY( QFileInfo::exists( projectDir + "/.mergin/" + base ) ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected - + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); // replace gpkg with a new version with a modified geometry QFile::remove( projectDir + "/" + base ); @@ -1167,6 +1171,7 @@ void TestMerginApi::testDiffSubdirsUpload() ProjectDiff expectedDiff; expectedDiff.localUpdated = QSet() << base ; QCOMPARE( diff, expectedDiff ); + QVERIFY( MerginApi::hasLocalProjectChanges( projectDir ) ); GeodiffUtils::ChangesetSummary expectedSummary; expectedSummary["simple"] = GeodiffUtils::TableSummary( 0, 1, 0 ); @@ -1177,6 +1182,7 @@ void TestMerginApi::testDiffSubdirsUpload() uploadRemoteProject( mApi, mUsername, projectName ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); } void TestMerginApi::testDiffUpdateBasic() @@ -1194,6 +1200,7 @@ void TestMerginApi::testDiffUpdateBasic() QVERIFY( QFileInfo::exists( projectDir + "/.mergin/base.gpkg" ) ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); QgsVectorLayer *vl0 = new QgsVectorLayer( projectDir + "/base.gpkg|layername=simple", "base", "ogr" ); QVERIFY( vl0->isValid() ); @@ -1223,6 +1230,7 @@ void TestMerginApi::testDiffUpdateBasic() delete vl; QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); QVERIFY( !GeodiffUtils::hasPendingChanges( projectDir, "base.gpkg" ) ); } @@ -1242,6 +1250,7 @@ void TestMerginApi::testDiffUpdateWithRebase() QVERIFY( QFileInfo::exists( projectDir + "/.mergin/base.gpkg" ) ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); // // download with mApiExtra + modify + upload @@ -1275,6 +1284,7 @@ void TestMerginApi::testDiffUpdateWithRebase() ProjectDiff expectedDiff; expectedDiff.localUpdated = QSet() << "base.gpkg"; QCOMPARE( diff, expectedDiff ); + QVERIFY( MerginApi::hasLocalProjectChanges( projectDir ) ); // check that geodiff knows there was one added feature GeodiffUtils::ChangesetSummary expectedSummary; @@ -1298,6 +1308,7 @@ void TestMerginApi::testDiffUpdateWithRebase() // like before the update - there should be locally modified base.gpkg with the changes we did QCOMPARE( MerginApi::localProjectChanges( projectDir ), expectedDiff ); QCOMPARE( GeodiffUtils::parseChangesetSummary( changes ), expectedSummary ); + QVERIFY( MerginApi::hasLocalProjectChanges( projectDir ) ); } void TestMerginApi::testDiffUpdateWithRebaseFailed() @@ -1318,6 +1329,7 @@ void TestMerginApi::testDiffUpdateWithRebaseFailed() QVERIFY( QFileInfo::exists( projectDir + "/.mergin/base.gpkg" ) ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); // // download with mApiExtra + modify + upload @@ -1343,6 +1355,7 @@ void TestMerginApi::testDiffUpdateWithRebaseFailed() expectedDiff.localUpdated = QSet() << "base.gpkg"; qDebug() << diff.dump(); QCOMPARE( diff, expectedDiff ); + QVERIFY( MerginApi::hasLocalProjectChanges( projectDir ) ); // check that geodiff knows there was one added feature QString changes = GeodiffUtils::diffableFilePendingChanges( projectDir, "base.gpkg", true ); @@ -1368,6 +1381,7 @@ void TestMerginApi::testDiffUpdateWithRebaseFailed() ProjectDiff expectedDiffFinal; expectedDiffFinal.localAdded = QSet() << conflictFilename; QCOMPARE( MerginApi::localProjectChanges( projectDir ), expectedDiffFinal ); + QVERIFY( MerginApi::hasLocalProjectChanges( projectDir ) ); } void TestMerginApi::testUpdateWithDiffs() @@ -1385,6 +1399,7 @@ void TestMerginApi::testUpdateWithDiffs() QVERIFY( QFileInfo::exists( projectDir + "/.mergin/base.gpkg" ) ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); // // download with mApiExtra + modify + upload @@ -1415,6 +1430,7 @@ void TestMerginApi::testUpdateWithDiffs() delete vl; QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); + QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); QVERIFY( !GeodiffUtils::hasPendingChanges( projectDir, "base.gpkg" ) ); } diff --git a/app/test/testprojectchecksumcache.cpp b/app/test/testprojectchecksumcache.cpp new file mode 100644 index 000000000..b7e3ca5f9 --- /dev/null +++ b/app/test/testprojectchecksumcache.cpp @@ -0,0 +1,138 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "testprojectchecksumcache.h" +#include "projectchecksumcache.h" +#include "coreutils.h" +#include "testutils.h" +#include "inpututils.h" + +#include +#include + +TestProjectChecksumCache::TestProjectChecksumCache() = default; + +TestProjectChecksumCache::~TestProjectChecksumCache() = default; + +void TestProjectChecksumCache::init() +{ +} + +void TestProjectChecksumCache::cleanup() +{ +} + +void TestProjectChecksumCache::testFilesCheckum() +{ + QString projectName = QStringLiteral( "testFilesCheckum" ); + QString projectDir = QDir::tempPath() + "/" + projectName; + + InputUtils::cpDir( TestUtils::testDataDir() + "/planes", projectDir ); + InputUtils::copyFile( TestUtils::testDataDir() + "/photo.jpg", projectDir + "/bigfile.jpg" ); + + QString cacheFilePath = projectDir + "/.mergin/checksum.cache" ; + QString checksumDirectTxt1 = CoreUtils::calculateChecksum( projectDir + "/lines.qml" ); + QVERIFY( !checksumDirectTxt1.isEmpty() ); + + QElapsedTimer timer; + timer.start(); + QString checksumDirectBigFile = CoreUtils::calculateChecksum( projectDir + "/bigfile.jpg" ); + qint64 elapsedForChecksumDirectBigFile = timer.elapsed(); + QVERIFY( !checksumDirectBigFile.isEmpty() ); + + { + // Cold start - delete cache file + InputUtils::removeFile( cacheFilePath ); + ProjectChecksumCache cache( projectDir ); + QCOMPARE( cache.cacheFilePath(), cacheFilePath ); + + // Test gpkg + QString checksumDirectGpkg = CoreUtils::calculateChecksum( projectDir + "/constraint-layers.gpkg" ); + QCOMPARE( checksumDirectGpkg, "c81be103072ecea025ff92a813916db8e42b7bbb" ); + QString checksumFromCacheGpkg = cache.get( "constraint-layers.gpkg" ); + QCOMPARE( checksumDirectGpkg, checksumFromCacheGpkg ); + + // Test non-existent file + InputUtils::removeFile( projectDir + "/photo.jpg" ); + QVERIFY( cache.get( "photo.jpg" ).isEmpty() ); + + // Test text file + QString checksumFromCacheTxt = cache.get( "lines.qml" ); + QCOMPARE( checksumDirectTxt1, checksumFromCacheTxt ); + + // Test photo - big file + QString checksumFromCacheBigFile = cache.get( "bigfile.jpg" ); + QCOMPARE( checksumDirectBigFile, checksumFromCacheBigFile ); + } + + // Test that cache is saved + QVERIFY( QFileInfo( cacheFilePath ).exists() ); + QDateTime cacheModifiedTime = QFileInfo( cacheFilePath ).lastModified(); + + // Modify txt file, remove constraint-layers.gpkg file and add photo file, do not touch bigfile.jpg + InputUtils::removeFile( projectDir + "/constraint-layers.gpkg" ); + InputUtils::copyFile( TestUtils::testDataDir() + "/photo.jpg", projectDir + "/photo.jpg" ); + QString checksumDirectPhoto = CoreUtils::calculateChecksum( projectDir + "/photo.jpg" ); + QVERIFY( !checksumDirectPhoto.isEmpty() ); + + QFile f( projectDir + "/lines.qml" ); + if ( f.open( QIODevice::WriteOnly ) ) + { + QTextStream stream( &f ); + stream << "something really really cool"; + f.close(); + } + QString checksumDirectTxt2 = CoreUtils::calculateChecksum( projectDir + "/lines.qml" ); + QVERIFY( !checksumDirectTxt2.isEmpty() ); + QVERIFY( checksumDirectTxt1 != checksumDirectTxt2 ); + + { + // Start with existent cache file + ProjectChecksumCache cache( projectDir ); + + // Test non-existent gpkg - NOT taken from previous cache + QVERIFY( cache.get( "constraint-layers.gpkg" ).isEmpty() ); + + // Test new file - NOT taken from previous cache + QString checksumDirectPhoto = CoreUtils::calculateChecksum( projectDir + "/photo.jpg" ); + QString checksumFromCachePhoto = cache.get( "photo.jpg" ); + QCOMPARE( checksumDirectPhoto, checksumFromCachePhoto ); + + // Test modified file - NOT taken from previous cache + QString checksumFromCacheTxt = cache.get( "lines.qml" ); + QCOMPARE( checksumFromCacheTxt, checksumDirectTxt2 ); + + // Test bigfile - checksum taken from previous cache! + // time should be faster than when calculated directly (let say at least 2 times) + QElapsedTimer timer2; + timer2.start(); + QString checksumFromCacheBigFile = cache.get( "bigfile.jpg" ); + qint64 elapsedTimeFromCache = timer2.elapsed(); + QVERIFY( elapsedTimeFromCache * 2 < elapsedForChecksumDirectBigFile ); + QCOMPARE( checksumDirectBigFile, checksumFromCacheBigFile ); + } + + // Test that cache is re-saved + QVERIFY( QFileInfo( cacheFilePath ).exists() ); + QDateTime cacheModifiedTime2 = QFileInfo( cacheFilePath ).lastModified(); + QVERIFY( cacheModifiedTime != cacheModifiedTime2 ); + + { + // Start with existent cache file + ProjectChecksumCache cache( projectDir ); + + // Test geo gpkg + QString checksumFromCacheGeoGpkg = cache.get( "bigfile.jpg" ); + QCOMPARE( checksumDirectBigFile, checksumFromCacheGeoGpkg ); + } + // Test that cache is NOT re-saved + QVERIFY( QFileInfo( cacheFilePath ).exists() ); + QDateTime cacheModifiedTime3 = QFileInfo( cacheFilePath ).lastModified(); + QCOMPARE( cacheModifiedTime2, cacheModifiedTime3 ); +} diff --git a/app/test/testprojectchecksumcache.h b/app/test/testprojectchecksumcache.h new file mode 100644 index 000000000..86a8be929 --- /dev/null +++ b/app/test/testprojectchecksumcache.h @@ -0,0 +1,29 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef TESTPROJECTCHECKSUMCACHE_H +#define TESTPROJECTCHECKSUMCACHE_H + +#include + +class TestProjectChecksumCache : public QObject +{ + Q_OBJECT + public: + explicit TestProjectChecksumCache( ); + ~TestProjectChecksumCache(); + + private slots: + void init(); + void cleanup(); + + void testFilesCheckum(); +}; + +#endif // TESTPROJECTCHECKSUMCACHE_H diff --git a/core/coreutils.cpp b/core/coreutils.cpp index abd2dcd94..4a0cd72b8 100644 --- a/core/coreutils.cpp +++ b/core/coreutils.cpp @@ -16,9 +16,10 @@ #include #include #include +#include +#include #include "qcoreapplication.h" -#include "merginapi.h" const QString CoreUtils::LOG_TO_DEVNULL = QStringLiteral(); const QString CoreUtils::LOG_TO_STDOUT = QStringLiteral( "TO_STDOUT" ); @@ -163,7 +164,7 @@ QString CoreUtils::findUniquePath( const QString &path ) return uniquePath; } -QByteArray CoreUtils::calculate( const QString &filePath ) +QByteArray CoreUtils::calculateChecksum( const QString &filePath ) { QFile f( filePath ); if ( f.open( QFile::ReadOnly ) ) diff --git a/core/coreutils.h b/core/coreutils.h index bb79a4e35..04db583ec 100644 --- a/core/coreutils.h +++ b/core/coreutils.h @@ -44,7 +44,7 @@ class CoreUtils * This is potentially resourcing-costly operation * \param filePath full path to the file on disk */ - static QByteArray calculate( const QString &filePath ); + static QByteArray calculateChecksum( const QString &filePath ); /** * Returns given path if it does not exist yet, otherwise adds a number to the path in format: diff --git a/core/merginapi.cpp b/core/merginapi.cpp index 120b2a75b..7d7688c4a 100644 --- a/core/merginapi.cpp +++ b/core/merginapi.cpp @@ -2540,7 +2540,7 @@ void MerginApi::pushInfoReplyFinished() if ( geodiffRes == GEODIFF_SUCCESS ) { - QByteArray checksumDiff = CoreUtils::calculate( diffPath ); + QByteArray checksumDiff = CoreUtils::calculateChecksum( diffPath ); // TODO: this is ugly. our basefile may not need to have the same checksum as the server's // basefile (because each of them have applied the diff independently) so we have to fake it diff --git a/core/projectchecksumcache.cpp b/core/projectchecksumcache.cpp index 8a7839a01..896b9a62a 100644 --- a/core/projectchecksumcache.cpp +++ b/core/projectchecksumcache.cpp @@ -20,7 +20,12 @@ const QString ProjectChecksumCache::sCacheFile = QStringLiteral( "checksum.cache QString ProjectChecksumCache::cacheFilePath() const { - return mProjectDir + "/" + MerginApi::sMetadataFolder + "/" + sCacheFile; + return cacheDirPath() + "/" + sCacheFile; +} + +QString ProjectChecksumCache::cacheDirPath() const +{ + return mProjectDir + "/" + MerginApi::sMetadataFolder; } ProjectChecksumCache::ProjectChecksumCache( const QString &projectDir ) @@ -52,8 +57,12 @@ ProjectChecksumCache::~ProjectChecksumCache() if ( !mCacheModified ) return; - QFile f( cacheFilePath() ); + // Make sure the directory exists + QDir dir; + if ( !dir.exists( cacheDirPath() ) ) + dir.mkpath( cacheDirPath() ); + QFile f( cacheFilePath() ); if ( f.open( QIODevice::WriteOnly ) ) // implies Truncate { QDataStream stream( &f ); @@ -84,7 +93,7 @@ QString ProjectChecksumCache::get( const QString &path ) } } - QByteArray localChecksumBytes = CoreUtils::calculate( mProjectDir + "/" + path ); + QByteArray localChecksumBytes = CoreUtils::calculateChecksum( mProjectDir + "/" + path ); QString localChecksum = QString::fromLatin1( localChecksumBytes.data(), localChecksumBytes.size() ); CacheValue entry; diff --git a/core/projectchecksumcache.h b/core/projectchecksumcache.h index 8cd8b2a8f..ba69b88d0 100644 --- a/core/projectchecksumcache.h +++ b/core/projectchecksumcache.h @@ -15,6 +15,12 @@ #include #include +#include "inputconfig.h" + +#if defined(INPUT_TEST) +class TestProjectChecksumCache; +#endif + /** * Calculates the checksums of local files and store the results in the local binary file */ @@ -34,8 +40,13 @@ class ProjectChecksumCache //! Name of the file in which the cache for the project is stored static const QString sCacheFile; +#if defined(INPUT_TEST) + friend class TestProjectChecksumCache; +#endif + private: QString cacheFilePath() const; + QString cacheDirPath() const; struct CacheValue { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6572558b0..4e1c7cf5d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,6 +24,7 @@ set(MM_TESTS testMapTools testLayerTree testActiveProject + testProjectChecksumCache ) foreach (test ${MM_TESTS})