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"