2 * Copyright (C) 2005-2006 Patrick Ohly
3 * Copyright (C) 2007 Funambol
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include <base/test.h>
23 #include <ClientTest.h>
25 #include <cppunit/extensions/HelperMacros.h>
32 #ifdef HAVE_VALGRIND_VALGRIND_H
33 # include <valgrind/valgrind.h>
35 #ifdef HAVE_EXECINFO_H
36 # include <execinfo.h>
39 #include "EvolutionSyncClient.h"
40 #include "EvolutionSyncSource.h"
41 #include "SyncEvolutionUtil.h"
44 * always provide this test class, even if not used:
45 * that way the test scripts can unconditionally
46 * invoke "client-test SyncEvolution"
48 CPPUNIT_REGISTRY_ADD_TO_DEFAULT("SyncEvolution");
51 * a wrapper class which automatically does an open() in the constructor and a close() in the destructor
52 * and ensures that the sync mode is "none" = testing mode
54 class TestEvolutionSyncSource : public EvolutionSyncSource {
56 TestEvolutionSyncSource(const string &type,
57 const EvolutionSyncSourceParams ¶ms) :
58 EvolutionSyncSource(params)
60 PersistentEvolutionSyncSourceConfig config(params.m_name, params.m_nodes);
61 config.setSourceType(type);
62 m_source.reset(EvolutionSyncSource::createSource(params));
63 CPPUNIT_ASSERT(m_source.get());
64 m_source->setSyncMode(SYNC_NONE);
67 virtual int beginSync() throw () {
68 CPPUNIT_ASSERT_NO_THROW(m_source->open());
69 CPPUNIT_ASSERT(!m_source->hasFailed());
70 return m_source->beginSync();
73 virtual int endSync() throw () {
74 int res = m_source->endSync();
75 CPPUNIT_ASSERT_NO_THROW(m_source->close());
76 CPPUNIT_ASSERT(!m_source->hasFailed());
80 virtual SyncItem* getFirstItem() throw () { return m_source->getFirstItem(); }
81 virtual SyncItem* getNextItem() throw () { return m_source->getNextItem(); }
82 virtual SyncItem* getFirstNewItem() throw () { return m_source->getFirstNewItem(); }
83 virtual SyncItem* getNextNewItem() throw () { return m_source->getNextNewItem(); }
84 virtual SyncItem* getFirstUpdatedItem() throw () { return m_source->getFirstUpdatedItem(); }
85 virtual SyncItem* getNextUpdatedItem() throw () { return m_source->getNextUpdatedItem(); }
86 virtual SyncItem* getFirstDeletedItem() throw () { return m_source->getFirstDeletedItem(); }
87 virtual SyncItem* getNextDeletedItem() throw () { return m_source->getNextDeletedItem(); }
88 virtual SyncItem* getFirstItemKey() throw () { return m_source->getFirstItemKey(); }
89 virtual SyncItem* getNextItemKey() throw () { return m_source->getNextItemKey(); }
90 virtual void setItemStatus(const char *key, int status) throw () { m_source->setItemStatus(key, status); }
91 virtual int addItem(SyncItem& item) throw () { return m_source->addItem(item); }
92 virtual int updateItem(SyncItem& item) throw () { return m_source->updateItem(item); }
93 virtual int deleteItem(SyncItem& item) throw () { return m_source->deleteItem(item); }
94 virtual int removeAllItems() throw () { return m_source->removeAllItems(); }
95 const char *getName() throw () { return m_source->getName(); }
97 virtual Databases getDatabases() { return m_source->getDatabases(); }
98 virtual void open() { m_source->open(); }
99 virtual SyncItem *createItem(const string &uid) { return m_source->createItem(uid); }
100 virtual void close() { m_source->close(); }
101 virtual void exportData(ostream &out) { m_source->exportData(out); }
102 virtual string fileSuffix() const { return m_source->fileSuffix(); }
103 virtual const char *getMimeType() const { return m_source->getMimeType(); }
104 virtual const char *getMimeVersion() const { return m_source->getMimeVersion(); }
105 virtual const char* getSupportedTypes() const { return m_source->getSupportedTypes(); }
106 virtual void beginSyncThrow(bool needAll,
108 bool deleteLocal) { m_source->beginSyncThrow(needAll, needPartial, deleteLocal); }
109 virtual void endSyncThrow() { m_source->endSyncThrow(); }
110 virtual int addItemThrow(SyncItem& item) { return m_source->addItemThrow(item); }
111 virtual int updateItemThrow(SyncItem& item) { return m_source->updateItemThrow(item); }
112 virtual int deleteItemThrow(SyncItem& item) { return m_source->deleteItemThrow(item); }
113 virtual void logItem(const string &uid, const string &info, bool debug = false) { m_source->logItem(uid, info, debug); }
114 virtual void logItem(const SyncItem &item, const string &info, bool debug = false) { m_source->logItem(item, info, debug); }
116 auto_ptr<EvolutionSyncSource> m_source;
119 class EvolutionLocalTests : public LocalTests {
121 EvolutionLocalTests(const std::string &name, ClientTest &cl, int sourceParam, ClientTest::Config &co) :
122 LocalTests(name, cl, sourceParam, co)
125 virtual void addTests() {
126 LocalTests::addTests();
129 if (config.createSourceA &&
130 config.createSourceB &&
131 config.templateItem &&
132 strstr(config.templateItem, "BEGIN:VCARD") &&
133 config.uniqueProperties) {
134 ADD_TEST(EvolutionLocalTests, testOssoDelete);
142 // overwrite it with an additional X-OSSO-CONTACT-STATE:DELETED as Maemoe address book does,
143 // iterate again and check that our own code deleted the item
144 void testOssoDelete() {
145 // get into clean state with one template item added
146 deleteAll(createSourceA);
147 insert(createSourceA, config.templateItem);
149 // add X-OSSO-CONTACT-STATE:DELETED
150 string item = config.templateItem;
151 const char *comma = strchr(config.uniqueProperties, ':');
152 size_t offset = item.find(config.uniqueProperties, 0,
153 comma ? comma - config.uniqueProperties : strlen(config.uniqueProperties));
154 CPPUNIT_ASSERT(offset != item.npos);
155 item.insert(offset, "X-OSSO-CONTACT-STATE:DELETED\n");
156 update(createSourceA, item.c_str(), false);
158 // opening and preparing the source should delete the item
159 std::auto_ptr<SyncSource> source;
160 SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
161 SOURCE_ASSERT(source.get(), source->beginSync() == 0 );
162 CPPUNIT_ASSERT_EQUAL(0, countItemsOfType(source.get(), TOTAL_ITEMS));
163 CPPUNIT_ASSERT_EQUAL(0, countItemsOfType(source.get(), NEW_ITEMS));
164 CPPUNIT_ASSERT_EQUAL(0, countItemsOfType(source.get(), UPDATED_ITEMS));
165 CPPUNIT_ASSERT_EQUAL(1, countItemsOfType(source.get(), DELETED_ITEMS));
170 * This code uses the ClientTest and and information provided by
171 * the backends in their RegisterSyncSourceTest instances to test
172 * real synchronization with a server.
174 * Configuration is done by environment variables which indicate which
175 * part below the root node "client-test" of the the configuration tree to use;
176 * beyond that everything needed for synchronization is read from the
177 * configuration tree.
179 * - CLIENT_TEST_SERVER = maps to name of root node in configuration tree
180 * - CLIENT_TEST_EVOLUTION_PREFIX = a common "evolutionsource" prefix for *all*
181 * sources; the source name followed by "_[12]"
182 * is appended to get unique names
183 * - CLIENT_TEST_EVOLUTION_USER = sets the "evolutionuser" property of all sources
184 * - CLIENT_TEST_EVOLUTION_PASSWORD = sets the "evolutionpassword" property of all sources
185 * - CLIENT_TEST_SOURCES = comma separated list of active sources,
186 * names as selected in their RegisterSyncSourceTest
188 * - CLIENT_TEST_DELAY = number of seconds to sleep between syncs, required
190 * - CLIENT_TEST_LOG = logfile name of a server, can be empty:
191 * if given, then the content of that file will be
192 * copied and stored together with the client log
193 * (only works on Unix)
194 * - CLIENT_TEST_NUM_ITEMS = numbers of contacts/events/... to use during
195 * local and sync tests which create artificial
198 * The CLIENT_TEST_SERVER also has another meaning: it is used as hint
199 * by the synccompare.pl script and causes it to automatically ignore
200 * known, acceptable data modifications caused by sending an item to
201 * a server and back again. Currently the script recognizes "funambol",
202 * "scheduleworld", "synthesis" and "egroupware" as special server
205 class TestEvolution : public ClientTest {
208 * can be instantiated as client A with id == "1" and client B with id == "2"
210 TestEvolution(const string &id) :
211 ClientTest(getenv("CLIENT_TEST_DELAY") ? atoi(getenv("CLIENT_TEST_DELAY")) : 0,
212 getenv("CLIENT_TEST_LOG") ? getenv("CLIENT_TEST_LOG") : ""),
214 m_configs(EvolutionSyncSource::getTestRegistry())
216 const char *server = getenv("CLIENT_TEST_SERVER");
219 m_clientB.reset(new TestEvolution("2"));
225 setenv("CLIENT_TEST_SERVER", "funambol", 1);
228 /* override Evolution database names? */
229 const char *evoprefix = getenv("CLIENT_TEST_EVOLUTION_PREFIX");
230 m_evoPrefix = evoprefix ? evoprefix : "SyncEvolution_Test_";
231 const char *evouser = getenv("CLIENT_TEST_EVOLUTION_USER");
235 const char *evopasswd = getenv("CLIENT_TEST_EVOLUTION_PASSWORD");
237 m_evoPassword = evopasswd;
241 const char *sourcelist = getenv("CLIENT_TEST_SOURCES");
244 boost::split(sources, sourcelist, boost::is_any_of(","));
246 BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) {
247 sources.insert(test->m_configName);
251 BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) {
252 if (sources.find(test->m_configName) != sources.end()) {
253 m_source2Config.push_back(test->m_configName);
257 // get configuration and set obligatory fields
258 LOG.setLevel(LOG_LEVEL_DEBUG);
259 std::string root = std::string("evolution/") + server + "_" + id;
260 EvolutionSyncConfig config(string(server) + "_" + id);
261 if (!config.exists()) {
262 // no configuration yet
263 config.setDefaults();
264 config.setDevID(id == "1" ? "sc-api-nat" : "sc-pim-ppc");
266 BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) {
267 ClientTest::Config testconfig;
268 getSourceConfig(test, testconfig);
269 CPPUNIT_ASSERT(testconfig.type);
271 boost::shared_ptr<EvolutionSyncSourceConfig> sc = config.getSyncSourceConfig(testconfig.sourceName);
272 if (!sc || !sc->exists()) {
273 // no configuration yet
274 config.setSourceDefaults(testconfig.sourceName);
275 sc = config.getSyncSourceConfig(testconfig.sourceName);
277 sc->setURI(testconfig.uri);
278 sc->setSourceType(testconfig.type);
281 // always set these properties: they might have changed since the last run
282 string database = getDatabaseName(test->m_configName);
283 sc->setDatabaseID(database);
284 sc->setUser(m_evoUser);
285 sc->setPassword(m_evoPassword);
290 virtual LocalTests *createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co) {
291 return new EvolutionLocalTests(name, *this, sourceParam, co);
294 virtual int getNumSources() {
295 return m_source2Config.size();
298 virtual void getSourceConfig(int source, Config &config) {
299 getSourceConfig(m_configs[m_source2Config[source]], config);
302 static void getSourceConfig(const RegisterSyncSourceTest *test, Config &config) {
303 memset(&config, 0, sizeof(config));
304 ClientTest::getTestData(test->m_testCaseName.c_str(), config);
305 config.createSourceA = createSource;
306 config.createSourceB = createSource;
307 config.compare = compare;
308 config.sourceName = test->m_configName.c_str();
310 test->updateConfig(config);
313 virtual ClientTest *getClientB() {
314 return m_clientB.get();
317 virtual bool isB64Enabled() {
324 const CheckSyncReport &checkReport,
327 bool loSupport = false,
328 const char *encoding = NULL) {
329 set<string> activeSources;
330 for(int i = 0; sources[i] >= 0; i++) {
331 activeSources.insert(m_source2Config[sources[i]]);
334 string server = getenv("CLIENT_TEST_SERVER") ? getenv("CLIENT_TEST_SERVER") : "funambol";
336 server += m_clientID;
338 class ClientTest : public EvolutionSyncClient {
340 ClientTest(const string &server,
341 const set<string> &activeSources,
346 const char *encoding) :
347 EvolutionSyncClient(server, false, activeSources),
348 m_syncMode(syncMode),
349 m_maxMsgSize(maxMsgSize),
350 m_maxObjSize(maxObjSize),
351 m_loSupport(loSupport),
356 virtual void prepare(SyncSource **sources) {
357 for (SyncSource **source = sources;
360 ((EvolutionSyncSource *)*source)->setEncoding(m_encoding ? m_encoding : "", true);
361 (*source)->setPreferredSyncMode(m_syncMode);
363 setLoSupport(m_loSupport, true);
364 setMaxObjSize(m_maxObjSize, true);
365 setMaxMsgSize(m_maxMsgSize, true);
366 EvolutionSyncClient::prepare(sources);
370 const SyncMode m_syncMode;
371 const long m_maxMsgSize;
372 const long m_maxObjSize;
373 const bool m_loSupport;
374 const char *m_encoding;
375 } client(server, activeSources, syncMode, maxMsgSize, maxObjSize, loSupport, encoding);
377 int res = client.sync();
378 CPPUNIT_ASSERT(client.getSyncReport());
379 checkReport.check(res, *client.getSyncReport());
383 static bool compare(ClientTest &client, const char *fileA, const char *fileB) {
384 std::string cmdstr = std::string("./synccompare ") + fileA + " " + fileB;
385 return system(cmdstr.c_str()) == 0;
390 std::auto_ptr<TestEvolution> m_clientB;
391 const TestRegistry &m_configs;
393 /** prefix, username, password to be used for local databases */
394 string m_evoPrefix, m_evoUser, m_evoPassword;
397 * The ClientTest framework identifies active configs with an integer.
398 * This is the mapping to the corresponding config name, created when
399 * constructing this instance.
401 vector<string> m_source2Config;
403 /** returns the name of the Evolution database */
404 string getDatabaseName(const string &configName) {
405 return m_evoPrefix + configName + "_" + m_clientID;
408 static SyncSource *createSource(ClientTest &client, int source, bool isSourceA) {
409 TestEvolution &evClient((TestEvolution &)client);
410 string changeID = "SyncEvolution Change ID #";
411 string name = evClient.m_source2Config[source];
412 changeID += isSourceA ? "1" : "2";
413 string database = evClient.getDatabaseName(name);
415 EvolutionSyncConfig config("client-test-changes");
416 SyncSourceNodes nodes = config.getSyncSourceNodes(name,
417 string("_") + ((TestEvolution &)client).m_clientID +
418 "_" + (isSourceA ? "A" : "B"));
420 // always set this property: the name might have changes since last test run
421 nodes.m_configNode->setProperty("evolutionsource", database.c_str());
422 nodes.m_configNode->setProperty("evolutionuser", evClient.m_evoUser.c_str());
423 nodes.m_configNode->setProperty("evolutionpassword", evClient.m_evoPassword.c_str());
425 EvolutionSyncSourceParams params(name,
429 const RegisterSyncSourceTest *test = evClient.m_configs[name];
430 ClientTestConfig testConfig;
431 getSourceConfig(test, testConfig);
433 SyncSource *ss = new TestEvolutionSyncSource(testConfig.type, params);
438 static void handler(int sig)
443 fprintf(stderr, "\ncaught signal %d\n", sig);
445 #ifdef HAVE_EXECINFO_H
446 size = backtrace(buffer, sizeof(buffer)/sizeof(buffer[0]));
447 backtrace_symbols_fd(buffer, size, 2);
449 #ifdef HAVE_VALGRIND_VALGRIND_H
450 VALGRIND_PRINTF_BACKTRACE("\ncaught signal %d\n", sig);
452 /* system("objdump -l -C -d client-test >&2"); */
453 struct sigaction act;
454 memset(&act, 0, sizeof(act));
455 act.sa_handler = SIG_DFL;
456 sigaction(SIGABRT, &act, NULL);
460 static class RegisterTestEvolution {
462 RegisterTestEvolution() :
464 struct sigaction act;
466 memset(&act, 0, sizeof(act));
467 act.sa_handler = handler;
468 sigaction(SIGABRT, &act, NULL);
469 sigaction(SIGSEGV, &act, NULL);
470 sigaction(SIGILL, &act, NULL);
472 #if defined(HAVE_GLIB) && defined(HAVE_EDS)
473 // this is required on Maemo and does not harm either on a normal
474 // desktop system with Evolution
477 testClient.registerTests();
481 TestEvolution testClient;
485 int RegisterSyncSourceTest::dump(ClientTest &client, SyncSource &source, const char *file)
487 std::ofstream out(file);
489 ((EvolutionSyncSource &)source).exportData(out);