File indexing completed on 2024-11-10 04:40:13
0001 /* 0002 SPDX-FileCopyrightText: 2013 Christian Mollekopf <mollekopf@kolabsys.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include <QObject> 0008 0009 #include "changerecorder_p.h" 0010 #include "collectioncreatejob.h" 0011 #include "control.h" 0012 #include "entitytreemodel.h" 0013 #include "entitytreemodel_p.h" 0014 #include "itemcreatejob.h" 0015 #include "monitor_p.h" 0016 #include "qtest_akonadi.h" 0017 0018 using namespace Akonadi; 0019 0020 class InspectableETM : public EntityTreeModel 0021 { 0022 public: 0023 explicit InspectableETM(ChangeRecorder *monitor, QObject *parent = nullptr) 0024 : EntityTreeModel(monitor, parent) 0025 { 0026 } 0027 EntityTreeModelPrivate *etmPrivate() 0028 { 0029 return d_ptr.get(); 0030 } 0031 }; 0032 0033 /** 0034 * This is a test for the LazyPopulation of the ETM and the associated refcounting in the Monitor. 0035 */ 0036 class LazyPopulationTest : public QObject 0037 { 0038 Q_OBJECT 0039 0040 private Q_SLOTS: 0041 void initTestCase(); 0042 0043 /** 0044 * Test a complete scenario that checks: 0045 * * loading on referencing 0046 * * buffering after referencing 0047 * * purging after the collection leaves the buffer 0048 * * not-fetching when a collection is not buffered and not referenced 0049 * * reloading after a collection becomes referenced again 0050 */ 0051 void testItemAdded(); 0052 /* 0053 * Test what happens if we 0054 * * Create an item 0055 * * Reference before item added signal arrives 0056 * * Try fetching rest of items 0057 */ 0058 void testItemAddedBeforeFetch(); 0059 0060 /* 0061 * We purge an empty collection and make sure it can be fetched later on. 0062 * * reference collection to remember empty status 0063 * * purge collection 0064 * * add item (it should not be added since not monitored) 0065 * * reference collection and make sure items are added 0066 */ 0067 void testPurgeEmptyCollection(); 0068 0069 private: 0070 Collection res3; 0071 static const int numberOfRootCollections = 4; 0072 static const int bufferSize; 0073 }; 0074 0075 const int LazyPopulationTest::bufferSize = MonitorPrivate::PurgeBuffer::buffersize(); 0076 0077 void LazyPopulationTest::initTestCase() 0078 { 0079 qRegisterMetaType<Akonadi::Collection::Id>("Akonadi::Collection::Id"); 0080 AkonadiTest::checkTestIsIsolated(); 0081 AkonadiTest::setAllResourcesOffline(); 0082 0083 res3 = Collection(AkonadiTest::collectionIdFromPath(QStringLiteral("res3"))); 0084 0085 // Set up a bunch of collections that we can select to purge a collection from the buffer 0086 0087 // Number of buffered collections in the monitor 0088 const int bufferSize = MonitorPrivate::PurgeBuffer::buffersize(); 0089 for (int i = 0; i < bufferSize; i++) { 0090 Collection col; 0091 col.setParentCollection(res3); 0092 col.setName(QStringLiteral("col%1").arg(i)); 0093 auto create = new CollectionCreateJob(col, this); 0094 AKVERIFYEXEC(create); 0095 } 0096 } 0097 0098 QModelIndex getIndex(const QString &string, EntityTreeModel *model) 0099 { 0100 QModelIndexList list = model->match(model->index(0, 0), Qt::DisplayRole, string, 1, Qt::MatchRecursive); 0101 if (list.isEmpty()) { 0102 return QModelIndex(); 0103 } 0104 return list.first(); 0105 } 0106 0107 /** 0108 * Since we have no sensible way to figure out if the model is fully populated, 0109 * we use the brute force approach. 0110 */ 0111 bool waitForPopulation(const QModelIndex &idx, EntityTreeModel *model, int count) 0112 { 0113 for (int i = 0; i < 500; i++) { 0114 if (model->rowCount(idx) >= count) { 0115 return true; 0116 } 0117 QTest::qWait(10); 0118 } 0119 return false; 0120 } 0121 0122 void referenceCollection(EntityTreeModel *model, int index) 0123 { 0124 QModelIndex idx = getIndex(QStringLiteral("col%1").arg(index), model); 0125 QVERIFY(idx.isValid()); 0126 model->setData(idx, QVariant(), EntityTreeModel::CollectionRefRole); 0127 model->setData(idx, QVariant(), EntityTreeModel::CollectionDerefRole); 0128 } 0129 0130 void referenceCollections(EntityTreeModel *model, int count) 0131 { 0132 for (int i = 0; i < count; i++) { 0133 referenceCollection(model, i); 0134 } 0135 } 0136 0137 void LazyPopulationTest::testItemAdded() 0138 { 0139 const int bufferSize = MonitorPrivate::PurgeBuffer::buffersize(); 0140 const QString mainCollectionName(QStringLiteral("main")); 0141 Collection monitorCol; 0142 { 0143 monitorCol.setParentCollection(res3); 0144 monitorCol.setName(mainCollectionName); 0145 auto create = new CollectionCreateJob(monitorCol, this); 0146 AKVERIFYEXEC(create); 0147 monitorCol = create->collection(); 0148 } 0149 0150 Item item1; 0151 { 0152 item1.setMimeType(QStringLiteral("application/octet-stream")); 0153 auto append = new ItemCreateJob(item1, monitorCol, this); 0154 AKVERIFYEXEC(append); 0155 item1 = append->item(); 0156 } 0157 0158 auto changeRecorder = new ChangeRecorder(this); 0159 changeRecorder->setCollectionMonitored(Collection::root()); 0160 QVERIFY(AkonadiTest::akWaitForSignal(changeRecorder, &Monitor::monitorReady)); 0161 auto model = new InspectableETM(changeRecorder, this); 0162 model->setItemPopulationStrategy(Akonadi::EntityTreeModel::LazyPopulation); 0163 0164 // Wait for initial listing to complete 0165 QVERIFY(waitForPopulation(QModelIndex(), model, numberOfRootCollections)); 0166 0167 const QModelIndex res3Index = getIndex(QStringLiteral("res3"), model); 0168 QVERIFY(waitForPopulation(res3Index, model, bufferSize + 1)); 0169 0170 QModelIndex monitorIndex = getIndex(mainCollectionName, model); 0171 QVERIFY(monitorIndex.isValid()); 0172 0173 // Start 0174 0175 //---Check that the item is present after the collection was referenced 0176 model->setData(monitorIndex, QVariant(), EntityTreeModel::CollectionRefRole); 0177 model->fetchMore(monitorIndex); 0178 // Wait for collection to be fetched 0179 QVERIFY(waitForPopulation(monitorIndex, model, 1)); 0180 0181 //---ensure we cannot fetchMore again 0182 QVERIFY(!model->etmPrivate()->canFetchMore(monitorIndex)); 0183 0184 // The item should now be present 0185 QCOMPARE(model->index(0, 0, monitorIndex).data(Akonadi::EntityTreeModel::ItemIdRole).value<Akonadi::Item::Id>(), item1.id()); 0186 0187 //---ensure item1 is still available after no longer being referenced due to buffering 0188 model->setData(monitorIndex, QVariant(), EntityTreeModel::CollectionDerefRole); 0189 QCOMPARE(model->index(0, 0, monitorIndex).data(Akonadi::EntityTreeModel::ItemIdRole).value<Akonadi::Item::Id>(), item1.id()); 0190 0191 //---ensure item1 gets purged after the collection is no longer buffered 0192 // Get the monitorCol out of the buffer 0193 referenceCollections(model, bufferSize - 1); 0194 // The collection is still in the buffer... 0195 QCOMPARE(model->rowCount(monitorIndex), 1); 0196 referenceCollection(model, bufferSize - 1); 0197 //...and now purged 0198 QCOMPARE(model->rowCount(monitorIndex), 0); 0199 QVERIFY(model->etmPrivate()->canFetchMore(monitorIndex)); 0200 0201 //---ensure item2 added to unbuffered and unreferenced collection is not added to the model 0202 Item item2; 0203 { 0204 item2.setMimeType(QStringLiteral("application/octet-stream")); 0205 auto append = new ItemCreateJob(item2, monitorCol, this); 0206 AKVERIFYEXEC(append); 0207 item2 = append->item(); 0208 } 0209 QCOMPARE(model->rowCount(monitorIndex), 0); 0210 0211 //---ensure all items are loaded after re-referencing the collection 0212 model->setData(monitorIndex, QVariant(), EntityTreeModel::CollectionRefRole); 0213 model->fetchMore(monitorIndex); 0214 // Wait for collection to be fetched 0215 QVERIFY(waitForPopulation(monitorIndex, model, 2)); 0216 QCOMPARE(model->rowCount(monitorIndex), 2); 0217 0218 QVERIFY(!model->etmPrivate()->canFetchMore(monitorIndex)); 0219 0220 // purge collection again 0221 model->setData(monitorIndex, QVariant(), EntityTreeModel::CollectionDerefRole); 0222 referenceCollections(model, bufferSize); 0223 QCOMPARE(model->rowCount(monitorIndex), 0); 0224 // fetch when not monitored 0225 QVERIFY(model->etmPrivate()->canFetchMore(monitorIndex)); 0226 model->fetchMore(monitorIndex); 0227 QVERIFY(waitForPopulation(monitorIndex, model, 2)); 0228 // ensure we cannot refetch 0229 QVERIFY(!model->etmPrivate()->canFetchMore(monitorIndex)); 0230 } 0231 0232 void LazyPopulationTest::testItemAddedBeforeFetch() 0233 { 0234 const QString mainCollectionName(QStringLiteral("main2")); 0235 Collection monitorCol; 0236 { 0237 monitorCol.setParentCollection(res3); 0238 monitorCol.setName(mainCollectionName); 0239 auto create = new CollectionCreateJob(monitorCol, this); 0240 AKVERIFYEXEC(create); 0241 monitorCol = create->collection(); 0242 } 0243 0244 auto changeRecorder = new ChangeRecorder(this); 0245 changeRecorder->setCollectionMonitored(Collection::root()); 0246 QVERIFY(AkonadiTest::akWaitForSignal(changeRecorder, &Monitor::monitorReady)); 0247 auto model = new InspectableETM(changeRecorder, this); 0248 model->setItemPopulationStrategy(Akonadi::EntityTreeModel::LazyPopulation); 0249 0250 // Wait for initial listing to complete 0251 QVERIFY(waitForPopulation(QModelIndex(), model, numberOfRootCollections)); 0252 0253 const QModelIndex res3Index = getIndex(QStringLiteral("res3"), model); 0254 QVERIFY(waitForPopulation(res3Index, model, bufferSize + 1)); 0255 0256 QModelIndex monitorIndex = getIndex(mainCollectionName, model); 0257 QVERIFY(monitorIndex.isValid()); 0258 0259 // Create a first item before referencing, it should not show up in the ETM 0260 { 0261 Item item1; 0262 item1.setMimeType(QStringLiteral("application/octet-stream")); 0263 auto append = new ItemCreateJob(item1, monitorCol, this); 0264 AKVERIFYEXEC(append); 0265 } 0266 0267 // Before referenced or fetchMore is called, the collection should be empty 0268 QTest::qWait(500); 0269 QCOMPARE(model->rowCount(monitorIndex), 0); 0270 0271 // Reference the collection 0272 QVERIFY(!model->etmPrivate()->isMonitored(monitorCol.id())); 0273 model->setData(monitorIndex, QVariant(), EntityTreeModel::CollectionRefRole); 0274 QVERIFY(model->etmPrivate()->isMonitored(monitorCol.id())); 0275 0276 // Create another item, it should not be added to the ETM although the signal is emitted from the monitor, but we should be able to fetchMore 0277 { 0278 QSignalSpy addedSpy(changeRecorder, &Monitor::itemAdded); 0279 QVERIFY(addedSpy.isValid()); 0280 Item item2; 0281 item2.setMimeType(QStringLiteral("application/octet-stream")); 0282 auto append = new ItemCreateJob(item2, monitorCol, this); 0283 AKVERIFYEXEC(append); 0284 QTRY_VERIFY(addedSpy.count() >= 1); 0285 } 0286 0287 QVERIFY(model->etmPrivate()->canFetchMore(monitorIndex)); 0288 0289 model->fetchMore(monitorIndex); 0290 // Wait for collection to be fetched 0291 QVERIFY(waitForPopulation(monitorIndex, model, 2)); 0292 } 0293 0294 void LazyPopulationTest::testPurgeEmptyCollection() 0295 { 0296 const QString mainCollectionName(QStringLiteral("main3")); 0297 Collection monitorCol; 0298 { 0299 monitorCol.setParentCollection(res3); 0300 monitorCol.setName(mainCollectionName); 0301 auto create = new CollectionCreateJob(monitorCol, this); 0302 AKVERIFYEXEC(create); 0303 monitorCol = create->collection(); 0304 } 0305 // Monitor without referencing so we get all signals 0306 auto monitor = new Monitor(this); 0307 monitor->setCollectionMonitored(Collection::root()); 0308 QVERIFY(AkonadiTest::akWaitForSignal(monitor, &Monitor::monitorReady)); 0309 0310 auto changeRecorder = new ChangeRecorder(this); 0311 changeRecorder->setCollectionMonitored(Collection::root()); 0312 QVERIFY(AkonadiTest::akWaitForSignal(changeRecorder, &Monitor::monitorReady)); 0313 auto model = new InspectableETM(changeRecorder, this); 0314 model->setItemPopulationStrategy(Akonadi::EntityTreeModel::LazyPopulation); 0315 0316 // Wait for initial listing to complete 0317 QVERIFY(waitForPopulation(QModelIndex(), model, numberOfRootCollections)); 0318 0319 const QModelIndex res3Index = getIndex(QStringLiteral("res3"), model); 0320 QVERIFY(waitForPopulation(res3Index, model, bufferSize + 1)); 0321 0322 QModelIndex monitorIndex = getIndex(mainCollectionName, model); 0323 QVERIFY(monitorIndex.isValid()); 0324 0325 // fetch the collection so we remember the empty status 0326 QSignalSpy populatedSpy(model, &EntityTreeModel::collectionPopulated); 0327 model->setData(monitorIndex, QVariant(), EntityTreeModel::CollectionRefRole); 0328 model->fetchMore(monitorIndex); 0329 // Wait for collection to be fetched 0330 QTRY_VERIFY(populatedSpy.count() >= 1); 0331 0332 // get the collection purged 0333 model->setData(monitorIndex, QVariant(), EntityTreeModel::CollectionDerefRole); 0334 referenceCollections(model, bufferSize); 0335 0336 // create an item 0337 { 0338 QSignalSpy addedSpy(monitor, &Monitor::itemAdded); 0339 QVERIFY(addedSpy.isValid()); 0340 Item item1; 0341 item1.setMimeType(QStringLiteral("application/octet-stream")); 0342 auto append = new ItemCreateJob(item1, monitorCol, this); 0343 AKVERIFYEXEC(append); 0344 QTRY_VERIFY(addedSpy.count() >= 1); 0345 } 0346 0347 // ensure it's not in the model 0348 // fetch the collection 0349 QVERIFY(model->etmPrivate()->canFetchMore(monitorIndex)); 0350 0351 model->fetchMore(monitorIndex); 0352 // Wait for collection to be fetched 0353 QVERIFY(waitForPopulation(monitorIndex, model, 1)); 0354 } 0355 0356 #include "lazypopulationtest.moc" 0357 0358 QTEST_AKONADIMAIN(LazyPopulationTest)