c2b5a486b0dda4f1936ecbf04acc5182c61a9e6f
[platform/upstream/syncevolution.git] / test / ClientTest.cpp
1 /*
2  * Copyright (C) 2008 Funambol, Inc.
3  * Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
4  * Copyright (C) 2009 Intel Corporation
5  *
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.
10  *
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.
15  *
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
19  * 02110-1301  USA
20  */
21
22 /** @cond API */
23 /** @addtogroup ClientTest */
24 /** @{ */
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #ifdef ENABLE_INTEGRATION_TESTS
31
32 #include "ClientTest.h"
33 #include "test.h"
34 #include <SyncSource.h>
35 #include <TransportAgent.h>
36 #include <Logging.h>
37 #include <syncevo/util.h>
38 #include <syncevo/SyncContext.h>
39 #include <VolatileConfigNode.h>
40
41 #include <synthesis/dataconversion.h>
42
43 #include <memory>
44 #include <vector>
45 #include <utility>
46 #include <sstream>
47 #include <iomanip>
48 #include <fstream>
49 #include <iostream>
50 #include <algorithm>
51
52 #include <fcntl.h>
53
54 #include <boost/bind.hpp>
55
56 #include <syncevo/declarations.h>
57 SE_BEGIN_CXX
58
59 /**
60  * Using this pointer automates the open()/beginSync()/endSync()/close()
61  * life cycle: it automatically calls these functions when a new
62  * pointer is assigned or deleted.
63  */
64 class TestingSyncSourcePtr : public std::auto_ptr<TestingSyncSource>
65 {
66     typedef std::auto_ptr<TestingSyncSource> base_t;
67 public:
68     TestingSyncSourcePtr() {}
69     TestingSyncSourcePtr(TestingSyncSource *source) :
70         base_t(source)
71     {
72         CPPUNIT_ASSERT(source);
73         SOURCE_ASSERT_NO_FAILURE(source, source->open());
74         SOURCE_ASSERT_NO_FAILURE(source, source->beginSync("", ""));
75         const char * serverMode = getenv ("CLIENT_TEST_MODE");
76         if (serverMode && !strcmp (serverMode, "server")) {
77             SOURCE_ASSERT_NO_FAILURE(source, source->enableServerMode());
78         }
79     }
80     ~TestingSyncSourcePtr()
81     {
82         reset(NULL);
83     }
84
85     void reset(TestingSyncSource *source = NULL)
86     {
87         if (this->get()) {
88             BOOST_FOREACH(const SyncSource::Operations::CallbackFunctor_t &callback,
89                           get()->getOperations().m_endSession) {
90                 callback();
91             }
92             SOURCE_ASSERT_NO_FAILURE(get(), get()->endSync(true));
93             SOURCE_ASSERT_NO_FAILURE(get(), get()->close());
94         }
95         CPPUNIT_ASSERT_NO_THROW(base_t::reset(source));
96         if (source) {
97             SOURCE_ASSERT_NO_FAILURE(source, source->open());
98             SOURCE_ASSERT_NO_FAILURE(source, source->beginSync("", ""));
99             const char * serverMode = getenv ("CLIENT_TEST_MODE");
100             if (serverMode && !strcmp (serverMode, "server")) {
101                 SOURCE_ASSERT_NO_FAILURE(source, source->enableServerMode());
102             }
103             BOOST_FOREACH(const SyncSource::Operations::CallbackFunctor_t &callback,
104                           source->getOperations().m_endSession) {
105                 callback();
106             }
107         }
108     }
109 };
110
111 bool SyncOptions::defaultWBXML()
112 {
113     const char *t = getenv("CLIENT_TEST_XML");
114     if (t && (!strcmp(t, "1") || !strcasecmp(t, "t"))) {
115         // use XML
116         return false;
117     } else {
118         return true;
119     }
120 }
121
122 std::list<std::string> listItemsOfType(TestingSyncSource *source, int state)
123 {
124     std::list<std::string> res;
125
126     BOOST_FOREACH(const string &luid, source->getItems(SyncSourceChanges::State(state))) {
127         res.push_back(luid);
128     }
129     return res;
130 }
131 static std::list<std::string> listNewItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::NEW); }
132 static std::list<std::string> listUpdatedItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::UPDATED); }
133 static std::list<std::string> listDeletedItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::DELETED); }
134 static std::list<std::string> listItems(TestingSyncSource *source) { return listItemsOfType(source, SyncSourceChanges::ANY); }
135
136 int countItemsOfType(TestingSyncSource *source, int type) { return source->getItems(SyncSourceChanges::State(type)).size(); }
137 static int countNewItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::NEW); }
138 static int countUpdatedItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::UPDATED); }
139 static int countDeletedItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::DELETED); }
140 static int countItems(TestingSyncSource *source) { return countItemsOfType(source, SyncSourceChanges::ANY); }
141
142
143 /** insert new item, return LUID */
144 static std::string importItem(TestingSyncSource *source, std::string &data)
145 {
146     CPPUNIT_ASSERT(source);
147     if (data.size()) {
148         SyncSourceRaw::InsertItemResult res;
149         SOURCE_ASSERT_NO_FAILURE(source, res = source->insertItemRaw("", data));
150         CPPUNIT_ASSERT(!res.m_luid.empty());
151         return res.m_luid;
152     } else {
153         return "";
154     }
155 }
156
157 /** adds the supported tests to the instance itself */
158 void LocalTests::addTests() {
159     if (config.createSourceA) {
160         ADD_TEST(LocalTests, testOpen);
161         ADD_TEST(LocalTests, testIterateTwice);
162         if (config.insertItem) {
163             ADD_TEST(LocalTests, testSimpleInsert);
164             ADD_TEST(LocalTests, testLocalDeleteAll);
165             ADD_TEST(LocalTests, testComplexInsert);
166
167             if (config.updateItem) {
168                 ADD_TEST(LocalTests, testLocalUpdate);
169
170                 if (config.createSourceB) {
171                     ADD_TEST(LocalTests, testChanges);
172                 }
173             }
174
175             if (config.import &&
176                 config.dump &&
177                 config.compare &&
178                 config.testcases) {
179                 ADD_TEST(LocalTests, testImport);
180                 ADD_TEST(LocalTests, testImportDelete);
181             }
182
183             if (config.templateItem &&
184                 config.uniqueProperties) {
185                 ADD_TEST(LocalTests, testManyChanges);
186             }
187
188             if (config.parentItem &&
189                 config.childItem) {
190                 ADD_TEST(LocalTests, testLinkedItemsParent);
191                 ADD_TEST(LocalTests, testLinkedItemsChild);
192                 ADD_TEST(LocalTests, testLinkedItemsParentChild);
193                 ADD_TEST(LocalTests, testLinkedItemsChildParent);
194                 ADD_TEST(LocalTests, testLinkedItemsChildChangesParent);
195                 ADD_TEST(LocalTests, testLinkedItemsRemoveParentFirst);
196                 ADD_TEST(LocalTests, testLinkedItemsRemoveNormal);
197                 if (config.sourceKnowsItemSemantic) {
198                     ADD_TEST(LocalTests, testLinkedItemsInsertParentTwice);
199                     ADD_TEST(LocalTests, testLinkedItemsInsertChildTwice);
200                 }
201                 ADD_TEST(LocalTests, testLinkedItemsParentUpdate);
202                 ADD_TEST(LocalTests, testLinkedItemsUpdateChild);
203                 ADD_TEST(LocalTests, testLinkedItemsInsertBothUpdateChild);
204                 ADD_TEST(LocalTests, testLinkedItemsInsertBothUpdateParent);
205             }
206         }
207     }
208 }
209
210 std::string LocalTests::insert(CreateSource createSource, const char *data, bool relaxed) {
211     // create source
212     TestingSyncSourcePtr source(createSource());
213
214     // count number of already existing items
215     int numItems = 0;
216     CPPUNIT_ASSERT_NO_THROW(numItems = countItems(source.get()));
217     SyncSourceRaw::InsertItemResult res;
218     SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw("", data));
219     CPPUNIT_ASSERT(!res.m_luid.empty());
220
221     // delete source again
222     source.reset();
223
224     if (!relaxed) {
225         // two possible results:
226         // - a new item was added
227         // - the item was matched against an existing one
228         CPPUNIT_ASSERT_NO_THROW(source.reset(createSource()));
229         CPPUNIT_ASSERT_EQUAL(numItems + (res.m_merged ? 0 : 1),
230                              countItems(source.get()));
231         CPPUNIT_ASSERT(countNewItems(source.get()) == 0);
232         CPPUNIT_ASSERT(countUpdatedItems(source.get()) == 0);
233         CPPUNIT_ASSERT(countDeletedItems(source.get()) == 0);
234     }
235
236     return res.m_luid;
237 }
238
239 /** deletes specific item locally via sync source */
240 static std::string updateItem(CreateSource createSource, const std::string &uid, const char *data) {
241     std::string newuid;
242
243     CPPUNIT_ASSERT(createSource.createSource);
244
245     // create source
246     TestingSyncSourcePtr source(createSource());
247
248     // insert item
249     SyncSourceRaw::InsertItemResult res;
250     SOURCE_ASSERT_NO_FAILURE(source.get(), res = source->insertItemRaw(uid, data));
251     SOURCE_ASSERT(source.get(), !res.m_luid.empty());
252
253     return res.m_luid;
254 }
255
256 /** updates specific item locally via sync source */
257 static void removeItem(CreateSource createSource, const std::string &luid)
258 {
259     CPPUNIT_ASSERT(createSource.createSource);
260
261     // create source
262     TestingSyncSourcePtr source(createSource());
263
264     // remove item
265     SOURCE_ASSERT_NO_FAILURE(source.get(), source->deleteItem(luid));
266 }
267
268 void LocalTests::update(CreateSource createSource, const char *data, bool check) {
269     CPPUNIT_ASSERT(createSource.createSource);
270     CPPUNIT_ASSERT(data);
271
272     // create source
273     TestingSyncSourcePtr source(createSource());
274
275     // get existing item, then update it
276     SyncSourceChanges::Items_t::const_iterator it;
277     SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin());
278     CPPUNIT_ASSERT(it != source->getAllItems().end());
279     string luid = *it;
280     SOURCE_ASSERT_NO_FAILURE(source.get(), source->insertItemRaw(luid, data));
281     CPPUNIT_ASSERT_NO_THROW(source.reset());
282
283     if (!check) {
284         return;
285     }
286
287     // check that the right changes are reported when reopening the source
288     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource()));
289     CPPUNIT_ASSERT_EQUAL(1, countItems(source.get()));
290     CPPUNIT_ASSERT_EQUAL(0, countNewItems(source.get()));
291     CPPUNIT_ASSERT_EQUAL(0, countUpdatedItems(source.get()));
292     CPPUNIT_ASSERT_EQUAL(0, countDeletedItems(source.get()));
293     
294     SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin());
295     CPPUNIT_ASSERT(it != source->getAllItems().end());
296     CPPUNIT_ASSERT_EQUAL(luid, *it);
297 }
298
299 void LocalTests::update(CreateSource createSource, const char *data, const std::string &luid) {
300     CPPUNIT_ASSERT(createSource.createSource);
301     CPPUNIT_ASSERT(data);
302
303     // create source
304     TestingSyncSourcePtr source(createSource());
305
306     // update it
307     SOURCE_ASSERT_NO_FAILURE(source.get(), source->insertItemRaw(luid, data));
308 }
309
310 /** deletes all items locally via sync source */
311 void LocalTests::deleteAll(CreateSource createSource) {
312     CPPUNIT_ASSERT(createSource.createSource);
313
314     // create source
315     TestingSyncSourcePtr source(createSource());
316
317     // delete all items
318     SOURCE_ASSERT_NO_FAILURE(source.get(), source->removeAllItems());
319     CPPUNIT_ASSERT_NO_THROW(source.reset());
320
321     // check that all items are gone
322     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSource()));
323     SOURCE_ASSERT_MESSAGE(
324         "should be empty now",
325         source.get(),
326         countItems(source.get()) == 0);
327     CPPUNIT_ASSERT_EQUAL( 0, countNewItems(source.get()) );
328     CPPUNIT_ASSERT_EQUAL( 0, countUpdatedItems(source.get()) );
329     CPPUNIT_ASSERT_EQUAL( 0, countDeletedItems(source.get()) );
330 }
331
332 /** deletes specific item locally via sync source */
333 static void deleteItem(CreateSource createSource, const std::string &uid) {
334     CPPUNIT_ASSERT(createSource.createSource);
335
336     // create source
337     TestingSyncSourcePtr source(createSource());
338
339     // delete item
340     SOURCE_ASSERT_NO_FAILURE(source.get(), source->deleteItem(uid));
341 }
342
343 /**
344  * takes two databases, exports them,
345  * then compares them using synccompare
346  *
347  * @param refFile      existing file with source reference items, NULL uses a dump of sync source A instead
348  * @param copy         a sync source which contains the copied items, begin/endSync will be called
349  * @param raiseAssert  raise assertion if comparison yields differences (defaults to true)
350  */
351 bool LocalTests::compareDatabases(const char *refFile, TestingSyncSource &copy, bool raiseAssert) {
352     CPPUNIT_ASSERT(config.dump);
353
354     std::string sourceFile, copyFile;
355
356     if (refFile) {
357         sourceFile = refFile;
358     } else {
359         sourceFile = getCurrentTest() + ".A.test.dat";
360         simplifyFilename(sourceFile);
361         TestingSyncSourcePtr source;
362         SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
363         SOURCE_ASSERT_EQUAL(source.get(), 0, config.dump(client, *source.get(), sourceFile.c_str()));
364         CPPUNIT_ASSERT_NO_THROW(source.reset());
365     }
366
367     copyFile = getCurrentTest() + ".B.test.dat";
368     simplifyFilename(copyFile);
369     SOURCE_ASSERT_EQUAL(&copy, 0, config.dump(client, copy, copyFile.c_str()));
370
371     bool equal = config.compare(client, sourceFile.c_str(), copyFile.c_str());
372     CPPUNIT_ASSERT(!raiseAssert || equal);
373
374     return equal;
375 }
376
377 std::string LocalTests::createItem(int item, const std::string &revision, int size)
378 {
379     std::string data = config.templateItem;
380     std::stringstream prefix;
381
382     prefix << std::setfill('0') << std::setw(3) << item << " ";
383
384     const char *prop = config.uniqueProperties;
385     const char *nextProp;
386     while (*prop) {
387         std::string curProp;
388         nextProp = strchr(prop, ':');
389         if (!nextProp) {
390             curProp = prop;
391         } else {
392             curProp = std::string(prop, 0, nextProp - prop);
393         }
394
395         std::string property;
396         // property is expected to not start directly at the
397         // beginning
398         property = "\n";
399         property += curProp;
400         property += ":";
401         size_t off = data.find(property);
402         if (off != data.npos) {
403             data.insert(off + property.size(), prefix.str());
404         }
405
406         if (!nextProp) {
407             break;
408         }
409         prop = nextProp + 1;
410     }
411     /** add check for if not found, STL will crash */
412     if(data.find("<<REVISION>>") != std::string::npos) {
413         data.replace(data.find("<<REVISION>>"), strlen("<<REVISION>>"), revision);
414     } else if (data.find("REVISION") != std::string::npos) {  
415         /* change "<<REVISION>>" to "REVISION" for memo */
416         data.replace(data.find("REVISION"), strlen("REVISION"), revision);
417     }
418     if (size > 0 && (int)data.size() < size) {
419         int additionalBytes = size - (int)data.size();
420         int added = 0;
421         /* vCard 2.1 and vCal 1.0 need quoted-printable line breaks */
422         bool quoted = data.find("VERSION:1.0") != data.npos ||
423             data.find("VERSION:2.1") != data.npos;
424         size_t toreplace = 1;
425
426         CPPUNIT_ASSERT(config.sizeProperty);
427
428         /* stuff the item so that it reaches at least that size */
429         size_t off = data.find(config.sizeProperty);
430         CPPUNIT_ASSERT(off != data.npos);
431         std::stringstream stuffing;
432         if (quoted) {
433             stuffing << ";ENCODING=QUOTED-PRINTABLE:";
434         } else {
435             stuffing << ":";
436         }
437
438         // insert after the first line, it often acts as the summary
439         if (data.find("BEGIN:VJOURNAL") != data.npos) {
440             size_t start = data.find(":", off);
441             CPPUNIT_ASSERT( start != data.npos );
442             size_t eol = data.find("\\n", off);
443             CPPUNIT_ASSERT( eol != data.npos );
444             stuffing << data.substr(start + 1, eol - start + 1);
445             toreplace += eol - start + 1;
446         }
447
448         while(added < additionalBytes) {
449             int linelen = 0;
450
451             while(added + 4 < additionalBytes &&
452                   linelen < 60) {
453                 stuffing << 'x';
454                 added++;
455                 linelen++;
456             }
457             // insert line breaks to allow folding
458             if (quoted) {
459                 stuffing << "x=0D=0Ax";
460                 added += 8;
461             } else {
462                 stuffing << "x\\nx";
463                 added += 4;
464             }
465         }
466         off = data.find(":", off);
467         data.replace(off, toreplace, stuffing.str());
468     }
469
470     return data;
471 }
472
473
474 /**
475  * insert artificial items, number of them determined by config.numItems
476  * unless passed explicitly
477  *
478  * @param createSource    a factory for the sync source that is to be used
479  * @param startIndex      IDs are generated starting with this value
480  * @param numItems        number of items to be inserted if non-null, otherwise config.numItems is used
481  * @param size            minimum size for new items
482  * @return LUIDs of all inserted items
483  */
484 std::list<std::string> LocalTests::insertManyItems(CreateSource createSource, int startIndex, int numItems, int size) {
485     std::list<std::string> luids;
486
487     CPPUNIT_ASSERT(config.templateItem);
488     CPPUNIT_ASSERT(config.uniqueProperties);
489
490     TestingSyncSourcePtr source;
491     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
492     CPPUNIT_ASSERT(startIndex > 1 || !countItems(source.get()));
493
494     int firstIndex = startIndex;
495     if (firstIndex < 0) {
496         firstIndex = 1;
497     }
498     int lastIndex = firstIndex + (numItems >= 1 ? numItems : config.numItems) - 1;
499     for (int item = firstIndex; item <= lastIndex; item++) {
500         std::string data = createItem(item, "", size);
501         luids.push_back(importItem(source.get(), data));
502     }
503
504     return luids;
505 }
506
507 // creating sync source
508 void LocalTests::testOpen() {
509     // check requirements
510     CPPUNIT_ASSERT(config.createSourceA);
511
512     // Intentionally use the plain auto_ptr here and
513     // call open directly. That way it is a bit more clear
514     // what happens and where it fails, if it fails.
515     std::auto_ptr<TestingSyncSource> source(createSourceA());
516     // got a sync source?
517     CPPUNIT_ASSERT(source.get() != 0);
518     // can it be opened?
519     SOURCE_ASSERT_NO_FAILURE(source.get(), source->open());
520     // delete it
521     CPPUNIT_ASSERT_NO_THROW(source.reset());
522 }
523
524 // restart scanning of items
525 void LocalTests::testIterateTwice() {
526     // check requirements
527     CPPUNIT_ASSERT(config.createSourceA);
528
529     // open source
530     TestingSyncSourcePtr source(createSourceA());
531     SOURCE_ASSERT_MESSAGE(
532         "iterating twice should produce identical results",
533         source.get(),
534         countItems(source.get()) == countItems(source.get()));
535 }
536
537 // insert one contact without clearing the source first
538 void LocalTests::testSimpleInsert() {
539     // check requirements
540     CPPUNIT_ASSERT(config.insertItem);
541     CPPUNIT_ASSERT(config.createSourceA);
542
543     insert(createSourceA, config.insertItem);
544 }
545
546 // delete all items
547 void LocalTests::testLocalDeleteAll() {
548     // check requirements
549     CPPUNIT_ASSERT(config.insertItem);
550     CPPUNIT_ASSERT(config.createSourceA);
551
552     // make sure there is something to delete, then delete again
553     insert(createSourceA, config.insertItem);
554     deleteAll(createSourceA);
555 }
556
557 // clean database, then insert
558 void LocalTests::testComplexInsert() {
559     testLocalDeleteAll();
560     testSimpleInsert();
561     testIterateTwice();
562 }
563
564 // clean database, insert item, update it
565 void LocalTests::testLocalUpdate() {
566     // check additional requirements
567     CPPUNIT_ASSERT(config.updateItem);
568
569     testLocalDeleteAll();
570     testSimpleInsert();
571     update(createSourceA, config.updateItem);
572 }
573
574 // complex sequence of changes
575 void LocalTests::testChanges() {
576     SyncSourceChanges::Items_t::const_iterator it, it2;
577
578     // check additional requirements
579     CPPUNIT_ASSERT(config.createSourceB);
580
581     testLocalDeleteAll();
582     testSimpleInsert();
583
584     // clean changes in sync source B by creating and closing it
585     TestingSyncSourcePtr source(createSourceB());
586     CPPUNIT_ASSERT_NO_THROW(source.reset());
587
588     // no new changes now
589     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
590     SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
591     SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
592     SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
593     SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
594     string item;
595     string luid;
596     SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin());
597     CPPUNIT_ASSERT(it != source->getAllItems().end());
598     luid = *it;
599     SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, item));
600     CPPUNIT_ASSERT_NO_THROW(source.reset());
601
602     // delete item again via sync source A
603     deleteAll(createSourceA);
604     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
605     SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
606     SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
607     SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
608     SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get()));
609     SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getDeletedItems().begin());
610     CPPUNIT_ASSERT(it != source->getDeletedItems().end());
611     CPPUNIT_ASSERT(!it->empty());
612     CPPUNIT_ASSERT_EQUAL(luid, *it);
613     CPPUNIT_ASSERT_NO_THROW(source.reset());
614
615     // insert another item via sync source A
616     testSimpleInsert();
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(), 1, countNewItems(source.get()));
620     SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
621     SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
622     SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin());
623     CPPUNIT_ASSERT(it != source->getAllItems().end());
624     luid = *it;
625     SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, item));
626     string newItem;
627     SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getNewItems().begin());
628     CPPUNIT_ASSERT(it != source->getNewItems().end());
629     SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, item));
630     CPPUNIT_ASSERT_EQUAL(luid, *it);
631     CPPUNIT_ASSERT_NO_THROW(source.reset());
632
633     // update item via sync source A
634     update(createSourceA, config.updateItem);
635     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
636     SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
637     SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
638     SOURCE_ASSERT_EQUAL(source.get(), 1, countUpdatedItems(source.get()));
639     SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
640     string updatedItem;
641     SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getUpdatedItems().begin());
642     CPPUNIT_ASSERT(it != source->getUpdatedItems().end());
643     SOURCE_ASSERT_NO_FAILURE(source.get(), source->readItem(*it, updatedItem));
644     CPPUNIT_ASSERT_EQUAL(luid, *it);
645     CPPUNIT_ASSERT_NO_THROW(source.reset());
646
647     // start anew, then create and update an item -> should only be listed as new
648     // or updated, but not both
649     deleteAll(createSourceA);
650     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
651     source.reset();
652     testSimpleInsert();
653     update(createSourceA, config.updateItem);
654     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
655     SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
656     SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()) + countUpdatedItems(source.get()));
657     SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
658
659     // start anew, then create, delete and recreate an item -> should only be listed as new or updated,
660     // even if (as for calendar with UID) the same LUID gets reused
661     deleteAll(createSourceA);
662     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
663     source.reset();
664     testSimpleInsert();
665     deleteAll(createSourceA);
666     testSimpleInsert();
667     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceB()));
668     SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
669     SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()) + countUpdatedItems(source.get()));
670     if (countDeletedItems(source.get()) == 1) {
671         // It's not nice, but acceptable to send the LUID of a deleted item to a
672         // server which has never seen that LUID. The LUID must not be the same as
673         // the one we list as new or updated, though.
674         SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getDeletedItems().begin());
675         CPPUNIT_ASSERT(it != source->getDeletedItems().end());
676         SOURCE_ASSERT_NO_FAILURE(source.get(), it2 = source->getNewItems().begin());
677         if (it2 == source->getNewItems().end()) {
678             SOURCE_ASSERT_NO_FAILURE(source.get(), it2 = source->getUpdatedItems().begin());
679             CPPUNIT_ASSERT(it2 != source->getUpdatedItems().end());
680         }
681         CPPUNIT_ASSERT(*it != *it2);
682     } else {
683         SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
684     }
685 }
686
687 // clean database, import file, then export again and compare
688 void LocalTests::testImport() {
689     // check additional requirements
690     CPPUNIT_ASSERT(config.import);
691     CPPUNIT_ASSERT(config.dump);
692     CPPUNIT_ASSERT(config.compare);
693     CPPUNIT_ASSERT(config.testcases);
694
695     testLocalDeleteAll();
696
697     // import via sync source A
698     TestingSyncSourcePtr source;
699     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
700     std::string testcases;
701     SOURCE_ASSERT_EQUAL(source.get(), 0, config.import(client, *source.get(), config.testcases, testcases));
702     CPPUNIT_ASSERT_NO_THROW(source.reset());
703
704     // export again and compare against original file
705     TestingSyncSourcePtr copy;
706     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceA()));
707     compareDatabases(testcases.c_str(), *copy.get());
708     CPPUNIT_ASSERT_NO_THROW(source.reset());
709 }
710
711 // same as testImport() with immediate delete
712 void LocalTests::testImportDelete() {
713     testImport();
714
715     // delete again, because it was observed that this did not
716     // work right with calendars in SyncEvolution
717     testLocalDeleteAll();
718 }
719
720 // test change tracking with large number of items
721 void LocalTests::testManyChanges() {
722     // check additional requirements
723     CPPUNIT_ASSERT(config.templateItem);
724     CPPUNIT_ASSERT(config.uniqueProperties);
725
726     deleteAll(createSourceA);
727
728     // check that everything is empty, also resets change counter of sync source B
729     TestingSyncSourcePtr copy;
730     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
731     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
732     CPPUNIT_ASSERT_NO_THROW(copy.reset());
733
734     // now insert plenty of items
735     int numItems = insertManyItems(createSourceA).size();
736
737     // check that exactly this number of items is listed as new
738     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
739     SOURCE_ASSERT_EQUAL(copy.get(), numItems, countItems(copy.get()));
740     SOURCE_ASSERT_EQUAL(copy.get(), numItems, countNewItems(copy.get()));
741     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
742     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
743     CPPUNIT_ASSERT_NO_THROW(copy.reset());
744
745     // delete all items
746     deleteAll(createSourceA);
747
748     // verify again
749     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
750     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
751     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
752     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
753     SOURCE_ASSERT_EQUAL(copy.get(), numItems, countDeletedItems(copy.get()));
754     CPPUNIT_ASSERT_NO_THROW(copy.reset());
755 }
756
757 template<class T, class V> int countEqual(const T &container,
758                                           const V &value) {
759     return count(container.begin(),
760                  container.end(),
761                  value);
762 }
763
764 // test inserting, removing and updating of parent + child item in
765 // various order plus change tracking
766 void LocalTests::testLinkedItemsParent() {
767     // check additional requirements
768     CPPUNIT_ASSERT(config.parentItem);
769     CPPUNIT_ASSERT(config.childItem);
770
771     deleteAll(createSourceA);
772     std::string parent, child;
773     TestingSyncSourcePtr copy;
774
775     // check that everything is empty, also resets change counter of sync source B
776     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
777     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
778     CPPUNIT_ASSERT_NO_THROW(copy.reset());
779
780     // now insert main item
781     parent = insert(createSourceA, config.parentItem, config.itemType);
782
783     // check that exactly the parent is listed as new
784     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
785     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
786     SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
787     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
788     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
789     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
790     CPPUNIT_ASSERT_NO_THROW(copy.reset());
791
792     // delete all items
793     deleteAll(createSourceA);
794
795     // verify again
796     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
797     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
798     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
799     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
800     SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
801     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
802 }
803
804 // test inserting, removing and updating of parent + child item in
805 // various order plus change tracking
806 void LocalTests::testLinkedItemsChild() {
807 #if LINKED_ITEMS_RELAXED_SEMANTIC
808     // check additional requirements
809     CPPUNIT_ASSERT(config.parentItem);
810     CPPUNIT_ASSERT(config.childItem);
811
812     deleteAll(createSourceA);
813     std::string parent, child;
814     TestingSyncSourcePtr copy;
815
816     // check that everything is empty, also resets change counter of sync source B
817     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
818     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
819     CPPUNIT_ASSERT_NO_THROW(copy.reset());
820
821     // same as above for child item
822     child = insert(createSourceA, config.childItem, config.itemType);
823
824     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
825     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
826     SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
827     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
828     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
829     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
830     CPPUNIT_ASSERT_NO_THROW(copy.reset());
831
832     deleteAll(createSourceA);
833
834     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
835     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
836     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
837     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
838     SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
839     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
840 #endif
841 }
842
843 // test inserting, removing and updating of parent + child item in
844 // various order plus change tracking
845 void LocalTests::testLinkedItemsParentChild() {
846     // check additional requirements
847     CPPUNIT_ASSERT(config.parentItem);
848     CPPUNIT_ASSERT(config.childItem);
849
850     deleteAll(createSourceA);
851     std::string parent, child;
852     TestingSyncSourcePtr copy;
853
854     // check that everything is empty, also resets change counter of sync source B
855     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
856     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
857     CPPUNIT_ASSERT_NO_THROW(copy.reset());
858
859     // insert parent first, then child
860     parent = insert(createSourceA, config.parentItem, config.itemType);
861     child = insert(createSourceA, config.childItem, config.itemType);
862
863     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
864     SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
865     SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
866     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
867     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
868     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
869     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
870     CPPUNIT_ASSERT_NO_THROW(copy.reset());
871
872     deleteAll(createSourceA);
873
874     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
875     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
876     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
877     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
878     SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get()));
879     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
880     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
881 }
882
883 // test inserting, removing and updating of parent + child item in
884 // various order plus change tracking
885 void LocalTests::testLinkedItemsChildParent() {
886 #if LINKED_ITEMS_RELAXED_SEMANTIC
887     // check additional requirements
888     CPPUNIT_ASSERT(config.parentItem);
889     CPPUNIT_ASSERT(config.childItem);
890
891     deleteAll(createSourceA);
892     std::string parent, child;
893     TestingSyncSourcePtr copy;
894
895     // check that everything is empty, also resets change counter of sync source B
896     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
897     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
898     CPPUNIT_ASSERT_NO_THROW(copy.reset());
899
900     // insert child first, then parent
901     child = insert(createSourceA, config.childItem);
902     parent = insert(createSourceA, config.parentItem, true);
903
904     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
905     SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
906     SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
907     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
908     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
909     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
910     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
911     CPPUNIT_ASSERT_NO_THROW(copy.reset());
912
913     deleteAll(createSourceA);
914
915     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
916     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
917     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
918     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
919     SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get()));
920     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
921     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
922 #endif
923 }
924
925 // test inserting, removing and updating of parent + child item in
926 // various order plus change tracking
927 void LocalTests::testLinkedItemsChildChangesParent() {
928 #if LINKED_ITEMS_RELAXED_SEMANTIC
929     // check additional requirements
930     CPPUNIT_ASSERT(config.parentItem);
931     CPPUNIT_ASSERT(config.childItem);
932
933     deleteAll(createSourceA);
934     std::string parent, child;
935     TestingSyncSourcePtr copy;
936
937     // check that everything is empty, also resets change counter of sync source B
938     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
939     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
940     CPPUNIT_ASSERT_NO_THROW(copy.reset());
941
942     // insert child first, check changes, then insert the parent
943     child = insert(createSourceA, config.childItem, config.itemType);
944
945     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
946     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
947     SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
948     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
949     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
950     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
951     CPPUNIT_ASSERT_NO_THROW(copy.reset());
952
953     parent = insert(createSourceA, config.parentItem, true);
954
955     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
956     SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
957     SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
958     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listNewItems(copy.get()), parent));
959     // relaxed semantic: the child item might be considered updated now if
960     // it had to be modified when inserting the parent
961     SOURCE_ASSERT(copy.get(), 1 >= countUpdatedItems(copy.get()));
962     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
963     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
964     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
965     CPPUNIT_ASSERT_NO_THROW(copy.reset());
966
967     deleteAll(createSourceA);
968
969     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
970     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
971     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
972     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
973     SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get()));
974     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
975     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
976 #endif
977 }
978
979 // test inserting, removing and updating of parent + child item in
980 // various order plus change tracking
981 void LocalTests::testLinkedItemsRemoveParentFirst() {
982 #if LINKED_ITEMS_RELAXED_SEMANTIC
983     // check additional requirements
984     CPPUNIT_ASSERT(config.parentItem);
985     CPPUNIT_ASSERT(config.childItem);
986
987     deleteAll(createSourceA);
988     std::string parent, child;
989     TestingSyncSourcePtr copy;
990
991     // check that everything is empty, also resets change counter of sync source B
992     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
993     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
994     CPPUNIT_ASSERT_NO_THROW(copy.reset());
995
996     // insert both items, remove parent, then child
997     parent = insert(createSourceA, config.parentItem);
998     child = insert(createSourceA, config.childItem);
999
1000     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1001     SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1002     SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
1003     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1004     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1005     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1006     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1007     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1008
1009     deleteItem(createSourceA, parent);
1010
1011     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1012     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1013     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1014     // deleting the parent may or may not modify the child
1015     SOURCE_ASSERT(copy.get(), 1 >= countUpdatedItems(copy.get()));
1016     SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1017     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1018     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1019
1020     deleteItem(createSourceA, child);
1021
1022     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1023     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1024     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1025     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1026     SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1027     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1028     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1029 #endif
1030 }
1031
1032 // test inserting, removing and updating of parent + child item in
1033 // various order plus change tracking
1034 void LocalTests::testLinkedItemsRemoveNormal() {
1035     // check additional requirements
1036     CPPUNIT_ASSERT(config.parentItem);
1037     CPPUNIT_ASSERT(config.childItem);
1038
1039     deleteAll(createSourceA);
1040     std::string parent, child;
1041     TestingSyncSourcePtr source, copy;
1042
1043     // check that everything is empty, also resets change counter of sync source B
1044     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1045     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1046     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1047
1048     // insert both items, remove child, then parent
1049     parent = insert(createSourceA, config.parentItem);
1050     child = insert(createSourceA, config.childItem);
1051
1052     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1053     SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1054     SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
1055     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1056     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1057     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1058     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1059     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1060
1061     deleteItem(createSourceA, child);
1062
1063     SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
1064     SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
1065     SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
1066     SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
1067     SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
1068     CPPUNIT_ASSERT_NO_THROW(source.reset());
1069
1070     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1071     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1072     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1073     // parent might have been updated
1074     int updated = countUpdatedItems(copy.get());
1075     SOURCE_ASSERT(copy.get(), 0 <= updated && updated <= 1);
1076     SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1077     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1078     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1079
1080     deleteItem(createSourceA, parent);
1081
1082     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1083     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1084     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1085     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1086     SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1087     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1088     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1089 }
1090
1091 // test inserting, removing and updating of parent + child item in
1092 // various order plus change tracking
1093 void LocalTests::testLinkedItemsInsertParentTwice() {
1094     // check additional requirements
1095     CPPUNIT_ASSERT(config.parentItem);
1096     CPPUNIT_ASSERT(config.childItem);
1097
1098     deleteAll(createSourceA);
1099     std::string parent, child;
1100     TestingSyncSourcePtr copy;
1101
1102     // check that everything is empty, also resets change counter of sync source B
1103     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1104     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1105     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1106
1107     // add parent twice (should be turned into update)
1108     parent = insert(createSourceA, config.parentItem);
1109
1110     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1111     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1112     SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
1113     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1114     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1115     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1116     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1117
1118     parent = insert(createSourceA, config.parentItem);
1119
1120     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1121     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1122     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1123     SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get()));
1124     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1125     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), parent));
1126     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1127
1128     deleteItem(createSourceA, parent);
1129
1130     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1131     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1132     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1133     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1134     SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1135     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1136 }
1137
1138 // test inserting, removing and updating of parent + child item in
1139 // various order plus change tracking
1140 void LocalTests::testLinkedItemsInsertChildTwice() {
1141     // check additional requirements
1142     CPPUNIT_ASSERT(config.parentItem);
1143     CPPUNIT_ASSERT(config.childItem);
1144
1145     deleteAll(createSourceA);
1146     std::string parent, child;
1147     TestingSyncSourcePtr copy;
1148
1149     // check that everything is empty, also resets change counter of sync source B
1150     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1151     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1152     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1153
1154 #if LINKED_ITEMS_RELAXED_SEMANTIC
1155     // add child twice (should be turned into update)
1156     child = insert(createSourceA, config.childItem);
1157
1158     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1159     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1160     SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
1161     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1162     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1163     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1164     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1165
1166     child = insert(createSourceA, config.childItem);
1167
1168     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1169     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1170     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1171     SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get()));
1172     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1173     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), child));
1174     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1175
1176     deleteItem(createSourceA, child);
1177
1178     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1179     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1180     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1181     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1182     SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1183     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1184 #endif
1185 }
1186
1187 // test inserting, removing and updating of parent + child item in
1188 // various order plus change tracking
1189 void LocalTests::testLinkedItemsParentUpdate() {
1190     // check additional requirements
1191     CPPUNIT_ASSERT(config.parentItem);
1192     CPPUNIT_ASSERT(config.childItem);
1193
1194     deleteAll(createSourceA);
1195     std::string parent, child;
1196     TestingSyncSourcePtr copy;
1197
1198     // check that everything is empty, also resets change counter of sync source B
1199     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1200     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1201     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1202
1203     // add parent, then update it
1204     parent = insert(createSourceA, config.parentItem);
1205
1206     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1207     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1208     SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
1209     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1210     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1211     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1212     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1213
1214     parent = updateItem(createSourceA, parent, config.parentItem);
1215
1216     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1217     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1218     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1219     SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get()));
1220     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1221     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), parent));
1222     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1223
1224     deleteItem(createSourceA, parent);
1225
1226     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1227     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1228     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1229     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1230     SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1231     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1232     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1233 }
1234
1235 // test inserting, removing and updating of parent + child item in
1236 // various order plus change tracking
1237 void LocalTests::testLinkedItemsUpdateChild() {
1238 #if LINKED_ITEMS_RELAXED_SEMANTIC
1239     // check additional requirements
1240     CPPUNIT_ASSERT(config.parentItem);
1241     CPPUNIT_ASSERT(config.childItem);
1242
1243     deleteAll(createSourceA);
1244     std::string parent, child;
1245     TestingSyncSourcePtr copy;
1246
1247     // check that everything is empty, also resets change counter of sync source B
1248     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1249     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1250     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1251
1252     // add child, then update it
1253     child = insert(createSourceA, config.childItem);
1254
1255     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1256     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1257     SOURCE_ASSERT_EQUAL(copy.get(), 1, countNewItems(copy.get()));
1258     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1259     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1260     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1261     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1262
1263     child = updateItem(createSourceA, child, config.childItem);
1264
1265     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1266     SOURCE_ASSERT_EQUAL(copy.get(), 1, countItems(copy.get()));
1267     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1268     SOURCE_ASSERT_EQUAL(copy.get(), 1, countUpdatedItems(copy.get()));
1269     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1270     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), child));
1271     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1272
1273     deleteItem(createSourceA, child);
1274
1275     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1276     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1277     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1278     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1279     SOURCE_ASSERT_EQUAL(copy.get(), 1, countDeletedItems(copy.get()));
1280     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1281     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1282 #endif
1283 }
1284
1285 // test inserting, removing and updating of parent + child item in
1286 // various order plus change tracking
1287 void LocalTests::testLinkedItemsInsertBothUpdateChild() {
1288     // check additional requirements
1289     CPPUNIT_ASSERT(config.parentItem);
1290     CPPUNIT_ASSERT(config.childItem);
1291
1292     deleteAll(createSourceA);
1293     std::string parent, child;
1294     TestingSyncSourcePtr copy;
1295
1296     // check that everything is empty, also resets change counter of sync source B
1297     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1298     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1299     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1300
1301     // add parent and child, then update child
1302     parent = insert(createSourceA, config.parentItem);
1303     child = insert(createSourceA, config.childItem);
1304
1305     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1306     SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1307     SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
1308     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1309     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1310     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1311     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1312     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1313
1314     child = updateItem(createSourceA, child, config.childItem);
1315
1316     // child has to be listed as modified, parent may be
1317     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1318     SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1319     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1320     SOURCE_ASSERT(copy.get(), 1 <= countUpdatedItems(copy.get()));
1321     SOURCE_ASSERT(copy.get(), 2 >= countUpdatedItems(copy.get()));
1322     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1323     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), child));
1324     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1325
1326     deleteItem(createSourceA, parent);
1327     deleteItem(createSourceA, child);
1328
1329     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1330     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1331     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1332     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1333     SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get()));
1334     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1335     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1336     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1337 }
1338
1339 // test inserting, removing and updating of parent + child item in
1340 // various order plus change tracking
1341 void LocalTests::testLinkedItemsInsertBothUpdateParent() {
1342     // check additional requirements
1343     CPPUNIT_ASSERT(config.parentItem);
1344     CPPUNIT_ASSERT(config.childItem);
1345
1346     deleteAll(createSourceA);
1347     std::string parent, child;
1348     TestingSyncSourcePtr copy;
1349
1350     // check that everything is empty, also resets change counter of sync source B
1351     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1352     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1353     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1354
1355     // add parent and child, then update parent
1356     parent = insert(createSourceA, config.parentItem);
1357     child = insert(createSourceA, config.childItem);
1358
1359     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1360     SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1361     SOURCE_ASSERT_EQUAL(copy.get(), 2, countNewItems(copy.get()));
1362     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1363     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1364     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), child));
1365     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listItems(copy.get()), parent));
1366     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1367
1368     parent = updateItem(createSourceA, parent, config.parentItem);
1369
1370     // parent has to be listed as modified, child may be
1371     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1372     SOURCE_ASSERT_EQUAL(copy.get(), 2, countItems(copy.get()));
1373     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1374     SOURCE_ASSERT(copy.get(), 1 <= countUpdatedItems(copy.get()));
1375     SOURCE_ASSERT(copy.get(), 2 >= countUpdatedItems(copy.get()));
1376     SOURCE_ASSERT_EQUAL(copy.get(), 0, countDeletedItems(copy.get()));
1377     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listUpdatedItems(copy.get()), parent));
1378     CPPUNIT_ASSERT_NO_THROW(copy.reset());
1379
1380     deleteItem(createSourceA, parent);
1381     deleteItem(createSourceA, child);
1382
1383     SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(createSourceB()));
1384     SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1385     SOURCE_ASSERT_EQUAL(copy.get(), 0, countNewItems(copy.get()));
1386     SOURCE_ASSERT_EQUAL(copy.get(), 0, countUpdatedItems(copy.get()));
1387     SOURCE_ASSERT_EQUAL(copy.get(), 2, countDeletedItems(copy.get()));
1388     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), parent));
1389     SOURCE_ASSERT_EQUAL(copy.get(), 1, countEqual(listDeletedItems(copy.get()), child));
1390 }
1391
1392
1393 SyncTests::SyncTests(const std::string &name, ClientTest &cl, std::vector<int> sourceIndices, bool isClientA) :
1394     CppUnit::TestSuite(name),
1395     client(cl) {
1396     sourceArray = new int[sourceIndices.size() + 1];
1397     int offset = 0;
1398     for (std::vector<int>::iterator it = sourceIndices.begin();
1399          it != sourceIndices.end();
1400          ++it) {
1401         ClientTest::Config config;
1402         client.getSyncSourceConfig(*it, config);
1403
1404         if (config.sourceName) {
1405             sourceArray[sources.size()+offset] = *it;
1406             if (config.subConfigs) {
1407                 vector<string> subs;
1408                 boost::split (subs, config.subConfigs, boost::is_any_of(","));
1409                 offset++;
1410                 ClientTest::Config subConfig;
1411                 BOOST_FOREACH (string sub, subs) {
1412                 client.getSourceConfig (sub, subConfig);
1413                 sources.push_back(std::pair<int,LocalTests *>(*it, cl.createLocalTests(sub, client.getLocalSourcePosition(sub), subConfig)));
1414                 offset--;
1415                 }
1416             } else {
1417                 sources.push_back(std::pair<int,LocalTests *>(*it, cl.createLocalTests(config.sourceName, client.getLocalSourcePosition(config.sourceName), config)));
1418             }
1419         }
1420     }
1421     sourceArray[sources.size()+ offset] = -1;
1422
1423     // check whether we have a second client
1424     ClientTest *clientB = cl.getClientB();
1425     if (clientB) {
1426         accessClientB = clientB->createSyncTests(name, sourceIndices, false);
1427     } else {
1428         accessClientB = 0;
1429     }
1430 }
1431
1432 SyncTests::~SyncTests() {
1433     for (source_it it = sources.begin();
1434          it != sources.end();
1435          ++it) {
1436         delete it->second;
1437     }
1438     delete [] sourceArray;
1439     if (accessClientB) {
1440         delete accessClientB;
1441     }
1442 }
1443
1444 /** adds the supported tests to the instance itself */
1445 void SyncTests::addTests() {
1446     if (sources.size()) {
1447         const ClientTest::Config &config(sources[0].second->config);
1448
1449         // run this test first, even if it is more complex:
1450         // if it works, all the following tests will run with
1451         // the server in a deterministic state
1452         if (config.createSourceA) {
1453             if (config.insertItem) {
1454                 ADD_TEST(SyncTests, testDeleteAllRefresh);
1455             }
1456         }
1457
1458         ADD_TEST(SyncTests, testTwoWaySync);
1459         ADD_TEST(SyncTests, testSlowSync);
1460         ADD_TEST(SyncTests, testRefreshFromServerSync);
1461         ADD_TEST(SyncTests, testRefreshFromClientSync);
1462
1463         if (config.compare &&
1464             config.testcases) {
1465             ADD_TEST(SyncTests, testConversion);
1466         }
1467
1468         if (config.createSourceA) {
1469             if (config.insertItem) {
1470                 ADD_TEST(SyncTests, testRefreshFromServerSemantic);
1471                 ADD_TEST(SyncTests, testRefreshFromClientSemantic);
1472                 ADD_TEST(SyncTests, testRefreshStatus);
1473
1474                 if (accessClientB &&
1475                     config.dump &&
1476                     config.compare) {
1477                     ADD_TEST(SyncTests, testCopy);
1478                     ADD_TEST(SyncTests, testDelete);
1479                     ADD_TEST(SyncTests, testAddUpdate);
1480                     ADD_TEST(SyncTests, testManyItems);
1481                     ADD_TEST(SyncTests, testManyDeletes);
1482                     ADD_TEST(SyncTests, testSlowSyncSemantic);
1483                     ADD_TEST(SyncTests, testComplexRefreshFromServerSemantic);
1484
1485                     if (config.updateItem) {
1486                         ADD_TEST(SyncTests, testUpdate);
1487                     }
1488                     if (config.complexUpdateItem) {
1489                         ADD_TEST(SyncTests, testComplexUpdate);
1490                     }
1491                     if (config.mergeItem1 && config.mergeItem2) {
1492                         ADD_TEST(SyncTests, testMerge);
1493                     }
1494                     if (config.import) {
1495                         ADD_TEST(SyncTests, testTwinning);
1496                         ADD_TEST(SyncTests, testItems);
1497                         ADD_TEST(SyncTests, testItemsXML);
1498                     }
1499                     if (config.templateItem) {
1500                         ADD_TEST(SyncTests, testMaxMsg);
1501                         ADD_TEST(SyncTests, testLargeObject);
1502                         ADD_TEST(SyncTests, testOneWayFromServer);
1503                         ADD_TEST(SyncTests, testOneWayFromClient);
1504                     }
1505                 }
1506             }
1507         }
1508
1509         if (config.retrySync &&
1510             config.insertItem &&
1511             config.updateItem &&
1512             accessClientB &&
1513             config.dump &&
1514             config.compare) {
1515             CppUnit::TestSuite *retryTests = new CppUnit::TestSuite(getName() + "::Retry");
1516             ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientAdd);
1517             ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientRemove);
1518             ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeClientUpdate);
1519             ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerAdd);
1520             ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerRemove);
1521             ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeServerUpdate);
1522             ADD_TEST_TO_SUITE(retryTests, SyncTests, testInterruptResumeFull);
1523             addTest(FilterTest(retryTests));
1524         }
1525
1526         if (config.suspendSync &&
1527             config.insertItem &&
1528             config.updateItem &&
1529             accessClientB &&
1530             config.dump &&
1531             config.compare) {
1532             CppUnit::TestSuite *suspendTests = new CppUnit::TestSuite(getName() + "::Suspend");
1533             ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientAdd);
1534             ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientRemove);
1535             ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendClientUpdate);
1536             ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerAdd);
1537             ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerRemove);
1538             ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendServerUpdate);
1539             ADD_TEST_TO_SUITE(suspendTests, SyncTests, testUserSuspendFull);
1540             addTest(FilterTest(suspendTests));
1541         }
1542
1543         if (config.resendSync &&
1544                 config.insertItem &&
1545                 config.updateItem &&
1546                 accessClientB &&
1547                 config.dump &&
1548                 config.compare) {
1549             CppUnit::TestSuite *resendTests = new CppUnit::TestSuite(getName() + "::Resend");
1550             ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendClientAdd);
1551             ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendClientRemove);
1552             ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendClientUpdate);
1553             ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendServerAdd);
1554             ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendServerRemove);
1555             ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendServerUpdate);
1556             ADD_TEST_TO_SUITE(resendTests, SyncTests, testResendFull);
1557             addTest(FilterTest(resendTests));
1558         }
1559
1560     }
1561 }
1562
1563 bool SyncTests::compareDatabases(const char *refFileBase, bool raiseAssert) {
1564     source_it it1;
1565     source_it it2;
1566     bool equal = true;
1567
1568     CPPUNIT_ASSERT(accessClientB);
1569     for (it1 = sources.begin(), it2 = accessClientB->sources.begin();
1570          it1 != sources.end() && it2 != accessClientB->sources.end();
1571          ++it1, ++it2) {
1572         TestingSyncSourcePtr copy;
1573         SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it2->second->createSourceB()));
1574         if (refFileBase) {
1575             std::string refFile = refFileBase;
1576             refFile += it1->second->config.sourceName;
1577             refFile += ".dat";
1578             simplifyFilename(refFile);
1579             if (!it1->second->compareDatabases(refFile.c_str(), *copy.get(), raiseAssert)) {
1580                 equal = false;
1581             }
1582         } else {
1583             if (!it1->second->compareDatabases(NULL, *copy.get(), raiseAssert)) {
1584                 equal = false;
1585             }
1586         }
1587         CPPUNIT_ASSERT_NO_THROW(copy.reset());
1588     }
1589     CPPUNIT_ASSERT(it1 == sources.end());
1590     CPPUNIT_ASSERT(it2 == accessClientB->sources.end());
1591
1592     CPPUNIT_ASSERT(!raiseAssert || equal);
1593     return equal;
1594 }
1595
1596 /** deletes all items locally and on server */
1597 void SyncTests::deleteAll(DeleteAllMode mode) {
1598     source_it it;
1599     SyncPrefix prefix("deleteall", *this);
1600
1601     switch(mode) {
1602      case DELETE_ALL_SYNC:
1603         // a refresh from server would slightly reduce the amount of data exchanged, but not all servers support it
1604         for (it = sources.begin(); it != sources.end(); ++it) {
1605             it->second->deleteAll(it->second->createSourceA);
1606         }
1607         doSync("init", SyncOptions(SYNC_SLOW));
1608         // now that client and server are in sync, delete locally and sync again
1609         for (it = sources.begin(); it != sources.end(); ++it) {
1610             it->second->deleteAll(it->second->createSourceA);
1611         }
1612         doSync("twoway",
1613                SyncOptions(SYNC_TWO_WAY,
1614                            CheckSyncReport(0,0,0, 0,0,-1, true, SYNC_TWO_WAY)));
1615         break;
1616      case DELETE_ALL_REFRESH:
1617         // delete locally and then tell the server to "copy" the empty databases
1618         for (it = sources.begin(); it != sources.end(); ++it) {
1619             it->second->deleteAll(it->second->createSourceA);
1620         }
1621         doSync("refreshserver",
1622                SyncOptions(SYNC_REFRESH_FROM_CLIENT,
1623                            CheckSyncReport(0,0,0, 0,0,-1, true, SYNC_REFRESH_FROM_CLIENT)));
1624         break;
1625     }
1626 }
1627
1628 /** get both clients in sync with empty server, then copy one item from client A to B */
1629 void SyncTests::doCopy() {
1630     SyncPrefix("copy", *this);
1631
1632     // check requirements
1633     CPPUNIT_ASSERT(accessClientB);
1634
1635     deleteAll();
1636     accessClientB->deleteAll();
1637
1638     // insert into first database, copy to server
1639     source_it it;
1640     for (it = sources.begin(); it != sources.end(); ++it) {
1641         it->second->testSimpleInsert();
1642     }
1643     doSync("send",
1644            SyncOptions(SYNC_TWO_WAY,
1645                        CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
1646
1647     // copy into second database
1648     accessClientB->doSync("recv",
1649                           SyncOptions(SYNC_TWO_WAY,
1650                                       CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY)));
1651
1652     compareDatabases();
1653 }
1654
1655 /**
1656  * replicate server database locally: same as SYNC_REFRESH_FROM_SERVER,
1657  * but done with explicit local delete and then a SYNC_SLOW because some
1658  * servers do no support SYNC_REFRESH_FROM_SERVER
1659  */
1660 void SyncTests::refreshClient(SyncOptions options) {
1661     source_it it;
1662     for (it = sources.begin(); it != sources.end(); ++it) {
1663         it->second->deleteAll(it->second->createSourceA);
1664     }
1665
1666     doSync("refresh",
1667            options
1668            .setSyncMode(SYNC_SLOW)
1669            .setCheckReport(CheckSyncReport(-1,0,0, 0,0,0, true, SYNC_SLOW)));
1670 }
1671
1672
1673 // delete all items, locally and on server using refresh-from-client sync
1674 void SyncTests::testDeleteAllRefresh() {
1675     source_it it;
1676
1677     // copy something to server first; doesn't matter whether it has the
1678     // item already or not, as long as it exists there afterwards
1679     for (it = sources.begin(); it != sources.end(); ++it) {
1680         it->second->testSimpleInsert();
1681     }
1682     doSync("insert", SyncOptions(SYNC_SLOW));
1683
1684     // now ensure we can delete it
1685     deleteAll(DELETE_ALL_SYNC);
1686
1687     // nothing stored locally?
1688     for (it = sources.begin(); it != sources.end(); ++it) {
1689         TestingSyncSourcePtr source;
1690         SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
1691         SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
1692         CPPUNIT_ASSERT_NO_THROW(source.reset());
1693     }
1694
1695     // make sure server really deleted everything
1696     doSync("check",
1697            SyncOptions(SYNC_SLOW,
1698                        CheckSyncReport(0,0,0, 0,0,0, true, SYNC_SLOW)));
1699     for (it = sources.begin(); it != sources.end(); ++it) {
1700         TestingSyncSourcePtr source;
1701         SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
1702         SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
1703         CPPUNIT_ASSERT_NO_THROW(source.reset());
1704     }
1705 }
1706
1707 // test that a refresh sync from an empty server leads to an empty datatbase
1708 // and no changes are sent to server during next two-way sync
1709 void SyncTests::testRefreshFromServerSemantic() {
1710     source_it it;
1711
1712     // clean client and server
1713     deleteAll();
1714
1715     // insert item, then refresh from empty server
1716     for (it = sources.begin(); it != sources.end(); ++it) {
1717         it->second->testSimpleInsert();
1718     }
1719     doSync("refresh",
1720            SyncOptions(SYNC_REFRESH_FROM_SERVER,
1721                        CheckSyncReport(0,0,-1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
1722
1723     // check
1724     for (it = sources.begin(); it != sources.end(); ++it) {
1725         TestingSyncSourcePtr source;
1726         SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
1727         SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
1728         CPPUNIT_ASSERT_NO_THROW(source.reset());
1729     }
1730     doSync("two-way",
1731            SyncOptions(SYNC_TWO_WAY,
1732                        CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY)));
1733 }
1734
1735 // test that a refresh sync from an empty client leads to an empty datatbase
1736 // and no changes are sent to server during next two-way sync
1737 void SyncTests::testRefreshFromClientSemantic() {
1738     source_it it;
1739
1740     // clean client and server
1741     deleteAll();
1742
1743     // insert item, send to server
1744     for (it = sources.begin(); it != sources.end(); ++it) {
1745         it->second->testSimpleInsert();
1746     }
1747     doSync("send",
1748            SyncOptions(SYNC_TWO_WAY,
1749                        CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
1750
1751     // delete locally
1752     for (it = sources.begin(); it != sources.end(); ++it) {
1753         it->second->deleteAll(it->second->createSourceA);
1754     }
1755
1756     // refresh from client
1757     doSync("refresh",
1758            SyncOptions(SYNC_REFRESH_FROM_CLIENT,
1759                        CheckSyncReport(0,0,0, 0,0,0, true, SYNC_REFRESH_FROM_CLIENT)));
1760
1761     // check
1762     doSync("check",
1763            SyncOptions(SYNC_REFRESH_FROM_SERVER,
1764                        CheckSyncReport(0,0,0, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
1765 }
1766
1767 // tests the following sequence of events:
1768 // - insert item
1769 // - delete all items
1770 // - insert one other item
1771 // - refresh from client
1772 // => no items should now be listed as new, updated or deleted for this client during another sync
1773 void SyncTests::testRefreshStatus() {
1774     source_it it;
1775
1776     for (it = sources.begin(); it != sources.end(); ++it) {
1777         it->second->testSimpleInsert();
1778     }
1779     for (it = sources.begin(); it != sources.end(); ++it) {
1780         it->second->deleteAll(it->second->createSourceA);
1781     }
1782     for (it = sources.begin(); it != sources.end(); ++it) {
1783         it->second->testSimpleInsert();
1784     }
1785     doSync("refresh-from-client",
1786            SyncOptions(SYNC_REFRESH_FROM_CLIENT,
1787                        CheckSyncReport(0,0,0, -1,-1,-1, /* strictly speaking 1,0,0, but not sure exactly what the server will be told */
1788                                        true, SYNC_REFRESH_FROM_CLIENT)));
1789     doSync("two-way",
1790            SyncOptions(SYNC_TWO_WAY,
1791                        CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY)));
1792 }
1793
1794 // test that a two-way sync copies updates from database to the other client,
1795 // using simple data commonly supported by servers
1796 void SyncTests::testUpdate() {
1797     CPPUNIT_ASSERT(sources.begin() != sources.end());
1798     CPPUNIT_ASSERT(sources.begin()->second->config.updateItem);
1799
1800     // setup client A, B and server so that they all contain the same item
1801     doCopy();
1802
1803     source_it it;
1804     for (it = sources.begin(); it != sources.end(); ++it) {
1805         it->second->update(it->second->createSourceA, it->second->config.updateItem);
1806     }
1807
1808     doSync("update",
1809            SyncOptions(SYNC_TWO_WAY,
1810                        CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY)));
1811     accessClientB->doSync("update",
1812                           SyncOptions(SYNC_TWO_WAY,
1813                                       CheckSyncReport(0,1,0, 0,0,0, true, SYNC_TWO_WAY)));
1814
1815     compareDatabases();
1816 }
1817
1818 // test that a two-way sync copies updates from database to the other client,
1819 // using data that some, but not all servers support, like adding a second
1820 // phone number to a contact
1821 void SyncTests::testComplexUpdate() {
1822     // setup client A, B and server so that they all contain the same item
1823     doCopy();
1824
1825     source_it it;
1826     for (it = sources.begin(); it != sources.end(); ++it) {
1827         it->second->update(it->second->createSourceA,
1828                            /* this test might get executed with some sources which have
1829                               a complex update item while others don't: use the normal update item
1830                               for them or even just the same item */
1831                            it->second->config.complexUpdateItem ? it->second->config.complexUpdateItem :
1832                            it->second->config.updateItem ? it->second->config.updateItem :
1833                            it->second->config.insertItem
1834                            );
1835     }
1836
1837     doSync("update",
1838            SyncOptions(SYNC_TWO_WAY,
1839                        CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY)));
1840     accessClientB->doSync("update",
1841                           SyncOptions(SYNC_TWO_WAY,
1842                                       CheckSyncReport(0,1,0, 0,0,0, true, SYNC_TWO_WAY)));
1843
1844     compareDatabases();
1845 }
1846
1847
1848 // test that a two-way sync deletes the copy of an item in the other database
1849 void SyncTests::testDelete() {
1850     // setup client A, B and server so that they all contain the same item
1851     doCopy();
1852
1853     // delete it on A
1854     source_it it;
1855     for (it = sources.begin(); it != sources.end(); ++it) {
1856         it->second->deleteAll(it->second->createSourceA);
1857     }
1858
1859     // transfer change from A to server to B
1860     doSync("delete",
1861            SyncOptions(SYNC_TWO_WAY,
1862                        CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY)));
1863     accessClientB->doSync("delete",
1864                           SyncOptions(SYNC_TWO_WAY,
1865                                       CheckSyncReport(0,0,1, 0,0,0, true, SYNC_TWO_WAY)));
1866
1867     // check client B: shouldn't have any items now
1868     for (it = sources.begin(); it != sources.end(); ++it) {
1869         TestingSyncSourcePtr copy;
1870         SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it->second->createSourceA()));
1871         SOURCE_ASSERT_EQUAL(copy.get(), 0, countItems(copy.get()));
1872         CPPUNIT_ASSERT_NO_THROW(copy.reset());
1873     }
1874 }
1875
1876 // test what the server does when it finds that different
1877 // fields of the same item have been modified
1878 void SyncTests::testMerge() {
1879     // setup client A, B and server so that they all contain the same item
1880     doCopy();
1881
1882     // update in client A
1883     source_it it;
1884     for (it = sources.begin(); it != sources.end(); ++it) {
1885         it->second->update(it->second->createSourceA, it->second->config.mergeItem1);
1886     }
1887
1888     // update in client B
1889     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
1890         it->second->update(it->second->createSourceA, it->second->config.mergeItem2);
1891     }
1892
1893     // send change to server from client A (no conflict)
1894     doSync("update",
1895            SyncOptions(SYNC_TWO_WAY,
1896                        CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY)));
1897     // Now the changes from client B (conflict!).
1898     // There are several possible outcomes:
1899     // - client item completely replaces server item
1900     // - server item completely replaces client item (update on client)
1901     // - server merges and updates client
1902     accessClientB->doSync("conflict",
1903                           SyncOptions(SYNC_TWO_WAY,
1904                                       CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_TWO_WAY)));
1905
1906     // figure out how the conflict during ".conflict" was handled
1907     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
1908         TestingSyncSourcePtr copy;
1909         SOURCE_ASSERT_NO_FAILURE(copy.get(), copy.reset(it->second->createSourceA()));
1910         int numItems = 0;
1911         SOURCE_ASSERT_NO_FAILURE(copy.get(), numItems = countItems(copy.get()));
1912         CPPUNIT_ASSERT(numItems >= 1);
1913         CPPUNIT_ASSERT(numItems <= 2);
1914         std::cout << " \"" << it->second->config.sourceName << ": " << (numItems == 1 ? "conflicting items were merged" : "both of the conflicting items were preserved") << "\" ";
1915         std::cout.flush();
1916         CPPUNIT_ASSERT_NO_THROW(copy.reset());        
1917     }
1918
1919     // now pull the same changes into client A
1920     doSync("refresh",
1921            SyncOptions(SYNC_TWO_WAY,
1922                        CheckSyncReport(-1,-1,-1, 0,0,0, true, SYNC_TWO_WAY)));
1923
1924     // client A and B should have identical data now
1925     compareDatabases();
1926
1927     // Furthermore, it should be identical with the server.
1928     // Be extra careful and pull that data anew and compare once more.
1929     doSync("check",
1930            SyncOptions(SYNC_REFRESH_FROM_SERVER,
1931                        CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_REFRESH_FROM_SERVER)));
1932     compareDatabases();
1933 }
1934
1935 // test what the server does when it has to execute a slow sync
1936 // with identical data on client and server:
1937 // expected behaviour is that nothing changes
1938 void SyncTests::testTwinning() {
1939     // clean server and client A
1940     deleteAll();
1941
1942     // import test data
1943     source_it it;
1944     for (it = sources.begin(); it != sources.end(); ++it) {
1945         it->second->testImport();
1946     }
1947
1948     // send to server
1949     doSync("send", SyncOptions(SYNC_TWO_WAY));
1950
1951     // ensure that client has the same data, thus ignoring data conversion
1952     // issues (those are covered by testItems())
1953     refreshClient();
1954
1955     // copy to client B to have another copy
1956     accessClientB->refreshClient();
1957
1958     // slow sync should not change anything
1959     doSync("twinning", SyncOptions(SYNC_SLOW));
1960
1961     // check
1962     compareDatabases();
1963 }
1964
1965 // tests one-way sync from server:
1966 // - get both clients and server in sync with no items anywhere
1967 // - add one item on first client, copy to server
1968 // - add a different item on second client, one-way-from-server
1969 // - two-way sync with first client
1970 // => one item on first client, two on second
1971 // - delete on first client, sync that to second client
1972 //   via two-way sync + one-way-from-server
1973 // => one item left on second client (the one inserted locally)
1974 void SyncTests::testOneWayFromServer() {
1975     // no items anywhere
1976     deleteAll();
1977     accessClientB->refreshClient();
1978
1979     // check that everything is empty, also resets change tracking
1980     // in second sources of each client
1981     source_it it;
1982     for (it = sources.begin(); it != sources.end(); ++it) {
1983         if (it->second->config.createSourceB) {
1984             TestingSyncSourcePtr source;
1985             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
1986             SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
1987             CPPUNIT_ASSERT_NO_THROW(source.reset());
1988         }
1989     }
1990     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
1991         if (it->second->config.createSourceB) {
1992             TestingSyncSourcePtr source;
1993             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
1994             SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
1995             CPPUNIT_ASSERT_NO_THROW(source.reset());
1996         }
1997     }
1998
1999     // add one item on first client, copy to server, and check change tracking via second source
2000     for (it = sources.begin(); it != sources.end(); ++it) {
2001         it->second->insertManyItems(it->second->createSourceA, 200, 1);
2002     }
2003     doSync("send",
2004            SyncOptions(SYNC_TWO_WAY,
2005                        CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
2006     for (it = sources.begin(); it != sources.end(); ++it) {
2007         if (it->second->config.createSourceB) {
2008             TestingSyncSourcePtr source;
2009             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2010             SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2011             SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2012             SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2013             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2014             CPPUNIT_ASSERT_NO_THROW(source.reset());
2015         }
2016     }
2017
2018     // add a different item on second client, one-way-from-server
2019     // => one item added locally, none sent to server
2020     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2021         it->second->insertManyItems(it->second->createSourceA, 2, 1);
2022
2023         if (it->second->config.createSourceB) {
2024             TestingSyncSourcePtr source;
2025             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2026             SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2027             SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2028             SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2029             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2030             CPPUNIT_ASSERT_NO_THROW(source.reset());
2031         }
2032     }
2033     accessClientB->doSync("recv",
2034                           SyncOptions(SYNC_ONE_WAY_FROM_SERVER,
2035                                       CheckSyncReport(1,0,0, 0,0,0, true, SYNC_ONE_WAY_FROM_SERVER)));
2036     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2037         if (it->second->config.createSourceB) {
2038             TestingSyncSourcePtr source;
2039             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2040             SOURCE_ASSERT_EQUAL(source.get(), 2, countItems(source.get()));
2041             SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2042             SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2043             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2044             CPPUNIT_ASSERT_NO_THROW(source.reset());
2045         }
2046     }
2047
2048     // two-way sync with first client for verification
2049     // => no changes
2050     doSync("check",
2051            SyncOptions(SYNC_TWO_WAY,
2052                        CheckSyncReport(0,0,0, 0,0,0, true, SYNC_TWO_WAY)));
2053     for (it = sources.begin(); it != sources.end(); ++it) {
2054         if (it->second->config.createSourceB) {
2055             TestingSyncSourcePtr source;
2056             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2057             SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2058             SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2059             SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2060             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2061             CPPUNIT_ASSERT_NO_THROW(source.reset());
2062         }
2063     }
2064
2065     // delete items on clientA, sync to server
2066     for (it = sources.begin(); it != sources.end(); ++it) {
2067         it->second->deleteAll(it->second->createSourceA);
2068
2069         if (it->second->config.createSourceB) {
2070             TestingSyncSourcePtr source;
2071             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2072             SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2073             SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2074             SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get()));
2075             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2076             CPPUNIT_ASSERT_NO_THROW(source.reset());
2077         }
2078     }
2079     doSync("delete",
2080            SyncOptions(SYNC_TWO_WAY,
2081                        CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY)));
2082     for (it = sources.begin(); it != sources.end(); ++it) {
2083         if (it->second->config.createSourceB) {
2084             TestingSyncSourcePtr source;
2085             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2086             SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2087             SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2088             SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2089             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2090             CPPUNIT_ASSERT_NO_THROW(source.reset());
2091         }
2092     }
2093
2094     // sync the same change to second client
2095     // => one item left (the one inserted locally)
2096     accessClientB->doSync("delete",
2097                           SyncOptions(SYNC_ONE_WAY_FROM_SERVER,
2098                                       CheckSyncReport(0,0,1, 0,0,0, true, SYNC_ONE_WAY_FROM_SERVER)));
2099     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2100         if (it->second->config.createSourceB) {
2101             TestingSyncSourcePtr source;
2102             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2103             SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2104             SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2105             SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get()));
2106             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2107             CPPUNIT_ASSERT_NO_THROW(source.reset());
2108         }
2109     }
2110 }
2111
2112 // tests one-way sync from client:
2113 // - get both clients and server in sync with no items anywhere
2114 // - add one item on first client, copy to server
2115 // - add a different item on second client, one-way-from-client
2116 // - two-way sync with first client
2117 // => two items on first client, one on second
2118 // - delete on second client, sync that to first client
2119 //   via one-way-from-client, two-way
2120 // => one item left on first client (the one inserted locally)
2121 void SyncTests::testOneWayFromClient() {
2122     // no items anywhere
2123     deleteAll();
2124     accessClientB->deleteAll();
2125
2126     // check that everything is empty, also resets change tracking
2127     // in second sources of each client
2128     source_it it;
2129     for (it = sources.begin(); it != sources.end(); ++it) {
2130         if (it->second->config.createSourceB) {
2131             TestingSyncSourcePtr source;
2132             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2133             SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2134             CPPUNIT_ASSERT_NO_THROW(source.reset());
2135         }
2136     }
2137     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2138         if (it->second->config.createSourceB) {
2139             TestingSyncSourcePtr source;
2140             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2141             SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2142             CPPUNIT_ASSERT_NO_THROW(source.reset());
2143         }
2144     }
2145
2146     // add one item on first client, copy to server, and check change tracking via second source
2147     for (it = sources.begin(); it != sources.end(); ++it) {
2148         it->second->insertManyItems(it->second->createSourceA, 1, 1);
2149     }
2150     doSync("send",
2151            SyncOptions(SYNC_TWO_WAY,
2152                        CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
2153     for (it = sources.begin(); it != sources.end(); ++it) {
2154         if (it->second->config.createSourceB) {
2155             TestingSyncSourcePtr source;
2156             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2157             SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2158             SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2159             SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2160             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2161             CPPUNIT_ASSERT_NO_THROW(source.reset());
2162         }
2163     }
2164
2165     // add a different item on second client, one-way-from-client
2166     // => no item added locally, one sent to server
2167     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2168         it->second->insertManyItems(it->second->createSourceA, 2, 1);
2169
2170         if (it->second->config.createSourceB) {
2171             TestingSyncSourcePtr source;
2172             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2173             SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2174             SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2175             SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2176             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2177             CPPUNIT_ASSERT_NO_THROW(source.reset());
2178         }
2179     }
2180     accessClientB->doSync("send",
2181                           SyncOptions(SYNC_ONE_WAY_FROM_CLIENT,
2182                                       CheckSyncReport(0,0,0, 1,0,0, true, SYNC_ONE_WAY_FROM_CLIENT)));
2183     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2184         if (it->second->config.createSourceB) {
2185             TestingSyncSourcePtr source;
2186             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2187             SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2188             SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2189             SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2190             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2191             CPPUNIT_ASSERT_NO_THROW(source.reset());
2192         }
2193     }
2194
2195     // two-way sync with client A for verification
2196     // => receive one item
2197     doSync("check",
2198            SyncOptions(SYNC_TWO_WAY,
2199                        CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY)));
2200     for (it = sources.begin(); it != sources.end(); ++it) {
2201         if (it->second->config.createSourceB) {
2202             TestingSyncSourcePtr source;
2203             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2204             SOURCE_ASSERT_EQUAL(source.get(), 2, countItems(source.get()));
2205             SOURCE_ASSERT_EQUAL(source.get(), 1, countNewItems(source.get()));
2206             SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2207             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2208             CPPUNIT_ASSERT_NO_THROW(source.reset());
2209         }
2210     }
2211
2212     // delete items on client B, sync to server
2213     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2214         it->second->deleteAll(it->second->createSourceA);
2215
2216         if (it->second->config.createSourceB) {
2217             TestingSyncSourcePtr source;
2218             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2219             SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2220             SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2221             SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get()));
2222             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2223             CPPUNIT_ASSERT_NO_THROW(source.reset());
2224         }
2225     }
2226     accessClientB->doSync("delete",
2227                           SyncOptions(SYNC_ONE_WAY_FROM_CLIENT,
2228                                       CheckSyncReport(0,0,0, 0,0,1, true, SYNC_ONE_WAY_FROM_CLIENT)));
2229     for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
2230         if (it->second->config.createSourceB) {
2231             TestingSyncSourcePtr source;
2232             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2233             SOURCE_ASSERT_EQUAL(source.get(), 0, countItems(source.get()));
2234             SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2235             SOURCE_ASSERT_EQUAL(source.get(), 0, countDeletedItems(source.get()));
2236             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2237             CPPUNIT_ASSERT_NO_THROW(source.reset());
2238         }
2239     }
2240
2241     // sync the same change to client A
2242     // => one item left (the one inserted locally)
2243     doSync("delete",
2244            SyncOptions(SYNC_TWO_WAY,
2245                        CheckSyncReport(0,0,1, 0,0,0, true, SYNC_TWO_WAY)));
2246     for (it = sources.begin(); it != sources.end(); ++it) {
2247         if (it->second->config.createSourceB) {
2248             TestingSyncSourcePtr source;
2249             SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
2250             SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
2251             SOURCE_ASSERT_EQUAL(source.get(), 0, countNewItems(source.get()));
2252             SOURCE_ASSERT_EQUAL(source.get(), 1, countDeletedItems(source.get()));
2253             SOURCE_ASSERT_EQUAL(source.get(), 0, countUpdatedItems(source.get()));
2254             CPPUNIT_ASSERT_NO_THROW(source.reset());
2255         }
2256     }
2257 }
2258
2259 // get engine ready, then use it to convert our test items
2260 // to and from the internal field list
2261 void SyncTests::testConversion() {
2262     bool success = false;
2263     SyncOptions::Callback_t callback = boost::bind(&SyncTests::doConversionCallback, this, &success, _1, _2);
2264
2265     doSync(SyncOptions(SYNC_TWO_WAY, CheckSyncReport(-1,-1,-1, -1,-1,-1, false))
2266            .setStartCallback(callback));
2267     CPPUNIT_ASSERT(success);
2268 }
2269
2270 bool SyncTests::doConversionCallback(bool *success,
2271                                      SyncContext &syncClient,
2272                                      SyncOptions &options) {
2273     *success = false;
2274
2275     for (source_it it = sources.begin(); it != sources.end(); ++it) {
2276         const ClientTest::Config *config = &it->second->config;
2277         TestingSyncSource *source = static_cast<TestingSyncSource *>(syncClient.findSource(config->sourceName));
2278         CPPUNIT_ASSERT(source);
2279
2280         std::string type = source->getNativeDatatypeName();
2281         if (type.empty()) {
2282             continue;
2283         }
2284
2285         std::list<std::string> items;
2286         std::string testcases;
2287         ClientTest::getItems(config->testcases, items, testcases);
2288         std::string converted = getCurrentTest();
2289         converted += ".converted.";
2290         converted += config->sourceName;
2291         converted += ".dat";
2292         simplifyFilename(converted);
2293         std::ofstream out(converted.c_str());
2294         BOOST_FOREACH(const string &item, items) {
2295             string convertedItem = item;
2296             if(!sysync::DataConversion(syncClient.getSession().get(),
2297                                        type.c_str(),
2298                                        type.c_str(),
2299                                        convertedItem)) {
2300                 SE_LOG_ERROR(NULL, NULL, "failed parsing as %s:\n%s",
2301                              type.c_str(),
2302                              item.c_str());
2303             } else {
2304                 out << convertedItem << "\n";
2305             }
2306         }
2307         out.close();
2308         CPPUNIT_ASSERT(config->compare(client, testcases.c_str(), converted.c_str()));
2309     }
2310
2311     // abort sync after completing the test successfully (no exception so far!)
2312     *success = true;
2313     return true;
2314 }
2315
2316 // creates several items, transmits them back and forth and
2317 // then compares which of them have been preserved
2318 void SyncTests::testItems() {
2319     // clean server and first test database
2320     deleteAll();
2321
2322     // import data
2323     source_it it;
2324     for (it = sources.begin(); it != sources.end(); ++it) {
2325         it->second->testImport();
2326     }
2327
2328     // transfer from client A to server to client B
2329     doSync("send", SyncOptions(SYNC_TWO_WAY).setWBXML(true));
2330     accessClientB->refreshClient(SyncOptions().setWBXML(true));
2331
2332     compareDatabases();
2333 }
2334
2335 // creates several items, transmits them back and forth and
2336 // then compares which of them have been preserved
2337 void SyncTests::testItemsXML() {
2338     // clean server and first test database
2339     deleteAll();
2340
2341     // import data
2342     source_it it;
2343     for (it = sources.begin(); it != sources.end(); ++it) {
2344         it->second->testImport();
2345     }
2346
2347     // transfer from client A to server to client B using the non-default XML format
2348     doSync("send", SyncOptions(SYNC_TWO_WAY).setWBXML(false));
2349     accessClientB->refreshClient(SyncOptions().setWBXML(false));
2350
2351     compareDatabases();
2352 }
2353
2354 // tests the following sequence of events:
2355 // - both clients in sync with server
2356 // - client 1 adds item
2357 // - client 1 updates the same item
2358 // - client 2 gets item: the client should be asked to add the item
2359 //
2360 // However it has been observed that sometimes the item was sent as "update"
2361 // for a non-existant local item. This is a server bug, the client does not
2362 // have to handle that. See
2363 // http://forge.objectweb.org/tracker/?func=detail&atid=100096&aid=305018&group_id=96
2364 void SyncTests::testAddUpdate() {
2365     // clean server and both test databases
2366     deleteAll();
2367     accessClientB->refreshClient();
2368
2369     // add item
2370     source_it it;
2371     for (it = sources.begin(); it != sources.end(); ++it) {
2372         it->second->insert(it->second->createSourceA, it->second->config.insertItem, it->second->config.itemType);
2373     }
2374     doSync("add",
2375            SyncOptions(SYNC_TWO_WAY,
2376                        CheckSyncReport(0,0,0, 1,0,0, true, SYNC_TWO_WAY)));
2377
2378     // update it
2379     for (it = sources.begin(); it != sources.end(); ++it) {
2380         it->second->update(it->second->createSourceB, it->second->config.updateItem);
2381     }
2382     doSync("update",
2383            SyncOptions(SYNC_TWO_WAY,
2384                        CheckSyncReport(0,0,0, 0,1,0, true, SYNC_TWO_WAY)));
2385
2386     // now download the updated item into the second client
2387     accessClientB->doSync("recv",
2388                           SyncOptions(SYNC_TWO_WAY,
2389                                       CheckSyncReport(1,0,0, 0,0,0, true, SYNC_TWO_WAY)));
2390
2391     // compare the two databases
2392     compareDatabases();
2393 }
2394
2395 //
2396 // stress tests: execute some of the normal operations,
2397 // but with large number of artificially generated items
2398 //
2399
2400 // two-way sync with clean client/server,
2401 // followed by slow sync and comparison
2402 // via second client
2403 void SyncTests::testManyItems() {
2404     // clean server and client A
2405     deleteAll();
2406
2407     // import artificial data: make them large to generate some
2408     // real traffic and test buffer handling
2409     source_it it;
2410     int num_items = -1;
2411     for (it = sources.begin(); it != sources.end(); ++it) {
2412         if (num_items == -1) {
2413             num_items = it->second->config.numItems;
2414         } else {
2415             CPPUNIT_ASSERT_EQUAL(num_items, it->second->config.numItems);
2416         }
2417         it->second->insertManyItems(it->second->createSourceA, 0, num_items, 2000);
2418     }
2419
2420     // send data to server
2421     doSync("send",
2422            SyncOptions(SYNC_TWO_WAY,
2423                        CheckSyncReport(0,0,0, num_items,0,0, true, SYNC_TWO_WAY),
2424                        SyncOptions::DEFAULT_MAX_MSG_SIZE,
2425                        SyncOptions::DEFAULT_MAX_OBJ_SIZE, 
2426                        true));
2427
2428     // ensure that client has the same data, ignoring data conversion
2429     // issues (those are covered by testItems())
2430     refreshClient();
2431
2432     // also copy to second client
2433     accessClientB->refreshClient();
2434
2435     // slow sync now should not change anything
2436     doSync("twinning",
2437            SyncOptions(SYNC_SLOW,
2438                        CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_SLOW),
2439                        SyncOptions::DEFAULT_MAX_MSG_SIZE,
2440                        SyncOptions::DEFAULT_MAX_OBJ_SIZE, 
2441                        true));
2442
2443     // compare
2444     compareDatabases();
2445 }
2446
2447 /**
2448  * Tell server to delete plenty of items.
2449  */
2450 void SyncTests::testManyDeletes() {
2451     // clean server and client A
2452     deleteAll();
2453
2454     // import artificial data: make them small, we just want
2455     // many of them
2456     source_it it;
2457     int num_items = -1;
2458     for (it = sources.begin(); it != sources.end(); ++it) {
2459         if (num_items == -1) {
2460             num_items = it->second->config.numItems;
2461         } else {
2462             CPPUNIT_ASSERT_EQUAL(num_items, it->second->config.numItems);
2463         }
2464         it->second->insertManyItems(it->second->createSourceA, 0, num_items, 100);
2465     }
2466
2467     // send data to server
2468     doSync("send",
2469            SyncOptions(SYNC_TWO_WAY,
2470                        CheckSyncReport(0,0,0, num_items,0,0, true, SYNC_TWO_WAY),
2471                        64 * 1024, 64 * 1024, true));
2472
2473     // ensure that client has the same data, ignoring data conversion
2474     // issues (those are covered by testItems())
2475     refreshClient();
2476
2477     // also copy to second client
2478     accessClientB->refreshClient();
2479
2480     // slow sync now should not change anything
2481     doSync("twinning",
2482            SyncOptions(SYNC_SLOW,
2483                        CheckSyncReport(-1,-1,-1, -1,-1,-1, true, SYNC_SLOW),
2484                        64 * 1024, 64 * 1024, true));
2485
2486     // compare
2487     compareDatabases();
2488
2489     // delete everything locally
2490     BOOST_FOREACH(source_array_t::value_type &source_pair, sources)  {
2491         source_pair.second->deleteAll(source_pair.second->createSourceA);
2492     }
2493     doSync("delete-server",
2494            SyncOptions(SYNC_TWO_WAY,
2495                        CheckSyncReport(0,0,0, 0,0,num_items, true, SYNC_TWO_WAY),
2496                        10 * 1024));
2497
2498     // update second client
2499     accessClientB->doSync("delete-client",
2500                           SyncOptions(SYNC_REFRESH_FROM_SERVER,
2501                                       CheckSyncReport(0,0,num_items, 0,0,0, true, SYNC_REFRESH_FROM_SERVER),
2502                                       10 & 1024));
2503 }
2504
2505 /**
2506  * - get client A, server, client B in sync with one item
2507  * - force slow sync in A: must not duplicate items, but may update it locally
2508  * - refresh client B (in case that the item was updated)
2509  * - delete item in B and server via two-way sync
2510  * - refresh-from-server in B to check that item is gone
2511  * - two-way in A: must delete the item
2512  */
2513 void SyncTests::testSlowSyncSemantic()
2514 {
2515     // set up one item everywhere
2516     doCopy();
2517
2518     // slow in A
2519     doSync("slow",
2520            SyncOptions(SYNC_SLOW,
2521                        CheckSyncReport(0,-1,0, -1,-1,0, true, SYNC_SLOW)));
2522
2523     // refresh B, delete item
2524     accessClientB->doSync("refresh",
2525                           SyncOptions(SYNC_TWO_WAY,
2526                                       CheckSyncReport(0,-1,0, 0,0,0, true, SYNC_TWO_WAY)));
2527     BOOST_FOREACH(source_array_t::value_type &source_pair, accessClientB->sources)  {
2528         source_pair.second->deleteAll(source_pair.second->createSourceA);
2529     }
2530     accessClientB->doSync("delete",
2531                           SyncOptions(SYNC_TWO_WAY,
2532                                       CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY)));
2533     accessClientB->doSync("check",
2534                           SyncOptions(SYNC_REFRESH_FROM_SERVER,
2535                                       CheckSyncReport(0,0,0, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
2536
2537     // now the item should also be deleted on A
2538     doSync("delete",
2539            SyncOptions(SYNC_TWO_WAY,
2540                        CheckSyncReport(0,0,1, 0,0,0, true, SYNC_TWO_WAY)));
2541 }
2542
2543 /**
2544  * check that refresh-from-server works correctly:
2545  * - create the same item on A, server, B via testCopy()
2546  * - refresh B (one item deleted, one created)
2547  * - delete item on A and server
2548  * - refresh B (one item deleted)
2549  */
2550 void SyncTests::testComplexRefreshFromServerSemantic()
2551 {
2552     testCopy();
2553
2554     // check refresh with one item on server
2555     accessClientB->doSync("refresh-one",
2556                           SyncOptions(SYNC_REFRESH_FROM_SERVER,
2557                                       CheckSyncReport(1,0,1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
2558
2559     // delete that item via A, check again
2560     BOOST_FOREACH(source_array_t::value_type &source_pair, sources)  {
2561         source_pair.second->deleteAll(source_pair.second->createSourceA);
2562     }
2563     doSync("delete-item",
2564            SyncOptions(SYNC_TWO_WAY,
2565                        CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY)));
2566     accessClientB->doSync("refresh-none",
2567                           SyncOptions(SYNC_REFRESH_FROM_SERVER,
2568                                       CheckSyncReport(0,0,1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
2569 }
2570
2571 /**
2572  * implements testMaxMsg(), testLargeObject(), testLargeObjectEncoded()
2573  * using a sequence of items with varying sizes
2574  */
2575 void SyncTests::doVarSizes(bool withMaxMsgSize,
2576                            bool withLargeObject) {
2577     int maxMsgSize = 8 * 1024;
2578     const char* maxItemSize = getenv("CLIENT_TEST_MAX_ITEMSIZE");
2579     int tmpSize = maxItemSize ? atoi(maxItemSize) : 0;
2580     if(tmpSize > 0) 
2581         maxMsgSize = tmpSize;
2582
2583     // clean server and client A
2584     deleteAll();
2585
2586     // insert items, doubling their size, then restart with small size
2587     source_it it;
2588     for (it = sources.begin(); it != sources.end(); ++it) {
2589         int item = 1;
2590         for (int i = 0; i < 2; i++ ) {
2591             int size = 1;
2592             while (size < 2 * maxMsgSize) {
2593                 it->second->insertManyItems(it->second->createSourceA, item, 1, (int)strlen(it->second->config.templateItem) + 10 + size);
2594                 size *= 2;
2595                 item++;
2596             }
2597         }
2598     }
2599
2600     // transfer to server
2601     doSync("send",
2602            SyncOptions(SYNC_TWO_WAY,
2603                        CheckSyncReport(0,0,0, -1,0,0, true, SYNC_TWO_WAY), // number of items sent to server depends on source
2604                        withMaxMsgSize ? SyncOptions::DEFAULT_MAX_MSG_SIZE: 0,
2605                        withMaxMsgSize ? SyncOptions::DEFAULT_MAX_OBJ_SIZE : 0,
2606                        withLargeObject));
2607
2608     // copy to second client
2609     const char *value = getenv ("CLIENT_TEST_NOREFRESH");
2610     // If refresh_from_server or refresh_from_client (depending on this is a
2611     // server or client) is not supported, we can still test via slow sync.
2612     if (value) {
2613         accessClientB->refreshClient();
2614     } else {
2615         accessClientB->doSync("recv",
2616                 SyncOptions(SYNC_REFRESH_FROM_SERVER,
2617                     CheckSyncReport(-1,0,-1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER), // number of items received from server depends on source
2618                     withLargeObject ? maxMsgSize : withMaxMsgSize ? maxMsgSize * 100 /* large enough so that server can sent the largest item */ : 0,
2619                     withMaxMsgSize ? maxMsgSize * 100 : 0,
2620                     withLargeObject));
2621     }
2622     // compare
2623     compareDatabases();
2624 }
2625
2626 class TransportResendInjector : public TransportWrapper{
2627 private:
2628     int timeout;
2629 public:
2630     TransportResendInjector()
2631          :TransportWrapper() {
2632              const char *s = getenv("CLIENT_TEST_RESEND_TIMEOUT");
2633              timeout = s ? atoi(s) : 0;
2634     }
2635
2636     ~TransportResendInjector() {
2637     }
2638
2639     virtual void send(const char *data, size_t len)
2640     {
2641         m_messageCount++;
2642         if (m_interruptAtMessage >= 0 &&
2643                 m_messageCount == m_interruptAtMessage+1) {
2644             m_wrappedAgent->send(data, len);
2645             m_status = m_wrappedAgent->wait();
2646             //trigger client side resend
2647             sleep (timeout);
2648             m_status = TIME_OUT;
2649         }
2650         else 
2651         {
2652             m_wrappedAgent->send(data, len);
2653             m_status = m_wrappedAgent->wait();
2654         }
2655     }
2656
2657     virtual void getReply(const char *&data, size_t &len, std::string &contentType) {
2658         if (m_status == FAILED) {
2659             data = "";
2660             len = 0;
2661         } else {
2662             m_wrappedAgent->getReply(data, len, contentType);
2663         }
2664     }
2665 };
2666
2667 class TransportFaultInjector : public TransportWrapper{
2668 public:
2669     TransportFaultInjector()
2670          :TransportWrapper() {
2671     }
2672
2673     ~TransportFaultInjector() {
2674     }
2675
2676     virtual void send(const char *data, size_t len)
2677     {
2678         if (m_interruptAtMessage == m_messageCount) {
2679             SE_LOG_DEBUG(NULL, NULL, "TransportFaultInjector: interrupt before sending message #%d", m_messageCount);
2680         }
2681         m_messageCount++;
2682         if (m_interruptAtMessage >= 0 &&
2683             m_messageCount > m_interruptAtMessage) {
2684             throw string("TransportFaultInjector: interrupt before send");
2685         }
2686     
2687         m_wrappedAgent->send(data, len);
2688
2689         m_status = m_wrappedAgent->wait();
2690         
2691         if (m_interruptAtMessage == m_messageCount) {
2692             SE_LOG_DEBUG(NULL, NULL, "TransportFaultInjector: interrupt after receiving reply #%d", m_messageCount);
2693         }
2694         m_messageCount++;
2695         if (m_interruptAtMessage >= 0 &&
2696             m_messageCount > m_interruptAtMessage) {
2697             m_status = FAILED;
2698         }
2699     }
2700
2701     virtual void getReply(const char *&data, size_t &len, std::string &contentType) {
2702         if (m_status == FAILED) {
2703             data = "";
2704             len = 0;
2705         } else {
2706             m_wrappedAgent->getReply(data, len, contentType);
2707         }
2708     }
2709 };
2710
2711 /**
2712  * Emulates a user suspend just after receving response 
2713  * from server.
2714  */
2715 class UserSuspendInjector : public TransportWrapper{
2716 public:
2717     UserSuspendInjector()
2718          :TransportWrapper() {
2719     }
2720
2721     ~UserSuspendInjector() {
2722     }
2723
2724     virtual void send(const char *data, size_t len)
2725     {
2726         m_wrappedAgent->send(data, len);
2727         m_status = m_wrappedAgent->wait();
2728     }
2729
2730     virtual void getReply(const char *&data, size_t &len, std::string &contentType) {
2731         if (m_status == FAILED) {
2732             data = "";
2733             len = 0;
2734         } else {
2735             if (m_interruptAtMessage == m_messageCount) {
2736                  SE_LOG_DEBUG(NULL, NULL, "UserSuspendInjector: user suspend after getting reply #%d", m_messageCount);
2737             }
2738             m_messageCount++;
2739             if (m_interruptAtMessage >= 0 &&
2740                     m_messageCount > m_interruptAtMessage) {
2741                 m_options->m_isSuspended = true;
2742             }
2743             m_wrappedAgent->getReply(data, len, contentType);
2744         }
2745     }
2746 };
2747
2748 /**
2749  * This function covers different error scenarios that can occur
2750  * during real synchronization. To pass, clients must either force a
2751  * slow synchronization after a failed synchronization or implement
2752  * the error handling described in the design guide (track server's
2753  * status for added/updated/deleted items and resend unacknowledged
2754  * changes).
2755  *
2756  * The items used during these tests are synthetic. They are
2757  * constructed so that normally a server should be able to handle
2758  * twinning during a slow sync correctly.
2759  *
2760  * Errors are injected into a synchronization by wrapping the normal
2761  * HTTP transport agent. The wrapper enumerates messages sent between
2762  * client and server (i.e., one message exchange increments the
2763  * counter by two), starting from zero. It "cuts" the connection before
2764  * sending out the next message to the server respectively after the 
2765  * server has replied, but before returning the reply to the client.
2766  * The first case simulates a lost message from the client to the server
2767  * and the second case a lost message from the server to the client.
2768  *
2769  * The expected result is the same as in an uninterrupted sync, which
2770  * is done once at the beginning.
2771  *
2772  * Each test goes through the following steps:
2773  * - client A and B reset local data store
2774  * - client A creates 3 new items, remembers LUIDs
2775  * - refresh-from-client A sync
2776  * - refresh-from-client B sync
2777  * - client B creates 3 different items, remembers LUIDs
2778  * - client B syncs
2779  * - client A syncs => A, B, server are in sync
2780  * - client A modifies his items (depends on test) and
2781  *   sends changes to server => server has changes for B
2782  * - client B modifies his items (depends on test)
2783  * - client B syncs, transport wrapper simulates lost message n
2784  * - client B syncs again, resuming synchronization if possible or
2785  *   slow sync otherwise (responsibility of the client!)
2786  * - client A syncs (not tested yet: A should be sent exactly the changes made by B)
2787  * - test that A and B contain same items
2788  * - test that A contains the same items as the uninterrupted reference run
2789  * - repeat the steps above ranging starting with lost message 0 until no
2790  *   message got lost
2791  *
2792  * Set the CLIENT_TEST_INTERRUPT_AT env variable to a message number
2793  * >= 0 to execute one uninterrupted run and then interrupt at that
2794  * message.
2795  */
2796 void SyncTests::doInterruptResume(int changes, 
2797                   boost::shared_ptr<TransportWrapper> wrapper)
2798 {
2799     int interruptAtMessage = -1;
2800     const char *t = getenv("CLIENT_TEST_INTERRUPT_AT");
2801     int requestedInterruptAt = t ? atoi(t) : -1;
2802     const char *s = getenv("CLIENT_TEST_INTERRUPT_SLEEP");
2803     int sleep_t = s ? atoi(s) : 0;
2804     size_t i;
2805     std::string refFileBase = getCurrentTest() + ".ref.";
2806     bool equal = true;
2807
2808     while (true) {
2809         char buffer[80];
2810         sprintf(buffer, "%d", interruptAtMessage);
2811         const char *prefix = interruptAtMessage == -1 ? "complete" : buffer;
2812         SyncPrefix prefixA(prefix, *this);
2813         SyncPrefix prefixB(prefix, *accessClientB);
2814
2815         std::vector< std::list<std::string> > clientAluids;
2816         std::vector< std::list<std::string> > clientBluids;
2817
2818         // create new items in client A and sync to server
2819         clientAluids.resize(sources.size());
2820         for (i = 0; i < sources.size(); i++) {
2821             sources[i].second->deleteAll(sources[i].second->createSourceA);
2822             clientAluids[i] =
2823                 sources[i].second->insertManyItems(sources[i].second->createSourceA,
2824                                                    1, 3, 0);
2825         }
2826         doSync("fromA", SyncOptions(SYNC_REFRESH_FROM_CLIENT));
2827
2828         // init client B and add its items to server and client A
2829         accessClientB->doSync("initB", SyncOptions(SYNC_REFRESH_FROM_SERVER));
2830         clientBluids.resize(sources.size());
2831         for (i = 0; i < sources.size(); i++) {
2832             clientBluids[i] =
2833                 accessClientB->sources[i].second->insertManyItems(accessClientB->sources[i].second->createSourceA,
2834                                                                   11, 3, 0);
2835         }
2836         accessClientB->doSync("fromB", SyncOptions(SYNC_TWO_WAY));
2837         doSync("updateA", SyncOptions(SYNC_TWO_WAY));
2838
2839         // => client A, B and server in sync with a total of six items
2840
2841         // make changes as requested on client A and sync to server
2842         for (i = 0; i < sources.size(); i++) {
2843             if (changes & SERVER_ADD) {
2844                 sources[i].second->insertManyItems(sources[i].second->createSourceA,
2845                                                    4, 1, 0);
2846             }
2847             if (changes & SERVER_REMOVE) {
2848                 // remove second item
2849                 removeItem(sources[i].second->createSourceA,
2850                            *(++clientAluids[i].begin()));
2851             }
2852             if (changes & SERVER_UPDATE) {
2853                 // update third item
2854                 updateItem(sources[i].second->createSourceA,
2855                            *(++ ++clientAluids[i].begin()),
2856                            sources[i].second->createItem(3, "updated", 0).c_str());
2857                                               
2858             }
2859         }
2860         if (changes & (SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE)) {
2861             doSync("changesFromA", SyncOptions(SYNC_TWO_WAY));
2862         }
2863
2864         // make changes as requested on client B
2865         for (i = 0; i < sources.size(); i++) {
2866             if (changes & CLIENT_ADD) {
2867                 accessClientB->sources[i].second->insertManyItems(accessClientB->sources[i].second->createSourceA,
2868                                                                   14, 1, 0);
2869             }
2870             if (changes & CLIENT_REMOVE) {
2871                 // remove second item
2872                 removeItem(accessClientB->sources[i].second->createSourceA,
2873                            *(++clientBluids[i].begin()));
2874             }
2875             if (changes & CLIENT_UPDATE) {
2876                 // update third item
2877                 updateItem(accessClientB->sources[i].second->createSourceA,
2878                            *(++ ++clientBluids[i].begin()),
2879                            accessClientB->sources[i].second->createItem(13, "updated", 0).c_str());
2880             }
2881         }
2882
2883         // Now do an interrupted sync between B and server.
2884         // The explicit delete of the TransportAgent is suppressed
2885         // by overloading the delete operator.
2886         int wasInterrupted;
2887         {
2888             wrapper->setInterruptAtMessage(interruptAtMessage);
2889             accessClientB->doSync("changesFromB",
2890                                   SyncOptions(SYNC_TWO_WAY,
2891                                               CheckSyncReport(-1, -1, -1, -1,
2892                                                   -1, -1, false)).setTransportAgent(wrapper));
2893             wasInterrupted = interruptAtMessage != -1 &&
2894                 wrapper->getMessageCount() <= interruptAtMessage;
2895             wrapper->rewind();
2896         }
2897
2898         if (interruptAtMessage != -1) {
2899             if (wasInterrupted) {
2900                 // uninterrupted sync, done
2901                 break;
2902             }
2903
2904             // continue, wait until server timeout
2905             if(sleep_t) 
2906                 sleep (sleep_t);
2907
2908             // no need for resend tests 
2909             if (!dynamic_cast <TransportResendInjector *> (wrapper.get())) {
2910                 accessClientB->doSync("retryB", SyncOptions(SYNC_TWO_WAY));
2911             }
2912         }
2913
2914         // copy changes to client A
2915         doSync("toA", SyncOptions(SYNC_TWO_WAY));
2916
2917         // compare client A and B
2918         if (interruptAtMessage != -1 &&
2919             !compareDatabases(refFileBase.c_str(), false)) {
2920             equal = false;
2921             std::cout << "====> comparison of client B against reference file(s) failed after interrupting at message #" <<
2922                 interruptAtMessage << std::endl;
2923             std::cout.flush();
2924         }
2925         if (!compareDatabases(NULL, false)) {
2926             equal = false;
2927             std::cout << "====> comparison of client A and B failed after interrupting at message #" <<
2928                 interruptAtMessage << std::endl;
2929             std::cout.flush();
2930         }
2931
2932         // save reference files from uninterrupted run?
2933         if (interruptAtMessage == -1) {
2934             for (source_it it = sources.begin();
2935                  it != sources.end();
2936                  ++it) {
2937                 std::string refFile = refFileBase;
2938                 refFile += it->second->config.sourceName;
2939                 refFile += ".dat";
2940                 simplifyFilename(refFile);
2941                 TestingSyncSourcePtr source;
2942                 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
2943                 SOURCE_ASSERT_EQUAL(source.get(), 0, it->second->config.dump(client, *source.get(), refFile.c_str()));
2944                 CPPUNIT_ASSERT_NO_THROW(source.reset());
2945             }
2946         }
2947
2948         // pick next iterration
2949         if (requestedInterruptAt != -1) {
2950             // only do one interrupted run of the test
2951             if (requestedInterruptAt == interruptAtMessage) {
2952                 break;
2953             } else {
2954                 interruptAtMessage = requestedInterruptAt;
2955             }
2956         } else {
2957             // interrupt one message later than before
2958             interruptAtMessage++;
2959         }
2960     }
2961
2962     CPPUNIT_ASSERT(equal);
2963 }
2964
2965 void SyncTests::testInterruptResumeClientAdd()
2966 {
2967     doInterruptResume(CLIENT_ADD, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
2968 }
2969
2970 void SyncTests::testInterruptResumeClientRemove()
2971 {
2972     doInterruptResume(CLIENT_REMOVE, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
2973 }
2974
2975 void SyncTests::testInterruptResumeClientUpdate()
2976 {
2977     doInterruptResume(CLIENT_UPDATE, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
2978 }
2979
2980 void SyncTests::testInterruptResumeServerAdd()
2981 {
2982     doInterruptResume(SERVER_ADD, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
2983 }
2984
2985 void SyncTests::testInterruptResumeServerRemove()
2986 {
2987     doInterruptResume(SERVER_REMOVE, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
2988 }
2989
2990 void SyncTests::testInterruptResumeServerUpdate()
2991 {
2992     doInterruptResume(SERVER_UPDATE, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
2993 }
2994
2995 void SyncTests::testInterruptResumeFull()
2996 {
2997     doInterruptResume(CLIENT_ADD|CLIENT_REMOVE|CLIENT_UPDATE|
2998                       SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE, boost::shared_ptr<TransportWrapper> (new TransportFaultInjector()));
2999 }
3000
3001 void SyncTests::testUserSuspendClientAdd()
3002 {
3003     doInterruptResume(CLIENT_ADD, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3004 }
3005
3006 void SyncTests::testUserSuspendClientRemove()
3007 {
3008     doInterruptResume(CLIENT_REMOVE, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3009 }
3010
3011 void SyncTests::testUserSuspendClientUpdate()
3012 {
3013     doInterruptResume(CLIENT_UPDATE, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3014 }
3015
3016 void SyncTests::testUserSuspendServerAdd()
3017 {
3018     doInterruptResume(SERVER_ADD, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3019 }
3020
3021 void SyncTests::testUserSuspendServerRemove()
3022 {
3023     doInterruptResume(SERVER_REMOVE, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3024 }
3025
3026 void SyncTests::testUserSuspendServerUpdate()
3027 {
3028     doInterruptResume(SERVER_UPDATE, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3029 }
3030
3031 void SyncTests::testUserSuspendFull()
3032 {
3033     doInterruptResume(CLIENT_ADD|CLIENT_REMOVE|CLIENT_UPDATE|
3034                       SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE, boost::shared_ptr<TransportWrapper> (new UserSuspendInjector()));
3035 }
3036
3037 void SyncTests::testResendClientAdd()
3038 {
3039     doInterruptResume(CLIENT_ADD, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3040 }
3041
3042 void SyncTests::testResendClientRemove()
3043 {
3044     doInterruptResume(CLIENT_REMOVE, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3045 }
3046
3047 void SyncTests::testResendClientUpdate()
3048 {
3049     doInterruptResume(CLIENT_UPDATE, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3050 }
3051
3052 void SyncTests::testResendServerAdd()
3053 {
3054     doInterruptResume(SERVER_ADD, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3055 }
3056
3057 void SyncTests::testResendServerRemove()
3058 {
3059     doInterruptResume(SERVER_REMOVE, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3060 }
3061
3062 void SyncTests::testResendServerUpdate()
3063 {
3064     doInterruptResume(SERVER_UPDATE, boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3065 }
3066
3067 void SyncTests::testResendFull()
3068 {
3069     doInterruptResume(CLIENT_ADD|CLIENT_REMOVE|CLIENT_UPDATE|
3070                       SERVER_ADD|SERVER_REMOVE|SERVER_UPDATE, 
3071                       boost::shared_ptr<TransportWrapper> (new TransportResendInjector()));
3072 }
3073
3074 void SyncTests::doSync(const SyncOptions &options)
3075 {
3076     int res = 0;
3077     static int syncCounter = 0;
3078     static std::string lastTest;
3079     std::stringstream logstream;
3080
3081     // reset counter when switching tests
3082     if (lastTest != getCurrentTest()) {
3083         syncCounter = 0;
3084         lastTest = getCurrentTest();
3085     }
3086
3087     std::string prefix;
3088     prefix.reserve(80);
3089     for (std::list<std::string>::iterator it = logPrefixes.begin();
3090          it != logPrefixes.end();
3091          ++it) {
3092         prefix += ".";
3093         prefix += *it;
3094     }
3095     if (!prefix.empty()) {
3096         printf(" %s", prefix.c_str() + 1);
3097         fflush(stdout);
3098     }
3099
3100     logstream /* << std::setw(4) << std::setfill('0') << syncCounter << "_" */ << getCurrentTest()
3101                  << prefix
3102                  << ".client." << (accessClientB ? "A" : "B");
3103     std::string logname = logstream.str();
3104     simplifyFilename(logname);
3105     syncCounter++;
3106
3107     SE_LOG_DEBUG(NULL, NULL, "%d. starting %s with sync mode %s",
3108                  syncCounter, logname.c_str(), PrettyPrintSyncMode(options.m_syncMode).c_str());
3109
3110     try {
3111         res = client.doSync(sourceArray,
3112                             logname,
3113                             options);
3114
3115         client.postSync(res, logname);
3116     } catch (CppUnit::Exception &ex) {
3117         res = 1;
3118         client.postSync(res, logname);
3119
3120         // report the original exception without altering the source line
3121         throw;
3122     } catch (...) {
3123         res = 1;
3124         client.postSync(res, logname);
3125
3126         // this logs the original exception using CPPUnit mechanisms,
3127         // with current line as source
3128         CPPUNIT_ASSERT_NO_THROW(throw);
3129     }
3130 }
3131
3132
3133 /** generates tests on demand based on what the client supports */
3134 class ClientTestFactory : public CppUnit::TestFactory {
3135 public:
3136     ClientTestFactory(ClientTest &c) :
3137         client(c) {}
3138
3139     virtual CppUnit::Test *makeTest() {
3140         int source;
3141         CppUnit::TestSuite *alltests = new CppUnit::TestSuite("Client");
3142         CppUnit::TestSuite *tests;
3143
3144         // create local source tests
3145         tests = new CppUnit::TestSuite(alltests->getName() + "::Source");
3146         for (source=0; source < client.getNumLocalSources(); source++) {
3147             ClientTest::Config config;
3148             client.getLocalSourceConfig(source, config);
3149             if (config.sourceName) {
3150                 LocalTests *sourcetests =
3151                     client.createLocalTests(tests->getName() + "::" + config.sourceName, source, config);
3152                 sourcetests->addTests();
3153                 tests->addTest(FilterTest(sourcetests));
3154             }
3155         }
3156         alltests->addTest(FilterTest(tests));
3157         tests = 0;
3158
3159         // create sync tests with just one source
3160         tests = new CppUnit::TestSuite(alltests->getName() + "::Sync");
3161         for (source=0; source < client.getNumSyncSources(); source++) {
3162             ClientTest::Config config;
3163             client.getSyncSourceConfig(source, config);
3164             if (config.sourceName) {
3165                 std::vector<int> sources;
3166                 sources.push_back(source);
3167                 SyncTests *synctests =
3168                     client.createSyncTests(tests->getName() + "::" + config.sourceName, sources);
3169                 synctests->addTests();
3170                 tests->addTest(FilterTest(synctests));
3171             }
3172         }
3173
3174         // create sync tests with all sources enabled, unless we only have one:
3175         // that would be identical to the test above
3176         std::vector<int> sources;
3177         std::string name, name_reversed;
3178         for (source=0; source < client.getNumSyncSources(); source++) {
3179             ClientTest::Config config;
3180             client.getSyncSourceConfig(source, config);
3181             if (config.sourceName) {
3182                 sources.push_back(source);
3183                 if (name.size() > 0) {
3184                     name += "_";
3185                     name_reversed = std::string("_") + name_reversed;
3186                 }
3187                 name += config.sourceName;
3188                 name_reversed = config.sourceName + name_reversed;
3189             }
3190         }
3191         if (sources.size() > 1) {
3192             SyncTests *synctests =
3193                 client.createSyncTests(tests->getName() + "::" + name, sources);
3194             synctests->addTests();
3195             tests->addTest(FilterTest(synctests));
3196             synctests = 0;
3197
3198             // now also in reversed order - who knows, it might make a difference
3199             std::reverse(sources.begin(), sources.end());
3200             synctests =
3201                 client.createSyncTests(tests->getName() + "::" + name_reversed, sources);
3202             synctests->addTests();
3203             tests->addTest(FilterTest(synctests));
3204             synctests = 0;
3205         }
3206
3207         alltests->addTest(FilterTest(tests));
3208         tests = 0;
3209
3210         return alltests;
3211     }
3212
3213 private:
3214     ClientTest &client;
3215 };
3216
3217 void ClientTest::registerTests()
3218 {
3219     factory = (void *)new ClientTestFactory(*this);
3220     CppUnit::TestFactoryRegistry::getRegistry().registerFactory((CppUnit::TestFactory *)factory);
3221 }
3222
3223 ClientTest::ClientTest(int serverSleepSec, const std::string &serverLog) :
3224     serverSleepSeconds(serverSleepSec),
3225     serverLogFileName(serverLog),
3226     factory(NULL)
3227 {
3228 }
3229
3230 ClientTest::~ClientTest()
3231 {
3232     if(factory) {
3233         CppUnit::TestFactoryRegistry::getRegistry().unregisterFactory((CppUnit::TestFactory *)factory);
3234         delete (CppUnit::TestFactory *)factory;
3235         factory = 0;
3236     }
3237 }
3238
3239 LocalTests *ClientTest::createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co)
3240 {
3241     return new LocalTests(name, *this, sourceParam, co);
3242 }
3243
3244 SyncTests *ClientTest::createSyncTests(const std::string &name, std::vector<int> sourceIndices, bool isClientA)
3245 {
3246     return new SyncTests(name, *this, sourceIndices, isClientA);
3247 }
3248
3249 int ClientTest::dump(ClientTest &client, TestingSyncSource &source, const char *file)
3250 {
3251     BackupReport report;
3252     boost::shared_ptr<ConfigNode> node(new VolatileConfigNode);
3253
3254     rm_r(file);
3255     mkdir_p(file);
3256     CPPUNIT_ASSERT(source.getOperations().m_backupData);
3257     source.getOperations().m_backupData(SyncSource::Operations::ConstBackupInfo(),
3258                                         SyncSource::Operations::BackupInfo(SyncSource::Operations::BackupInfo::BACKUP_OTHER, file, node),
3259                                         report);
3260     return 0;
3261 }
3262
3263 void ClientTest::getItems(const char *file, list<string> &items, std::string &testcases)
3264 {
3265     items.clear();
3266
3267     // import the file
3268     std::ifstream input;
3269     string server = getenv("CLIENT_TEST_SERVER");
3270     testcases = string(file) + '.' + server +".tem";
3271     input.open(testcases.c_str());
3272
3273     if(input.fail()) {
3274         testcases = file;
3275         input.open(testcases.c_str());
3276     }
3277     CPPUNIT_ASSERT(!input.bad());
3278     CPPUNIT_ASSERT(input.is_open());
3279     std::string data, line;
3280     while (input) {
3281         bool wasend = false;
3282         do {
3283             getline(input, line);
3284             CPPUNIT_ASSERT(!input.bad());
3285             // empty lines directly after line which starts with END mark end of record;
3286             // check for END necessary becayse vCard 2.1 ENCODING=BASE64 may have empty lines in body of VCARD!
3287             if ((line != "\r" && line.size() > 0) || !wasend) {
3288                 data += line;
3289                 data += "\n";
3290             } else {
3291                 if (!data.empty()) {
3292                     items.push_back(data);
3293                 }
3294                 data = "";
3295             }
3296             wasend = !line.compare(0, 4, "END:");
3297         } while(!input.eof());
3298     }
3299     if (!data.empty()) {
3300         items.push_back(data);
3301     }
3302 }
3303
3304 int ClientTest::import(ClientTest &client, TestingSyncSource &source, const char *file, std::string &realfile)
3305 {
3306     list<string> items;
3307     getItems(file, items, realfile);
3308     BOOST_FOREACH(string &data, items) {
3309         importItem(&source, data);
3310     }
3311     return 0;
3312 }
3313
3314 bool ClientTest::compare(ClientTest &client, const char *fileA, const char *fileB)
3315 {
3316     std::string cmdstr = std::string("env PATH=.:$PATH synccompare ") + fileA + " " + fileB;
3317     setenv("CLIENT_TEST_HEADER", "\n\n", 1);
3318     setenv("CLIENT_TEST_LEFT_NAME", fileA, 1);
3319     setenv("CLIENT_TEST_RIGHT_NAME", fileB, 1);
3320     setenv("CLIENT_TEST_REMOVED", "only in left file", 1);
3321     setenv("CLIENT_TEST_ADDED", "only in right file", 1);
3322     const char* compareLog = getenv("CLIENT_TEST_COMPARE_LOG");
3323     if(compareLog && strlen(compareLog))
3324     {
3325        string tmpfile = "____compare.log";
3326        cmdstr =string("bash -c 'set -o pipefail;") + cmdstr;
3327        cmdstr += " 2>&1|tee " +tmpfile+"'";
3328     }
3329     bool success = system(cmdstr.c_str()) == 0;
3330     if (!success) {
3331         printf("failed: env CLIENT_TEST_SERVER=%s PATH=.:$PATH synccompare %s %s\n",
3332                getenv("CLIENT_TEST_SERVER") ? getenv("CLIENT_TEST_SERVER") : "",
3333                fileA, fileB);
3334     }
3335     return success;
3336 }
3337
3338 void ClientTest::postSync(int res, const std::string &logname)
3339 {
3340 #ifdef WIN32
3341     Sleep(serverSleepSeconds * 1000);
3342 #else
3343     sleep(serverSleepSeconds);
3344
3345     // make a copy of the server's log (if found), then truncate it
3346     if (serverLogFileName.size()) {
3347         int fd = open(serverLogFileName.c_str(), O_RDWR);
3348
3349         if (fd >= 0) {
3350             std::string cmd = std::string("cp ") + serverLogFileName + " " + logname + ".server.log";
3351             if (system(cmd.c_str())) {
3352                 fprintf(stderr, "copying log file failed: %s\n", cmd.c_str());
3353             }
3354             if (ftruncate(fd, 0)) {
3355                 perror("truncating log file");
3356             }
3357         } else {
3358             perror(serverLogFileName.c_str());
3359         }
3360     }
3361 #endif
3362 }
3363
3364 void ClientTest::getTestData(const char *type, Config &config)
3365 {
3366     memset(&config, 0, sizeof(config));
3367     char *numitems = getenv("CLIENT_TEST_NUM_ITEMS");
3368     config.numItems = numitems ? atoi(numitems) : 100;
3369     char *env = getenv("CLIENT_TEST_RETRY");
3370     config.retrySync = (env && !strcmp (env, "t")) ?true :false;
3371     env = getenv("CLIENT_TEST_RESEND");
3372     config.resendSync = (env && !strcmp (env, "t")) ?true :false;
3373     env = getenv("CLIENT_TEST_SUSPEND");
3374     config.suspendSync = (env && !strcmp (env, "t")) ?true :false;
3375     config.sourceKnowsItemSemantic = true;
3376     config.itemType = "";
3377     config.import = import;
3378     config.dump = dump;
3379     config.compare = compare;
3380
3381     // redirect requests for "ical20" towards "ical20_noutc"?
3382     bool noutc = false;
3383     env = getenv ("CLIENT_TEST_NOUTC");
3384     if (env && !strcmp (env, "t")) {
3385         noutc = true;
3386     }
3387
3388     if (!strcmp(type, "vcard30")) {
3389         config.sourceName = "vcard30";
3390         config.sourceNameServerTemplate = "addressbook";
3391         config.uri = "card3"; // ScheduleWorld
3392         config.type = "text/vcard";
3393         config.insertItem =
3394             "BEGIN:VCARD\n"
3395             "VERSION:3.0\n"
3396             "TITLE:tester\n"
3397             "FN:John Doe\n"
3398             "N:Doe;John;;;\n"
3399             "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3400             "X-EVOLUTION-FILE-AS:Doe\\, John\n"
3401             "X-MOZILLA-HTML:FALSE\n"
3402             "NOTE:<<REVISION>>\n"
3403             "END:VCARD\n";
3404         config.updateItem =
3405             "BEGIN:VCARD\n"
3406             "VERSION:3.0\n"
3407             "TITLE:tester\n"
3408             "FN:Joan Doe\n"
3409             "N:Doe;Joan;;;\n"
3410             "X-EVOLUTION-FILE-AS:Doe\\, Joan\n"
3411             "TEL;TYPE=WORK;TYPE=VOICE:business 2\n"
3412             "BDAY:2006-01-08\n"
3413             "X-MOZILLA-HTML:TRUE\n"
3414             "END:VCARD\n";
3415         /* adds a second phone number: */
3416         config.complexUpdateItem =
3417             "BEGIN:VCARD\n"
3418             "VERSION:3.0\n"
3419             "TITLE:tester\n"
3420             "FN:Joan Doe\n"
3421             "N:Doe;Joan;;;\n"
3422             "X-EVOLUTION-FILE-AS:Doe\\, Joan\n"
3423             "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3424             "TEL;TYPE=HOME;TYPE=VOICE:home 2\n"
3425             "BDAY:2006-01-08\n"
3426             "X-MOZILLA-HTML:TRUE\n"
3427             "END:VCARD\n";
3428         /* add a telephone number, email and X-AIM to initial item */
3429         config.mergeItem1 =
3430             "BEGIN:VCARD\n"
3431             "VERSION:3.0\n"
3432             "TITLE:tester\n"
3433             "FN:John Doe\n"
3434             "N:Doe;John;;;\n"
3435             "X-EVOLUTION-FILE-AS:Doe\\, John\n"
3436             "X-MOZILLA-HTML:FALSE\n"
3437             "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3438             "EMAIL:john.doe@work.com\n"
3439             "X-AIM:AIM JOHN\n"
3440             "END:VCARD\n";
3441         config.mergeItem2 =
3442             "BEGIN:VCARD\n"
3443             "VERSION:3.0\n"
3444             "TITLE:developer\n"
3445             "FN:John Doe\n"
3446             "N:Doe;John;;;\n"
3447             "TEL;TYPE=WORK;TYPE=VOICE:123456\n"
3448             "X-EVOLUTION-FILE-AS:Doe\\, John\n"
3449             "X-MOZILLA-HTML:TRUE\n"
3450             "BDAY:2006-01-08\n"
3451             "END:VCARD\n";
3452         config.templateItem = config.insertItem;
3453         config.uniqueProperties = "FN:N:X-EVOLUTION-FILE-AS";
3454         config.sizeProperty = "NOTE";
3455         config.testcases = "testcases/vcard30.vcf";
3456     } else if (!strcmp(type, "vcard21")) {
3457         config.sourceName = "vcard21";
3458         config.sourceNameServerTemplate = "addressbook";
3459         config.uri = "card"; // Funambol
3460         config.type = "text/x-vcard";
3461         config.insertItem =
3462             "BEGIN:VCARD\n"
3463             "VERSION:2.1\n"
3464             "TITLE:tester\n"
3465             "FN:John Doe\n"
3466             "N:Doe;John;;;\n"
3467             "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3468             "X-MOZILLA-HTML:FALSE\n"
3469             "NOTE:<<REVISION>>\n"
3470             "END:VCARD\n";
3471         config.updateItem =
3472             "BEGIN:VCARD\n"
3473             "VERSION:2.1\n"
3474             "TITLE:tester\n"
3475             "FN:Joan Doe\n"
3476             "N:Doe;Joan;;;\n"
3477             "TEL;TYPE=WORK;TYPE=VOICE:business 2\n"
3478             "BDAY:2006-01-08\n"
3479             "X-MOZILLA-HTML:TRUE\n"
3480             "END:VCARD\n";
3481         /* adds a second phone number: */
3482         config.complexUpdateItem =
3483             "BEGIN:VCARD\n"
3484             "VERSION:2.1\n"
3485             "TITLE:tester\n"
3486             "FN:Joan Doe\n"
3487             "N:Doe;Joan;;;\n"
3488             "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3489             "TEL;TYPE=HOME;TYPE=VOICE:home 2\n"
3490             "BDAY:2006-01-08\n"
3491             "X-MOZILLA-HTML:TRUE\n"
3492             "END:VCARD\n";
3493         /* add email and X-AIM to initial item */
3494         config.mergeItem1 =
3495             "BEGIN:VCARD\n"
3496             "VERSION:2.1\n"
3497             "TITLE:tester\n"
3498             "FN:John Doe\n"
3499             "N:Doe;John;;;\n"
3500             "X-MOZILLA-HTML:FALSE\n"
3501             "TEL;TYPE=WORK;TYPE=VOICE:business 1\n"
3502             "EMAIL:john.doe@work.com\n"
3503             "X-AIM:AIM JOHN\n"
3504             "END:VCARD\n";
3505         /* change X-MOZILLA-HTML */
3506         config.mergeItem2 =
3507             "BEGIN:VCARD\n"
3508             "VERSION:2.1\n"
3509             "TITLE:developer\n"
3510             "FN:John Doe\n"
3511             "N:Doe;John;;;\n"
3512             "X-MOZILLA-HTML:TRUE\n"
3513             "BDAY:2006-01-08\n"
3514             "END:VCARD\n";
3515         config.templateItem = config.insertItem;
3516         config.uniqueProperties = "FN:N";
3517         config.sizeProperty = "NOTE";
3518         config.testcases = "testcases/vcard21.vcf";
3519     } else if (!strcmp(type, "ical20") && !noutc) {
3520         config.sourceName = "ical20";
3521         config.sourceNameServerTemplate = "calendar";
3522         config.uri = "cal2"; // ScheduleWorld
3523         config.type = "text/x-vcalendar";
3524         config.insertItem =
3525             "BEGIN:VCALENDAR\n"
3526             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3527             "VERSION:2.0\n"
3528             "METHOD:PUBLISH\n"
3529             "BEGIN:VEVENT\n"
3530             "SUMMARY:phone meeting\n"
3531             "DTEND:20060406T163000Z\n"
3532             "DTSTART:20060406T160000Z\n"
3533             "UID:1234567890!@#$%^&*()<>@dummy\n"
3534             "DTSTAMP:20060406T211449Z\n"
3535             "LAST-MODIFIED:20060409T213201\n"
3536             "CREATED:20060409T213201\n"
3537             "LOCATION:my office\n"
3538             "DESCRIPTION:let's talk<<REVISION>>\n"
3539             "CLASS:PUBLIC\n"
3540             "TRANSP:OPAQUE\n"
3541             "SEQUENCE:1\n"
3542             "END:VEVENT\n"
3543             "END:VCALENDAR\n";
3544         config.updateItem =
3545             "BEGIN:VCALENDAR\n"
3546             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3547             "VERSION:2.0\n"
3548             "METHOD:PUBLISH\n"
3549             "BEGIN:VEVENT\n"
3550             "SUMMARY:meeting on site\n"
3551             "DTEND:20060406T163000Z\n"
3552             "DTSTART:20060406T160000Z\n"
3553             "UID:1234567890!@#$%^&*()<>@dummy\n"
3554             "DTSTAMP:20060406T211449Z\n"
3555             "LAST-MODIFIED:20060409T213201\n"
3556             "CREATED:20060409T213201\n"
3557             "LOCATION:big meeting room\n"
3558             "DESCRIPTION:nice to see you\n"
3559             "CLASS:PUBLIC\n"
3560             "TRANSP:OPAQUE\n"
3561             "SEQUENCE:1\n"
3562             "END:VEVENT\n"
3563             "END:VCALENDAR\n";
3564         /* change location and description of insertItem in testMerge(), add alarm */
3565         config.mergeItem1 =
3566             "BEGIN:VCALENDAR\n"
3567             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3568             "VERSION:2.0\n"
3569             "METHOD:PUBLISH\n"
3570             "BEGIN:VEVENT\n"
3571             "SUMMARY:phone meeting\n"
3572             "DTEND:20060406T163000Z\n"
3573             "DTSTART:20060406T160000Z\n"
3574             "UID:1234567890!@#$%^&*()<>@dummy\n"
3575             "DTSTAMP:20060406T211449Z\n"
3576             "LAST-MODIFIED:20060409T213201\n"
3577             "CREATED:20060409T213201\n"
3578             "LOCATION:calling from home\n"
3579             "DESCRIPTION:let's talk\n"
3580             "CLASS:PUBLIC\n"
3581             "TRANSP:OPAQUE\n"
3582             "SEQUENCE:1\n"
3583             "BEGIN:VALARM\n"
3584             "DESCRIPTION:alarm\n"
3585             "ACTION:DISPLAY\n"
3586             "TRIGGER;VALUE=DURATION;RELATED=START:-PT15M\n"
3587             "END:VALARM\n"
3588             "END:VEVENT\n"
3589             "END:VCALENDAR\n";
3590         /* change location to something else, add category */
3591         config.mergeItem2 =
3592             "BEGIN:VCALENDAR\n"
3593             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3594             "VERSION:2.0\n"
3595             "METHOD:PUBLISH\n"
3596             "BEGIN:VEVENT\n"
3597             "SUMMARY:phone meeting\n"
3598             "DTEND:20060406T163000Z\n"
3599             "DTSTART:20060406T160000Z\n"
3600             "UID:1234567890!@#$%^&*()<>@dummy\n"
3601             "DTSTAMP:20060406T211449Z\n"
3602             "LAST-MODIFIED:20060409T213201\n"
3603             "CREATED:20060409T213201\n"
3604             "LOCATION:my office\n"
3605             "CATEGORIES:WORK\n"
3606             "DESCRIPTION:what the heck\\, let's even shout a bit\n"
3607             "CLASS:PUBLIC\n"
3608             "TRANSP:OPAQUE\n"
3609             "SEQUENCE:1\n"
3610             "END:VEVENT\n"
3611             "END:VCALENDAR\n";
3612         config.parentItem =
3613             "BEGIN:VCALENDAR\n"
3614             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3615             "VERSION:2.0\n"
3616             "METHOD:PUBLISH\n"
3617             "BEGIN:VEVENT\n"
3618             "UID:20080407T193125Z-19554-727-1-50@gollum\n"
3619             "DTSTAMP:20080407T193125Z\n"
3620             "DTSTART:20080406T090000Z\n"
3621             "DTEND:20080406T093000Z\n"
3622             "TRANSP:OPAQUE\n"
3623             "SEQUENCE:2\n"
3624             "SUMMARY:Recurring\n"
3625             "DESCRIPTION:recurs each Monday\\, 10 times\n"
3626             "CLASS:PUBLIC\n"
3627             "RRULE:FREQ=WEEKLY;COUNT=10;INTERVAL=1;BYDAY=SU\n"
3628             "CREATED:20080407T193241\n"
3629             "LAST-MODIFIED:20080407T193241\n"
3630             "END:VEVENT\n"
3631             "END:VCALENDAR\n";
3632         config.childItem =
3633             "BEGIN:VCALENDAR\n"
3634             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3635             "VERSION:2.0\n"
3636             "METHOD:PUBLISH\n"
3637             "BEGIN:VEVENT\n"
3638             "UID:20080407T193125Z-19554-727-1-50@gollum\n"
3639             "DTSTAMP:20080407T193125Z\n"
3640             "DTSTART:20080413T090000Z\n"
3641             "DTEND:20080413T093000Z\n"
3642             "TRANSP:OPAQUE\n"
3643             "SEQUENCE:7\n"
3644             "SUMMARY:Recurring: Modified\n"
3645             "CLASS:PUBLIC\n"
3646             "CREATED:20080407T193241\n"
3647             "LAST-MODIFIED:20080407T193647\n"
3648             "RECURRENCE-ID:20080413T090000Z\n"
3649             "DESCRIPTION:second instance modified\n"
3650             "END:VEVENT\n"
3651             "END:VCALENDAR\n";
3652         config.templateItem = config.insertItem;
3653         config.uniqueProperties = "SUMMARY:UID:LOCATION";
3654         config.sizeProperty = "DESCRIPTION";
3655         config.testcases = "testcases/ical20.ics";
3656     } if(!strcmp(type, "vcal10")) {
3657         config.sourceName = "vcal10";
3658         config.sourceNameServerTemplate = "calendar";
3659         config.uri = "cal"; // Funambol 3.0
3660         config.type = "text/x-vcalendar";
3661         config.insertItem =
3662             "BEGIN:VCALENDAR\n"
3663             "VERSION:1.0\n"
3664             "BEGIN:VEVENT\n"
3665             "SUMMARY:phone meeting\n"
3666             "DTEND:20060406T163000Z\n"
3667             "DTSTART:20060406T160000Z\n"
3668             "DTSTAMP:20060406T211449Z\n"
3669             "LOCATION:my office\n"
3670             "DESCRIPTION:let's talk<<REVISION>>\n"
3671             "END:VEVENT\n"
3672             "END:VCALENDAR\n";
3673         config.updateItem =
3674             "BEGIN:VCALENDAR\n"
3675             "VERSION:1.0\n"
3676             "BEGIN:VEVENT\n"
3677             "SUMMARY:meeting on site\n"
3678             "DTEND:20060406T163000Z\n"
3679             "DTSTART:20060406T160000Z\n"
3680             "DTSTAMP:20060406T211449Z\n"
3681             "LOCATION:big meeting room\n"
3682             "DESCRIPTION:nice to see you\n"
3683             "END:VEVENT\n"
3684             "END:VCALENDAR\n";
3685         /* change location in insertItem in testMerge() */
3686         config.mergeItem1 =
3687             "BEGIN:VCALENDAR\n"
3688             "VERSION:1.0\n"
3689             "BEGIN:VEVENT\n"
3690             "SUMMARY:phone meeting\n"
3691             "DTEND:20060406T163000Z\n"
3692             "DTSTART:20060406T160000Z\n"
3693             "DTSTAMP:20060406T211449Z\n"
3694             "LOCATION:calling from home\n"
3695             "DESCRIPTION:let's talk\n"
3696             "END:VEVENT\n"
3697             "END:VCALENDAR\n";
3698         config.mergeItem2 =
3699             "BEGIN:VCALENDAR\n"
3700             "VERSION:1.0\n"
3701             "BEGIN:VEVENT\n"
3702             "SUMMARY:phone meeting\n"
3703             "DTEND:20060406T163000Z\n"
3704             "DTSTART:20060406T160000Z\n"
3705             "DTSTAMP:20060406T211449Z\n"
3706             "LOCATION:my office\n"
3707             "DESCRIPTION:what the heck, let's even shout a bit\n"
3708             "END:VEVENT\n"
3709             "END:VCALENDAR\n";
3710         config.templateItem = config.insertItem;
3711         config.uniqueProperties = "SUMMARY:UID:LOCATION";
3712         config.sizeProperty = "DESCRIPTION";
3713         config.testcases = "testcases/vcal10.ics";
3714     } else if (!strcmp(type, "ical20_noutc") ||
3715                (!strcmp(type, "ical20") && noutc)) {
3716         config.sourceName = "ical20";
3717         config.sourceNameServerTemplate = "calendar";
3718         config.uri = "cal2"; // ScheduleWorld
3719         config.type = "text/x-vcalendar";
3720         config.insertItem =
3721             "BEGIN:VCALENDAR\n"
3722             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3723             "VERSION:2.0\n"
3724             "METHOD:PUBLISH\n"
3725             "BEGIN:VTIMEZONE\n"
3726             "TZID:Asia/Shanghai\n"
3727             "BEGIN:STANDARD\n"
3728             "DTSTART:19670101T000000\n"
3729             "TZOFFSETFROM:+0800\n"
3730             "TZOFFSETTO:+0800\n"
3731             "END:STANDARD\n"
3732             "END:VTIMEZONE\n"
3733             "BEGIN:VTIMEZONE\n"
3734             "TZID:/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai\n"
3735             "X-LIC-LOCATION:Asia/Shanghai\n"
3736             "BEGIN:STANDARD\n"
3737             "TZNAME:CST\n"
3738             "DTSTART:19700914T230000\n"
3739             "TZOFFSETFROM:+0800\n"
3740             "TZOFFSETTO:+0800\n"
3741             "END:STANDARD\n"
3742             "END:VTIMEZONE\n"
3743             "BEGIN:VEVENT\n"
3744             "SUMMARY:phone meeting\n"
3745             "DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T163000\n"
3746             "DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T160000\n"
3747             "UID:1234567890!@#$%^&*()<>@dummy\n"
3748             "DTSTAMP:20060406T211449Z\n"
3749             "LAST-MODIFIED:20060409T213201\n"
3750             "CREATED:20060409T213201\n"
3751             "LOCATION:my office\n"
3752             "DESCRIPTION:let's talk<<REVISION>>\n"
3753             "CLASS:PUBLIC\n"
3754             "TRANSP:OPAQUE\n"
3755             "SEQUENCE:1\n"
3756             "END:VEVENT\n"
3757             "END:VCALENDAR\n";
3758         config.updateItem =
3759             "BEGIN:VCALENDAR\n"
3760             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3761             "VERSION:2.0\n"
3762             "METHOD:PUBLISH\n"
3763             "BEGIN:VTIMEZONE\n"
3764             "TZID:Asia/Shanghai\n"
3765             "BEGIN:STANDARD\n"
3766             "DTSTART:19670101T000000\n"
3767             "TZOFFSETFROM:+0800\n"
3768             "TZOFFSETTO:+0800\n"
3769             "END:STANDARD\n"
3770             "END:VTIMEZONE\n"
3771             "BEGIN:VTIMEZONE\n"
3772             "TZID:/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai\n"
3773             "X-LIC-LOCATION:Asia/Shanghai\n"
3774             "BEGIN:STANDARD\n"
3775             "TZNAME:CST\n"
3776             "DTSTART:19700914T230000\n"
3777             "TZOFFSETFROM:+0800\n"
3778             "TZOFFSETTO:+0800\n"
3779             "END:STANDARD\n"
3780             "END:VTIMEZONE\n"
3781             "BEGIN:VEVENT\n"
3782             "SUMMARY:meeting on site\n"
3783             "DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T163000\n"
3784             "DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T160000\n"
3785             "UID:1234567890!@#$%^&*()<>@dummy\n"
3786             "DTSTAMP:20060406T211449Z\n"
3787             "LAST-MODIFIED:20060409T213201\n"
3788             "CREATED:20060409T213201\n"
3789             "LOCATION:big meeting room\n"
3790             "DESCRIPTION:nice to see you\n"
3791             "CLASS:PUBLIC\n"
3792             "TRANSP:OPAQUE\n"
3793             "SEQUENCE:1\n"
3794             "END:VEVENT\n"
3795             "END:VCALENDAR\n";
3796         /* change location and description of insertItem in testMerge(), add alarm */
3797         config.mergeItem1 = "";
3798         config.mergeItem2 = "";
3799         config.parentItem = "";
3800         config.childItem = "";
3801         config.templateItem = config.insertItem;
3802         config.uniqueProperties = "SUMMARY:UID:LOCATION";
3803         config.sizeProperty = "DESCRIPTION";
3804         config.testcases = "testcases/ical20.ics";
3805     } else if(!strcmp(type, "itodo20")) {
3806         config.sourceName = "itodo20";
3807         config.sourceNameServerTemplate = "todo";
3808         config.uri = "task2"; // ScheduleWorld
3809         config.type = "text/x-vcalendar";
3810         config.insertItem =
3811             "BEGIN:VCALENDAR\n"
3812             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3813             "VERSION:2.0\n"
3814             "METHOD:PUBLISH\n"
3815             "BEGIN:VTODO\n"
3816             "UID:20060417T173712Z-4360-727-1-2730@gollum\n"
3817             "DTSTAMP:20060417T173712Z\n"
3818             "SUMMARY:do me\n"
3819             "DESCRIPTION:to be done<<REVISION>>\n"
3820             "PRIORITY:0\n"
3821             "STATUS:IN-PROCESS\n"
3822             "CREATED:20060417T173712\n"
3823             "LAST-MODIFIED:20060417T173712\n"
3824             "END:VTODO\n"
3825             "END:VCALENDAR\n";
3826         config.updateItem =
3827             "BEGIN:VCALENDAR\n"
3828             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3829             "VERSION:2.0\n"
3830             "METHOD:PUBLISH\n"
3831             "BEGIN:VTODO\n"
3832             "UID:20060417T173712Z-4360-727-1-2730@gollum\n"
3833             "DTSTAMP:20060417T173712Z\n"
3834             "SUMMARY:do me ASAP\n"
3835             "DESCRIPTION:to be done\n"
3836             "PRIORITY:1\n"
3837             "STATUS:IN-PROCESS\n"
3838             "CREATED:20060417T173712\n"
3839             "LAST-MODIFIED:20060417T173712\n"
3840             "END:VTODO\n"
3841             "END:VCALENDAR\n";
3842         /* change summary in insertItem in testMerge() */
3843         config.mergeItem1 =
3844             "BEGIN:VCALENDAR\n"
3845             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3846             "VERSION:2.0\n"
3847             "METHOD:PUBLISH\n"
3848             "BEGIN:VTODO\n"
3849             "UID:20060417T173712Z-4360-727-1-2730@gollum\n"
3850             "DTSTAMP:20060417T173712Z\n"
3851             "SUMMARY:do me please\\, please\n"
3852             "DESCRIPTION:to be done\n"
3853             "PRIORITY:0\n"
3854             "STATUS:IN-PROCESS\n"
3855             "CREATED:20060417T173712\n"
3856             "LAST-MODIFIED:20060417T173712\n"
3857             "END:VTODO\n"
3858             "END:VCALENDAR\n";
3859         config.mergeItem2 =
3860             "BEGIN:VCALENDAR\n"
3861             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3862             "VERSION:2.0\n"
3863             "METHOD:PUBLISH\n"
3864             "BEGIN:VTODO\n"
3865             "UID:20060417T173712Z-4360-727-1-2730@gollum\n"
3866             "DTSTAMP:20060417T173712Z\n"
3867             "SUMMARY:do me\n"
3868             "DESCRIPTION:to be done\n"
3869             "PRIORITY:7\n"
3870             "STATUS:IN-PROCESS\n"
3871             "CREATED:20060417T173712\n"
3872             "LAST-MODIFIED:20060417T173712\n"
3873             "END:VTODO\n"
3874             "END:VCALENDAR\n";
3875         config.templateItem = config.insertItem;
3876         config.uniqueProperties = "SUMMARY:UID";
3877         config.sizeProperty = "DESCRIPTION";
3878         config.testcases = "testcases/itodo20.ics";
3879     } else if(!strcmp(type, "text")) {
3880         // The "text" test uses iCalendar 2.0 VJOURNAL
3881         // as format because synccompare doesn't handle
3882         // plain text. A backend which wants to use this
3883         // test data must support importing/exporting
3884         // the test data in that format, see EvolutionMemoSource
3885         // for an example.
3886         config.uri = "note"; // ScheduleWorld
3887         config.sourceNameServerTemplate = "memo";
3888         config.type = "memo";
3889         config.itemType = "text/calendar";
3890         config.insertItem =
3891             "BEGIN:VCALENDAR\n"
3892             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3893             "VERSION:2.0\n"
3894             "METHOD:PUBLISH\n"
3895             "BEGIN:VJOURNAL\n"
3896             "SUMMARY:Summary\n"
3897             "DESCRIPTION:Summary\\nBody text REVISION\n"
3898             "END:VJOURNAL\n"
3899             "END:VCALENDAR\n";
3900         config.updateItem =
3901             "BEGIN:VCALENDAR\n"
3902             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3903             "VERSION:2.0\n"
3904             "METHOD:PUBLISH\n"
3905             "BEGIN:VJOURNAL\n"
3906             "SUMMARY:Summary Modified\n"
3907             "DESCRIPTION:Summary Modified\\nBody text\n"
3908             "END:VJOURNAL\n"
3909             "END:VCALENDAR\n";
3910         /* change summary, as in updateItem, and the body in the other merge item */
3911         config.mergeItem1 = config.updateItem;
3912         config.mergeItem2 =
3913             "BEGIN:VCALENDAR\n"
3914             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
3915             "VERSION:2.0\n"
3916             "METHOD:PUBLISH\n"
3917             "BEGIN:VJOURNAL\n"
3918             "SUMMARY:Summary\n"
3919             "DESCRIPTION:Summary\\nBody modified\n"
3920             "END:VJOURNAL\n"
3921             "END:VCALENDAR\n";                
3922         config.templateItem = config.insertItem;
3923         config.uniqueProperties = "SUMMARY:DESCRIPTION";
3924         config.sizeProperty = "DESCRIPTION";
3925         config.testcases = "testcases/imemo20.ics";
3926     }else if (!strcmp (type, "super")) {
3927         config.subConfigs = "ical20,itodo20";
3928         config.uri="";
3929     }
3930 }
3931
3932 void CheckSyncReport::check(SyncMLStatus status, SyncReport &report) const
3933 {
3934     stringstream str;
3935
3936     str << report;
3937     str << "----------|--------CLIENT---------|--------SERVER---------|\n";
3938     str << "          |  NEW  |  MOD  |  DEL  |  NEW  |  MOD  |  DEL  |\n";
3939     str << "----------|-----------------------------------------------|\n";
3940     str << StringPrintf("Expected  |  %3d  |  %3d  |  %3d  |  %3d  |  %3d  |  %3d  |\n",
3941                         clientAdded, clientUpdated, clientDeleted,
3942                         serverAdded, serverUpdated, serverDeleted);
3943     str << "Expected sync mode: " << PrettyPrintSyncMode(syncMode) << "\n";
3944     SE_LOG_INFO(NULL, NULL, "sync report:\n%s\n", str.str().c_str());
3945
3946     if (mustSucceed) {
3947         CPPUNIT_ASSERT_EQUAL(STATUS_OK, status);
3948     }
3949
3950     // this code is intentionally duplicated to produce nicer CPPUNIT asserts
3951     BOOST_FOREACH(SyncReport::value_type &entry, report) {
3952         const std::string &name = entry.first;
3953         const SyncSourceReport &source = entry.second;
3954
3955         SE_LOG_DEBUG(NULL, NULL, "Checking sync source %s...", name.c_str());
3956         if (mustSucceed) {
3957             CLIENT_TEST_EQUAL(name, STATUS_OK, source.getStatus());
3958         }
3959         CLIENT_TEST_EQUAL(name, 0, source.getItemStat(SyncSourceReport::ITEM_LOCAL,
3960                                                       SyncSourceReport::ITEM_ANY,
3961                                                       SyncSourceReport::ITEM_REJECT));
3962         CLIENT_TEST_EQUAL(name, 0, source.getItemStat(SyncSourceReport::ITEM_REMOTE,
3963                                                       SyncSourceReport::ITEM_ANY,
3964                                                       SyncSourceReport::ITEM_REJECT));
3965
3966         const char* checkSyncModeStr = getenv("CLIENT_TEST_NOCHECK_SYNCMODE");
3967         bool checkSyncMode = true;
3968         bool checkSyncStats = getenv ("CLIENT_TEST_NOCHECK_SYNCSTATS") ? false : true;
3969         if (checkSyncModeStr && 
3970                 (!strcmp(checkSyncModeStr, "1") || !strcasecmp(checkSyncModeStr, "t"))) {
3971             checkSyncMode = false;
3972         }
3973
3974         if (syncMode != SYNC_NONE && checkSyncMode) {
3975             CLIENT_TEST_EQUAL(name, syncMode, source.getFinalSyncMode());
3976         }
3977
3978         if (clientAdded != -1 && checkSyncStats) {
3979             CLIENT_TEST_EQUAL(name, clientAdded,
3980                               source.getItemStat(SyncSourceReport::ITEM_LOCAL,
3981                                                  SyncSourceReport::ITEM_ADDED,
3982                                                  SyncSourceReport::ITEM_TOTAL));
3983         }
3984         if (clientUpdated != -1 && checkSyncStats) {
3985             CLIENT_TEST_EQUAL(name, clientUpdated,
3986                               source.getItemStat(SyncSourceReport::ITEM_LOCAL,
3987                                                  SyncSourceReport::ITEM_UPDATED,
3988                                                  SyncSourceReport::ITEM_TOTAL));
3989         }
3990         if (clientDeleted != -1 && checkSyncStats) {
3991             CLIENT_TEST_EQUAL(name, clientDeleted,
3992                               source.getItemStat(SyncSourceReport::ITEM_LOCAL,
3993                                                  SyncSourceReport::ITEM_REMOVED,
3994                                                  SyncSourceReport::ITEM_TOTAL));
3995         }
3996
3997         if (serverAdded != -1 && checkSyncStats) {
3998             CLIENT_TEST_EQUAL(name, serverAdded,
3999                               source.getItemStat(SyncSourceReport::ITEM_REMOTE,
4000                                                  SyncSourceReport::ITEM_ADDED,
4001                                                  SyncSourceReport::ITEM_TOTAL));
4002         }
4003         if (serverUpdated != -1 && checkSyncStats) {
4004             CLIENT_TEST_EQUAL(name, serverUpdated,
4005                               source.getItemStat(SyncSourceReport::ITEM_REMOTE,
4006                                                  SyncSourceReport::ITEM_UPDATED,
4007                                                  SyncSourceReport::ITEM_TOTAL));
4008         }
4009         if (serverDeleted != -1 && checkSyncStats) {
4010             CLIENT_TEST_EQUAL(name, serverDeleted,
4011                               source.getItemStat(SyncSourceReport::ITEM_REMOTE,
4012                                                  SyncSourceReport::ITEM_REMOVED,
4013                                                  SyncSourceReport::ITEM_TOTAL));
4014         }
4015     }
4016     SE_LOG_DEBUG(NULL, NULL, "Done with checking sync report.");
4017 }
4018
4019 /** @} */
4020 /** @endcond */
4021 #endif // ENABLE_INTEGRATION_TESTS
4022
4023 SE_END_CXX