939d48adf39dcc924f9c883d4c8771896a8129bd
[platform/upstream/syncevolution.git] / src / client-test-app.cpp
1 /*
2  * Copyright (C) 2005-2006 Patrick Ohly
3  * Copyright (C) 2007 Funambol
4  *
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.
9  *
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.
14  *
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
18  */
19
20 #include <config.h>
21
22 #include <base/test.h>
23 #include <ClientTest.h>
24
25 #include <cppunit/extensions/HelperMacros.h>
26 #include <exception>
27 #include <fstream>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <errno.h>
31 #include <signal.h>
32 #ifdef HAVE_VALGRIND_VALGRIND_H
33 # include <valgrind/valgrind.h>
34 #endif
35 #ifdef HAVE_EXECINFO_H
36 # include <execinfo.h>
37 #endif
38
39 #include "EvolutionSyncClient.h"
40 #include "EvolutionSyncSource.h"
41 #include "SyncEvolutionUtil.h"
42
43 /*
44  * always provide this test class, even if not used:
45  * that way the test scripts can unconditionally
46  * invoke "client-test SyncEvolution"
47  */
48 CPPUNIT_REGISTRY_ADD_TO_DEFAULT("SyncEvolution");
49
50 /**
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
53  */
54 class TestEvolutionSyncSource : public EvolutionSyncSource {
55 public:
56     TestEvolutionSyncSource(const string &type,
57                             const EvolutionSyncSourceParams &params) :
58         EvolutionSyncSource(params)
59     {
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);
65     }
66
67     virtual int beginSync() throw () {
68         CPPUNIT_ASSERT_NO_THROW(m_source->open());
69         CPPUNIT_ASSERT(!m_source->hasFailed());
70         return m_source->beginSync();
71     }
72
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());
77         return res;
78     }
79
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(); }
96
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,
107                                 bool needPartial,
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); }
115
116     auto_ptr<EvolutionSyncSource> m_source;
117 };
118
119 class EvolutionLocalTests : public LocalTests {
120 public:
121     EvolutionLocalTests(const std::string &name, ClientTest &cl, int sourceParam, ClientTest::Config &co) :
122         LocalTests(name, cl, sourceParam, co)
123         {}
124
125     virtual void addTests() {
126         LocalTests::addTests();
127
128 #ifdef ENABLE_MAEMO
129         if (config.createSourceA &&
130             config.createSourceB &&
131             config.templateItem &&
132             strstr(config.templateItem, "BEGIN:VCARD") &&
133             config.uniqueProperties) {
134             ADD_TEST(EvolutionLocalTests, testOssoDelete);
135         }
136 #endif
137     }
138
139 private:
140
141     // insert am item,
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);
148
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);
157
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));
166     }
167 };
168
169 /**
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.
173  *
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.
178  *
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
187  *                         instances
188  * - CLIENT_TEST_DELAY = number of seconds to sleep between syncs, required
189  *                       by some servers
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
196  *                           items
197  *
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
203  * names.
204  */
205 class TestEvolution : public ClientTest {
206 public:
207     /**
208      * can be instantiated as client A with id == "1" and client B with id == "2"
209      */
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") : ""),
213         m_clientID(id),
214         m_configs(EvolutionSyncSource::getTestRegistry())
215     {
216         const char *server = getenv("CLIENT_TEST_SERVER");
217
218         if (id == "1") {
219             m_clientB.reset(new TestEvolution("2"));
220         }
221
222         /* check server */
223         if (!server) {
224             server = "funambol";
225             setenv("CLIENT_TEST_SERVER", "funambol", 1);
226         }
227
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");
232         if (evouser) {
233             m_evoUser = evouser;
234         }
235         const char *evopasswd = getenv("CLIENT_TEST_EVOLUTION_PASSWORD");
236         if (evopasswd) {
237             m_evoPassword = evopasswd;
238         }
239
240         /* check sources */
241         const char *sourcelist = getenv("CLIENT_TEST_SOURCES");
242         set<string> sources;
243         if (sourcelist) {
244             boost::split(sources, sourcelist, boost::is_any_of(","));
245         } else {
246             BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) {
247                 sources.insert(test->m_configName);
248             }
249         }
250
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);
254             }
255         }
256
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");
265         }
266         BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) {
267             ClientTest::Config testconfig;
268             getSourceConfig(test, testconfig);
269             CPPUNIT_ASSERT(testconfig.type);
270
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);
276                 CPPUNIT_ASSERT(sc);
277                 sc->setURI(testconfig.uri);
278                 sc->setSourceType(testconfig.type);
279             }
280
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);
286         }
287         config.flush();
288     }
289
290     virtual LocalTests *createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co) {
291         return new EvolutionLocalTests(name, *this, sourceParam, co);
292     }
293
294     virtual int getNumSources() {
295         return m_source2Config.size();
296     }
297
298     virtual void getSourceConfig(int source, Config &config) {
299         getSourceConfig(m_configs[m_source2Config[source]], config);
300     }
301
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();
309
310         test->updateConfig(config);
311     }
312
313     virtual ClientTest *getClientB() {
314         return m_clientB.get();
315     }
316
317     virtual bool isB64Enabled() {
318         return false;
319     }
320
321     virtual int sync(
322         const int *sources,
323         SyncMode syncMode,
324         const CheckSyncReport &checkReport,
325         long maxMsgSize = 0,
326         long maxObjSize = 0,
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]]);
332         }
333
334         string server = getenv("CLIENT_TEST_SERVER") ? getenv("CLIENT_TEST_SERVER") : "funambol";
335         server += "_";
336         server += m_clientID;
337         
338         class ClientTest : public EvolutionSyncClient {
339         public:
340             ClientTest(const string &server,
341                            const set<string> &activeSources,
342                            SyncMode syncMode,
343                            long maxMsgSize,
344                            long maxObjSize,
345                            bool loSupport,
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),
352                 m_encoding(encoding)
353                 {}
354
355         protected:
356             virtual void prepare(SyncSource **sources) {
357                 for (SyncSource **source = sources;
358                      *source;
359                      source++) {
360                     ((EvolutionSyncSource *)*source)->setEncoding(m_encoding ? m_encoding : "", true);
361                     (*source)->setPreferredSyncMode(m_syncMode);
362                 }
363                 setLoSupport(m_loSupport, true);
364                 setMaxObjSize(m_maxObjSize, true);
365                 setMaxMsgSize(m_maxMsgSize, true);
366                 EvolutionSyncClient::prepare(sources);
367             }
368
369         private:
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);
376
377         int res = client.sync();
378         CPPUNIT_ASSERT(client.getSyncReport());
379         checkReport.check(res, *client.getSyncReport());
380         return res;
381     }
382
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;
386     }
387     
388 private:
389     string m_clientID;
390     std::auto_ptr<TestEvolution> m_clientB;
391     const TestRegistry &m_configs;
392
393     /** prefix, username, password to be used for local databases */
394     string m_evoPrefix, m_evoUser, m_evoPassword;
395
396     /**
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.
400      */
401     vector<string> m_source2Config;
402
403     /** returns the name of the Evolution database */
404     string getDatabaseName(const string &configName) {
405         return m_evoPrefix + configName + "_" + m_clientID;
406     }
407     
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);
414
415         EvolutionSyncConfig config("client-test-changes");
416         SyncSourceNodes nodes = config.getSyncSourceNodes(name,
417                                                           string("_") + ((TestEvolution &)client).m_clientID +
418                                                           "_" + (isSourceA ? "A" : "B"));
419
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());
424
425         EvolutionSyncSourceParams params(name,
426                                          nodes,
427                                          changeID);
428
429         const RegisterSyncSourceTest *test = evClient.m_configs[name];
430         ClientTestConfig testConfig;
431         getSourceConfig(test, testConfig);
432
433         SyncSource *ss = new TestEvolutionSyncSource(testConfig.type, params);
434         return ss;
435     }
436 };
437
438 static void handler(int sig)
439 {
440     void *buffer[100];
441     int size;
442     
443     fprintf(stderr, "\ncaught signal %d\n", sig);
444     fflush(stderr);
445 #ifdef HAVE_EXECINFO_H
446     size = backtrace(buffer, sizeof(buffer)/sizeof(buffer[0]));
447     backtrace_symbols_fd(buffer, size, 2);
448 #endif
449 #ifdef HAVE_VALGRIND_VALGRIND_H
450     VALGRIND_PRINTF_BACKTRACE("\ncaught signal %d\n", sig);
451 #endif
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);
457     abort();
458 }
459
460 static class RegisterTestEvolution {
461 public:
462     RegisterTestEvolution() :
463         testClient("1") {
464         struct sigaction act;
465
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);
471
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
475         g_type_init();
476 #endif
477         testClient.registerTests();
478     }
479
480 private:
481     TestEvolution testClient;
482     
483 } testEvolution;
484
485 int RegisterSyncSourceTest::dump(ClientTest &client, SyncSource &source, const char *file)
486 {
487     std::ofstream out(file);
488     
489     ((EvolutionSyncSource &)source).exportData(out);
490
491     out.close();
492     return out.bad();
493 }