- added checking of the SyncReport - changed expectations for refresh-from-server sync: number of deleted items depends on SyncSource implementation and cannot be checked - make LOCATION unique in calendar events to avoid having events merged by Funambol server

=> testing passed with Funambol 3.0 using:
   CLIENT_TEST_DELAY=5 \
   CLIENT_TEST_LOG=/scratch/Funambol/ds-server/logs/funambol_ds.log \
   CLIENT_TEST_SOURCES=vcal10,vcard21 \
   CLIENT_TEST_SERVER=funambol \
       ./client-test Client::Sync::vcal10_vcard21

git-svn-id: https://core.forge.funambol.org/svn/core/top-level/trunk/3x/client-api/native@14292 e8e8ed6c-164c-0410-afcf-9e9a7c7d8c10
This commit is contained in:
Patrick Ohly 2007-02-14 21:01:27 +00:00
parent cca9cd3c13
commit 2f69d6ba94
3 changed files with 187 additions and 36 deletions

View File

@ -22,6 +22,7 @@
#include "ClientTest.h"
#include "base/test.h"
#include "base/util/StringBuffer.h"
#ifdef ENABLE_INTEGRATION_TESTS
@ -864,14 +865,14 @@ private:
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->deleteAll(it->second->createSourceA);
}
sync(SYNC_TWO_WAY, ".deleteall.twoway");
sync(SYNC_TWO_WAY, ".deleteall.twoway", CheckSyncReport(0,0,0, 0,0,-1));
break;
case DELETE_ALL_REFRESH:
// delete locally and then tell the server to "copy" the empty databases
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->deleteAll(it->second->createSourceA);
}
sync(SYNC_REFRESH_FROM_CLIENT, ".deleteall.refreshserver");
sync(SYNC_REFRESH_FROM_CLIENT, ".deleteall.refreshserver", CheckSyncReport(0,0,0, 0,0,-1));
break;
}
}
@ -889,10 +890,10 @@ private:
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->testSimpleInsert();
}
sync(SYNC_TWO_WAY, ".send");
sync(SYNC_TWO_WAY, ".send", CheckSyncReport(0,0,0, 1,0,0));
// copy into second database
accessClientB->sync(SYNC_TWO_WAY, ".recv");
accessClientB->sync(SYNC_TWO_WAY, ".recv", CheckSyncReport(1,0,0, 0,0,0));
compareDatabases();
}
@ -907,7 +908,7 @@ private:
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->deleteAll(it->second->createSourceA);
}
sync(SYNC_SLOW, ".refresh");
sync(SYNC_SLOW, ".refresh", CheckSyncReport(-1,0,0, 0,0,0));
}
// do a two-way sync without additional checks
@ -937,11 +938,12 @@ private:
void testDeleteAllRefresh() {
source_it it;
// copy something to server first
// copy something to server first; doesn't matter whether it has the
// item already or not, as long as it exists there afterwards
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->testSimpleInsert();
}
sync(SYNC_SLOW, ".insert.client.log");
sync(SYNC_SLOW, ".insert");
// now ensure we can delete it
deleteAll(DELETE_ALL_SYNC);
@ -957,7 +959,7 @@ private:
}
// make sure server really deleted everything
sync(SYNC_SLOW, ".check.client.log");
sync(SYNC_SLOW, ".check", CheckSyncReport(0,0,0, 0,0,0));
for (it = sources.begin(); it != sources.end(); ++it) {
std::auto_ptr<SyncSource> source;
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceA()));
@ -979,7 +981,7 @@ private:
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->testSimpleInsert();
}
sync(SYNC_REFRESH_FROM_SERVER, "");
sync(SYNC_REFRESH_FROM_SERVER, "", CheckSyncReport(0,0,-1, 0,0,0));
// check
for (it = sources.begin(); it != sources.end(); ++it) {
@ -1010,9 +1012,8 @@ private:
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->testSimpleInsert();
}
sync(SYNC_REFRESH_FROM_CLIENT, ".refresh-from-client");
sync(SYNC_TWO_WAY, ".two-way");
// TODO: check sync report, no items should have been exchanged
sync(SYNC_REFRESH_FROM_CLIENT, ".refresh-from-client", CheckSyncReport(0,0,0 /* 1,0,0 - not sure exactly what the server will be told */));
sync(SYNC_TWO_WAY, ".two-way", CheckSyncReport(0,0,0, 0,0,0));
}
// test that a two-way sync copies an item from one address book into the other
@ -1035,8 +1036,8 @@ private:
it->second->update(it->second->createSourceA, it->second->config.updateItem);
}
sync(SYNC_TWO_WAY, ".update");
accessClientB->sync(SYNC_TWO_WAY, ".update");
sync(SYNC_TWO_WAY, ".update", CheckSyncReport(0,0,0, 0,1,0));
accessClientB->sync(SYNC_TWO_WAY, ".update", CheckSyncReport(0,1,0, 0,0,0));
compareDatabases();
}
@ -1053,8 +1054,8 @@ private:
it->second->update(it->second->createSourceA, it->second->config.complexUpdateItem);
}
sync(SYNC_TWO_WAY, ".update");
accessClientB->sync(SYNC_TWO_WAY, ".update");
sync(SYNC_TWO_WAY, ".update", CheckSyncReport(0,0,0, 0,1,0));
accessClientB->sync(SYNC_TWO_WAY, ".update", CheckSyncReport(0,1,0, 0,0,0));
compareDatabases();
}
@ -1072,8 +1073,8 @@ private:
}
// transfer change from A to server to B
sync(SYNC_TWO_WAY, ".delete");
accessClientB->sync(SYNC_TWO_WAY, ".delete");
sync(SYNC_TWO_WAY, ".delete", CheckSyncReport(0,0,0, 0,0,1));
accessClientB->sync(SYNC_TWO_WAY, ".delete", CheckSyncReport(0,0,1, 0,0,0));
// check client B: shouldn't have any items now
for (it = sources.begin(); it != sources.end(); ++it) {
@ -1104,7 +1105,7 @@ private:
}
// send change to server from client A (no conflict), then from B (conflict)
sync(SYNC_TWO_WAY, ".send");
sync(SYNC_TWO_WAY, ".send", CheckSyncReport(0,0,0, 0,1,0));
sync(SYNC_TWO_WAY, ".recv");
// figure out how the conflict during ".recv" was handled
@ -1195,7 +1196,7 @@ private:
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->insertManyItems(it->second->createSourceA, 1, 1);
}
sync(SYNC_TWO_WAY, ".send");
sync(SYNC_TWO_WAY, ".send", CheckSyncReport(0,0,0, 1,0,0));
for (it = sources.begin(); it != sources.end(); ++it) {
if (it->second->config.createSourceB) {
std::auto_ptr<SyncSource> source;
@ -1227,7 +1228,7 @@ private:
CPPUNIT_ASSERT_NO_THROW(source.reset());
}
}
accessClientB->sync(SYNC_ONE_WAY_FROM_SERVER, ".recv");
accessClientB->sync(SYNC_ONE_WAY_FROM_SERVER, ".recv", CheckSyncReport(1,0,0, 0,0,0));
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
if (it->second->config.createSourceB) {
std::auto_ptr<SyncSource> source;
@ -1244,7 +1245,7 @@ private:
// two-way sync with first client for verification
// => no changes
sync(SYNC_TWO_WAY, ".check");
sync(SYNC_TWO_WAY, ".check", CheckSyncReport(0,0,0, 0,0,0));
for (it = sources.begin(); it != sources.end(); ++it) {
if (it->second->config.createSourceB) {
std::auto_ptr<SyncSource> source;
@ -1275,7 +1276,7 @@ private:
CPPUNIT_ASSERT_NO_THROW(source.reset());
}
}
sync(SYNC_TWO_WAY, ".delete");
sync(SYNC_TWO_WAY, ".delete", CheckSyncReport(0,0,0, 0,0,1));
for (it = sources.begin(); it != sources.end(); ++it) {
if (it->second->config.createSourceB) {
std::auto_ptr<SyncSource> source;
@ -1292,7 +1293,7 @@ private:
// sync the same change to second client
// => one item left (the one inserted locally)
accessClientB->sync(SYNC_ONE_WAY_FROM_SERVER, ".delete");
accessClientB->sync(SYNC_ONE_WAY_FROM_SERVER, ".delete", CheckSyncReport(0,0,1, 0,0,0));
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
if (it->second->config.createSourceB) {
std::auto_ptr<SyncSource> source;
@ -1350,7 +1351,7 @@ private:
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->insertManyItems(it->second->createSourceA, 1, 1);
}
sync(SYNC_TWO_WAY, ".send");
sync(SYNC_TWO_WAY, ".send", CheckSyncReport(0,0,0, 1,0,0));
for (it = sources.begin(); it != sources.end(); ++it) {
if (it->second->config.createSourceB) {
std::auto_ptr<SyncSource> source;
@ -1382,7 +1383,7 @@ private:
CPPUNIT_ASSERT_NO_THROW(source.reset());
}
}
accessClientB->sync(SYNC_ONE_WAY_FROM_CLIENT, ".send");
accessClientB->sync(SYNC_ONE_WAY_FROM_CLIENT, ".send", CheckSyncReport(0,0,0, 1,0,0));
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
if (it->second->config.createSourceB) {
std::auto_ptr<SyncSource> source;
@ -1399,7 +1400,7 @@ private:
// two-way sync with client A for verification
// => receive one item
sync(SYNC_TWO_WAY, ".check");
sync(SYNC_TWO_WAY, ".check", CheckSyncReport(1,0,0, 0,0,0));
for (it = sources.begin(); it != sources.end(); ++it) {
if (it->second->config.createSourceB) {
std::auto_ptr<SyncSource> source;
@ -1430,7 +1431,7 @@ private:
CPPUNIT_ASSERT_NO_THROW(source.reset());
}
}
accessClientB->sync(SYNC_ONE_WAY_FROM_CLIENT, ".delete");
accessClientB->sync(SYNC_ONE_WAY_FROM_CLIENT, ".delete", CheckSyncReport(0,0,0, 0,0,1));
for (it = accessClientB->sources.begin(); it != accessClientB->sources.end(); ++it) {
if (it->second->config.createSourceB) {
std::auto_ptr<SyncSource> source;
@ -1447,7 +1448,7 @@ private:
// sync the same change to client A
// => one item left (the one inserted locally)
sync(SYNC_TWO_WAY, ".delete");
sync(SYNC_TWO_WAY, ".delete", CheckSyncReport(0,0,1, 0,0,0));
for (it = sources.begin(); it != sources.end(); ++it) {
if (it->second->config.createSourceB) {
std::auto_ptr<SyncSource> source;
@ -1502,16 +1503,16 @@ private:
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->insert(it->second->createSourceA, it->second->config.insertItem);
}
sync(SYNC_TWO_WAY, ".add");
sync(SYNC_TWO_WAY, ".add", CheckSyncReport(0,0,0, 1,0,0));
// update it
for (it = sources.begin(); it != sources.end(); ++it) {
it->second->insert(it->second->createSourceB, it->second->config.updateItem);
it->second->update(it->second->createSourceB, it->second->config.updateItem);
}
sync(SYNC_TWO_WAY, ".update");
sync(SYNC_TWO_WAY, ".update", CheckSyncReport(0,0,0, 0,1,0));
// now download the updated item into the second client
accessClientB->sync(SYNC_TWO_WAY, ".recv");
accessClientB->sync(SYNC_TWO_WAY, ".recv", CheckSyncReport(1,0,0, 0,0,0));
// compare the two databases
compareDatabases();
@ -1553,7 +1554,7 @@ private:
}
// send data to server
sync(SYNC_TWO_WAY, ".send");
sync(SYNC_TWO_WAY, ".send", CheckSyncReport(0,0,0, -1,0,0));
// ensure that client has the same data, ignoring data conversion
// issues (those are covered by testItems())
@ -1598,6 +1599,7 @@ private:
// transfer to server
sync(SYNC_TWO_WAY, ".send",
CheckSyncReport(0,0,0, -1,0,0), // number of items sent to server depends on source
withMaxMsgSize ? maxMsgSize : 0,
withMaxMsgSize ? maxMsgSize * 100 : 0,
withLargeObject,
@ -1605,6 +1607,7 @@ private:
// copy to second client
accessClientB->sync(SYNC_REFRESH_FROM_SERVER, ".recv",
CheckSyncReport(-1,0,-1, 0,0,0), // number of items received from server depends on source
withLargeObject ? maxMsgSize : withMaxMsgSize ? maxMsgSize * 100 /* large enough so that server can sent the largest item */ : 0,
withMaxMsgSize ? maxMsgSize * 100 : 0,
withLargeObject,
@ -1616,6 +1619,7 @@ private:
void sync(SyncMode syncMode,
const std::string &logprefix = "",
CheckSyncReport checkReport = CheckSyncReport(),
long maxMsgSize = 0,
long maxObjSize = 0,
bool loSupport = false,
@ -1644,6 +1648,7 @@ private:
try {
res = client.sync(sourceArray,
syncMode,
checkReport,
maxMsgSize,
maxObjSize,
loSupport,
@ -2073,7 +2078,7 @@ void ClientTest::getTestData(const char *type, Config &config)
"END:VEVENT\n"
"END:VCALENDAR\n";
config.templateItem = config.insertItem;
config.uniqueProperties = "SUMMARY:UID";
config.uniqueProperties = "SUMMARY:UID:LOCATION";
config.sizeProperty = "DESCRIPTION";
config.import = import;
config.dump = dump;
@ -2133,7 +2138,7 @@ void ClientTest::getTestData(const char *type, Config &config)
"END:VEVENT\n"
"END:VCALENDAR\n";
config.templateItem = config.insertItem;
config.uniqueProperties = "SUMMARY:UID";
config.uniqueProperties = "SUMMARY:UID:LOCATION";
config.sizeProperty = "DESCRIPTION";
config.import = import;
config.dump = dump;
@ -2217,3 +2222,106 @@ void ClientTest::getTestData(const char *type, Config &config)
config.testcases = "testcases/itodo20.ics";
}
}
void CheckSyncReport::check(SyncReport &report) const
{
// first dump the report - beware, this is a direct copy of code from client.cpp,
// needs to be refactored
StringBuffer res;
char tmp[512];
res = "===========================================================\n";
res.append("================ SYNCHRONIZATION REPORT ===============\n");
res.append("===========================================================\n");
sprintf(tmp, "Last error code = %d\n", report.getLastErrorCode());
res.append(tmp);
sprintf(tmp, "Last error msg = %s\n\n", report.getLastErrorMsg());
res.append(tmp);
res.append("----------|--------CLIENT---------|--------SERVER---------|\n");
res.append(" Source | NEW | MOD | DEL | NEW | MOD | DEL |\n");
res.append("----------|-----------------------------------------------|\n");
for (unsigned int i=0; report.getSyncSourceReport(i); i++) {
SyncSourceReport* ssr = report.getSyncSourceReport(i);
if (ssr->getState() == SOURCE_INACTIVE) {
continue;
}
sprintf(tmp, "%10s|", ssr->getSourceName());
res.append(tmp);
sprintf(tmp, "%3d/%3d|", ssr->getItemReportSuccessfulCount(CLIENT, COMMAND_ADD), ssr->getItemReportCount(CLIENT, COMMAND_ADD));
res.append(tmp);
sprintf(tmp, "%3d/%3d|", ssr->getItemReportSuccessfulCount(CLIENT, COMMAND_REPLACE), ssr->getItemReportCount(CLIENT, COMMAND_REPLACE));
res.append(tmp);
sprintf(tmp, "%3d/%3d|", ssr->getItemReportSuccessfulCount(CLIENT, COMMAND_DELETE), ssr->getItemReportCount(CLIENT, COMMAND_DELETE));
res.append(tmp);
sprintf(tmp, "%3d/%3d|", ssr->getItemReportSuccessfulCount(SERVER, COMMAND_ADD), ssr->getItemReportCount(SERVER, COMMAND_ADD));
res.append(tmp);
sprintf(tmp, "%3d/%3d|", ssr->getItemReportSuccessfulCount(SERVER, COMMAND_REPLACE), ssr->getItemReportCount(SERVER, COMMAND_REPLACE));
res.append(tmp);
sprintf(tmp, "%3d/%3d|\n", ssr->getItemReportSuccessfulCount(SERVER, COMMAND_DELETE), ssr->getItemReportCount(SERVER, COMMAND_DELETE));
res.append(tmp);
res.append("----------|-----------------------------------------------|\n\n");
sprintf(tmp, "%s:\n----------", ssr->getSourceName());
res.append(tmp);
sprintf(tmp, "\nSource State = %d\n", ssr->getState());
res.append(tmp);
sprintf(tmp, "Last error code = %d\n", ssr->getLastErrorCode());
res.append(tmp);
sprintf(tmp, "Last error msg = %s\n\n", ssr->getLastErrorMsg());
res.append(tmp);
}
res.append("----------|--------CLIENT---------|--------SERVER---------|\n");
res.append(" | NEW | MOD | DEL | NEW | MOD | DEL |\n");
res.append("----------|-----------------------------------------------|\n");
sprintf(tmp, "Expected | %3d | %3d | %3d | %3d | %3d | %3d |\n",
clientAdded, clientUpdated, clientDeleted,
serverAdded, serverUpdated, serverDeleted);
res.append(tmp);
LOG.info("%s", res.c_str());
// this code is intentionally duplicated to produce nicer CPPUNIT asserts
for (unsigned int i=0; report.getSyncSourceReport(i); i++) {
SyncSourceReport* ssr = report.getSyncSourceReport(i);
if (ssr->getState() == SOURCE_INACTIVE) {
continue;
}
const char *name = ssr->getSourceName();
LOG.debug("Checking sync source %s...", name);
CLIENT_TEST_EQUAL(name, 0, ssr->getItemReportFailedCount(CLIENT, COMMAND_ADD));
if (clientAdded != -1) {
CLIENT_TEST_EQUAL(name, clientAdded, ssr->getItemReportSuccessfulCount(CLIENT, COMMAND_ADD));
}
CLIENT_TEST_EQUAL(name, 0, ssr->getItemReportFailedCount(CLIENT, COMMAND_REPLACE));
if (clientUpdated != -1) {
CLIENT_TEST_EQUAL(name, clientUpdated, ssr->getItemReportSuccessfulCount(CLIENT, COMMAND_REPLACE));
}
CLIENT_TEST_EQUAL(name, 0, ssr->getItemReportFailedCount(CLIENT, COMMAND_DELETE));
if (clientDeleted != -1) {
CLIENT_TEST_EQUAL(name, clientDeleted, ssr->getItemReportSuccessfulCount(CLIENT, COMMAND_DELETE));
}
CLIENT_TEST_EQUAL(name, 0, ssr->getItemReportFailedCount(SERVER, COMMAND_ADD));
if (serverAdded != -1) {
CLIENT_TEST_EQUAL(name, serverAdded, ssr->getItemReportSuccessfulCount(SERVER, COMMAND_ADD));
}
CLIENT_TEST_EQUAL(name, 0, ssr->getItemReportFailedCount(SERVER, COMMAND_REPLACE));
if (serverUpdated != -1) {
CLIENT_TEST_EQUAL(name, serverUpdated, ssr->getItemReportSuccessfulCount(SERVER, COMMAND_REPLACE));
}
CLIENT_TEST_EQUAL(name, 0, ssr->getItemReportFailedCount(SERVER, COMMAND_DELETE));
if (serverDeleted != -1) {
CLIENT_TEST_EQUAL(name, serverDeleted, ssr->getItemReportSuccessfulCount(SERVER, COMMAND_DELETE));
}
}
LOG.debug("Done with checking sync report.");
}

View File

@ -21,6 +21,34 @@
#include <string>
#include "spds/SyncSource.h"
#include "spds/SyncReport.h"
/**
* This class encapsulates logging and checking of a SyncReport.
* When constructed with default parameters, no checking will be done.
* Otherwise the sync report has to contain exactly the expected result.
* When multiple sync sources are active, _all_ of them have to behave
* alike (which is how the tests are constructed).
*
* No item is ever supposed to fail.
*/
class CheckSyncReport {
public:
CheckSyncReport(int clAdded = -1, int clUpdated = -1, int clDeleted = -1,
int srAdded = -1, int srUpdated = -1, int srDeleted = -1) :
clientAdded(clAdded),
clientUpdated(clUpdated),
clientDeleted(clDeleted),
serverAdded(srAdded),
serverUpdated(srUpdated),
serverDeleted(srDeleted)
{}
const int clientAdded, clientUpdated, clientDeleted,
serverAdded, serverUpdated, serverDeleted;
virtual void check(SyncReport &report) const;
};
/**
* This is the interface expected by the testing framework for sync
@ -329,6 +357,9 @@ class ClientTest {
*
* @param sources a -1 terminated array of sync source indices
* @param syncMode the synchronization mode to be used
* @param checkReport has to be called after a successful or unsuccessful sync,
* will dump the report and (optionally) check the result;
* beware, the later may throw exceptions inside CPPUNIT macros
* @param maxMsgSize >0: enable the maximum message size, else disable it
* @param maxObjSize same as maxMsgSize for maximum object size
* @param encoding if non-empty, then let client library transform all items
@ -339,6 +370,7 @@ class ClientTest {
virtual int sync(
const int *sources,
SyncMode syncMode,
const CheckSyncReport &checkReport,
long maxMsgSize = 0,
long maxObjSize = 0,
bool loSupport = false,
@ -377,4 +409,12 @@ class ClientTest {
void *factory;
};
/** assert equality, include string in message if unequal */
#define CLIENT_TEST_EQUAL( _prefix, \
_expected, \
_actual ) \
CPPUNIT_ASSERT_EQUAL_MESSAGE( std::string(_prefix) + ": " + #_expected + " == " + #_actual, \
_expected, \
_actual )
#endif

View File

@ -189,6 +189,7 @@ public:
virtual int sync(
const int *activeSources,
SyncMode syncMode,
const CheckSyncReport &checkReport,
long maxMsgSize,
long maxObjSize,
bool loSupport,
@ -213,6 +214,8 @@ public:
SyncClient client;
int res = client.sync(*config, syncSources);
CPPUNIT_ASSERT(client.getSyncReport());
checkReport.check(*client.getSyncReport());
for (source = 0; syncSources[source]; source++) {
delete syncSources[source];