File indexing completed on 2024-12-22 04:57:38
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 0003 SPDX-FileContributor: Kevin Krammer <krake@kdab.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "mixedmaildirstore.h" 0009 0010 #include "testdatautil.h" 0011 0012 #include "filestore/entitycompactchangeattribute.h" 0013 #include "filestore/itemdeletejob.h" 0014 #include "filestore/storecompactjob.h" 0015 0016 #include <KMbox/MBox> 0017 0018 #include <QTemporaryDir> 0019 0020 #include <QSignalSpy> 0021 0022 #include <QDir> 0023 #include <QFileInfo> 0024 #include <QTest> 0025 using namespace Akonadi; 0026 using namespace KMBox; 0027 0028 static Collection::List collectionsFromSpy(QSignalSpy *spy) 0029 { 0030 Collection::List collections; 0031 0032 QListIterator<QList<QVariant>> it(*spy); 0033 while (it.hasNext()) { 0034 const QList<QVariant> invocation = it.next(); 0035 Q_ASSERT(invocation.count() == 1); 0036 0037 collections << invocation.first().value<Collection::List>(); 0038 } 0039 0040 return collections; 0041 } 0042 0043 static Item::List itemsFromSpy(QSignalSpy *spy) 0044 { 0045 Item::List items; 0046 0047 QListIterator<QList<QVariant>> it(*spy); 0048 while (it.hasNext()) { 0049 const QList<QVariant> invocation = it.next(); 0050 Q_ASSERT(invocation.count() == 1); 0051 0052 items << invocation.first().value<Item::List>(); 0053 } 0054 0055 return items; 0056 } 0057 0058 static bool fullEntryCompare(const MBoxEntry &a, const MBoxEntry &b) 0059 { 0060 return a.messageOffset() == b.messageOffset() && a.separatorSize() == b.separatorSize() && a.messageSize() == b.messageSize(); 0061 } 0062 0063 static quint64 changedOffset(const Item &item) 0064 { 0065 Q_ASSERT(item.hasAttribute<FileStore::EntityCompactChangeAttribute>()); 0066 0067 const QString remoteId = item.attribute<FileStore::EntityCompactChangeAttribute>()->remoteId(); 0068 Q_ASSERT(!remoteId.isEmpty()); 0069 0070 bool ok = false; 0071 const quint64 result = remoteId.toULongLong(&ok); 0072 Q_ASSERT(ok); 0073 0074 return result; 0075 } 0076 0077 class StoreCompactTest : public QObject 0078 { 0079 Q_OBJECT 0080 0081 public: 0082 StoreCompactTest() 0083 : QObject() 0084 , mStore(nullptr) 0085 , mDir(nullptr) 0086 { 0087 // for monitoring signals 0088 qRegisterMetaType<Akonadi::Collection::List>(); 0089 qRegisterMetaType<Akonadi::Item::List>(); 0090 } 0091 0092 ~StoreCompactTest() override 0093 { 0094 delete mStore; 0095 delete mDir; 0096 } 0097 0098 private: 0099 MixedMaildirStore *mStore = nullptr; 0100 QTemporaryDir *mDir = nullptr; 0101 0102 private Q_SLOTS: 0103 void init(); 0104 void cleanup(); 0105 void testCompact(); 0106 }; 0107 0108 void StoreCompactTest::init() 0109 { 0110 mStore = new MixedMaildirStore; 0111 0112 mDir = new QTemporaryDir; 0113 QVERIFY(mDir->isValid()); 0114 QVERIFY(QDir(mDir->path()).exists()); 0115 } 0116 0117 void StoreCompactTest::cleanup() 0118 { 0119 delete mStore; 0120 mStore = nullptr; 0121 delete mDir; 0122 mDir = nullptr; 0123 } 0124 0125 void StoreCompactTest::testCompact() 0126 { 0127 QDir topDir(mDir->path()); 0128 0129 QVERIFY(TestDataUtil::installFolder(QLatin1StringView("mbox"), topDir.path(), QStringLiteral("collection1"))); 0130 QVERIFY(TestDataUtil::installFolder(QLatin1StringView("mbox"), topDir.path(), QStringLiteral("collection2"))); 0131 QVERIFY(TestDataUtil::installFolder(QLatin1StringView("mbox"), topDir.path(), QStringLiteral("collection3"))); 0132 QVERIFY(TestDataUtil::installFolder(QLatin1StringView("mbox"), topDir.path(), QStringLiteral("collection4"))); 0133 0134 QFileInfo fileInfo1(topDir.path(), QStringLiteral("collection1")); 0135 MBox mbox1; 0136 QVERIFY(mbox1.load(fileInfo1.absoluteFilePath())); 0137 MBoxEntry::List entryList1 = mbox1.entries(); 0138 QCOMPARE((int)entryList1.count(), 4); 0139 0140 QFileInfo fileInfo2(topDir.path(), QStringLiteral("collection2")); 0141 MBox mbox2; 0142 QVERIFY(mbox2.load(fileInfo2.absoluteFilePath())); 0143 MBoxEntry::List entryList2 = mbox2.entries(); 0144 QCOMPARE((int)entryList2.count(), 4); 0145 0146 QFileInfo fileInfo3(topDir.path(), QStringLiteral("collection3")); 0147 MBox mbox3; 0148 QVERIFY(mbox3.load(fileInfo3.absoluteFilePath())); 0149 MBoxEntry::List entryList3 = mbox3.entries(); 0150 QCOMPARE((int)entryList3.count(), 4); 0151 0152 QFileInfo fileInfo4(topDir.path(), QStringLiteral("collection4")); 0153 MBox mbox4; 0154 QVERIFY(mbox4.load(fileInfo4.absoluteFilePath())); 0155 MBoxEntry::List entryList4 = mbox4.entries(); 0156 QCOMPARE((int)entryList4.count(), 4); 0157 0158 mStore->setPath(topDir.path()); 0159 0160 // common variables 0161 FileStore::CollectionFetchJob *collectionFetch = nullptr; 0162 FileStore::ItemDeleteJob *itemDelete = nullptr; 0163 FileStore::StoreCompactJob *job = nullptr; 0164 0165 Collection::List collections; 0166 Item::List items; 0167 0168 QSignalSpy *collectionSpy = nullptr; 0169 QSignalSpy *itemSpy = nullptr; 0170 0171 MBoxEntry::List entryList; 0172 Collection collection; 0173 FileStore::EntityCompactChangeAttribute *attribute = nullptr; 0174 0175 const QVariant colListVar = QVariant::fromValue<Collection::List>(Collection::List()); 0176 QVariant var; 0177 0178 // test compact after delete from the end of an mbox 0179 Collection collection1; 0180 collection1.setName(QStringLiteral("collection1")); 0181 collection1.setRemoteId(QStringLiteral("collection1")); 0182 collection1.setParentCollection(mStore->topLevelCollection()); 0183 0184 Item item1; 0185 item1.setRemoteId(QString::number(entryList1.last().messageOffset())); 0186 item1.setParentCollection(collection1); 0187 0188 itemDelete = mStore->deleteItem(item1); 0189 0190 QVERIFY(itemDelete->exec()); 0191 0192 job = mStore->compactStore(); 0193 0194 collectionSpy = new QSignalSpy(job, &FileStore::StoreCompactJob::collectionsChanged); 0195 itemSpy = new QSignalSpy(job, &FileStore::StoreCompactJob::itemsChanged); 0196 0197 QVERIFY(job->exec()); 0198 QCOMPARE(job->error(), 0); 0199 0200 collections = job->changedCollections(); 0201 items = job->changedItems(); 0202 0203 QCOMPARE(collections.count(), 0); 0204 QCOMPARE(items.count(), 0); 0205 0206 QCOMPARE(collectionSpy->count(), 0); 0207 QCOMPARE(itemSpy->count(), 0); 0208 0209 QVERIFY(mbox1.load(mbox1.fileName())); 0210 entryList = mbox1.entries(); 0211 entryList1.pop_back(); 0212 QVERIFY(std::equal(entryList.begin(), entryList.end(), entryList1.begin(), fullEntryCompare)); 0213 0214 var = job->property("onDiskIndexInvalidated"); 0215 QVERIFY(var.isValid()); 0216 QCOMPARE(var.userType(), colListVar.userType()); 0217 0218 collections = var.value<Collection::List>(); 0219 QCOMPARE((int)collections.count(), 1); 0220 QCOMPARE(collections, Collection::List() << collection1); 0221 0222 // test compact after delete from before the end of an mbox 0223 Collection collection2; 0224 collection2.setName(QStringLiteral("collection2")); 0225 collection2.setRemoteId(QStringLiteral("collection2")); 0226 collection2.setParentCollection(mStore->topLevelCollection()); 0227 0228 Item item2; 0229 item2.setRemoteId(QString::number(entryList2.first().messageOffset())); 0230 item2.setParentCollection(collection2); 0231 0232 itemDelete = mStore->deleteItem(item2); 0233 0234 QVERIFY(itemDelete->exec()); 0235 0236 job = mStore->compactStore(); 0237 0238 collectionSpy = new QSignalSpy(job, &FileStore::StoreCompactJob::collectionsChanged); 0239 itemSpy = new QSignalSpy(job, &FileStore::StoreCompactJob::itemsChanged); 0240 0241 QVERIFY(job->exec()); 0242 QCOMPARE(job->error(), 0); 0243 0244 collections = job->changedCollections(); 0245 items = job->changedItems(); 0246 0247 QCOMPARE(collections.count(), 1); 0248 QCOMPARE(items.count(), 3); 0249 0250 QCOMPARE(collectionSpy->count(), 1); 0251 QCOMPARE(itemSpy->count(), 1); 0252 0253 QCOMPARE(collectionsFromSpy(collectionSpy), collections); 0254 QCOMPARE(itemsFromSpy(itemSpy), items); 0255 0256 collection = collections.first(); 0257 QCOMPARE(collection, collection2); 0258 QVERIFY(collection.hasAttribute<FileStore::EntityCompactChangeAttribute>()); 0259 attribute = collection.attribute<FileStore::EntityCompactChangeAttribute>(); 0260 const QString remoteRevision = attribute->remoteRevision(); 0261 QCOMPARE(remoteRevision.toInt(), collection2.remoteRevision().toInt() + 1); 0262 0263 QVERIFY(mbox2.load(mbox2.fileName())); 0264 entryList = mbox2.entries(); 0265 0266 entryList2.pop_front(); 0267 for (int i = 0; i < items.count(); ++i) { 0268 entryList2[i] = MBoxEntry(changedOffset(items[i])); 0269 } 0270 QCOMPARE(entryList, entryList2); 0271 0272 var = job->property("onDiskIndexInvalidated"); 0273 QVERIFY(var.isValid()); 0274 QCOMPARE(var.userType(), colListVar.userType()); 0275 0276 collections = var.value<Collection::List>(); 0277 QCOMPARE((int)collections.count(), 1); 0278 QCOMPARE(collections, Collection::List() << collection2); 0279 0280 collectionFetch = mStore->fetchCollections(collection2, FileStore::CollectionFetchJob::Base); 0281 0282 QVERIFY(collectionFetch->exec()); 0283 0284 collections = collectionFetch->collections(); 0285 QCOMPARE((int)collections.count(), 1); 0286 0287 collection = collections.first(); 0288 QCOMPARE(collection, collection2); 0289 QCOMPARE(collection.remoteRevision(), remoteRevision); 0290 0291 // test compact after delete from before the end of more than one mbox 0292 Collection collection3; 0293 collection3.setName(QStringLiteral("collection3")); 0294 collection3.setRemoteId(QStringLiteral("collection3")); 0295 collection3.setParentCollection(mStore->topLevelCollection()); 0296 0297 Item item3; 0298 item3.setRemoteId(QString::number(entryList3.first().messageOffset())); 0299 item3.setParentCollection(collection3); 0300 0301 itemDelete = mStore->deleteItem(item3); 0302 0303 QVERIFY(itemDelete->exec()); 0304 0305 Collection collection4; 0306 collection4.setName(QStringLiteral("collection4")); 0307 collection4.setRemoteId(QStringLiteral("collection4")); 0308 collection4.setParentCollection(mStore->topLevelCollection()); 0309 0310 Item item4; 0311 item4.setRemoteId(QString::number(entryList3.value(1).messageOffset())); 0312 item4.setParentCollection(collection4); 0313 0314 itemDelete = mStore->deleteItem(item4); 0315 0316 QVERIFY(itemDelete->exec()); 0317 0318 job = mStore->compactStore(); 0319 0320 collectionSpy = new QSignalSpy(job, &FileStore::StoreCompactJob::collectionsChanged); 0321 itemSpy = new QSignalSpy(job, &FileStore::StoreCompactJob::itemsChanged); 0322 0323 QVERIFY(job->exec()); 0324 QCOMPARE(job->error(), 0); 0325 0326 collections = job->changedCollections(); 0327 items = job->changedItems(); 0328 0329 QCOMPARE(collections.count(), 2); 0330 QCOMPARE(items.count(), 5); 0331 0332 QCOMPARE(collectionSpy->count(), 2); 0333 QCOMPARE(itemSpy->count(), 2); 0334 0335 QCOMPARE(collectionsFromSpy(collectionSpy), collections); 0336 QCOMPARE(itemsFromSpy(itemSpy), items); 0337 0338 QHash<QString, Collection> compactedCollections; 0339 for (const Collection &col : std::as_const(collections)) { 0340 compactedCollections.insert(col.remoteId(), col); 0341 } 0342 QCOMPARE(compactedCollections.count(), 2); 0343 0344 QVERIFY(compactedCollections.contains(collection3.remoteId())); 0345 collection = compactedCollections[collection3.remoteId()]; 0346 QCOMPARE(collection, collection3); 0347 QVERIFY(collection.hasAttribute<FileStore::EntityCompactChangeAttribute>()); 0348 attribute = collection.attribute<FileStore::EntityCompactChangeAttribute>(); 0349 QCOMPARE(attribute->remoteRevision().toInt(), collection3.remoteRevision().toInt() + 1); 0350 0351 QVERIFY(mbox3.load(mbox3.fileName())); 0352 entryList = mbox3.entries(); 0353 0354 // The order of items depends on the order of iteration of a QHash in MixedMaildirStore. 0355 // This makes sure that the items are always sorted by collection and offset 0356 std::sort(items.begin(), items.end(), [](const Akonadi::Item &left, const Akonadi::Item &right) { 0357 return left.parentCollection().remoteId().compare(right.parentCollection().remoteId()) < 0 0358 || (left.parentCollection().remoteId() == right.parentCollection().remoteId() && changedOffset(left) < changedOffset(right)); 0359 }); 0360 0361 entryList3.pop_front(); 0362 for (int i = 0; i < entryList3.count(); ++i) { 0363 entryList3[i] = MBoxEntry(changedOffset(items.first())); 0364 items.pop_front(); 0365 } 0366 QCOMPARE(entryList, entryList3); 0367 0368 QVERIFY(compactedCollections.contains(collection4.remoteId())); 0369 collection = compactedCollections[collection4.remoteId()]; 0370 QCOMPARE(collection, collection4); 0371 QVERIFY(collection.hasAttribute<FileStore::EntityCompactChangeAttribute>()); 0372 attribute = collection.attribute<FileStore::EntityCompactChangeAttribute>(); 0373 QCOMPARE(attribute->remoteRevision().toInt(), collection4.remoteRevision().toInt() + 1); 0374 0375 QVERIFY(mbox4.load(mbox4.fileName())); 0376 entryList = mbox4.entries(); 0377 0378 entryList4.removeAt(1); 0379 for (int i = 0; i < items.count(); ++i) { 0380 entryList4[i + 1] = MBoxEntry(changedOffset(items[i])); 0381 } 0382 QCOMPARE(entryList, entryList4); 0383 0384 var = job->property("onDiskIndexInvalidated"); 0385 QVERIFY(var.isValid()); 0386 QCOMPARE(var.userType(), colListVar.userType()); 0387 0388 collections = var.value<Collection::List>(); 0389 QCOMPARE((int)collections.count(), 2); 0390 QCOMPARE(collections, Collection::List() << collection3 << collection4); 0391 } 0392 0393 QTEST_MAIN(StoreCompactTest) 0394 0395 #include "storecompacttest.moc"