File indexing completed on 2024-12-22 04:57:36

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/itemfetchjob.h"
0015 #include "filestore/storecompactjob.h"
0016 
0017 #include "libmaildir/maildir.h"
0018 
0019 #include <KMbox/MBox>
0020 
0021 #include <KMime/Message>
0022 
0023 #include <QRandomGenerator>
0024 #include <QTemporaryDir>
0025 
0026 #include <QSignalSpy>
0027 
0028 #include <QDir>
0029 #include <QFileInfo>
0030 #include <QTest>
0031 
0032 using namespace Akonadi;
0033 
0034 static Collection::List collectionsFromSpy(QSignalSpy *spy)
0035 {
0036     Collection::List collections;
0037 
0038     QListIterator<QList<QVariant>> it(*spy);
0039     while (it.hasNext()) {
0040         const QList<QVariant> invocation = it.next();
0041         Q_ASSERT(invocation.count() == 1);
0042 
0043         collections << invocation.first().value<Collection::List>();
0044     }
0045 
0046     return collections;
0047 }
0048 
0049 static Item::List itemsFromSpy(QSignalSpy *spy)
0050 {
0051     Item::List items;
0052 
0053     QListIterator<QList<QVariant>> it(*spy);
0054     while (it.hasNext()) {
0055         const QList<QVariant> invocation = it.next();
0056         Q_ASSERT(invocation.count() == 1);
0057 
0058         items << invocation.first().value<Item::List>();
0059     }
0060 
0061     return items;
0062 }
0063 
0064 static bool fullEntryCompare(const KMBox::MBoxEntry &a, const KMBox::MBoxEntry &b)
0065 {
0066     return a.messageOffset() == b.messageOffset() && a.separatorSize() == b.separatorSize() && a.messageSize() == b.messageSize();
0067 }
0068 
0069 class ItemDeleteTest : public QObject
0070 {
0071     Q_OBJECT
0072 
0073 public:
0074     ItemDeleteTest()
0075         : QObject()
0076         , mStore(nullptr)
0077         , mDir(nullptr)
0078     {
0079         // for monitoring signals
0080         qRegisterMetaType<Akonadi::Collection::List>();
0081         qRegisterMetaType<Akonadi::Item::List>();
0082     }
0083 
0084     ~ItemDeleteTest() override
0085     {
0086         delete mStore;
0087         delete mDir;
0088     }
0089 
0090 private:
0091     MixedMaildirStore *mStore = nullptr;
0092     QTemporaryDir *mDir = nullptr;
0093 
0094 private Q_SLOTS:
0095     void init();
0096     void cleanup();
0097     void testMaildir();
0098     void testMBox();
0099     void testCachePreservation();
0100     void testExpectedFailure();
0101 };
0102 
0103 void ItemDeleteTest::init()
0104 {
0105     mStore = new MixedMaildirStore;
0106 
0107     mDir = new QTemporaryDir;
0108     QVERIFY(mDir->isValid());
0109     QVERIFY(QDir(mDir->path()).exists());
0110 }
0111 
0112 void ItemDeleteTest::cleanup()
0113 {
0114     delete mStore;
0115     mStore = nullptr;
0116     delete mDir;
0117     mDir = nullptr;
0118 }
0119 
0120 void ItemDeleteTest::testMaildir()
0121 {
0122     QDir topDir(mDir->path());
0123 
0124     QVERIFY(TestDataUtil::installFolder(QStringLiteral("maildir"), topDir.path(), QStringLiteral("collection1")));
0125 
0126     KPIM::Maildir topLevelMd(topDir.path(), true);
0127     KPIM::Maildir md1 = topLevelMd.subFolder(QStringLiteral("collection1"));
0128     QVERIFY(md1.isValid());
0129 
0130     QStringList md1EntryList = md1.entryList();
0131     QSet<QString> entrySet1(md1EntryList.cbegin(), md1EntryList.cend());
0132     QCOMPARE((int)entrySet1.count(), 4);
0133 
0134     mStore->setPath(topDir.path());
0135 
0136     // common variables
0137     FileStore::ItemDeleteJob *job = nullptr;
0138     QSet<QString> entrySet;
0139     QSet<QString> delIdSet;
0140     QString delId;
0141 
0142     // test deleting one message
0143     Collection collection1;
0144     collection1.setName(QStringLiteral("collection1"));
0145     collection1.setRemoteId(QStringLiteral("collection1"));
0146     collection1.setParentCollection(mStore->topLevelCollection());
0147 
0148     Item item1;
0149     item1.setMimeType(KMime::Message::mimeType());
0150     item1.setId(QRandomGenerator::global()->generate());
0151     item1.setRemoteId(*entrySet1.cbegin());
0152     item1.setParentCollection(collection1);
0153 
0154     job = mStore->deleteItem(item1);
0155 
0156     QVERIFY(job->exec());
0157     QCOMPARE(job->error(), 0);
0158 
0159     Item item = job->item();
0160     QCOMPARE(item.id(), item1.id());
0161 
0162     md1EntryList = md1.entryList();
0163     entrySet = QSet<QString>(md1EntryList.cbegin(), md1EntryList.cend());
0164     QCOMPARE((int)entrySet.count(), 3);
0165 
0166     delIdSet = entrySet1.subtract(entrySet);
0167     QCOMPARE((int)delIdSet.count(), 1);
0168 
0169     delId = *delIdSet.cbegin();
0170     QCOMPARE(delId, *entrySet1.cbegin());
0171     QCOMPARE(delId, item.remoteId());
0172 
0173     // test failure of deleting again
0174     job = mStore->deleteItem(item1);
0175 
0176     QVERIFY(!job->exec());
0177     QCOMPARE(job->error(), (int)FileStore::Job::InvalidJobContext);
0178 }
0179 
0180 void ItemDeleteTest::testMBox()
0181 {
0182     QDir topDir(mDir->path());
0183 
0184     QVERIFY(TestDataUtil::installFolder(QStringLiteral("mbox"), topDir.path(), QStringLiteral("collection1")));
0185 
0186     QFileInfo fileInfo1(topDir.path(), QStringLiteral("collection1"));
0187     KMBox::MBox mbox1;
0188     QVERIFY(mbox1.load(fileInfo1.absoluteFilePath()));
0189     KMBox::MBoxEntry::List entryList1 = mbox1.entries();
0190     QCOMPARE((int)entryList1.count(), 4);
0191     int size1 = fileInfo1.size();
0192 
0193     mStore->setPath(topDir.path());
0194 
0195     // common variables
0196     FileStore::ItemDeleteJob *job = nullptr;
0197     FileStore::ItemFetchJob *itemFetch = nullptr;
0198     FileStore::StoreCompactJob *storeCompact = nullptr;
0199 
0200     Item::List items;
0201     Collection::List collections;
0202     KMBox::MBoxEntry::List entryList;
0203 
0204     QSignalSpy *collectionsSpy = nullptr;
0205     QSignalSpy *itemsSpy = nullptr;
0206 
0207     QVariant var;
0208 
0209     // test deleting last item in mbox
0210     // file stays untouched, message still accessible through MBox, but item gone
0211     Collection collection1;
0212     collection1.setName(QStringLiteral("collection1"));
0213     collection1.setRemoteId(QStringLiteral("collection1"));
0214     collection1.setParentCollection(mStore->topLevelCollection());
0215 
0216     Item item4;
0217     item4.setMimeType(KMime::Message::mimeType());
0218     item4.setId(QRandomGenerator::global()->generate());
0219     item4.setRemoteId(QString::number(entryList1.value(3).messageOffset()));
0220     item4.setParentCollection(collection1);
0221 
0222     job = mStore->deleteItem(item4);
0223 
0224     QVERIFY(job->exec());
0225     QCOMPARE(job->error(), 0);
0226 
0227     Item item = job->item();
0228     QCOMPARE(item.id(), item4.id());
0229 
0230     fileInfo1.refresh();
0231     QCOMPARE((int)fileInfo1.size(), size1);
0232     QVERIFY(mbox1.load(fileInfo1.absoluteFilePath()));
0233     entryList = mbox1.entries();
0234     QCOMPARE(entryList.count(), entryList1.count());
0235     QCOMPARE(entryList.value(3).messageOffset(), entryList1.value(3).messageOffset());
0236 
0237     var = job->property("compactStore");
0238     QVERIFY(var.isValid());
0239     QCOMPARE(var.userType(), QMetaType::Bool);
0240     QCOMPARE(var.toBool(), true);
0241 
0242     itemFetch = mStore->fetchItems(collection1);
0243 
0244     QVERIFY(itemFetch->exec());
0245     QCOMPARE(itemFetch->error(), 0);
0246 
0247     items = itemFetch->items();
0248     QCOMPARE((int)items.count(), 3);
0249     QCOMPARE(items.value(0).remoteId(), QString::number(entryList1.value(0).messageOffset()));
0250     QCOMPARE(items.value(1).remoteId(), QString::number(entryList1.value(1).messageOffset()));
0251     QCOMPARE(items.value(2).remoteId(), QString::number(entryList1.value(2).messageOffset()));
0252 
0253     // test that the item is purged from the file on store compaction
0254     // last item purging does not change any others
0255     storeCompact = mStore->compactStore();
0256 
0257     collectionsSpy = new QSignalSpy(storeCompact, &FileStore::StoreCompactJob::collectionsChanged);
0258     itemsSpy = new QSignalSpy(storeCompact, &FileStore::StoreCompactJob::itemsChanged);
0259 
0260     QVERIFY(storeCompact->exec());
0261     QCOMPARE(storeCompact->error(), 0);
0262 
0263     collections = storeCompact->changedCollections();
0264     QCOMPARE(collections.count(), 0);
0265     items = storeCompact->changedItems();
0266     QCOMPARE(items.count(), 0);
0267 
0268     QCOMPARE(collectionsFromSpy(collectionsSpy), collections);
0269     QCOMPARE(itemsFromSpy(itemsSpy), items);
0270 
0271     fileInfo1.refresh();
0272     QVERIFY(fileInfo1.size() < size1);
0273     size1 = fileInfo1.size();
0274     QVERIFY(mbox1.load(fileInfo1.absoluteFilePath()));
0275     entryList = mbox1.entries();
0276     entryList1.pop_back();
0277     QVERIFY(std::equal(entryList1.begin(), entryList1.end(), entryList.begin(), fullEntryCompare));
0278 
0279     // test deleting item somewhere between first and last
0280     // again, file stays untouched, message still accessible through MBox, but item gone
0281     Item item2;
0282     item2.setMimeType(KMime::Message::mimeType());
0283     item2.setId(QRandomGenerator::global()->generate());
0284     item2.setRemoteId(QString::number(entryList1.value(1).messageOffset()));
0285     item2.setParentCollection(collection1);
0286 
0287     job = mStore->deleteItem(item2);
0288 
0289     QVERIFY(job->exec());
0290     QCOMPARE(job->error(), 0);
0291 
0292     item = job->item();
0293     QCOMPARE(item.id(), item2.id());
0294 
0295     fileInfo1.refresh();
0296     QCOMPARE((int)fileInfo1.size(), size1);
0297     QVERIFY(mbox1.load(fileInfo1.absoluteFilePath()));
0298     entryList = mbox1.entries();
0299     QCOMPARE(entryList.count(), entryList1.count());
0300     QCOMPARE(entryList.value(1).messageOffset(), entryList1.value(1).messageOffset());
0301 
0302     var = job->property("compactStore");
0303     QVERIFY(var.isValid());
0304     QCOMPARE(var.userType(), QMetaType::Bool);
0305     QCOMPARE(var.toBool(), true);
0306 
0307     itemFetch = mStore->fetchItems(collection1);
0308 
0309     QVERIFY(itemFetch->exec());
0310     QCOMPARE(itemFetch->error(), 0);
0311 
0312     items = itemFetch->items();
0313     QCOMPARE((int)items.count(), 2);
0314     QCOMPARE(items.value(0).remoteId(), QString::number(entryList1.value(0).messageOffset()));
0315     QCOMPARE(items.value(1).remoteId(), QString::number(entryList1.value(2).messageOffset()));
0316 
0317     // test that the item is purged from the file on store compaction
0318     // non-last item purging changes all items after it
0319     storeCompact = mStore->compactStore();
0320 
0321     collectionsSpy = new QSignalSpy(storeCompact, &FileStore::StoreCompactJob::collectionsChanged);
0322     itemsSpy = new QSignalSpy(storeCompact, &FileStore::StoreCompactJob::itemsChanged);
0323 
0324     QVERIFY(storeCompact->exec());
0325     QCOMPARE(storeCompact->error(), 0);
0326 
0327     collections = storeCompact->changedCollections();
0328     QCOMPARE(collections.count(), 1);
0329     items = storeCompact->changedItems();
0330     QCOMPARE(items.count(), 1);
0331 
0332     QCOMPARE(collectionsFromSpy(collectionsSpy), collections);
0333     QCOMPARE(itemsFromSpy(itemsSpy), items);
0334 
0335     Item item3;
0336     item3.setRemoteId(QString::number(entryList1.value(2).messageOffset()));
0337 
0338     item = items.first();
0339     QCOMPARE(item3.remoteId(), item.remoteId());
0340 
0341     QVERIFY(item.hasAttribute<FileStore::EntityCompactChangeAttribute>());
0342     auto attribute = item.attribute<FileStore::EntityCompactChangeAttribute>();
0343 
0344     QString newRemoteId = attribute->remoteId();
0345     QVERIFY(!newRemoteId.isEmpty());
0346 
0347     fileInfo1.refresh();
0348     QVERIFY(fileInfo1.size() < size1);
0349     size1 = fileInfo1.size();
0350     QVERIFY(mbox1.load(fileInfo1.absoluteFilePath()));
0351     entryList = mbox1.entries();
0352     QCOMPARE(QString::number(entryList.value(1).messageOffset()), newRemoteId);
0353 
0354     entryList1.removeAt(1);
0355     QCOMPARE(entryList1.count(), entryList.count());
0356     QCOMPARE(QString::number(entryList1.value(1).messageOffset()), item3.remoteId());
0357 }
0358 
0359 void ItemDeleteTest::testCachePreservation()
0360 {
0361     QDir topDir(mDir->path());
0362 
0363     QVERIFY(TestDataUtil::installFolder(QStringLiteral("maildir"), topDir.path(), QStringLiteral("collection1")));
0364     QVERIFY(TestDataUtil::installFolder(QStringLiteral("mbox"), topDir.path(), QStringLiteral("collection2")));
0365 
0366     KPIM::Maildir topLevelMd(topDir.path(), true);
0367     KPIM::Maildir md1 = topLevelMd.subFolder(QStringLiteral("collection1"));
0368     QVERIFY(md1.isValid());
0369 
0370     const QStringList md1EntryList = md1.entryList();
0371     QSet<QString> entrySet1(md1EntryList.cbegin(), md1EntryList.cend());
0372     QCOMPARE((int)entrySet1.count(), 4);
0373 
0374     QFileInfo fileInfo2(topDir.path(), QStringLiteral("collection2"));
0375     KMBox::MBox mbox2;
0376     QVERIFY(mbox2.load(fileInfo2.absoluteFilePath()));
0377     KMBox::MBoxEntry::List entryList2 = mbox2.entries();
0378     QCOMPARE((int)entryList2.count(), 4);
0379 
0380     mStore->setPath(topDir.path());
0381 
0382     // common variables
0383     const QVariant colListVar = QVariant::fromValue<Collection::List>(Collection::List());
0384     QVariant var;
0385     Collection::List collections;
0386     Item::List items;
0387     QMap<QByteArray, int> flagCounts;
0388 
0389     FileStore::ItemDeleteJob *job = nullptr;
0390     FileStore::ItemFetchJob *itemFetch = nullptr;
0391 
0392     // test deleting from maildir
0393     Collection collection1;
0394     collection1.setName(QStringLiteral("collection1"));
0395     collection1.setRemoteId(QStringLiteral("collection1"));
0396     collection1.setParentCollection(mStore->topLevelCollection());
0397 
0398     Item item1;
0399     item1.setMimeType(KMime::Message::mimeType());
0400     item1.setId(QRandomGenerator::global()->generate());
0401     item1.setRemoteId(*entrySet1.cbegin());
0402     item1.setParentCollection(collection1);
0403 
0404     job = mStore->deleteItem(item1);
0405 
0406     QVERIFY(job->exec());
0407     QCOMPARE(job->error(), 0);
0408 
0409     Item item = job->item();
0410     QCOMPARE(item.id(), item1.id());
0411 
0412     // check for index preservation
0413     var = job->property("onDiskIndexInvalidated");
0414     QVERIFY(var.isValid());
0415     QCOMPARE(var.userType(), colListVar.userType());
0416 
0417     collections = var.value<Collection::List>();
0418     QCOMPARE((int)collections.count(), 1);
0419     QCOMPARE(collections.first(), collection1);
0420 
0421     // get the items and check the flags (see data/README)
0422     itemFetch = mStore->fetchItems(collection1);
0423     QVERIFY(itemFetch->exec());
0424     QCOMPARE(itemFetch->error(), 0);
0425 
0426     items = itemFetch->items();
0427     QCOMPARE((int)items.count(), 3);
0428     for (const Item &item : std::as_const(items)) {
0429         const auto flags = item.flags();
0430         for (const QByteArray &flag : flags) {
0431             ++flagCounts[flag];
0432         }
0433     }
0434 
0435     // TODO since we don't know which message we've deleted, we can only check if some flags are present
0436     int flagCountTotal = 0;
0437     for (int count : std::as_const(flagCounts)) {
0438         flagCountTotal += count;
0439     }
0440     QVERIFY(flagCountTotal > 0);
0441     flagCounts.clear();
0442 
0443     // test deleting from mbox
0444     Collection collection2;
0445     collection2.setName(QStringLiteral("collection2"));
0446     collection2.setRemoteId(QStringLiteral("collection2"));
0447     collection2.setParentCollection(mStore->topLevelCollection());
0448 
0449     Item item2;
0450     item2.setMimeType(KMime::Message::mimeType());
0451     item2.setId(QRandomGenerator::global()->generate());
0452     item2.setRemoteId(QString::number(entryList2.value(1).messageOffset()));
0453     item2.setParentCollection(collection2);
0454 
0455     job = mStore->deleteItem(item2);
0456 
0457     QVERIFY(job->exec());
0458     QCOMPARE(job->error(), 0);
0459 
0460     item = job->item();
0461     QCOMPARE(item.id(), item2.id());
0462 
0463     // at this point no change has been written to disk yet, so index and mbox file are
0464     // still in sync
0465     var = job->property("onDiskIndexInvalidated");
0466     QVERIFY(!var.isValid());
0467 
0468     FileStore::StoreCompactJob *storeCompact = mStore->compactStore();
0469 
0470     QVERIFY(storeCompact->exec());
0471     QCOMPARE(storeCompact->error(), 0);
0472 
0473     // check for index preservation
0474     var = storeCompact->property("onDiskIndexInvalidated");
0475     QVERIFY(var.isValid());
0476     QCOMPARE(var.userType(), colListVar.userType());
0477 
0478     collections = var.value<Collection::List>();
0479     QCOMPARE((int)collections.count(), 1);
0480     QCOMPARE(collections.first(), collection2);
0481 
0482     // get the items and check the flags (see data/README)
0483     itemFetch = mStore->fetchItems(collection2);
0484     QVERIFY(itemFetch->exec());
0485     QCOMPARE(itemFetch->error(), 0);
0486 
0487     items = itemFetch->items();
0488     QCOMPARE((int)items.count(), 3);
0489     for (const Item &item : std::as_const(items)) {
0490         const auto flags = item.flags();
0491         for (const QByteArray &flag : flags) {
0492             ++flagCounts[flag];
0493         }
0494     }
0495 
0496     // we've deleted message 2, it flagged TODO and seen
0497     QCOMPARE(flagCounts["\\SEEN"], 1);
0498     QCOMPARE(flagCounts["\\FLAGGED"], 1);
0499     flagCounts.clear();
0500 }
0501 
0502 void ItemDeleteTest::testExpectedFailure()
0503 {
0504     QDir topDir(mDir->path());
0505 
0506     QVERIFY(TestDataUtil::installFolder(QStringLiteral("maildir"), topDir.path(), QStringLiteral("collection1")));
0507     QVERIFY(TestDataUtil::installFolder(QStringLiteral("mbox"), topDir.path(), QStringLiteral("collection2")));
0508 
0509     KPIM::Maildir topLevelMd(topDir.path(), true);
0510     KPIM::Maildir md1 = topLevelMd.subFolder(QStringLiteral("collection1"));
0511     QVERIFY(md1.isValid());
0512 
0513     const QStringList md1EntryList = md1.entryList();
0514     QSet<QString> entrySet1(md1EntryList.cbegin(), md1EntryList.cend());
0515     QCOMPARE((int)entrySet1.count(), 4);
0516 
0517     QFileInfo fileInfo2(topDir.path(), QStringLiteral("collection2"));
0518     KMBox::MBox mbox2;
0519     QVERIFY(mbox2.load(fileInfo2.absoluteFilePath()));
0520     KMBox::MBoxEntry::List entryList2 = mbox2.entries();
0521     QCOMPARE((int)entryList2.count(), 4);
0522 
0523     mStore->setPath(topDir.path());
0524 
0525     // common variables
0526     FileStore::ItemDeleteJob *job = nullptr;
0527     FileStore::ItemFetchJob *itemFetch = nullptr;
0528     FileStore::StoreCompactJob *storeCompact = nullptr;
0529 
0530     // test failure of fetching an item previously deleted from maildir
0531     Collection collection1;
0532     collection1.setName(QStringLiteral("collection1"));
0533     collection1.setRemoteId(QStringLiteral("collection1"));
0534     collection1.setParentCollection(mStore->topLevelCollection());
0535 
0536     Item item1_1;
0537     item1_1.setRemoteId(*entrySet1.cbegin());
0538     item1_1.setParentCollection(collection1);
0539 
0540     job = mStore->deleteItem(item1_1);
0541 
0542     QVERIFY(job->exec());
0543 
0544     itemFetch = mStore->fetchItem(item1_1);
0545 
0546     QVERIFY(!itemFetch->exec());
0547     QCOMPARE(itemFetch->error(), (int)FileStore::Job::InvalidJobContext);
0548 
0549     // test failure of deleting an item from maildir again
0550     job = mStore->deleteItem(item1_1);
0551 
0552     QVERIFY(!job->exec());
0553     QCOMPARE(job->error(), (int)FileStore::Job::InvalidJobContext);
0554 
0555     // test failure of fetching an item previously deleted from mbox
0556     Collection collection2;
0557     collection2.setName(QStringLiteral("collection2"));
0558     collection2.setRemoteId(QStringLiteral("collection2"));
0559     collection2.setParentCollection(mStore->topLevelCollection());
0560 
0561     Item item2_1;
0562     item2_1.setRemoteId(QString::number(entryList2.value(0).messageOffset()));
0563     item2_1.setParentCollection(collection2);
0564 
0565     job = mStore->deleteItem(item2_1);
0566 
0567     QVERIFY(job->exec());
0568 
0569     itemFetch = mStore->fetchItem(item2_1);
0570 
0571     QVERIFY(!itemFetch->exec());
0572     QCOMPARE(itemFetch->error(), (int)FileStore::Job::InvalidJobContext);
0573 
0574     // test failure of deleting an item from mbox again
0575     job = mStore->deleteItem(item2_1);
0576 
0577     QVERIFY(!job->exec());
0578     QCOMPARE(job->error(), (int)FileStore::Job::InvalidJobContext);
0579 
0580     // compact store and check that offset 0 is a valid remoteId again, but
0581     // offset of other items (e.f. item 4) are no longer valid (moved to the front of the file)
0582     storeCompact = mStore->compactStore();
0583 
0584     QVERIFY(storeCompact->exec());
0585 
0586     itemFetch = mStore->fetchItem(item2_1);
0587 
0588     QVERIFY(itemFetch->exec());
0589 
0590     Item item4_1;
0591     item4_1.setRemoteId(QString::number(entryList2.value(3).messageOffset()));
0592     item4_1.setParentCollection(collection2);
0593 
0594     itemFetch = mStore->fetchItem(item4_1);
0595 
0596     QVERIFY(!itemFetch->exec());
0597     QCOMPARE(itemFetch->error(), (int)FileStore::Job::InvalidJobContext);
0598 }
0599 
0600 QTEST_MAIN(ItemDeleteTest)
0601 
0602 #include "itemdeletetest.moc"