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