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)