2 * Copyright (C) 2008 Funambol, Inc.
3 * Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
4 * Copyright (C) 2009 Intel Corporation
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) version 3.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 /** @addtogroup ClientTest */
30 #ifdef ENABLE_INTEGRATION_TESTS
32 #include "ClientTest.h"
34 #include <SyncSource.h>
35 #include <TransportAgent.h>
37 #include <syncevo/util.h>
38 #include <syncevo/SyncContext.h>
39 #include <VolatileConfigNode.h>
41 #include <synthesis/dataconversion.h>
55 #include <boost/bind.hpp>
57 #include <syncevo/declarations.h>
60 static set<ClientTest::Cleanup_t> cleanupSet;
63 * Using this pointer automates the open()/beginSync()/endSync()/close()
64 * life cycle: it automatically calls these functions when a new
65 * pointer is assigned or deleted.
67 * Anchors are stored globally in a hash which uses the tracking node
68 * name as key. This name happens to be the unique file path that
69 * is created for each source (see TestEvolution::createSource() and
70 * SyncConfig::getSyncSourceNodes()).
72 class TestingSyncSourcePtr : public std::auto_ptr<TestingSyncSource>
74 typedef std::auto_ptr<TestingSyncSource> base_t;
76 static StringMap m_anchors;
79 TestingSyncSourcePtr() {}
80 TestingSyncSourcePtr(TestingSyncSource *source) :
83 CPPUNIT_ASSERT(source);
84 SOURCE_ASSERT_NO_FAILURE(source, source->open());
85 string node = source->getTrackingNode()->getName();
86 SOURCE_ASSERT_NO_FAILURE(source, source->beginSync(m_anchors[node], ""));
87 const char * serverMode = getenv ("CLIENT_TEST_MODE");
88 if (serverMode && !strcmp (serverMode, "server")) {
89 SOURCE_ASSERT_NO_FAILURE(source, source->enableServerMode());
92 ~TestingSyncSourcePtr()
97 void reset(TestingSyncSource *source = NULL)
100 BOOST_FOREACH(const SyncSource::Operations::CallbackFunctor_t &callback,
101 get()->getOperations().m_endSession) {
104 string node = get()->getTrackingNode()->getName();
105 SOURCE_ASSERT_NO_FAILURE(get(), (m_anchors[node] = get()->endSync(true)));
106 SOURCE_ASSERT_NO_FAILURE(get(), get()->close());
108 CPPUNIT_ASSERT_NO_THROW(base_t::reset(source));
110 SOURCE_ASSERT_NO_FAILURE(source, source->open());
111 string node = source->getTrackingNode()->getName();
112 SOURCE_ASSERT_NO_FAILURE(source, source->beginSync(m_anchors[node], ""));
113 const char * serverMode = getenv ("CLIENT_TEST_MODE");
114 if (serverMode && !strcmp (serverMode, "server")) {
115 SOURCE_ASSERT_NO_FAILURE(source, source->enableServerMode());
117 BOOST_FOREACH(const SyncSource::Operations::CallbackFunctor_t &callback,
118 source->getOperations().m_endSession) {
125 StringMap TestingSyncSourcePtr::m_anchors;
127 bool SyncOptions::defaultWBXML()
129 const char *t = getenv("CLIENT_TEST_XML");
130 if (t && (!strcmp(t, "1") || !strcasecmp(t, "t"))) {
138 std::list<std::string> listItemsOfType(TestingSyncSource *source, int state)
140 std::list<std::string> res;
142 BOOST_FOREACH(const string &luid, source->getItems(SyncSourceChanges::State(state))) {
147 static std::list<std::string> listNewItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::NEW); }
148 static std::list<std::string> listUpdatedItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::UPDATED); }
149 static std::list<std::string> listDeletedItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::DELETED); }
150 static std::list<std::string> listItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::ANY); }
152 int countItemsOfType(TestingSyncSource *source, int type) { return source->getItems(SyncSourceChanges::State(type)).size(); }
153 static int countNewItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::NEW); }
154 static int countUpdatedItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::UPDATED); }
155 static int countDeletedItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::DELETED); }
156 static int countItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::ANY); }
159 /** insert new item, return LUID */
160 static std::string importItem(TestingSyncSource *source, std::string &data)
162 CPPUNIT_ASSERT(source);
164 SyncSourceRaw::InsertItemResult res;
165 SOURCE_ASSERT_NO_FAILURE(source, res = source->insertItemRaw("", data));
166 CPPUNIT_ASSERT(!res.m_luid.empty());
173 /** adds the supported tests to the instance itself */
174 void LocalTests::addTests() {
175 if (config.createSourceA) {
176 ADD_TEST(LocalTests, testOpen);
177 ADD_TEST(LocalTests, testIterateTwice);
178 if (config.insertItem) {
179 ADD_TEST(LocalTests, testSimpleInsert);
180 ADD_TEST(LocalTests, testLocalDeleteAll);
181 ADD_TEST(LocalTests, testComplexInsert);
183 if (config.updateItem) {
184 ADD_TEST(LocalTests, testLocalUpdate);
186 if (config.createSourceB) {
187 ADD_TEST(LocalTests, testChanges);
195 ADD_TEST(LocalTests, testImport);
196 ADD_TEST(LocalTests, testImportDelete);
199 if (config.templateItem &&
200 config.uniqueProperties) {
201 ADD_TEST(LocalTests, testManyChanges);
204 if (config.parentItem &&
206 ADD_TEST(LocalTests, testLinkedItemsParent);
207 if (config.linkedItemsRelaxedSemantic) {
208 ADD_TEST(LocalTests, testLinkedItemsChild);
210 ADD_TEST(LocalTests, testLinkedItemsParentChild);
211 if (config.linkedItemsRelaxedSemantic) {
212 ADD_TEST(LocalTests, testLinkedItemsChildParent);
214 if (config.linkedItemsRelaxedSemantic) {
215 ADD_TEST(LocalTests, testLinkedItemsChildChangesParent);
217 if (config.linkedItemsRelaxedSemantic) {
218 ADD_TEST(LocalTests, testLinkedItemsRemoveParentFirst);
220 ADD_TEST(LocalTests, testLinkedItemsRemoveNormal);
221 if (config.sourceKnowsItemSemantic) {
222 ADD_TEST(LocalTests, testLinkedItemsInsertParentTwice);
223 if (config.linkedItemsRelaxedSemantic) {
224 ADD_TEST(LocalTests, testLinkedItemsInsertChildTwice);
227 ADD_TEST(LocalTests, testLinkedItemsParentUpdate);
228 if (config.linkedItemsRelaxedSemantic) {
229 ADD_TEST(LocalTests, testLinkedItemsUpdateChild);
231 ADD_TEST(LocalTests, testLinkedItemsInsertBothUpdateChild);
232 ADD_TEST(LocalTests, testLinkedItemsInsertBothUpdateParent);
238 std::string LocalTests::insert(CreateSource createSource, const char *data, bool relaxed) {
240 TestingSyncSourcePtr source(createSource());
242 // count number of already existing items
244 CPPUNIT_ASSERT_NO_THROW(numItems = countItems(source.get()));
245 SyncSourceRaw::InsertItemResult res;
246 SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw("", data));
247 CPPUNIT_ASSERT(!res.m_luid.empty());
249 // delete source again
253 // two possible results:
254 // - a new item was added
255 // - the item was matched against an existing one
256 CPPUNIT_ASSERT_NO_THROW(source.reset(createSource()));
257 CPPUNIT_ASSERT_EQUAL(numItems + (res.m_merged ? 0 : 1),
258 countItems(source.get()));
259 CPPUNIT_ASSERT(countNewItems(source.get()) == 0);
260 CPPUNIT_ASSERT(countUpdatedItems(source.get()) == 0);
261 CPPUNIT_ASSERT(countDeletedItems(source.get()) == 0);
267 /** deletes specific item locally via sync source */
268 static std::string updateItem(CreateSource createSource, const std::string &uid, const char *data) {
271 CPPUNIT_ASSERT(createSource.createSource);
274 TestingSyncSourcePtr source(createSource());
277 SyncSourceRaw::InsertItemResult res;
278 SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw(uid, data));
279 SOURCE_ASSERT(source.get(), !res.m_luid.empty());
284 /** updates specific item locally via sync source */
285 static void removeItem(CreateSource createSource, const std::string &luid)
287 CPPUNIT_ASSERT(createSource.createSource);
290 TestingSyncSourcePtr source(createSource());
293 SOURCE_ASSERT_NO_FAILURE(source.get(), source->deleteItem(luid));
296 void LocalTests::update(CreateSource createSource, const char *data, bool check) {
297 CPPUNIT_ASSERT(createSource.createSource);
298 CPPUNIT_ASSERT(data);
301 TestingSyncSourcePtr source(createSource());
303 // get existing item, then update it
304 SyncSourceChanges::Items_t::const_iterator it;
305 SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin());
306 CPPUNIT_ASSERT(it != source->getAllItems().end());
308 SOURCE_ASSERT_NO_FAILURE(source.get(), source->insertItemRaw(luid, data));
309 CPPUNIT_ASSERT_NO_THROW(source.reset());
315 // check that the right changes are reported when reopening the source
316 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource()));
317 CPPUNIT_ASSERT_EQUAL(1, countItems(source.get()));
318 CPPUNIT_ASSERT_EQUAL(0, countNewItems(source.get()));
319 CPPUNIT_ASSERT_EQUAL(0, countUpdatedItems(source.get()));
320 CPPUNIT_ASSERT_EQUAL(0, countDeletedItems(source.get()));
322 SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin());
323 CPPUNIT_ASSERT(it != source->getAllItems().end());
324 CPPUNIT_ASSERT_EQUAL(luid, *it);
327 void LocalTests::update(CreateSource createSource, const char *data, const std::string &luid) {
328 CPPUNIT_ASSERT(createSource.createSource);
329 CPPUNIT_ASSERT(data);
332 TestingSyncSourcePtr source(createSource());
335 SOURCE_ASSERT_NO_FAILURE(source.get(), source->insertItemRaw(luid, data));
338 /** deletes all items locally via sync source */
339 void LocalTests::deleteAll(CreateSource createSource) {
340 CPPUNIT_ASSERT(createSource.createSource);
343 TestingSyncSourcePtr source(createSource());
346 SOURCE_ASSERT_NO_FAILURE(source.get(), source->removeAllItems());
347 CPPUNIT_ASSERT_NO_THROW(source.reset());
349 // check that all items are gone
350 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource()));
351 SOURCE_ASSERT_MESSAGE(
352 "should be empty now",
354 countItems(source.get()) == 0);
355 CPPUNIT_ASSERT_EQUAL( 0, countNewItems(source.get()) );
356 CPPUNIT_ASSERT_EQUAL( 0, countUpdatedItems(source.get()) );
357 CPPUNIT_ASSERT_EQUAL( 0, countDeletedItems(source.get()) );
360 /** deletes specific item locally via sync source */
361 static void deleteItem(CreateSource createSource, const std::string &uid) {
362 CPPUNIT_ASSERT(createSource.createSource);
365 TestingSyncSourcePtr source(createSource());
368 SOURCE_ASSERT_NO_FAILURE(source.get(), source->deleteItem(uid));
372 * takes two databases, exports them,
373 * then compares them using synccompare
375 * @param refFile existing file with source reference items, NULL uses a dump of sync source A instead
376 * @param copy a sync source which contains the copied items, begin/endSync will be called
377 * @param raiseAssert raise assertion if comparison yields differences (defaults to true)
379 bool LocalTests::compareDatabases(const char *refFile, TestingSyncSource ©, bool raiseAssert) {
380 CPPUNIT_ASSERT(config.dump);
382 std::string sourceFile, copyFile;
385 sourceFile = refFile;
387 sourceFile = getCurrentTest() + ".A.test.dat";
388 simplifyFilename(sourceFile);
389 TestingSyncSourcePtr source;
390 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
391 SOURCE_ASSERT_EQUAL(source.get(), 0, config.dump(client, *source.get(), sourceFile.c_str()));
392 CPPUNIT_ASSERT_NO_THROW(source.reset());
395 copyFile = getCurrentTest() + ".B.test.dat";
396 simplifyFilename(copyFile);
397 SOURCE_ASSERT_EQUAL(©, 0, config.dump(client, copy, copyFile.c_str()));
399 bool equal = config.compare(client, sourceFile.c_str(), copyFile.c_str());
400 CPPUNIT_ASSERT(!raiseAssert || equal);
405 std::string LocalTests::createItem(int item, const std::string &revision, int size)
407 std::string data = config.templateItem;
408 std::stringstream prefix;
410 prefix << std::setfill('0') << std::setw(3) << item << " ";
412 const char *prop = config.uniqueProperties;
413 const char *nextProp;
416 nextProp = strchr(prop, ':');
420 curProp = std::string(prop, 0, nextProp - prop);
423 std::string property;
424 // property is expected to not start directly at the
429 size_t off = data.find(property);
430 if (off != data.npos) {
431 data.insert(off + property.size(), prefix.str());
439 /** add check for if not found, STL will crash */
440 if(data.find("<<REVISION>>") != std::string::npos) {
441 data.replace(data.find("<<REVISION>>"), strlen("<<REVISION>>"), revision);
442 } else if (data.find("REVISION") != std::string::npos) {
443 /* change "<<REVISION>>" to "REVISION" for memo */
444 data.replace(data.find("REVISION"), strlen("REVISION"), revision);
446 if (size > 0 && (int)data.size() < size) {
447 int additionalBytes = size - (int)data.size();
449 /* vCard 2.1 and vCal 1.0 need quoted-printable line breaks */
450 bool quoted = data.find("VERSION:1.0") != data.npos ||
451 data.find("VERSION:2.1") != data.npos;
452 size_t toreplace = 1;
454 CPPUNIT_ASSERT(config.sizeProperty);
456 /* stuff the item so that it reaches at least that size */
457 size_t off = data.find(config.sizeProperty);
458 CPPUNIT_ASSERT(off != data.npos);
459 std::stringstream stuffing;
461 stuffing << ";ENCODING=QUOTED-PRINTABLE:";
466 // insert after the first line, it often acts as the summary
467 if (data.find("BEGIN:VJOURNAL") != data.npos) {
468 size_t start = data.find(":", off);
469 CPPUNIT_ASSERT( start != data.npos );
470 size_t eol = data.find("\\n", off);
471 CPPUNIT_ASSERT( eol != data.npos );
472 stuffing << data.substr(start + 1, eol - start + 1);
473 toreplace += eol - start + 1;
476 while(added < additionalBytes) {
479 while(added + 4 < additionalBytes &&
485 // insert line breaks to allow folding
487 stuffing << "x=0D=0Ax";
494 off = data.find(":", off);
495 data.replace(off, toreplace, stuffing.str());
503 * insert artificial items, number of them determined by config.numItems
504 * unless passed explicitly
506 * @param createSource a factory for the sync source that is to be used
507 * @param startIndex IDs are generated starting with this value
508 * @param numItems number of items to be inserted if non-null, otherwise config.numItems is used
509 * @param size minimum size for new items
510 * @return LUIDs of all inserted items
512 std::list<std::string> LocalTests::insertManyItems(CreateSource createSource, int startIndex, int numItems, int size) {
513 std::list<std::string> luids;
515 CPPUNIT_ASSERT(config.templateItem);
516 CPPUNIT_ASSERT(config.uniqueProperties);
518 TestingSyncSourcePtr source;
519 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
520 CPPUNIT_ASSERT(startIndex > 1 || !countItems(source.get()));
522 int firstIndex = startIndex;
523 if (firstIndex < 0) {
526 int lastIndex = firstIndex + (numItems >= 1 ? numItems : config.numItems) - 1;
527 for (int item = firstIndex; item <= lastIndex; item++) {
528 std::string data = createItem(item, "", size);
529 luids.push_back(importItem(source.get(), data));
535 // creating sync source
536 void LocalTests::testOpen() {
537 // check requirements
538 CPPUNIT_ASSERT(config.createSourceA);
540 // Intentionally use the plain auto_ptr here and
541 // call open directly. That way it is a bit more clear
542 // what happens and where it fails, if it fails.
543 std::auto_ptr<TestingSyncSource> source(createSourceA());
544 // got a sync source?
545 CPPUNIT_ASSERT(source.get() != 0);
547 SOURCE_ASSERT_NO_FAILURE(source.get(), source->open());
549 CPPUNIT_ASSERT_NO_THROW(source.reset());
552 // restart scanning of items
553 void LocalTests::testIterateTwice() {
554 // check requirements
555 CPPUNIT_ASSERT(config.createSourceA);
558 TestingSyncSourcePtr source(createSourceA());
559 SOURCE_ASSERT_MESSAGE(
560 "iterating twice should produce identical results",
562 countItems(source.get()) == countItems(source.get()));
565 // insert one contact without clearing the source first
566 void LocalTests::testSimpleInsert() {
567 // check requirements
568 CPPUNIT_ASSERT(config.insertItem);
569 CPPUNIT_ASSERT(config.createSourceA);
571 insert(createSourceA, config.insertItem);
575 void LocalTests::testLocalDeleteAll() {
576 // check requirements
577 CPPUNIT_ASSERT(config.insertItem);
578 CPPUNIT_ASSERT(config.createSourceA);
580 // make sure there is something to delete, then delete again
581 insert(createSourceA, config.insertItem);
582 deleteAll(createSourceA);
585 // clean database, then insert
586 void LocalTests::testComplexInsert() {
587 testLocalDeleteAll();
592 // clean database, insert item, update it
593 void LocalTests::testLocalUpdate() {
594 // check additional requirements
595 CPPUNIT_ASSERT(config.updateItem);
597 testLocalDeleteAll();
599 update(createSourceA, config.updateItem);
602 // complex sequence of changes
603 void LocalTests::testChanges() {
604 SyncSourceChanges::Items_t::const_iterator it, it2;
606 // check additional requirements
607 CPPUNIT_ASSERT(config.createSourceB);
609 testLocalDeleteAll();
612 // clean changes in sync source B by creating and closing it
613 TestingSyncSourcePtr source(createSourceB());
614 CPPUNIT_ASSERT_NO_THROW(source.reset());
616 // no new changes now
617 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
618 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
619 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
620 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
621 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
624 SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin());
625 CPPUNIT_ASSERT(it != source->getAllItems().end());
627 SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, item));
628 CPPUNIT_ASSERT_NO_THROW(source.reset());
630 // delete item again via sync source A
631 deleteAll(createSourceA);
632 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
633 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
634 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
635 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
636 SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get()));
637 SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getDeletedItems().begin());
638 CPPUNIT_ASSERT(it != source->getDeletedItems().end());
639 CPPUNIT_ASSERT(!it->empty());
640 CPPUNIT_ASSERT_EQUAL(luid, *it);
641 CPPUNIT_ASSERT_NO_THROW(source.reset());
643 // insert another item via sync source A
645 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
646 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
647 SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
648 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
649 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
650 SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin());
651 CPPUNIT_ASSERT(it != source->getAllItems().end());
653 SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, item));
655 SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getNewItems().begin());
656 CPPUNIT_ASSERT(it != source->getNewItems().end());
657 SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, item));
658 CPPUNIT_ASSERT_EQUAL(luid, *it);
659 CPPUNIT_ASSERT_NO_THROW(source.reset());
661 // update item via sync source A
662 update(createSourceA, config.updateItem);
663 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
664 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
665 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
666 SOURCE_ASSERT_EQUAL(source.get(), 1, countUpdatedItems(source.get()));
667 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
669 SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getUpdatedItems().begin());
670 CPPUNIT_ASSERT(it != source->getUpdatedItems().end());
671 SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, updatedItem));
672 CPPUNIT_ASSERT_EQUAL(luid, *it);
673 CPPUNIT_ASSERT_NO_THROW(source.reset());
675 // start anew, then create and update an item -> should only be listed as new
676 // or updated, but not both
677 deleteAll(createSourceA);
678 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
681 update(createSourceA, config.updateItem);
682 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
683 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
684 SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()) + countUpdatedItems(source.get()));
685 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
687 // start anew, then create, delete and recreate an item -> should only be listed as new or updated,
688 // even if (as for calendar with UID) the same LUID gets reused
689 deleteAll(createSourceA);
690 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
693 deleteAll(createSourceA);
695 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
696 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
697 SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()) + countUpdatedItems(source.get()));
698 if (countDeletedItems(source.get()) == 1) {
699 // It's not nice, but acceptable to send the LUID of a deleted item to a
700 // server which has never seen that LUID. The LUID must not be the same as
701 // the one we list as new or updated, though.
702 SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getDeletedItems().begin());
703 CPPUNIT_ASSERT(it != source->getDeletedItems().end());
704 SOURCE_ASSERT_NO_FAILURE(source.get(), it2 = source->getNewItems().begin());
705 if (it2 == source->getNewItems().end()) {
706 SOURCE_ASSERT_NO_FAILURE(source.get(), it2 = source->getUpdatedItems().begin());
707 CPPUNIT_ASSERT(it2 != source->getUpdatedItems().end());
709 CPPUNIT_ASSERT(*it != *it2);
711 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
715 // clean database, import file, then export again and compare
716 void LocalTests::testImport() {
717 // check additional requirements
718 CPPUNIT_ASSERT(config.import);
719 CPPUNIT_ASSERT(config.dump);
720 CPPUNIT_ASSERT(config.compare);
721 CPPUNIT_ASSERT(config.testcases);
723 testLocalDeleteAll();
725 // import via sync source A
726 TestingSyncSourcePtr source;
727 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
728 std::string testcases;
729 SOURCE_ASSERT_EQUAL(source.get(), 0, config.import(client, *source.get(), config.testcases, testcases));
730 CPPUNIT_ASSERT_NO_THROW(source.reset());
732 // export again and compare against original file
733 TestingSyncSourcePtr copy;
734 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceA()));
735 compareDatabases(testcases.c_str(), *copy.get());
736 CPPUNIT_ASSERT_NO_THROW(source.reset());
739 // same as testImport() with immediate delete
740 void LocalTests::testImportDelete() {
743 // delete again, because it was observed that this did not
744 // work right with calendars in SyncEvolution
745 testLocalDeleteAll();
748 // test change tracking with large number of items
749 void LocalTests::testManyChanges() {
750 // check additional requirements
751 CPPUNIT_ASSERT(config.templateItem);
752 CPPUNIT_ASSERT(config.uniqueProperties);
754 deleteAll(createSourceA);
756 // check that everything is empty, also resets change counter of sync source B
757 TestingSyncSourcePtr copy;
758 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
759 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
760 CPPUNIT_ASSERT_NO_THROW(copy.reset());
762 // now insert plenty of items
763 int numItems = insertManyItems(createSourceA).size();
765 // check that exactly this number of items is listed as new
766 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
767 SOURCE_ASSERT_EQUAL(copy.get(), numItems, countItems(copy.get()));
768 SOURCE_ASSERT_EQUAL(copy.get(), numItems, countNewItems(copy.get()));
769 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
770 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
771 CPPUNIT_ASSERT_NO_THROW(copy.reset());
774 deleteAll(createSourceA);
777 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
778 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
779 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
780 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
781 SOURCE_ASSERT_EQUAL(copy.get(), numItems, countDeletedItems(copy.get()));
782 CPPUNIT_ASSERT_NO_THROW(copy.reset());
785 template<class T, class V> int countEqual(const T &container,
787 return count(container.begin(),
792 // test inserting, removing and updating of parent + child item in
793 // various order plus change tracking
794 void LocalTests::testLinkedItemsParent() {
795 // check additional requirements
796 CPPUNIT_ASSERT(config.parentItem);
797 CPPUNIT_ASSERT(config.childItem);
799 deleteAll(createSourceA);
800 std::string parent, child;
801 TestingSyncSourcePtr copy;
803 // check that everything is empty, also resets change counter of sync source B
804 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
805 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
806 CPPUNIT_ASSERT_NO_THROW(copy.reset());
808 // now insert main item
809 parent = insert(createSourceA, config.parentItem, config.itemType);
811 // check that exactly the parent is listed as new
812 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
813 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
814 SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
815 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
816 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
817 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
818 CPPUNIT_ASSERT_NO_THROW(copy.reset());
821 deleteAll(createSourceA);
824 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
825 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
826 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
827 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
828 SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
829 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
832 // test inserting, removing and updating of parent + child item in
833 // various order plus change tracking
834 void LocalTests::testLinkedItemsChild() {
835 // check additional requirements
836 CPPUNIT_ASSERT(config.parentItem);
837 CPPUNIT_ASSERT(config.childItem);
839 deleteAll(createSourceA);
840 std::string parent, child;
841 TestingSyncSourcePtr copy;
843 // check that everything is empty, also resets change counter of sync source B
844 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
845 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
846 CPPUNIT_ASSERT_NO_THROW(copy.reset());
848 // same as above for child item
849 child = insert(createSourceA, config.childItem, config.itemType);
851 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
852 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
853 SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
854 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
855 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
856 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
857 CPPUNIT_ASSERT_NO_THROW(copy.reset());
859 deleteAll(createSourceA);
861 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
862 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
863 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
864 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
865 SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
866 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
869 // test inserting, removing and updating of parent + child item in
870 // various order plus change tracking
871 void LocalTests::testLinkedItemsParentChild() {
872 // check additional requirements
873 CPPUNIT_ASSERT(config.parentItem);
874 CPPUNIT_ASSERT(config.childItem);
876 deleteAll(createSourceA);
877 std::string parent, child;
878 TestingSyncSourcePtr copy;
880 // check that everything is empty, also resets change counter of sync source B
881 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
882 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
883 CPPUNIT_ASSERT_NO_THROW(copy.reset());
885 // insert parent first, then child
886 parent = insert(createSourceA, config.parentItem, config.itemType);
887 child = insert(createSourceA, config.childItem, config.itemType);
889 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
890 SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
891 SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
892 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
893 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
894 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
895 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
896 CPPUNIT_ASSERT_NO_THROW(copy.reset());
898 deleteAll(createSourceA);
900 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
901 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
902 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
903 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
904 SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get()));
905 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
906 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
909 // test inserting, removing and updating of parent + child item in
910 // various order plus change tracking
911 void LocalTests::testLinkedItemsChildParent() {
912 // check additional requirements
913 CPPUNIT_ASSERT(config.parentItem);
914 CPPUNIT_ASSERT(config.childItem);
916 deleteAll(createSourceA);
917 std::string parent, child;
918 TestingSyncSourcePtr copy;
920 // check that everything is empty, also resets change counter of sync source B
921 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
922 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
923 CPPUNIT_ASSERT_NO_THROW(copy.reset());
925 // insert child first, then parent
926 child = insert(createSourceA, config.childItem);
927 parent = insert(createSourceA, config.parentItem, true);
929 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
930 SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
931 SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
932 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
933 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
934 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
935 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
936 CPPUNIT_ASSERT_NO_THROW(copy.reset());
938 deleteAll(createSourceA);
940 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
941 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
942 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
943 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
944 SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get()));
945 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
946 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
949 // test inserting, removing and updating of parent + child item in
950 // various order plus change tracking
951 void LocalTests::testLinkedItemsChildChangesParent() {
952 // check additional requirements
953 CPPUNIT_ASSERT(config.parentItem);
954 CPPUNIT_ASSERT(config.childItem);
956 deleteAll(createSourceA);
957 std::string parent, child;
958 TestingSyncSourcePtr copy;
960 // check that everything is empty, also resets change counter of sync source B
961 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
962 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
963 CPPUNIT_ASSERT_NO_THROW(copy.reset());
965 // insert child first, check changes, then insert the parent
966 child = insert(createSourceA, config.childItem, config.itemType);
968 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
969 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
970 SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
971 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
972 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
973 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
974 CPPUNIT_ASSERT_NO_THROW(copy.reset());
976 parent = insert(createSourceA, config.parentItem, true);
978 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
979 SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
980 SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
981 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listNewItems(copy.get()), parent));
982 // relaxed semantic: the child item might be considered updated now if
983 // it had to be modified when inserting the parent
984 SOURCE_ASSERT(copy.get(), 1 >= countUpdatedItems(copy.get()));
985 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
986 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
987 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
988 CPPUNIT_ASSERT_NO_THROW(copy.reset());
990 deleteAll(createSourceA);
992 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
993 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
994 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
995 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
996 SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get()));
997 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
998 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1001 // test inserting, removing and updating of parent + child item in
1002 // various order plus change tracking
1003 void LocalTests::testLinkedItemsRemoveParentFirst() {
1004 // check additional requirements
1005 CPPUNIT_ASSERT(config.parentItem);
1006 CPPUNIT_ASSERT(config.childItem);
1008 deleteAll(createSourceA);
1009 std::string parent, child;
1010 TestingSyncSourcePtr copy;
1012 // check that everything is empty, also resets change counter of sync source B
1013 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1014 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1015 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1017 // insert both items, remove parent, then child
1018 parent = insert(createSourceA, config.parentItem);
1019 child = insert(createSourceA, config.childItem);
1021 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1022 SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1023 SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
1024 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1025 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1026 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1027 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1028 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1030 deleteItem(createSourceA, parent);
1032 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1033 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1034 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1035 // deleting the parent may or may not modify the child
1036 SOURCE_ASSERT(copy.get(), 1 >= countUpdatedItems(copy.get()));
1037 SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1038 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1039 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1041 deleteItem(createSourceA, child);
1043 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1044 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1045 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1046 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1047 SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1048 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1049 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1052 // test inserting, removing and updating of parent + child item in
1053 // various order plus change tracking
1054 void LocalTests::testLinkedItemsRemoveNormal() {
1055 // check additional requirements
1056 CPPUNIT_ASSERT(config.parentItem);
1057 CPPUNIT_ASSERT(config.childItem);
1059 deleteAll(createSourceA);
1060 std::string parent, child;
1061 TestingSyncSourcePtr source, copy;
1063 // check that everything is empty, also resets change counter of sync source B
1064 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1065 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1066 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1068 // insert both items, remove child, then parent
1069 parent = insert(createSourceA, config.parentItem);
1070 child = insert(createSourceA, config.childItem);
1072 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1073 SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1074 SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
1075 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1076 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1077 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1078 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1079 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1081 deleteItem(createSourceA, child);
1083 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
1084 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
1085 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
1086 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
1087 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
1088 CPPUNIT_ASSERT_NO_THROW(source.reset());
1090 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1091 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1092 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1093 // parent might have been updated
1094 int updated = countUpdatedItems(copy.get());
1095 SOURCE_ASSERT(copy.get(), 0 <= updated && updated <= 1);
1096 SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1097 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1098 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1100 deleteItem(createSourceA, parent);
1102 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1103 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1104 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1105 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1106 SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1107 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1108 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1111 // test inserting, removing and updating of parent + child item in
1112 // various order plus change tracking
1113 void LocalTests::testLinkedItemsInsertParentTwice() {
1114 // check additional requirements
1115 CPPUNIT_ASSERT(config.parentItem);
1116 CPPUNIT_ASSERT(config.childItem);
1118 deleteAll(createSourceA);
1119 std::string parent, child;
1120 TestingSyncSourcePtr copy;
1122 // check that everything is empty, also resets change counter of sync source B
1123 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1124 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1125 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1127 // add parent twice (should be turned into update)
1128 parent = insert(createSourceA, config.parentItem);
1130 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1131 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1132 SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
1133 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1134 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1135 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1136 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1138 parent = insert(createSourceA, config.parentItem);
1140 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1141 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1142 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1143 SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get()));
1144 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1145 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), parent));
1146 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1148 deleteItem(createSourceA, parent);
1150 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1151 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1152 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1153 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1154 SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1155 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1158 // test inserting, removing and updating of parent + child item in
1159 // various order plus change tracking
1160 void LocalTests::testLinkedItemsInsertChildTwice() {
1161 // check additional requirements
1162 CPPUNIT_ASSERT(config.parentItem);
1163 CPPUNIT_ASSERT(config.childItem);
1165 deleteAll(createSourceA);
1166 std::string parent, child;
1167 TestingSyncSourcePtr copy;
1169 // check that everything is empty, also resets change counter of sync source B
1170 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1171 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1172 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1174 // add child twice (should be turned into update)
1175 child = insert(createSourceA, config.childItem);
1177 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1178 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1179 SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
1180 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1181 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1182 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1183 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1185 child = insert(createSourceA, config.childItem);
1187 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1188 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1189 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1190 SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get()));
1191 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1192 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), child));
1193 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1195 deleteItem(createSourceA, child);
1197 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1198 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1199 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1200 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1201 SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1202 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1205 // test inserting, removing and updating of parent + child item in
1206 // various order plus change tracking
1207 void LocalTests::testLinkedItemsParentUpdate() {
1208 // check additional requirements
1209 CPPUNIT_ASSERT(config.parentItem);
1210 CPPUNIT_ASSERT(config.childItem);
1212 deleteAll(createSourceA);
1213 std::string parent, child;
1214 TestingSyncSourcePtr copy;
1216 // check that everything is empty, also resets change counter of sync source B
1217 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1218 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1219 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1221 // add parent, then update it
1222 parent = insert(createSourceA, config.parentItem);
1224 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1225 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1226 SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
1227 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1228 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1229 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1230 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1232 parent = updateItem(createSourceA, parent, config.parentItem);
1234 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1235 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1236 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1237 SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get()));
1238 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1239 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), parent));
1240 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1242 deleteItem(createSourceA, parent);
1244 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1245 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1246 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1247 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1248 SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1249 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1250 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1253 // test inserting, removing and updating of parent + child item in
1254 // various order plus change tracking
1255 void LocalTests::testLinkedItemsUpdateChild() {
1256 // check additional requirements
1257 CPPUNIT_ASSERT(config.parentItem);
1258 CPPUNIT_ASSERT(config.childItem);
1260 deleteAll(createSourceA);
1261 std::string parent, child;
1262 TestingSyncSourcePtr copy;
1264 // check that everything is empty, also resets change counter of sync source B
1265 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1266 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1267 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1269 // add child, then update it
1270 child = insert(createSourceA, config.childItem);
1272 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1273 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1274 SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
1275 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1276 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1277 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1278 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1280 child = updateItem(createSourceA, child, config.childItem);
1282 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1283 SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1284 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1285 SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get()));
1286 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1287 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), child));
1288 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1290 deleteItem(createSourceA, child);
1292 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1293 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1294 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1295 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1296 SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1297 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1300 // test inserting, removing and updating of parent + child item in
1301 // various order plus change tracking
1302 void LocalTests::testLinkedItemsInsertBothUpdateChild() {
1303 // check additional requirements
1304 CPPUNIT_ASSERT(config.parentItem);
1305 CPPUNIT_ASSERT(config.childItem);
1307 deleteAll(createSourceA);
1308 std::string parent, child;
1309 TestingSyncSourcePtr copy;
1311 // check that everything is empty, also resets change counter of sync source B
1312 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1313 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1314 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1316 // add parent and child, then update child
1317 parent = insert(createSourceA, config.parentItem);
1318 child = insert(createSourceA, config.childItem);
1320 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1321 SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1322 SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
1323 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1324 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1325 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1326 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1327 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1329 child = updateItem(createSourceA, child, config.childItem);
1331 // child has to be listed as modified, parent may be
1332 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1333 SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1334 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1335 SOURCE_ASSERT(copy.get(), 1 <= countUpdatedItems(copy.get()));
1336 SOURCE_ASSERT(copy.get(), 2 >= countUpdatedItems(copy.get()));
1337 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1338 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), child));
1339 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1341 deleteItem(createSourceA, parent);
1342 deleteItem(createSourceA, child);
1344 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1345 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1346 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1347 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1348 SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get()));
1349 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1350 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1351 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1354 // test inserting, removing and updating of parent + child item in
1355 // various order plus change tracking
1356 void LocalTests::testLinkedItemsInsertBothUpdateParent() {
1357 // check additional requirements
1358 CPPUNIT_ASSERT(config.parentItem);
1359 CPPUNIT_ASSERT(config.childItem);
1361 deleteAll(createSourceA);
1362 std::string parent, child;
1363 TestingSyncSourcePtr copy;
1365 // check that everything is empty, also resets change counter of sync source B
1366 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1367 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1368 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1370 // add parent and child, then update parent
1371 parent = insert(createSourceA, config.parentItem);
1372 child = insert(createSourceA, config.childItem);
1374 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1375 SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1376 SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
1377 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1378 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1379 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1380 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1381 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1383 parent = updateItem(createSourceA, parent, config.parentItem);
1385 // parent has to be listed as modified, child may be
1386 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1387 SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1388 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1389 SOURCE_ASSERT(copy.get(), 1 <= countUpdatedItems(copy.get()));
1390 SOURCE_ASSERT(copy.get(), 2 >= countUpdatedItems(copy.get()));
1391 SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1392 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), parent));
1393 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1395 deleteItem(createSourceA, parent);
1396 deleteItem(createSourceA, child);
1398 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1399 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1400 SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1401 SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1402 SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get()));
1403 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1404 SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1408 SyncTests::SyncTests(const std::string &name, ClientTest &cl, std::vector<int> sourceIndices, bool isClientA) :
1409 CppUnit::TestSuite(name),
1411 sourceArray = new int[sourceIndices.size() + 1];
1413 for (std::vector<int>::iterator it = sourceIndices.begin();
1414 it != sourceIndices.end();
1416 ClientTest::Config config;
1417 client.getSyncSourceConfig(*it, config);
1419 if (config.sourceName) {
1420 sourceArray[sources.size()+offset] = *it;
1421 if (config.subConfigs) {
1422 vector<string> subs;
1423 boost::split (subs, config.subConfigs, boost::is_any_of(","));
1425 ClientTest::Config subConfig;
1426 BOOST_FOREACH (string sub, subs) {
1427 client.getSourceConfig (sub, subConfig);
1428 sources.push_back(std::pair<int,LocalTests *>(*it, cl.createLocalTests(sub, client.getLocalSourcePosition(sub), subConfig)));
1432 sources.push_back(std::pair<int,LocalTests *>(*it, cl.createLocalTests(config.sourceName, client.getLocalSourcePosition(config.sourceName), config)));
1436 sourceArray[sources.size()+ offset] = -1;
1438 // check whether we have a second client
1439 ClientTest *clientB = cl.getClientB();
1441 accessClientB = clientB->createSyncTests(name, sourceIndices, false);
1447 SyncTests::~SyncTests() {
1448 for (source_it it = sources.begin();
1449 it != sources.end();
1453 delete [] sourceArray;
1454 if (accessClientB) {
1455 delete accessClientB;
1459 /** adds the supported tests to the instance itself */
1460 void SyncTests::addTests() {
1461 if (sources.size()) {
1462 const ClientTest::Config &config(sources[0].second->config);
1464 // run this test first, even if it is more complex:
1465 // if it works, all the following tests will run with
1466 // the server in a deterministic state
1467 if (config.createSourceA) {
1468 if (config.insertItem) {
1469 ADD_TEST(SyncTests, testDeleteAllRefresh);
1473 ADD_TEST(SyncTests, testTwoWaySync);
1474 ADD_TEST(SyncTests, testSlowSync);
1475 ADD_TEST(SyncTests, testRefreshFromServerSync);
1476 ADD_TEST(SyncTests, testRefreshFromClientSync);
1478 if (config.compare &&
1480 ADD_TEST(SyncTests, testConversion);
1483 if (config.createSourceA) {
1484 if (config.insertItem) {
1485 ADD_TEST(SyncTests, testRefreshFromServerSemantic);
1486 ADD_TEST(SyncTests, testRefreshFromClientSemantic);
1487 ADD_TEST(SyncTests, testRefreshStatus);
1489 if (accessClientB &&
1492 ADD_TEST(SyncTests, testCopy);
1493 ADD_TEST(SyncTests, testDelete);
1494 ADD_TEST(SyncTests, testAddUpdate);
1495 ADD_TEST(SyncTests, testManyItems);
1496 ADD_TEST(SyncTests, testManyDeletes);
1497 ADD_TEST(SyncTests, testSlowSyncSemantic);
1498 ADD_TEST(SyncTests, testComplexRefreshFromServerSemantic);
1500 if (config.updateItem) {
1501 ADD_TEST(SyncTests, testUpdate);
1503 if (config.complexUpdateItem) {
1504 ADD_TEST(SyncTests, testComplexUpdate);
1506 if (config.mergeItem1 && config.mergeItem2) {
1507 ADD_TEST(SyncTests, testMerge);
1509 if (config.import) {
1510 ADD_TEST(SyncTests, testTwinning);
1511 ADD_TEST(SyncTests, testItems);
1512 ADD_TEST(SyncTests, testItemsXML);
1514 if (config.templateItem) {
1515 ADD_TEST(SyncTests, testMaxMsg);
1516 ADD_TEST(SyncTests, testLargeObject);
1517 ADD_TEST(SyncTests, testOneWayFromServer);
1518 ADD_TEST(SyncTests, testOneWayFromClient);
1524 if (config.retrySync &&
1525 config.insertItem &&
1526 config.updateItem &&
1530 CppUnit::TestSuite *retryTests = new CppUnit::TestSuite(getName() + "::Retry");
1531 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientAdd);
1532 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientRemove);
1533 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientUpdate);
1534 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerAdd);
1535 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerRemove);
1536 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerUpdate);
1537 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientAddBig);
1538 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientUpdateBig);
1539 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerAddBig);
1540 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerUpdateBig);
1541 ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeFull);
1542 addTest(FilterTest(retryTests));
1545 if (config.suspendSync &&
1546 config.insertItem &&
1547 config.updateItem &&
1551 CppUnit::TestSuite *suspendTests = new CppUnit::TestSuite(getName() + "::Suspend");
1552 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientAdd);
1553 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientRemove);
1554 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientUpdate);
1555 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerAdd);
1556 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerRemove);
1557 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerUpdate);
1558 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientAddBig);
1559 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientUpdateBig);
1560 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerAddBig);
1561 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerUpdateBig);
1562 ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendFull);
1563 addTest(FilterTest(suspendTests));
1566 if (config.resendSync &&
1567 config.insertItem &&
1568 config.updateItem &&
1572 CppUnit::TestSuite *resendTests = new CppUnit::TestSuite(getName() + "::Resend");
1573 ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendClientAdd);
1574 ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendClientRemove);
1575 ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendClientUpdate);
1576 ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendServerAdd);
1577 ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendServerRemove);
1578 ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendServerUpdate);
1579 ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendFull);
1580 addTest(FilterTest(resendTests));
1586 bool SyncTests::compareDatabases(const char *refFileBase, bool raiseAssert) {
1591 CPPUNIT_ASSERT(accessClientB);
1592 for (it1 = sources.begin(), it2 = accessClientB->sources.begin();
1593 it1 != sources.end() && it2 != accessClientB->sources.end();
1595 TestingSyncSourcePtr copy;
1596 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it2->second->createSourceB()));
1598 std::string refFile = refFileBase;
1599 refFile += it1->second->config.sourceName;
1601 simplifyFilename(refFile);
1602 if (!it1->second->compareDatabases(refFile.c_str(), *copy.get(), raiseAssert)) {
1606 if (!it1->second->compareDatabases(NULL, *copy.get(), raiseAssert)) {
1610 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1612 CPPUNIT_ASSERT(it1 == sources.end());
1613 CPPUNIT_ASSERT(it2 == accessClientB->sources.end());
1615 CPPUNIT_ASSERT(!raiseAssert || equal);
1619 /** deletes all items locally and on server */
1620 void SyncTests::deleteAll(DeleteAllMode mode) {
1622 SyncPrefix prefix("deleteall", *this);
1624 const char *value = getenv ("CLIENT_TEST_DELETE_REFRESH");
1626 mode = DELETE_ALL_REFRESH;
1630 case DELETE_ALL_SYNC:
1631 // a refresh from server would slightly reduce the amount of data exchanged, but not all servers support it
1632 for (it = sources.begin(); it != sources.end(); ++it) {
1633 it->second->deleteAll(it->second->createSourceA);
1635 doSync("init", SyncOptions(SYNC_SLOW));
1636 // now that client and server are in sync, delete locally and sync again
1637 for (it = sources.begin(); it != sources.end(); ++it) {
1638 it->second->deleteAll(it->second->createSourceA);
1641 SyncOptions(SYNC_TWO_WAY,
1642 CheckSyncReport(0,0,0, 0,0,-1, true, SYNC_TWO_WAY)));
1644 case DELETE_ALL_REFRESH:
1645 // delete locally and then tell the server to "copy" the empty databases
1646 for (it = sources.begin(); it != sources.end(); ++it) {
1647 it->second->deleteAll(it->second->createSourceA);
1649 doSync("refreshserver",
1650 SyncOptions(SYNC_REFRESH_FROM_CLIENT,
1651 CheckSyncReport(0,0,0, 0,0,-1, true, SYNC_REFRESH_FROM_CLIENT)));
1656 /** get both clients in sync with empty server, then copy one item from client A to B */
1657 void SyncTests::doCopy() {
1658 SyncPrefix("copy", *this);
1660 // check requirements
1661 CPPUNIT_ASSERT(accessClientB);
1664 accessClientB->deleteAll();
1666 // insert into first database, copy to server
1668 for (it = sources.begin(); it != sources.end(); ++it) {
1669 it->second->testSimpleInsert();
1672 SyncOptions(SYNC_TWO_WAY,
1673 CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
1675 // copy into second database
1676 accessClientB->doSync("recv",
1677 SyncOptions(SYNC_TWO_WAY,
1678 CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY)));
1684 * replicate server database locally: same as SYNC_REFRESH_FROM_SERVER,
1685 * but done with explicit local delete and then a SYNC_SLOW because some
1686 * servers do no support SYNC_REFRESH_FROM_SERVER
1688 void SyncTests::refreshClient(SyncOptions options) {
1690 for (it = sources.begin(); it != sources.end(); ++it) {
1691 it->second->deleteAll(it->second->createSourceA);
1696 .setSyncMode(SYNC_SLOW)
1697 .setCheckReport(CheckSyncReport(-1,0,0, 0,0,0, true, SYNC_SLOW)));
1701 // delete all items, locally and on server using refresh-from-client sync
1702 void SyncTests::testDeleteAllRefresh() {
1705 // copy something to server first; doesn't matter whether it has the
1706 // item already or not, as long as it exists there afterwards
1707 for (it = sources.begin(); it != sources.end(); ++it) {
1708 it->second->testSimpleInsert();
1710 doSync("insert", SyncOptions(SYNC_SLOW));
1712 // now ensure we can delete it
1713 deleteAll(DELETE_ALL_REFRESH);
1715 // nothing stored locally?
1716 for (it = sources.begin(); it != sources.end(); ++it) {
1717 TestingSyncSourcePtr source;
1718 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
1719 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
1720 CPPUNIT_ASSERT_NO_THROW(source.reset());
1723 // make sure server really deleted everything
1725 SyncOptions(SYNC_SLOW,
1726 CheckSyncReport(0,0,0, 0,0,0, true, SYNC_SLOW)));
1727 for (it = sources.begin(); it != sources.end(); ++it) {
1728 TestingSyncSourcePtr source;
1729 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
1730 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
1731 CPPUNIT_ASSERT_NO_THROW(source.reset());
1735 // test that a refresh sync from an empty server leads to an empty datatbase
1736 // and no changes are sent to server during next two-way sync
1737 void SyncTests::testRefreshFromServerSemantic() {
1740 // clean client and server
1743 // insert item, then refresh from empty server
1744 for (it = sources.begin(); it != sources.end(); ++it) {
1745 it->second->testSimpleInsert();
1748 SyncOptions(SYNC_REFRESH_FROM_SERVER,
1749 CheckSyncReport(0,0,-1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
1752 for (it = sources.begin(); it != sources.end(); ++it) {
1753 TestingSyncSourcePtr source;
1754 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
1755 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
1756 CPPUNIT_ASSERT_NO_THROW(source.reset());
1759 SyncOptions(SYNC_TWO_WAY,
1760 CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY)));
1763 // test that a refresh sync from an empty client leads to an empty datatbase
1764 // and no changes are sent to server during next two-way sync
1765 void SyncTests::testRefreshFromClientSemantic() {
1768 // clean client and server
1771 // insert item, send to server
1772 for (it = sources.begin(); it != sources.end(); ++it) {
1773 it->second->testSimpleInsert();
1776 SyncOptions(SYNC_TWO_WAY,
1777 CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
1780 for (it = sources.begin(); it != sources.end(); ++it) {
1781 it->second->deleteAll(it->second->createSourceA);
1784 // refresh from client
1786 SyncOptions(SYNC_REFRESH_FROM_CLIENT,
1787 CheckSyncReport(0,0,0, 0,0,0, true, SYNC_REFRESH_FROM_CLIENT)));
1791 SyncOptions(SYNC_REFRESH_FROM_SERVER,
1792 CheckSyncReport(0,0,0, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
1795 // tests the following sequence of events:
1797 // - delete all items
1798 // - insert one other item
1799 // - refresh from client
1800 // => no items should now be listed as new, updated or deleted for this client during another sync
1801 void SyncTests::testRefreshStatus() {
1804 for (it = sources.begin(); it != sources.end(); ++it) {
1805 it->second->testSimpleInsert();
1807 for (it = sources.begin(); it != sources.end(); ++it) {
1808 it->second->deleteAll(it->second->createSourceA);
1810 for (it = sources.begin(); it != sources.end(); ++it) {
1811 it->second->testSimpleInsert();
1813 doSync("refresh-from-client",
1814 SyncOptions(SYNC_REFRESH_FROM_CLIENT,
1815 CheckSyncReport(0,0,0, -1,-1,-1, /* strictly speaking 1,0,0, but not sure exactly what the server will be told */
1816 true, SYNC_REFRESH_FROM_CLIENT)));
1818 SyncOptions(SYNC_TWO_WAY,
1819 CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY)));
1822 // test that a two-way sync copies updates from database to the other client,
1823 // using simple data commonly supported by servers
1824 void SyncTests::testUpdate() {
1825 CPPUNIT_ASSERT(sources.begin() != sources.end());
1826 CPPUNIT_ASSERT(sources.begin()->second->config.updateItem);
1828 // setup client A, B and server so that they all contain the same item
1832 for (it = sources.begin(); it != sources.end(); ++it) {
1833 it->second->update(it->second->createSourceA, it->second->config.updateItem);
1837 SyncOptions(SYNC_TWO_WAY,
1838 CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY)));
1839 accessClientB->doSync("update",
1840 SyncOptions(SYNC_TWO_WAY,
1841 CheckSyncReport(0,1,0, 0,0,0, true, SYNC_TWO_WAY)));
1846 // test that a two-way sync copies updates from database to the other client,
1847 // using data that some, but not all servers support, like adding a second
1848 // phone number to a contact
1849 void SyncTests::testComplexUpdate() {
1850 // setup client A, B and server so that they all contain the same item
1854 for (it = sources.begin(); it != sources.end(); ++it) {
1855 it->second->update(it->second->createSourceA,
1856 /* this test might get executed with some sources which have
1857 a complex update item while others don't: use the normal update item
1858 for them or even just the same item */
1859 it->second->config.complexUpdateItem ? it->second->config.complexUpdateItem :
1860 it->second->config.updateItem ? it->second->config.updateItem :
1861 it->second->config.insertItem
1866 SyncOptions(SYNC_TWO_WAY,
1867 CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY)));
1868 accessClientB->doSync("update",
1869 SyncOptions(SYNC_TWO_WAY,
1870 CheckSyncReport(0,1,0, 0,0,0, true, SYNC_TWO_WAY)));
1876 // test that a two-way sync deletes the copy of an item in the other database
1877 void SyncTests::testDelete() {
1878 // setup client A, B and server so that they all contain the same item
1883 for (it = sources.begin(); it != sources.end(); ++it) {
1884 it->second->deleteAll(it->second->createSourceA);
1887 // transfer change from A to server to B
1889 SyncOptions(SYNC_TWO_WAY,
1890 CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY)));
1891 accessClientB->doSync("delete",
1892 SyncOptions(SYNC_TWO_WAY,
1893 CheckSyncReport(0,0,1, 0,0,0, true, SYNC_TWO_WAY)));
1895 // check client B: shouldn't have any items now
1896 for (it = sources.begin(); it != sources.end(); ++it) {
1897 TestingSyncSourcePtr copy;
1898 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it->second->createSourceA()));
1899 SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1900 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1904 // test what the server does when it finds that different
1905 // fields of the same item have been modified
1906 void SyncTests::testMerge() {
1907 // setup client A, B and server so that they all contain the same item
1910 // update in client A
1912 for (it = sources.begin(); it != sources.end(); ++it) {
1913 it->second->update(it->second->createSourceA, it->second->config.mergeItem1);
1916 // update in client B
1917 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
1918 it->second->update(it->second->createSourceA, it->second->config.mergeItem2);
1921 // send change to server from client A (no conflict)
1923 SyncOptions(SYNC_TWO_WAY,
1924 CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY)));
1925 // Now the changes from client B (conflict!).
1926 // There are several possible outcomes:
1927 // - client item completely replaces server item
1928 // - server item completely replaces client item (update on client)
1929 // - server merges and updates client
1930 accessClientB->doSync("conflict",
1931 SyncOptions(SYNC_TWO_WAY,
1932 CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_TWO_WAY)));
1934 // figure out how the conflict during ".conflict" was handled
1935 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
1936 TestingSyncSourcePtr copy;
1937 SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it->second->createSourceA()));
1939 SOURCE_ASSERT_NO_FAILURE(copy.get(), numItems = countItems(copy.get()));
1940 CPPUNIT_ASSERT(numItems >= 1);
1941 CPPUNIT_ASSERT(numItems <= 2);
1942 std::cout << " \"" << it->second->config.sourceName << ": " << (numItems == 1 ? "conflicting items were merged" : "both of the conflicting items were preserved") << "\" ";
1944 CPPUNIT_ASSERT_NO_THROW(copy.reset());
1947 // now pull the same changes into client A
1949 SyncOptions(SYNC_TWO_WAY,
1950 CheckSyncReport(-1,-1,-1, 0,0,0, true, SYNC_TWO_WAY)));
1952 // client A and B should have identical data now
1955 // Furthermore, it should be identical with the server.
1956 // Be extra careful and pull that data anew and compare once more.
1958 SyncOptions(SYNC_REFRESH_FROM_SERVER,
1959 CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_REFRESH_FROM_SERVER)));
1963 // test what the server does when it has to execute a slow sync
1964 // with identical data on client and server:
1965 // expected behaviour is that nothing changes
1966 void SyncTests::testTwinning() {
1967 // clean server and client A
1972 for (it = sources.begin(); it != sources.end(); ++it) {
1973 it->second->testImport();
1977 doSync("send", SyncOptions(SYNC_TWO_WAY));
1979 // ensure that client has the same data, thus ignoring data conversion
1980 // issues (those are covered by testItems())
1983 // copy to client B to have another copy
1984 accessClientB->refreshClient();
1986 // slow sync should not change anything
1987 doSync("twinning", SyncOptions(SYNC_SLOW));
1993 // tests one-way sync from server:
1994 // - get both clients and server in sync with no items anywhere
1995 // - add one item on first client, copy to server
1996 // - add a different item on second client, one-way-from-server
1997 // - two-way sync with first client
1998 // => one item on first client, two on second
1999 // - delete on first client, sync that to second client
2000 // via two-way sync + one-way-from-server
2001 // => one item left on second client (the one inserted locally)
2002 void SyncTests::testOneWayFromServer() {
2003 // no items anywhere
2005 accessClientB->refreshClient();
2007 // check that everything is empty, also resets change tracking
2008 // in second sources of each client
2010 for (it = sources.begin(); it != sources.end(); ++it) {
2011 if (it->second->config.createSourceB) {
2012 TestingSyncSourcePtr source;
2013 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2014 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2015 CPPUNIT_ASSERT_NO_THROW(source.reset());
2018 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2019 if (it->second->config.createSourceB) {
2020 TestingSyncSourcePtr source;
2021 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2022 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2023 CPPUNIT_ASSERT_NO_THROW(source.reset());
2027 // add one item on first client, copy to server, and check change tracking via second source
2028 for (it = sources.begin(); it != sources.end(); ++it) {
2029 it->second->insertManyItems(it->second->createSourceA, 200, 1);
2032 SyncOptions(SYNC_TWO_WAY,
2033 CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
2034 for (it = sources.begin(); it != sources.end(); ++it) {
2035 if (it->second->config.createSourceB) {
2036 TestingSyncSourcePtr source;
2037 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2038 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2039 SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2040 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2041 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2042 CPPUNIT_ASSERT_NO_THROW(source.reset());
2046 // add a different item on second client, one-way-from-server
2047 // => one item added locally, none sent to server
2048 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2049 it->second->insertManyItems(it->second->createSourceA, 2, 1);
2051 if (it->second->config.createSourceB) {
2052 TestingSyncSourcePtr source;
2053 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2054 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2055 SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2056 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2057 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2058 CPPUNIT_ASSERT_NO_THROW(source.reset());
2061 accessClientB->doSync("recv",
2062 SyncOptions(SYNC_ONE_WAY_FROM_SERVER,
2063 CheckSyncReport(1,0,0, 0,0,0, true, SYNC_ONE_WAY_FROM_SERVER)));
2064 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2065 if (it->second->config.createSourceB) {
2066 TestingSyncSourcePtr source;
2067 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2068 SOURCE_ASSERT_EQUAL(source.get(), 2, countItems(source.get()));
2069 SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2070 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2071 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2072 CPPUNIT_ASSERT_NO_THROW(source.reset());
2076 // two-way sync with first client for verification
2079 SyncOptions(SYNC_TWO_WAY,
2080 CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY)));
2081 for (it = sources.begin(); it != sources.end(); ++it) {
2082 if (it->second->config.createSourceB) {
2083 TestingSyncSourcePtr source;
2084 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2085 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2086 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2087 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2088 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2089 CPPUNIT_ASSERT_NO_THROW(source.reset());
2093 // delete items on clientA, sync to server
2094 for (it = sources.begin(); it != sources.end(); ++it) {
2095 it->second->deleteAll(it->second->createSourceA);
2097 if (it->second->config.createSourceB) {
2098 TestingSyncSourcePtr source;
2099 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2100 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2101 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2102 SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get()));
2103 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2104 CPPUNIT_ASSERT_NO_THROW(source.reset());
2108 SyncOptions(SYNC_TWO_WAY,
2109 CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY)));
2110 for (it = sources.begin(); it != sources.end(); ++it) {
2111 if (it->second->config.createSourceB) {
2112 TestingSyncSourcePtr source;
2113 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2114 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2115 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2116 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2117 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2118 CPPUNIT_ASSERT_NO_THROW(source.reset());
2122 // sync the same change to second client
2123 // => one item left (the one inserted locally)
2124 accessClientB->doSync("delete",
2125 SyncOptions(SYNC_ONE_WAY_FROM_SERVER,
2126 CheckSyncReport(0,0,1, 0,0,0, true, SYNC_ONE_WAY_FROM_SERVER)));
2127 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2128 if (it->second->config.createSourceB) {
2129 TestingSyncSourcePtr source;
2130 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2131 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2132 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2133 SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get()));
2134 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2135 CPPUNIT_ASSERT_NO_THROW(source.reset());
2140 // tests one-way sync from client:
2141 // - get both clients and server in sync with no items anywhere
2142 // - add one item on first client, copy to server
2143 // - add a different item on second client, one-way-from-client
2144 // - two-way sync with first client
2145 // => two items on first client, one on second
2146 // - delete on second client, sync that to first client
2147 // via one-way-from-client, two-way
2148 // => one item left on first client (the one inserted locally)
2149 void SyncTests::testOneWayFromClient() {
2150 // no items anywhere
2152 accessClientB->deleteAll();
2154 // check that everything is empty, also resets change tracking
2155 // in second sources of each client
2157 for (it = sources.begin(); it != sources.end(); ++it) {
2158 if (it->second->config.createSourceB) {
2159 TestingSyncSourcePtr source;
2160 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2161 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2162 CPPUNIT_ASSERT_NO_THROW(source.reset());
2165 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2166 if (it->second->config.createSourceB) {
2167 TestingSyncSourcePtr source;
2168 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2169 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2170 CPPUNIT_ASSERT_NO_THROW(source.reset());
2174 // add one item on first client, copy to server, and check change tracking via second source
2175 for (it = sources.begin(); it != sources.end(); ++it) {
2176 it->second->insertManyItems(it->second->createSourceA, 1, 1);
2179 SyncOptions(SYNC_TWO_WAY,
2180 CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
2181 for (it = sources.begin(); it != sources.end(); ++it) {
2182 if (it->second->config.createSourceB) {
2183 TestingSyncSourcePtr source;
2184 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2185 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2186 SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2187 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2188 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2189 CPPUNIT_ASSERT_NO_THROW(source.reset());
2193 // add a different item on second client, one-way-from-client
2194 // => no item added locally, one sent to server
2195 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2196 it->second->insertManyItems(it->second->createSourceA, 2, 1);
2198 if (it->second->config.createSourceB) {
2199 TestingSyncSourcePtr source;
2200 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2201 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2202 SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2203 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2204 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2205 CPPUNIT_ASSERT_NO_THROW(source.reset());
2208 accessClientB->doSync("send",
2209 SyncOptions(SYNC_ONE_WAY_FROM_CLIENT,
2210 CheckSyncReport(0,0,0, 1,0,0, true, SYNC_ONE_WAY_FROM_CLIENT)));
2211 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2212 if (it->second->config.createSourceB) {
2213 TestingSyncSourcePtr source;
2214 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2215 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2216 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2217 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2218 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2219 CPPUNIT_ASSERT_NO_THROW(source.reset());
2223 // two-way sync with client A for verification
2224 // => receive one item
2226 SyncOptions(SYNC_TWO_WAY,
2227 CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY)));
2228 for (it = sources.begin(); it != sources.end(); ++it) {
2229 if (it->second->config.createSourceB) {
2230 TestingSyncSourcePtr source;
2231 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2232 SOURCE_ASSERT_EQUAL(source.get(), 2, countItems(source.get()));
2233 SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2234 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2235 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2236 CPPUNIT_ASSERT_NO_THROW(source.reset());
2240 // delete items on client B, sync to server
2241 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2242 it->second->deleteAll(it->second->createSourceA);
2244 if (it->second->config.createSourceB) {
2245 TestingSyncSourcePtr source;
2246 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2247 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2248 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2249 SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get()));
2250 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2251 CPPUNIT_ASSERT_NO_THROW(source.reset());
2254 accessClientB->doSync("delete",
2255 SyncOptions(SYNC_ONE_WAY_FROM_CLIENT,
2256 CheckSyncReport(0,0,0, 0,0,1, true, SYNC_ONE_WAY_FROM_CLIENT)));
2257 for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2258 if (it->second->config.createSourceB) {
2259 TestingSyncSourcePtr source;
2260 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2261 SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2262 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2263 SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2264 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2265 CPPUNIT_ASSERT_NO_THROW(source.reset());
2269 // sync the same change to client A
2270 // => one item left (the one inserted locally)
2272 SyncOptions(SYNC_TWO_WAY,
2273 CheckSyncReport(0,0,1, 0,0,0, true, SYNC_TWO_WAY)));
2274 for (it = sources.begin(); it != sources.end(); ++it) {
2275 if (it->second->config.createSourceB) {
2276 TestingSyncSourcePtr source;
2277 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2278 SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2279 SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2280 SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get()));
2281 SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2282 CPPUNIT_ASSERT_NO_THROW(source.reset());
2287 // get engine ready, then use it to convert our test items
2288 // to and from the internal field list
2289 void SyncTests::testConversion() {
2290 bool success = false;
2291 SyncOptions::Callback_t callback = boost::bind(&SyncTests::doConversionCallback, this, &success, _1, _2);
2293 doSync(SyncOptions(SYNC_TWO_WAY, CheckSyncReport(-1,-1,-1, -1,-1,-1, false))
2294 .setStartCallback(callback));
2295 CPPUNIT_ASSERT(success);
2298 bool SyncTests::doConversionCallback(bool *success,
2299 SyncContext &syncClient,
2300 SyncOptions &options) {
2303 for (source_it it = sources.begin(); it != sources.end(); ++it) {
2304 const ClientTest::Config *config = &it->second->config;
2305 TestingSyncSource *source = static_cast<TestingSyncSource *>(syncClient.findSource(config->sourceName));
2306 CPPUNIT_ASSERT(source);
2308 std::string type = source->getNativeDatatypeName();
2313 std::list<std::string> items;
2314 std::string testcases;
2315 ClientTest::getItems(config->testcases, items, testcases);
2316 std::string converted = getCurrentTest();
2317 converted += ".converted.";
2318 converted += config->sourceName;
2319 converted += ".dat";
2320 simplifyFilename(converted);
2321 std::ofstream out(converted.c_str());
2322 BOOST_FOREACH(const string &item, items) {
2323 string convertedItem = item;
2324 if(!sysync::DataConversion(syncClient.getSession().get(),
2328 SE_LOG_ERROR(NULL, NULL, "failed parsing as %s:\n%s",
2332 out << convertedItem << "\n";
2336 CPPUNIT_ASSERT(config->compare(client, testcases.c_str(), converted.c_str()));
2339 // abort sync after completing the test successfully (no exception so far!)
2344 // creates several items, transmits them back and forth and
2345 // then compares which of them have been preserved
2346 void SyncTests::testItems() {
2347 // clean server and first test database
2352 for (it = sources.begin(); it != sources.end(); ++it) {
2353 it->second->testImport();
2356 // transfer from client A to server to client B
2357 doSync("send", SyncOptions(SYNC_TWO_WAY).setWBXML(true));
2358 accessClientB->refreshClient(SyncOptions().setWBXML(true));
2363 // creates several items, transmits them back and forth and
2364 // then compares which of them have been preserved
2365 void SyncTests::testItemsXML() {
2366 // clean server and first test database
2371 for (it = sources.begin(); it != sources.end(); ++it) {
2372 it->second->testImport();
2375 // transfer from client A to server to client B using the non-default XML format
2376 doSync("send", SyncOptions(SYNC_TWO_WAY).setWBXML(false));
2377 accessClientB->refreshClient(SyncOptions().setWBXML(false));
2382 // tests the following sequence of events:
2383 // - both clients in sync with server
2384 // - client 1 adds item
2385 // - client 1 updates the same item
2386 // - client 2 gets item: the client should be asked to add the item
2388 // However it has been observed that sometimes the item was sent as "update"
2389 // for a non-existant local item. This is a server bug, the client does not
2390 // have to handle that. See
2391 // http://forge.objectweb.org/tracker/?func=detail&atid=100096&aid=305018&group_id=96
2392 void SyncTests::testAddUpdate() {
2393 // clean server and both test databases
2395 accessClientB->refreshClient();
2399 for (it = sources.begin(); it != sources.end(); ++it) {
2400 it->second->insert(it->second->createSourceA, it->second->config.insertItem, it->second->config.itemType);
2403 SyncOptions(SYNC_TWO_WAY,
2404 CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
2407 for (it = sources.begin(); it != sources.end(); ++it) {
2408 it->second->update(it->second->createSourceB, it->second->config.updateItem);
2411 SyncOptions(SYNC_TWO_WAY,
2412 CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY)));
2414 // now download the updated item into the second client
2415 accessClientB->doSync("recv",
2416 SyncOptions(SYNC_TWO_WAY,
2417 CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY)));
2419 // compare the two databases
2424 // stress tests: execute some of the normal operations,
2425 // but with large number of artificially generated items
2428 // two-way sync with clean client/server,
2429 // followed by slow sync and comparison
2430 // via second client
2431 void SyncTests::testManyItems() {
2432 // clean server and client A
2435 // import artificial data: make them large to generate some
2436 // real traffic and test buffer handling
2439 for (it = sources.begin(); it != sources.end(); ++it) {
2440 if (num_items == -1) {
2441 num_items = it->second->config.numItems;
2443 CPPUNIT_ASSERT_EQUAL(num_items, it->second->config.numItems);
2445 it->second->insertManyItems(it->second->createSourceA, 0, num_items, 2000);
2448 // send data to server
2450 SyncOptions(SYNC_TWO_WAY,
2451 CheckSyncReport(0,0,0, num_items,0,0, true, SYNC_TWO_WAY),
2452 SyncOptions::DEFAULT_MAX_MSG_SIZE,
2453 SyncOptions::DEFAULT_MAX_OBJ_SIZE,
2456 // ensure that client has the same data, ignoring data conversion
2457 // issues (those are covered by testItems())
2460 // also copy to second client
2461 accessClientB->refreshClient();
2463 // slow sync now should not change anything
2465 SyncOptions(SYNC_SLOW,
2466 CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_SLOW),
2467 SyncOptions::DEFAULT_MAX_MSG_SIZE,
2468 SyncOptions::DEFAULT_MAX_OBJ_SIZE,
2476 * Tell server to delete plenty of items.
2478 void SyncTests::testManyDeletes() {
2479 // clean server and client A
2482 // import artificial data: make them small, we just want
2486 for (it = sources.begin(); it != sources.end(); ++it) {
2487 if (num_items == -1) {
2488 num_items = it->second->config.numItems;
2490 CPPUNIT_ASSERT_EQUAL(num_items, it->second->config.numItems);
2492 it->second->insertManyItems(it->second->createSourceA, 0, num_items, 100);
2495 // send data to server
2497 SyncOptions(SYNC_TWO_WAY,
2498 CheckSyncReport(0,0,0, num_items,0,0, true, SYNC_TWO_WAY),
2499 64 * 1024, 64 * 1024, true));
2501 // ensure that client has the same data, ignoring data conversion
2502 // issues (those are covered by testItems())
2505 // also copy to second client
2506 accessClientB->refreshClient();
2508 // slow sync now should not change anything
2510 SyncOptions(SYNC_SLOW,
2511 CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_SLOW),
2512 64 * 1024, 64 * 1024, true));
2517 // delete everything locally
2518 BOOST_FOREACH(source_array_t::value_type &source_pair, sources) {
2519 source_pair.second->deleteAll(source_pair.second->createSourceA);
2521 doSync("delete-server",
2522 SyncOptions(SYNC_TWO_WAY,
2523 CheckSyncReport(0,0,0, 0,0,num_items, true, SYNC_TWO_WAY),
2526 // update second client
2527 accessClientB->doSync("delete-client",
2528 SyncOptions(SYNC_REFRESH_FROM_SERVER,
2529 CheckSyncReport(0,0,num_items, 0,0,0, true, SYNC_REFRESH_FROM_SERVER),
2534 * - get client A, server, client B in sync with one item
2535 * - force slow sync in A: must not duplicate items, but may update it locally
2536 * - refresh client B (in case that the item was updated)
2537 * - delete item in B and server via two-way sync
2538 * - refresh-from-server in B to check that item is gone
2539 * - two-way in A: must delete the item
2541 void SyncTests::testSlowSyncSemantic()
2543 // set up one item everywhere
2548 SyncOptions(SYNC_SLOW,
2549 CheckSyncReport(0,-1,0, -1,-1,0, true, SYNC_SLOW)));
2551 // refresh B, delete item
2552 accessClientB->doSync("refresh",
2553 SyncOptions(SYNC_TWO_WAY,
2554 CheckSyncReport(0,-1,0, 0,0,0, true, SYNC_TWO_WAY)));
2555 BOOST_FOREACH(source_array_t::value_type &source_pair, accessClientB->sources) {
2556 source_pair.second->deleteAll(source_pair.second->createSourceA);
2558 accessClientB->doSync("delete",
2559 SyncOptions(SYNC_TWO_WAY,
2560 CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY)));
2561 accessClientB->doSync("check",
2562 SyncOptions(SYNC_REFRESH_FROM_SERVER,
2563 CheckSyncReport(0,0,0, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
2565 // now the item should also be deleted on A
2567 SyncOptions(SYNC_TWO_WAY,
2568 CheckSyncReport(0,0,1, 0,0,0, true, SYNC_TWO_WAY)));
2572 * check that refresh-from-server works correctly:
2573 * - create the same item on A, server, B via testCopy()
2574 * - refresh B (one item deleted, one created)
2575 * - delete item on A and server
2576 * - refresh B (one item deleted)
2578 void SyncTests::testComplexRefreshFromServerSemantic()
2582 // check refresh with one item on server
2583 accessClientB->doSync("refresh-one",
2584 SyncOptions(SYNC_REFRESH_FROM_SERVER,
2585 CheckSyncReport(1,0,1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
2587 // delete that item via A, check again
2588 BOOST_FOREACH(source_array_t::value_type &source_pair, sources) {
2589 source_pair.second->deleteAll(source_pair.second->createSourceA);
2591 doSync("delete-item",
2592 SyncOptions(SYNC_TWO_WAY,
2593 CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY)));
2594 accessClientB->doSync("refresh-none",
2595 SyncOptions(SYNC_REFRESH_FROM_SERVER,
2596 CheckSyncReport(0,0,1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
2600 * implements testMaxMsg(), testLargeObject(), testLargeObjectEncoded()
2601 * using a sequence of items with varying sizes
2603 void SyncTests::doVarSizes(bool withMaxMsgSize,
2604 bool withLargeObject) {
2605 int maxMsgSize = 8 * 1024;
2606 const char* maxItemSize = getenv("CLIENT_TEST_MAX_ITEMSIZE");
2607 int tmpSize = maxItemSize ? atoi(maxItemSize) : 0;
2609 maxMsgSize = tmpSize;
2611 // clean server and client A
2614 // insert items, doubling their size, then restart with small size
2616 for (it = sources.begin(); it != sources.end(); ++it) {
2618 for (int i = 0; i < 2; i++ ) {
2620 while (size < 2 * maxMsgSize) {
2621 it->second->insertManyItems(it->second->createSourceA, item, 1, (int)strlen(it->second->config.templateItem) + 10 + size);
2628 // transfer to server
2630 SyncOptions(SYNC_TWO_WAY,
2631 CheckSyncReport(0,0,0, -1,0,0, true, SYNC_TWO_WAY), // number of items sent to server depends on source
2632 withMaxMsgSize ? SyncOptions::DEFAULT_MAX_MSG_SIZE: 0,
2633 withMaxMsgSize ? SyncOptions::DEFAULT_MAX_OBJ_SIZE : 0,
2636 // copy to second client
2637 const char *value = getenv ("CLIENT_TEST_NOREFRESH");
2638 // If refresh_from_server or refresh_from_client (depending on this is a
2639 // server or client) is not supported, we can still test via slow sync.
2641 accessClientB->refreshClient();
2643 accessClientB->doSync("recv",
2644 SyncOptions(SYNC_REFRESH_FROM_SERVER,
2645 CheckSyncReport(-1,0,-1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER), // number of items received from server depends on source
2646 withLargeObject ? maxMsgSize : withMaxMsgSize ? maxMsgSize * 100 /* large enough so that server can sent the largest item */ : 0,
2647 withMaxMsgSize ? maxMsgSize * 100 : 0,
2655 * Send message to server, then pretend that we timed out at exactly
2656 * one specific message, specified via m_interruptAtMessage. The
2657 * caller is expected to resend the message, without aborting the
2658 * session. That resend and all following message will get through
2661 * Each send() is counted as one message, starting at 1 for the first
2664 class TransportResendInjector : public TransportWrapper{
2668 TransportResendInjector()
2669 :TransportWrapper() {
2670 const char *s = getenv("CLIENT_TEST_RESEND_TIMEOUT");
2671 timeout = s ? atoi(s) : 0;
2674 ~TransportResendInjector() {
2677 virtual void send(const char *data, size_t len)
2680 if (m_interruptAtMessage >= 0 &&
2681 m_messageCount == m_interruptAtMessage+1) {
2682 m_wrappedAgent->send(data, len);
2683 m_status = m_wrappedAgent->wait();
2684 //trigger client side resend
2686 m_status = TIME_OUT;
2690 m_wrappedAgent->send(data, len);
2691 m_status = m_wrappedAgent->wait();
2695 virtual void getReply(const char *&data, size_t &len, std::string &contentType) {
2696 if (m_status == FAILED) {
2700 m_wrappedAgent->getReply(data, len, contentType);
2706 * Stop sending at m_interruptAtMessage. The caller is forced to abort
2707 * the current session and will recover by retrying in another
2710 * Each send() increments the counter by two, so that 1 aborts before
2711 * the first message and 2 after it.
2713 class TransportFaultInjector : public TransportWrapper{
2715 TransportFaultInjector()
2716 :TransportWrapper() {
2719 ~TransportFaultInjector() {
2722 virtual void send(const char *data, size_t len)
2724 if (m_interruptAtMessage == m_messageCount) {
2725 SE_LOG_DEBUG(NULL, NULL, "TransportFaultInjector: interrupt before sending message #%d", m_messageCount);
2728 if (m_interruptAtMessage >= 0 &&
2729 m_messageCount > m_interruptAtMessage) {
2730 throw string("TransportFaultInjector: interrupt before send");
2733 m_wrappedAgent->send(data, len);
2735 m_status = m_wrappedAgent->wait();
2737 if (m_interruptAtMessage == m_messageCount) {
2738 SE_LOG_DEBUG(NULL, NULL, "TransportFaultInjector: interrupt after receiving reply #%d", m_messageCount);
2741 if (m_interruptAtMessage >= 0 &&
2742 m_messageCount > m_interruptAtMessage) {
2747 virtual void getReply(const char *&data, size_t &len, std::string &contentType) {
2748 if (m_status == FAILED) {
2752 m_wrappedAgent->getReply(data, len, contentType);
2758 * Emulates a user suspend just after receving response
2761 class UserSuspendInjector : public TransportWrapper{
2763 UserSuspendInjector()
2764 :TransportWrapper() {
2767 ~UserSuspendInjector() {
2770 virtual void send(const char *data, size_t len)
2772 m_wrappedAgent->send(data, len);
2773 m_status = m_wrappedAgent->wait();
2776 virtual void getReply(const char *&data, size_t &len, std::string &contentType) {
2777 if (m_status == FAILED) {
2781 if (m_interruptAtMessage == m_messageCount) {
2782 SE_LOG_DEBUG(NULL, NULL, "UserSuspendInjector: user suspend after getting reply #%d", m_messageCount);
2785 if (m_interruptAtMessage >= 0 &&
2786 m_messageCount > m_interruptAtMessage) {
2787 m_options->m_isSuspended = true;
2789 m_wrappedAgent->getReply(data, len, contentType);
2795 * This function covers different error scenarios that can occur
2796 * during real synchronization. To pass, clients must either force a
2797 * slow synchronization after a failed synchronization or implement
2798 * the error handling described in the design guide (track server's
2799 * status for added/updated/deleted items and resend unacknowledged
2802 * The items used during these tests are synthetic. They are
2803 * constructed so that normally a server should be able to handle
2804 * twinning during a slow sync correctly.
2806 * Errors are injected into a synchronization by wrapping the normal
2807 * HTTP transport agent. The wrapper enumerates messages sent between
2808 * client and server (i.e., one message exchange increments the
2809 * counter by two), starting from zero. It "cuts" the connection before
2810 * sending out the next message to the server respectively after the
2811 * server has replied, but before returning the reply to the client.
2812 * The first case simulates a lost message from the client to the server
2813 * and the second case a lost message from the server to the client.
2815 * The expected result is the same as in an uninterrupted sync, which
2816 * is done once at the beginning.
2818 * Each test goes through the following steps:
2819 * - client A and B reset local data store
2820 * - client A creates 3 new items, remembers LUIDs
2821 * - refresh-from-client A sync
2822 * - refresh-from-client B sync
2823 * - client B creates 3 different items, remembers LUIDs
2825 * - client A syncs => A, B, server are in sync
2826 * - client A modifies his items (depends on test) and
2827 * sends changes to server => server has changes for B
2828 * - client B modifies his items (depends on test)
2829 * - client B syncs, transport wrapper simulates lost message n
2830 * - client B syncs again, resuming synchronization if possible or
2831 * slow sync otherwise (responsibility of the client!)
2832 * - client A syncs (not tested yet: A should be sent exactly the changes made by B)
2833 * - test that A and B contain same items
2834 * - test that A contains the same items as the uninterrupted reference run
2835 * - repeat the steps above ranging starting with lost message 0 until no
2838 * Set the CLIENT_TEST_INTERRUPT_AT env variable to a message number
2839 * >= 0 to execute one uninterrupted run and then interrupt at that
2840 * message. Set to -1 to just do the uninterrupted run.
2842 void SyncTests::doInterruptResume(int changes,
2843 boost::shared_ptr<TransportWrapper> wrapper)
2845 int interruptAtMessage = -1;
2846 const char *t = getenv("CLIENT_TEST_INTERRUPT_AT");
2847 int requestedInterruptAt = t ? atoi(t) : -2;
2848 const char *s = getenv("CLIENT_TEST_INTERRUPT_SLEEP");
2849 int sleep_t = s ? atoi(s) : 0;
2851 std::string refFileBase = getCurrentTest() + ".ref.";
2853 bool resend = dynamic_cast <TransportResendInjector *> (wrapper.get()) != NULL;
2854 bool suspend = dynamic_cast <UserSuspendInjector *> (wrapper.get()) != NULL;
2855 bool interrupt = dynamic_cast <TransportFaultInjector *> (wrapper.get()) != NULL;
2857 // better be large enough for complete DevInf, 20000 is already a
2858 // bit small when running with many stores
2859 size_t maxMsgSize = 20000;
2860 size_t changedItemSize = (changes & BIG) ?
2861 5 * maxMsgSize / 2 : // large enough to be split over three messages
2864 // After running the uninterrupted sync, we remember the number
2865 // of sent messages. We never interrupt between sending our
2866 // own last message and receiving the servers last reply,
2867 // because the server is unable to detect that we didn't get
2868 // the reply. It will complete the session whereas the client
2869 // suspends, leading to an unexpected slow sync the next time.
2874 sprintf(buffer, "%d", interruptAtMessage);
2875 const char *prefix = interruptAtMessage == -1 ? "complete" : buffer;
2876 SyncPrefix prefixA(prefix, *this);
2877 SyncPrefix prefixB(prefix, *accessClientB);
2879 std::vector< std::list<std::string> > clientAluids;
2880 std::vector< std::list<std::string> > clientBluids;
2882 // create new items in client A and sync to server
2883 clientAluids.resize(sources.size());
2884 for (i = 0; i < sources.size(); i++) {
2885 sources[i].second->deleteAll(sources[i].second->createSourceA);
2887 sources[i].second->insertManyItems(sources[i].second->createSourceA,
2890 doSync("fromA", SyncOptions(SYNC_REFRESH_FROM_CLIENT));
2892 // init client B and add its items to server and client A
2893 accessClientB->doSync("initB", SyncOptions(SYNC_REFRESH_FROM_SERVER));
2894 clientBluids.resize(sources.size());
2895 for (i = 0; i < sources.size(); i++) {
2897 accessClientB->sources[i].second->insertManyItems(accessClientB->sources[i].second->createSourceA,
2900 accessClientB->doSync("fromB", SyncOptions(SYNC_TWO_WAY));
2901 doSync("updateA", SyncOptions(SYNC_TWO_WAY));
2903 // => client A, B and server in sync with a total of six items
2905 // make changes as requested on client A and sync to server
2906 for (i = 0; i < sources.size(); i++) {
2907 if (changes & SERVER_ADD) {
2908 sources[i].second->insertManyItems(sources[i].second->createSourceA,
2909 4, 1, changedItemSize);
2911 if (changes & SERVER_REMOVE) {
2912 // remove second item
2913 removeItem(sources[i].second->createSourceA,
2914 *(++clientAluids[i].begin()));
2916 if (changes & SERVER_UPDATE) {
2917 // update third item
2918 updateItem(sources[i].second->createSourceA,
2919 *(++ ++clientAluids[i].begin()),
2920 sources[i].second->createItem(3, "updated", changedItemSize).c_str());
2925 // send using the same mode as in the interrupted sync with client B
2926 if (changes & (SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE)) {
2927 doSync("changesFromA", SyncOptions(SYNC_TWO_WAY).setMaxMsgSize(maxMsgSize));
2930 // make changes as requested on client B
2931 for (i = 0; i < sources.size(); i++) {
2932 if (changes & CLIENT_ADD) {
2933 accessClientB->sources[i].second->insertManyItems(accessClientB->sources[i].second->createSourceA,
2934 14, 1, changedItemSize);
2936 if (changes & CLIENT_REMOVE) {
2937 // remove second item
2938 removeItem(accessClientB->sources[i].second->createSourceA,
2939 *(++clientBluids[i].begin()));
2941 if (changes & CLIENT_UPDATE) {
2942 // update third item
2943 updateItem(accessClientB->sources[i].second->createSourceA,
2944 *(++ ++clientBluids[i].begin()),
2945 accessClientB->sources[i].second->createItem(13, "updated", changedItemSize).c_str());
2949 // Now do an interrupted sync between B and server.
2950 // The explicit delete of the TransportAgent is suppressed
2951 // by overloading the delete operator.
2954 CheckSyncReport check(-1, -1, -1, -1, -1, -1, false);
2955 if (resend && interruptAtMessage != 0) {
2956 // resend tests must succeed, except for the first
2957 // message in the session, which is not resent
2958 check.mustSucceed = true;
2960 SyncOptions options(SYNC_TWO_WAY, check);
2961 options.setTransportAgent(wrapper);
2962 options.setMaxMsgSize(maxMsgSize);
2964 // disable resending completely
2965 options.setRetryInterval(0);
2967 wrapper->setInterruptAtMessage(interruptAtMessage);
2968 accessClientB->doSync("changesFromB", options);
2969 wasInterrupted = interruptAtMessage != -1 &&
2970 wrapper->getMessageCount() <= interruptAtMessage;
2972 maxMsgNum = wrapper->getMessageCount();
2977 if (interruptAtMessage != -1) {
2978 if (wasInterrupted) {
2979 // uninterrupted sync, done
2983 // continue, wait until server timeout
2987 // no need for resend tests, unless they were interrupted at the first message
2988 if (!resend || interruptAtMessage == 0) {
2990 accessClientB->doSync("retryB",
2991 SyncOptions(SYNC_TWO_WAY,
2992 CheckSyncReport().setMode(SYNC_TWO_WAY).setReport(&report)));
2993 // Suspending at first and last message doesn't need a
2994 // resume, everything else does. When multiple sources
2995 // are involved, some may suspend, some may not, so we
2998 interruptAtMessage != 0 &&
2999 interruptAtMessage + 1 != maxMsgNum &&
3000 report.size() == 1) {
3001 BOOST_FOREACH(const SyncReport::SourceReport_t &sourceReport, report) {
3002 CPPUNIT_ASSERT(sourceReport.second.isResumeSync());
3008 // copy changes to client A
3009 doSync("toA", SyncOptions(SYNC_TWO_WAY));
3011 // compare client A and B
3012 if (interruptAtMessage != -1 &&
3013 !compareDatabases(refFileBase.c_str(), false)) {
3015 std::cout << "====> comparison of client B against reference file(s) failed after interrupting at message #" <<
3016 interruptAtMessage << std::endl;
3019 if (!compareDatabases(NULL, false)) {
3021 std::cout << "====> comparison of client A and B failed after interrupting at message #" <<
3022 interruptAtMessage << std::endl;
3026 // save reference files from uninterrupted run?
3027 if (interruptAtMessage == -1) {
3028 for (source_it it = sources.begin();
3029 it != sources.end();
3031 std::string refFile = refFileBase;
3032 refFile += it->second->config.sourceName;
3034 simplifyFilename(refFile);
3035 TestingSyncSourcePtr source;
3036 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
3037 SOURCE_ASSERT_EQUAL(source.get(), 0, it->second->config.dump(client, *source.get(), refFile.c_str()));
3038 CPPUNIT_ASSERT_NO_THROW(source.reset());
3042 // pick next iteration
3043 if (requestedInterruptAt == -1) {
3044 // user requested to stop after first iteration
3046 } else if (requestedInterruptAt >= 0) {
3047 // only do one interrupted run of the test
3048 if (requestedInterruptAt == interruptAtMessage) {
3051 interruptAtMessage = requestedInterruptAt;
3054 // interrupt one message later than before
3055 interruptAtMessage++;
3057 interruptAtMessage + 1 >= maxMsgNum) {
3058 // Don't interrupt before the server's last reply,
3059 // because then the server thinks we completed the
3060 // session when we think we didn't, which leads to a
3061 // slow sync. Testing that is better done with a
3065 if (interruptAtMessage >= maxMsgNum) {
3066 // next run would not interrupt at all, stop now
3072 CPPUNIT_ASSERT(equal);
3075 void SyncTests::testInterruptResumeClientAdd()
3077 doInterruptResume(CLIENT_ADD, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3080 void SyncTests::testInterruptResumeClientRemove()
3082 doInterruptResume(CLIENT_REMOVE, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3085 void SyncTests::testInterruptResumeClientUpdate()
3087 doInterruptResume(CLIENT_UPDATE, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3090 void SyncTests::testInterruptResumeServerAdd()
3092 doInterruptResume(SERVER_ADD, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3095 void SyncTests::testInterruptResumeServerRemove()
3097 doInterruptResume(SERVER_REMOVE, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3100 void SyncTests::testInterruptResumeServerUpdate()
3102 doInterruptResume(SERVER_UPDATE, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3105 void SyncTests::testInterruptResumeClientAddBig()
3107 doInterruptResume(CLIENT_ADD|BIG, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3110 void SyncTests::testInterruptResumeClientUpdateBig()
3112 doInterruptResume(CLIENT_UPDATE|BIG, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3115 void SyncTests::testInterruptResumeServerAddBig()
3117 doInterruptResume(SERVER_ADD|BIG, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3120 void SyncTests::testInterruptResumeServerUpdateBig()
3122 doInterruptResume(SERVER_UPDATE|BIG, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3125 void SyncTests::testInterruptResumeFull()
3127 doInterruptResume(CLIENT_ADD|CLIENT_REMOVE|CLIENT_UPDATE|
3128 SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
3131 void SyncTests::testUserSuspendClientAdd()
3133 doInterruptResume(CLIENT_ADD, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3136 void SyncTests::testUserSuspendClientRemove()
3138 doInterruptResume(CLIENT_REMOVE, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3141 void SyncTests::testUserSuspendClientUpdate()
3143 doInterruptResume(CLIENT_UPDATE, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3146 void SyncTests::testUserSuspendServerAdd()
3148 doInterruptResume(SERVER_ADD, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3151 void SyncTests::testUserSuspendServerRemove()
3153 doInterruptResume(SERVER_REMOVE, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3156 void SyncTests::testUserSuspendServerUpdate()
3158 doInterruptResume(SERVER_UPDATE, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3161 void SyncTests::testUserSuspendClientAddBig()
3163 doInterruptResume(CLIENT_ADD|BIG, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3166 void SyncTests::testUserSuspendClientUpdateBig()
3168 doInterruptResume(CLIENT_UPDATE|BIG, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3171 void SyncTests::testUserSuspendServerAddBig()
3173 doInterruptResume(SERVER_ADD|BIG, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3176 void SyncTests::testUserSuspendServerUpdateBig()
3178 doInterruptResume(SERVER_UPDATE|BIG, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3181 void SyncTests::testUserSuspendFull()
3183 doInterruptResume(CLIENT_ADD|CLIENT_REMOVE|CLIENT_UPDATE|
3184 SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3187 void SyncTests::testResendClientAdd()
3189 doInterruptResume(CLIENT_ADD, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3192 void SyncTests::testResendClientRemove()
3194 doInterruptResume(CLIENT_REMOVE, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3197 void SyncTests::testResendClientUpdate()
3199 doInterruptResume(CLIENT_UPDATE, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3202 void SyncTests::testResendServerAdd()
3204 doInterruptResume(SERVER_ADD, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3207 void SyncTests::testResendServerRemove()
3209 doInterruptResume(SERVER_REMOVE, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3212 void SyncTests::testResendServerUpdate()
3214 doInterruptResume(SERVER_UPDATE, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3217 void SyncTests::testResendFull()
3219 doInterruptResume(CLIENT_ADD|CLIENT_REMOVE|CLIENT_UPDATE|
3220 SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE,
3221 boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3224 void SyncTests::doSync(const SyncOptions &options)
3227 static int syncCounter = 0;
3228 static std::string lastTest;
3229 std::stringstream logstream;
3231 // reset counter when switching tests
3232 if (lastTest != getCurrentTest()) {
3234 lastTest = getCurrentTest();
3239 for (std::list<std::string>::iterator it = logPrefixes.begin();
3240 it != logPrefixes.end();
3245 if (!prefix.empty()) {
3246 printf(" %s", prefix.c_str() + 1);
3250 logstream /* << std::setw(4) << std::setfill('0') << syncCounter << "_" */ << getCurrentTest()
3252 << ".client." << (accessClientB ? "A" : "B");
3253 std::string logname = logstream.str();
3254 simplifyFilename(logname);
3257 SE_LOG_DEBUG(NULL, NULL, "%d. starting %s with sync mode %s",
3258 syncCounter, logname.c_str(), PrettyPrintSyncMode(options.m_syncMode).c_str());
3261 res = client.doSync(sourceArray,
3265 client.postSync(res, logname);
3266 } catch (CppUnit::Exception &ex) {
3268 client.postSync(res, logname);
3270 // report the original exception without altering the source line
3274 client.postSync(res, logname);
3276 // this logs the original exception using CPPUnit mechanisms,
3277 // with current line as source
3278 CPPUNIT_ASSERT_NO_THROW(throw);
3283 /** generates tests on demand based on what the client supports */
3284 class ClientTestFactory : public CppUnit::TestFactory {
3286 ClientTestFactory(ClientTest &c) :
3289 virtual CppUnit::Test *makeTest() {
3291 CppUnit::TestSuite *alltests = new CppUnit::TestSuite("Client");
3292 CppUnit::TestSuite *tests;
3294 // create local source tests
3295 tests = new CppUnit::TestSuite(alltests->getName() + "::Source");
3296 for (source=0; source < client.getNumLocalSources(); source++) {
3297 ClientTest::Config config;
3298 client.getLocalSourceConfig(source, config);
3299 if (config.sourceName) {
3300 LocalTests *sourcetests =
3301 client.createLocalTests(tests->getName() + "::" + config.sourceName, source, config);
3302 sourcetests->addTests();
3303 tests->addTest(FilterTest(sourcetests));
3306 alltests->addTest(FilterTest(tests));
3309 // create sync tests with just one source
3310 tests = new CppUnit::TestSuite(alltests->getName() + "::Sync");
3311 for (source=0; source < client.getNumSyncSources(); source++) {
3312 ClientTest::Config config;
3313 client.getSyncSourceConfig(source, config);
3314 if (config.sourceName) {
3315 std::vector<int> sources;
3316 sources.push_back(source);
3317 SyncTests *synctests =
3318 client.createSyncTests(tests->getName() + "::" + config.sourceName, sources);
3319 synctests->addTests();
3320 tests->addTest(FilterTest(synctests));
3324 // create sync tests with all sources enabled, unless we only have one:
3325 // that would be identical to the test above
3326 std::vector<int> sources;
3327 std::string name, name_reversed;
3328 for (source=0; source < client.getNumSyncSources(); source++) {
3329 ClientTest::Config config;
3330 client.getSyncSourceConfig(source, config);
3331 if (config.sourceName) {
3332 sources.push_back(source);
3333 if (name.size() > 0) {
3335 name_reversed = std::string("_") + name_reversed;
3337 name += config.sourceName;
3338 name_reversed = config.sourceName + name_reversed;
3341 if (sources.size() > 1) {
3342 SyncTests *synctests =
3343 client.createSyncTests(tests->getName() + "::" + name, sources);
3344 synctests->addTests();
3345 tests->addTest(FilterTest(synctests));
3348 // now also in reversed order - who knows, it might make a difference
3349 std::reverse(sources.begin(), sources.end());
3351 client.createSyncTests(tests->getName() + "::" + name_reversed, sources);
3352 synctests->addTests();
3353 tests->addTest(FilterTest(synctests));
3357 alltests->addTest(FilterTest(tests));
3367 void ClientTest::registerTests()
3369 factory = (void *)new ClientTestFactory(*this);
3370 CppUnit::TestFactoryRegistry::getRegistry().registerFactory((CppUnit::TestFactory *)factory);
3373 ClientTest::ClientTest(int serverSleepSec, const std::string &serverLog) :
3374 serverSleepSeconds(serverSleepSec),
3375 serverLogFileName(serverLog),
3380 ClientTest::~ClientTest()
3383 CppUnit::TestFactoryRegistry::getRegistry().unregisterFactory((CppUnit::TestFactory *)factory);
3384 delete (CppUnit::TestFactory *)factory;
3389 void ClientTest::registerCleanup(Cleanup_t cleanup)
3391 cleanupSet.insert(cleanup);
3394 void ClientTest::shutdown()
3396 BOOST_FOREACH(Cleanup_t cleanup, cleanupSet) {
3401 LocalTests *ClientTest::createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co)
3403 return new LocalTests(name, *this, sourceParam, co);
3406 SyncTests *ClientTest::createSyncTests(const std::string &name, std::vector<int> sourceIndices, bool isClientA)
3408 return new SyncTests(name, *this, sourceIndices, isClientA);
3411 int ClientTest::dump(ClientTest &client, TestingSyncSource &source, const char *file)
3413 BackupReport report;
3414 boost::shared_ptr<ConfigNode> node(new VolatileConfigNode);
3418 CPPUNIT_ASSERT(source.getOperations().m_backupData);
3419 source.getOperations().m_backupData(SyncSource::Operations::ConstBackupInfo(),
3420 SyncSource::Operations::BackupInfo(SyncSource::Operations::BackupInfo::BACKUP_OTHER, file, node),
3425 void ClientTest::getItems(const char *file, list<string> &items, std::string &testcases)
3429 // import the file, trying a .tem file (base file plus patch)
3431 std::ifstream input;
3432 string server = getenv("CLIENT_TEST_SERVER");
3433 testcases = string(file) + '.' + server +".tem";
3434 input.open(testcases.c_str());
3437 // try server-specific file (like ical20.ics.local)
3438 testcases = string(file) + '.' + server;
3439 input.open(testcases.c_str());
3445 input.open(testcases.c_str());
3447 CPPUNIT_ASSERT(!input.bad());
3448 CPPUNIT_ASSERT(input.is_open());
3449 std::string data, line;
3451 bool wasend = false;
3453 getline(input, line);
3454 CPPUNIT_ASSERT(!input.bad());
3455 // empty lines directly after line which starts with END mark end of record;
3456 // check for END necessary becayse vCard 2.1 ENCODING=BASE64 may have empty lines in body of VCARD!
3457 if ((line != "\r" && line.size() > 0) || !wasend) {
3461 if (!data.empty()) {
3462 items.push_back(data);
3466 wasend = !line.compare(0, 4, "END:");
3467 } while(!input.eof());
3469 if (!data.empty()) {
3470 items.push_back(data);
3474 int ClientTest::import(ClientTest &client, TestingSyncSource &source, const char *file, std::string &realfile)
3477 getItems(file, items, realfile);
3478 BOOST_FOREACH(string &data, items) {
3479 importItem(&source, data);
3484 bool ClientTest::compare(ClientTest &client, const char *fileA, const char *fileB)
3486 std::string cmdstr = std::string("env PATH=.:$PATH synccompare ") + fileA + " " + fileB;
3487 setenv("CLIENT_TEST_HEADER", "\n\n", 1);
3488 setenv("CLIENT_TEST_LEFT_NAME", fileA, 1);
3489 setenv("CLIENT_TEST_RIGHT_NAME", fileB, 1);
3490 setenv("CLIENT_TEST_REMOVED", "only in left file", 1);
3491 setenv("CLIENT_TEST_ADDED", "only in right file", 1);
3492 const char* compareLog = getenv("CLIENT_TEST_COMPARE_LOG");
3493 if(compareLog && strlen(compareLog))
3495 string tmpfile = "____compare.log";
3496 cmdstr =string("bash -c 'set -o pipefail;") + cmdstr;
3497 cmdstr += " 2>&1|tee " +tmpfile+"'";
3499 bool success = system(cmdstr.c_str()) == 0;
3501 printf("failed: env CLIENT_TEST_SERVER=%s PATH=.:$PATH synccompare %s %s\n",
3502 getenv("CLIENT_TEST_SERVER") ? getenv("CLIENT_TEST_SERVER") : "",
3508 void ClientTest::postSync(int res, const std::string &logname)
3511 Sleep(serverSleepSeconds * 1000);
3513 sleep(serverSleepSeconds);
3515 // make a copy of the server's log (if found), then truncate it
3516 if (serverLogFileName.size()) {
3517 int fd = open(serverLogFileName.c_str(), O_RDWR);
3520 std::string cmd = std::string("cp ") + serverLogFileName + " " + logname + ".server.log";
3521 if (system(cmd.c_str())) {
3522 fprintf(stderr, "copying log file failed: %s\n", cmd.c_str());
3524 if (ftruncate(fd, 0)) {
3525 perror("truncating log file");
3528 perror(serverLogFileName.c_str());
3534 void ClientTest::getTestData(const char *type, Config &config)
3536 memset(&config, 0, sizeof(config));
3537 char *numitems = getenv("CLIENT_TEST_NUM_ITEMS");
3538 config.numItems = numitems ? atoi(numitems) : 100;
3539 char *env = getenv("CLIENT_TEST_RETRY");
3540 config.retrySync = (env && !strcmp (env, "t")) ?true :false;
3541 env = getenv("CLIENT_TEST_RESEND");
3542 config.resendSync = (env && !strcmp (env, "t")) ?true :false;
3543 env = getenv("CLIENT_TEST_SUSPEND");
3544 config.suspendSync = (env && !strcmp (env, "t")) ?true :false;
3545 config.sourceKnowsItemSemantic = true;
3546 config.linkedItemsRelaxedSemantic = true;
3547 config.itemType = "";
3548 config.import = import;
3550 config.compare = compare;
3552 // redirect requests for "ical20" towards "ical20_noutc"?
3554 env = getenv ("CLIENT_TEST_NOUTC");
3555 if (env && !strcmp (env, "t")) {
3559 if (!strcmp(type, "vcard30")) {
3560 config.sourceName = "vcard30";
3561 config.sourceNameServerTemplate = "addressbook";
3562 config.uri = "card3"; // ScheduleWorld
3563 config.type = "text/vcard";
3570 "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3571 "X-EVOLUTION-FILE-AS:Doe\\, John\n"
3572 "X-MOZILLA-HTML:FALSE\n"
3573 "NOTE:<<REVISION>>\n"
3581 "X-EVOLUTION-FILE-AS:Doe\\, Joan\n"
3582 "TEL;TYPE=WORK;TYPE=VOICE:business 2\n"
3584 "X-MOZILLA-HTML:TRUE\n"
3586 /* adds a second phone number: */
3587 config.complexUpdateItem =
3593 "X-EVOLUTION-FILE-AS:Doe\\, Joan\n"
3594 "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3595 "TEL;TYPE=HOME;TYPE=VOICE:home 2\n"
3597 "X-MOZILLA-HTML:TRUE\n"
3599 /* add a telephone number, email and X-AIM to initial item */
3606 "X-EVOLUTION-FILE-AS:Doe\\, John\n"
3607 "X-MOZILLA-HTML:FALSE\n"
3608 "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3609 "EMAIL:john.doe@work.com\n"
3618 "TEL;TYPE=WORK;TYPE=VOICE:123456\n"
3619 "X-EVOLUTION-FILE-AS:Doe\\, John\n"
3620 "X-MOZILLA-HTML:TRUE\n"
3623 config.templateItem = config.insertItem;
3624 config.uniqueProperties = "FN:N:X-EVOLUTION-FILE-AS";
3625 config.sizeProperty = "NOTE";
3626 config.testcases = "testcases/vcard30.vcf";
3627 } else if (!strcmp(type, "vcard21")) {
3628 config.sourceName = "vcard21";
3629 config.sourceNameServerTemplate = "addressbook";
3630 config.uri = "card"; // Funambol
3631 config.type = "text/x-vcard";
3638 "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3639 "X-MOZILLA-HTML:FALSE\n"
3640 "NOTE:<<REVISION>>\n"
3648 "TEL;TYPE=WORK;TYPE=VOICE:business 2\n"
3650 "X-MOZILLA-HTML:TRUE\n"
3652 /* adds a second phone number: */
3653 config.complexUpdateItem =
3659 "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3660 "TEL;TYPE=HOME;TYPE=VOICE:home 2\n"
3662 "X-MOZILLA-HTML:TRUE\n"
3664 /* add email and X-AIM to initial item */
3671 "X-MOZILLA-HTML:FALSE\n"
3672 "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3673 "EMAIL:john.doe@work.com\n"
3676 /* change X-MOZILLA-HTML */
3683 "X-MOZILLA-HTML:TRUE\n"
3686 config.templateItem = config.insertItem;
3687 config.uniqueProperties = "FN:N";
3688 config.sizeProperty = "NOTE";
3689 config.testcases = "testcases/vcard21.vcf";
3690 } else if (!strcmp(type, "ical20") && !noutc) {
3691 config.sourceName = "ical20";
3692 config.sourceNameServerTemplate = "calendar";
3693 config.uri = "cal2"; // ScheduleWorld
3694 config.type = "text/x-vcalendar";
3695 static string insertItem =
3697 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3701 "SUMMARY:phone meeting\n"
3702 "DTEND:20060406T163000Z\n"
3703 "DTSTART:20060406T160000Z\n"
3704 "UID:1234567890!@#$%^&*()<>@dummy\n"
3705 "DTSTAMP:20060406T211449Z\n"
3706 "LAST-MODIFIED:20060409T213201\n"
3707 "CREATED:20060409T213201\n"
3708 "LOCATION:my office\n"
3709 "DESCRIPTION:let's talk<<REVISION>>\n"
3715 static string updateItem =
3717 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3721 "SUMMARY:meeting on site\n"
3722 "DTEND:20060406T163000Z\n"
3723 "DTSTART:20060406T160000Z\n"
3724 "UID:1234567890!@#$%^&*()<>@dummy\n"
3725 "DTSTAMP:20060406T211449Z\n"
3726 "LAST-MODIFIED:20060409T213201\n"
3727 "CREATED:20060409T213201\n"
3728 "LOCATION:big meeting room\n"
3729 "DESCRIPTION:nice to see you\n"
3735 /* change location and description of insertItem in testMerge(), add alarm */
3736 static string mergeItem1 =
3738 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3742 "SUMMARY:phone meeting\n"
3743 "DTEND:20060406T163000Z\n"
3744 "DTSTART:20060406T160000Z\n"
3745 "UID:1234567890!@#$%^&*()<>@dummy\n"
3746 "DTSTAMP:20060406T211449Z\n"
3747 "LAST-MODIFIED:20060409T213201\n"
3748 "CREATED:20060409T213201\n"
3749 "LOCATION:calling from home\n"
3750 "DESCRIPTION:let's talk\n"
3755 "DESCRIPTION:alarm\n"
3757 "TRIGGER;VALUE=DURATION;RELATED=START:-PT15M\n"
3761 /* change location to something else, add category */
3762 static string mergeItem2 =
3764 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3768 "SUMMARY:phone meeting\n"
3769 "DTEND:20060406T163000Z\n"
3770 "DTSTART:20060406T160000Z\n"
3771 "UID:1234567890!@#$%^&*()<>@dummy\n"
3772 "DTSTAMP:20060406T211449Z\n"
3773 "LAST-MODIFIED:20060409T213201\n"
3774 "CREATED:20060409T213201\n"
3775 "LOCATION:my office\n"
3777 "DESCRIPTION:what the heck\\, let's even shout a bit\n"
3784 if (getenv("CLIENT_TEST_NO_UID")) {
3785 boost::replace_all(insertItem, "UID:1234567890!@#$%^&*()<>@dummy\n", "");
3786 boost::replace_all(updateItem, "UID:1234567890!@#$%^&*()<>@dummy\n", "");
3787 boost::replace_all(mergeItem1, "UID:1234567890!@#$%^&*()<>@dummy\n", "");
3788 boost::replace_all(mergeItem2, "UID:1234567890!@#$%^&*()<>@dummy\n", "");
3789 } else if (getenv("CLIENT_TEST_SIMPLE_UID")) {
3790 boost::replace_all(insertItem, "UID:1234567890!@#$%^&*()<>@dummy", "UID:1234567890@dummy");
3791 boost::replace_all(updateItem, "UID:1234567890!@#$%^&*()<>@dummy", "UID:1234567890@dummy");
3792 boost::replace_all(mergeItem1, "UID:1234567890!@#$%^&*()<>@dummy", "UID:1234567890@dummy");
3793 boost::replace_all(mergeItem2, "UID:1234567890!@#$%^&*()<>@dummy", "UID:1234567890@dummy");
3796 config.insertItem = insertItem.c_str();
3797 config.updateItem = updateItem.c_str();
3798 config.mergeItem1 = mergeItem1.c_str();
3799 config.mergeItem2 = mergeItem2.c_str();
3803 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3807 "UID:20080407T193125Z-19554-727-1-50@gollum\n"
3808 "DTSTAMP:20080407T193125Z\n"
3809 "DTSTART:20080406T090000Z\n"
3810 "DTEND:20080406T093000Z\n"
3813 "SUMMARY:Recurring\n"
3814 "DESCRIPTION:recurs each Monday\\, 10 times\n"
3816 "RRULE:FREQ=WEEKLY;COUNT=10;INTERVAL=1;BYDAY=SU\n"
3817 "CREATED:20080407T193241\n"
3818 "LAST-MODIFIED:20080407T193241\n"
3823 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3827 "UID:20080407T193125Z-19554-727-1-50@gollum\n"
3828 "DTSTAMP:20080407T193125Z\n"
3829 "DTSTART:20080413T090000Z\n"
3830 "DTEND:20080413T093000Z\n"
3833 "SUMMARY:Recurring: Modified\n"
3835 "CREATED:20080407T193241\n"
3836 "LAST-MODIFIED:20080407T193647\n"
3837 "RECURRENCE-ID:20080413T090000Z\n"
3838 "DESCRIPTION:second instance modified\n"
3841 config.templateItem = config.insertItem;
3842 config.uniqueProperties = "SUMMARY:UID:LOCATION";
3843 config.sizeProperty = "DESCRIPTION";
3844 config.testcases = "testcases/ical20.ics";
3845 } if(!strcmp(type, "vcal10")) {
3846 config.sourceName = "vcal10";
3847 config.sourceNameServerTemplate = "calendar";
3848 config.uri = "cal"; // Funambol 3.0
3849 config.type = "text/x-vcalendar";
3854 "SUMMARY:phone meeting\n"
3855 "DTEND:20060406T163000Z\n"
3856 "DTSTART:20060406T160000Z\n"
3857 "DTSTAMP:20060406T211449Z\n"
3858 "LOCATION:my office\n"
3859 "DESCRIPTION:let's talk<<REVISION>>\n"
3866 "SUMMARY:meeting on site\n"
3867 "DTEND:20060406T163000Z\n"
3868 "DTSTART:20060406T160000Z\n"
3869 "DTSTAMP:20060406T211449Z\n"
3870 "LOCATION:big meeting room\n"
3871 "DESCRIPTION:nice to see you\n"
3874 /* change location in insertItem in testMerge() */
3879 "SUMMARY:phone meeting\n"
3880 "DTEND:20060406T163000Z\n"
3881 "DTSTART:20060406T160000Z\n"
3882 "DTSTAMP:20060406T211449Z\n"
3883 "LOCATION:calling from home\n"
3884 "DESCRIPTION:let's talk\n"
3891 "SUMMARY:phone meeting\n"
3892 "DTEND:20060406T163000Z\n"
3893 "DTSTART:20060406T160000Z\n"
3894 "DTSTAMP:20060406T211449Z\n"
3895 "LOCATION:my office\n"
3896 "DESCRIPTION:what the heck, let's even shout a bit\n"
3899 config.templateItem = config.insertItem;
3900 config.uniqueProperties = "SUMMARY:UID:LOCATION";
3901 config.sizeProperty = "DESCRIPTION";
3902 config.testcases = "testcases/vcal10.ics";
3903 } else if (!strcmp(type, "ical20_noutc") ||
3904 (!strcmp(type, "ical20") && noutc)) {
3905 config.sourceName = "ical20";
3906 config.sourceNameServerTemplate = "calendar";
3907 config.uri = "cal2"; // ScheduleWorld
3908 config.type = "text/x-vcalendar";
3911 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3915 "TZID:Asia/Shanghai\n"
3917 "DTSTART:19670101T000000\n"
3918 "TZOFFSETFROM:+0800\n"
3919 "TZOFFSETTO:+0800\n"
3923 "TZID:/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai\n"
3924 "X-LIC-LOCATION:Asia/Shanghai\n"
3927 "DTSTART:19700914T230000\n"
3928 "TZOFFSETFROM:+0800\n"
3929 "TZOFFSETTO:+0800\n"
3933 "SUMMARY:phone meeting\n"
3934 "DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T163000\n"
3935 "DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T160000\n"
3936 "UID:1234567890!@#$%^&*()<>@dummy\n"
3937 "DTSTAMP:20060406T211449Z\n"
3938 "LAST-MODIFIED:20060409T213201\n"
3939 "CREATED:20060409T213201\n"
3940 "LOCATION:my office\n"
3941 "DESCRIPTION:let's talk<<REVISION>>\n"
3949 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3953 "TZID:Asia/Shanghai\n"
3955 "DTSTART:19670101T000000\n"
3956 "TZOFFSETFROM:+0800\n"
3957 "TZOFFSETTO:+0800\n"
3961 "TZID:/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai\n"
3962 "X-LIC-LOCATION:Asia/Shanghai\n"
3965 "DTSTART:19700914T230000\n"
3966 "TZOFFSETFROM:+0800\n"
3967 "TZOFFSETTO:+0800\n"
3971 "SUMMARY:meeting on site\n"
3972 "DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T163000\n"
3973 "DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T160000\n"
3974 "UID:1234567890!@#$%^&*()<>@dummy\n"
3975 "DTSTAMP:20060406T211449Z\n"
3976 "LAST-MODIFIED:20060409T213201\n"
3977 "CREATED:20060409T213201\n"
3978 "LOCATION:big meeting room\n"
3979 "DESCRIPTION:nice to see you\n"
3985 /* change location and description of insertItem in testMerge(), add alarm */
3986 config.mergeItem1 = "";
3987 config.mergeItem2 = "";
3988 config.parentItem = "";
3989 config.childItem = "";
3990 config.templateItem = config.insertItem;
3991 config.uniqueProperties = "SUMMARY:UID:LOCATION";
3992 config.sizeProperty = "DESCRIPTION";
3993 config.testcases = "testcases/ical20.ics";
3994 } else if(!strcmp(type, "itodo20")) {
3995 config.sourceName = "itodo20";
3996 config.sourceNameServerTemplate = "todo";
3997 config.uri = "task2"; // ScheduleWorld
3998 config.type = "text/x-vcalendar";
4001 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
4005 "UID:20060417T173712Z-4360-727-1-2730@gollum\n"
4006 "DTSTAMP:20060417T173712Z\n"
4008 "DESCRIPTION:to be done<<REVISION>>\n"
4010 "STATUS:IN-PROCESS\n"
4011 "CREATED:20060417T173712\n"
4012 "LAST-MODIFIED:20060417T173712\n"
4017 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
4021 "UID:20060417T173712Z-4360-727-1-2730@gollum\n"
4022 "DTSTAMP:20060417T173712Z\n"
4023 "SUMMARY:do me ASAP\n"
4024 "DESCRIPTION:to be done\n"
4026 "STATUS:IN-PROCESS\n"
4027 "CREATED:20060417T173712\n"
4028 "LAST-MODIFIED:20060417T173712\n"
4031 /* change summary in insertItem in testMerge() */
4034 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
4038 "UID:20060417T173712Z-4360-727-1-2730@gollum\n"
4039 "DTSTAMP:20060417T173712Z\n"
4040 "SUMMARY:do me please\\, please\n"
4041 "DESCRIPTION:to be done\n"
4043 "STATUS:IN-PROCESS\n"
4044 "CREATED:20060417T173712\n"
4045 "LAST-MODIFIED:20060417T173712\n"
4050 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
4054 "UID:20060417T173712Z-4360-727-1-2730@gollum\n"
4055 "DTSTAMP:20060417T173712Z\n"
4057 "DESCRIPTION:to be done\n"
4059 "STATUS:IN-PROCESS\n"
4060 "CREATED:20060417T173712\n"
4061 "LAST-MODIFIED:20060417T173712\n"
4064 config.templateItem = config.insertItem;
4065 config.uniqueProperties = "SUMMARY:UID";
4066 config.sizeProperty = "DESCRIPTION";
4067 config.testcases = "testcases/itodo20.ics";
4068 } else if(!strcmp(type, "text")) {
4069 // The "text" test uses iCalendar 2.0 VJOURNAL
4070 // as format because synccompare doesn't handle
4071 // plain text. A backend which wants to use this
4072 // test data must support importing/exporting
4073 // the test data in that format, see EvolutionMemoSource
4075 config.uri = "note"; // ScheduleWorld
4076 config.sourceNameServerTemplate = "memo";
4077 config.type = "memo";
4078 config.itemType = "text/calendar";
4081 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
4086 "DESCRIPTION:Summary\\nBody text REVISION\n"
4091 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
4095 "SUMMARY:Summary Modified\n"
4096 "DESCRIPTION:Summary Modified\\nBody text\n"
4099 /* change summary, as in updateItem, and the body in the other merge item */
4100 config.mergeItem1 = config.updateItem;
4103 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
4108 "DESCRIPTION:Summary\\nBody modified\n"
4111 config.templateItem = config.insertItem;
4112 config.uniqueProperties = "SUMMARY:DESCRIPTION";
4113 config.sizeProperty = "DESCRIPTION";
4114 config.testcases = "testcases/imemo20.ics";
4115 }else if (!strcmp (type, "calendar+todo")) {
4117 config.sourceNameServerTemplate = "calendar+todo";
4121 void CheckSyncReport::check(SyncMLStatus status, SyncReport &report) const
4130 str << "----------|--------CLIENT---------|--------SERVER---------|\n";
4131 str << " | NEW | MOD | DEL | NEW | MOD | DEL |\n";
4132 str << "----------|-----------------------------------------------|\n";
4133 str << StringPrintf("Expected | %3d | %3d | %3d | %3d | %3d | %3d |\n",
4134 clientAdded, clientUpdated, clientDeleted,
4135 serverAdded, serverUpdated, serverDeleted);
4136 str << "Expected sync mode: " << PrettyPrintSyncMode(syncMode) << "\n";
4137 SE_LOG_INFO(NULL, NULL, "sync report:\n%s\n", str.str().c_str());
4140 // both STATUS_OK and STATUS_HTTP_OK map to the same
4141 // string, so check the formatted status first, then
4142 // the numerical one
4143 CPPUNIT_ASSERT_EQUAL(string("no error (remote, status 0)"), Status2String(status));
4144 CPPUNIT_ASSERT_EQUAL(STATUS_OK, status);
4147 // this code is intentionally duplicated to produce nicer CPPUNIT asserts
4148 BOOST_FOREACH(SyncReport::value_type &entry, report) {
4149 const std::string &name = entry.first;
4150 const SyncSourceReport &source = entry.second;
4152 SE_LOG_DEBUG(NULL, NULL, "Checking sync source %s...", name.c_str());
4154 CLIENT_TEST_EQUAL(name, STATUS_OK, source.getStatus());
4156 CLIENT_TEST_EQUAL(name, 0, source.getItemStat(SyncSourceReport::ITEM_LOCAL,
4157 SyncSourceReport::ITEM_ANY,
4158 SyncSourceReport::ITEM_REJECT));
4159 CLIENT_TEST_EQUAL(name, 0, source.getItemStat(SyncSourceReport::ITEM_REMOTE,
4160 SyncSourceReport::ITEM_ANY,
4161 SyncSourceReport::ITEM_REJECT));
4163 const char* checkSyncModeStr = getenv("CLIENT_TEST_NOCHECK_SYNCMODE");
4164 bool checkSyncMode = true;
4165 bool checkSyncStats = getenv ("CLIENT_TEST_NOCHECK_SYNCSTATS") ? false : true;
4166 if (checkSyncModeStr &&
4167 (!strcmp(checkSyncModeStr, "1") || !strcasecmp(checkSyncModeStr, "t"))) {
4168 checkSyncMode = false;
4171 if (syncMode != SYNC_NONE && checkSyncMode) {
4172 CLIENT_TEST_EQUAL(name, syncMode, source.getFinalSyncMode());
4175 if (clientAdded != -1 && checkSyncStats) {
4176 CLIENT_TEST_EQUAL(name, clientAdded,
4177 source.getItemStat(SyncSourceReport::ITEM_LOCAL,
4178 SyncSourceReport::ITEM_ADDED,
4179 SyncSourceReport::ITEM_TOTAL));
4181 if (clientUpdated != -1 && checkSyncStats) {
4182 CLIENT_TEST_EQUAL(name, clientUpdated,
4183 source.getItemStat(SyncSourceReport::ITEM_LOCAL,
4184 SyncSourceReport::ITEM_UPDATED,
4185 SyncSourceReport::ITEM_TOTAL));
4187 if (clientDeleted != -1 && checkSyncStats) {
4188 CLIENT_TEST_EQUAL(name, clientDeleted,
4189 source.getItemStat(SyncSourceReport::ITEM_LOCAL,
4190 SyncSourceReport::ITEM_REMOVED,
4191 SyncSourceReport::ITEM_TOTAL));
4194 if (serverAdded != -1 && checkSyncStats) {
4195 CLIENT_TEST_EQUAL(name, serverAdded,
4196 source.getItemStat(SyncSourceReport::ITEM_REMOTE,
4197 SyncSourceReport::ITEM_ADDED,
4198 SyncSourceReport::ITEM_TOTAL));
4200 if (serverUpdated != -1 && checkSyncStats) {
4201 CLIENT_TEST_EQUAL(name, serverUpdated,
4202 source.getItemStat(SyncSourceReport::ITEM_REMOTE,
4203 SyncSourceReport::ITEM_UPDATED,
4204 SyncSourceReport::ITEM_TOTAL));
4206 if (serverDeleted != -1 && checkSyncStats) {
4207 CLIENT_TEST_EQUAL(name, serverDeleted,
4208 source.getItemStat(SyncSourceReport::ITEM_REMOTE,
4209 SyncSourceReport::ITEM_REMOVED,
4210 SyncSourceReport::ITEM_TOTAL));
4213 SE_LOG_DEBUG(NULL, NULL, "Done with checking sync report.");
4218 #endif // ENABLE_INTEGRATION_TESTS