cc82e159768bbee8ce43b67d4d03b27887e9fdb2
[platform/upstream/syncevolution.git] / src / client-test-app.cpp
1 /*
2  * Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
3  * Copyright (C) 2009 Intel Corporation
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) version 3.
9  *
10  * This library 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 GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301  USA
19  */
20
21 #include "config.h"
22
23 #include <ClientTest.h>
24
25 #include <exception>
26 #include <fstream>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <errno.h>
30 #include <signal.h>
31 #ifdef HAVE_VALGRIND_VALGRIND_H
32 # include <valgrind/valgrind.h>
33 #endif
34 #ifdef HAVE_EXECINFO_H
35 # include <execinfo.h>
36 #endif
37
38 #include <syncevo/SyncContext.h>
39 #include "EvolutionSyncSource.h"
40 #include <syncevo/util.h>
41 #include <syncevo/VolatileConfigNode.h>
42
43 #include <syncevo/declarations.h>
44 SE_BEGIN_CXX
45
46 /*
47  * always provide this test class, even if not used:
48  * that way the test scripts can unconditionally
49  * invoke "client-test SyncEvolution"
50  */
51 CPPUNIT_REGISTRY_ADD_TO_DEFAULT("SyncEvolution");
52
53 class EvolutionLocalTests : public LocalTests {
54 public:
55     EvolutionLocalTests(const std::string &name, ClientTest &cl, int sourceParam, ClientTest::Config &co) :
56         LocalTests(name, cl, sourceParam, co)
57         {}
58
59     virtual void addTests() {
60         LocalTests::addTests();
61
62 #ifdef ENABLE_MAEMO
63         if (config.createSourceA &&
64             config.createSourceB &&
65             config.templateItem &&
66             strstr(config.templateItem, "BEGIN:VCARD") &&
67             config.uniqueProperties) {
68             ADD_TEST(EvolutionLocalTests, testOssoDelete);
69         }
70 #endif
71     }
72
73 private:
74
75     // insert am item,
76     // overwrite it with an additional X-OSSO-CONTACT-STATE:DELETED as Maemoe address book does,
77     // iterate again and check that our own code deleted the item
78     void testOssoDelete() {
79         // get into clean state with one template item added
80         deleteAll(createSourceA);
81         insert(createSourceA, config.templateItem, config.itemType);
82
83         // add X-OSSO-CONTACT-STATE:DELETED
84         string item = config.templateItem;
85         const char *comma = strchr(config.uniqueProperties, ':');
86         size_t offset = item.find(config.uniqueProperties, 0,
87                                   comma ? comma - config.uniqueProperties : strlen(config.uniqueProperties));
88         CPPUNIT_ASSERT(offset != item.npos);
89         item.insert(offset, "X-OSSO-CONTACT-STATE:DELETED\n");
90         update(createSourceA, item.c_str(), false);
91
92         // opening and preparing the source should delete the item
93         std::auto_ptr<TestingSyncSource> source;
94         SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
95         SOURCE_ASSERT_NO_FAILURE(source.get(), source->open());
96         SOURCE_ASSERT_NO_FAILURE(source.get(), source->beginSync("", "") );
97         CPPUNIT_ASSERT_EQUAL(0, countItemsOfType(source.get(), SyncSourceChanges::ANY));
98         CPPUNIT_ASSERT_EQUAL(0, countItemsOfType(source.get(), SyncSourceChanges::NEW));
99         CPPUNIT_ASSERT_EQUAL(0, countItemsOfType(source.get(), SyncSourceChanges::UPDATED));
100         CPPUNIT_ASSERT_EQUAL(1, countItemsOfType(source.get(), SyncSourceChanges::DELETED));
101     }
102 };
103
104 /**
105  * This code uses the ClientTest and and information provided by
106  * the backends in their RegisterSyncSourceTest instances to test
107  * real synchronization with a server.
108  *
109  * Configuration is done by environment variables which indicate which
110  * part below the root node "client-test" of the the configuration tree to use;
111  * beyond that everything needed for synchronization is read from the
112  * configuration tree.
113  *
114  * - CLIENT_TEST_SERVER = maps to name of root node in configuration tree
115  * - CLIENT_TEST_EVOLUTION_PREFIX = a common "evolutionsource" prefix for *all*
116  *                                  sources; the source name followed by "_[12]"
117  *                                  is appended to get unique names
118  * - CLIENT_TEST_EVOLUTION_USER = sets the "evolutionuser" property of all sources
119  * - CLIENT_TEST_EVOLUTION_PASSWORD = sets the "evolutionpassword" property of all sources
120  * - CLIENT_TEST_SOURCES = comma separated list of active sources,
121  *                         names as selected in their RegisterSyncSourceTest
122  *                         instances
123  * - CLIENT_TEST_DELAY = number of seconds to sleep between syncs, required
124  *                       by some servers
125  * - CLIENT_TEST_LOG = logfile name of a server, can be empty:
126  *                     if given, then the content of that file will be
127  *                     copied and stored together with the client log
128  *                     (only works on Unix)
129  * - CLIENT_TEST_NUM_ITEMS = numbers of contacts/events/... to use during
130  *                           local and sync tests which create artificial
131  *                           items
132  *
133  * The CLIENT_TEST_SERVER also has another meaning: it is used as hint
134  * by the synccompare.pl script and causes it to automatically ignore
135  * known, acceptable data modifications caused by sending an item to
136  * a server and back again. Currently the script recognizes "funambol",
137  * "scheduleworld", "synthesis" and "egroupware" as special server
138  * names.
139  */
140 class TestEvolution : public ClientTest {
141 public:
142     /**
143      * can be instantiated as client A with id == "1" and client B with id == "2"
144      */
145     TestEvolution(const string &id) :
146         ClientTest(getenv("CLIENT_TEST_DELAY") ? atoi(getenv("CLIENT_TEST_DELAY")) : 0,
147                    getenv("CLIENT_TEST_LOG") ? getenv("CLIENT_TEST_LOG") : ""),
148         m_clientID(id),
149         m_configs(EvolutionSyncSource::getTestRegistry())
150     {
151         const char *server = getenv("CLIENT_TEST_SERVER");
152
153         if (id == "1") {
154             m_clientB.reset(new TestEvolution("2"));
155         }
156
157         /* check server */
158         if (!server) {
159             server = "funambol";
160             setenv("CLIENT_TEST_SERVER", "funambol", 1);
161         }
162
163         /* override Evolution database names? */
164         const char *evoprefix = getenv("CLIENT_TEST_EVOLUTION_PREFIX");
165         m_evoPrefix = evoprefix ? evoprefix :  "SyncEvolution_Test_";
166         const char *evouser = getenv("CLIENT_TEST_EVOLUTION_USER");
167         if (evouser) {
168             m_evoUser = evouser;
169         }
170         const char *evopasswd = getenv("CLIENT_TEST_EVOLUTION_PASSWORD");
171         if (evopasswd) {
172             m_evoPassword = evopasswd;
173         }
174
175         /* check sources */
176         const char *sourcelist = getenv("CLIENT_TEST_SOURCES");
177         set<string> sources;
178         if (sourcelist) {
179             boost::split(sources, sourcelist, boost::is_any_of(","));
180         } else {
181             BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) {
182                 sources.insert(test->m_configName);
183             }
184         }
185
186         BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) {
187             if (sources.find(test->m_configName) != sources.end()) {
188                 m_source2Config.push_back(test->m_configName);
189             }
190         }
191
192         // get configuration and set obligatory fields
193         LoggerBase::instance().setLevel(Logger::DEBUG);
194         std::string root = std::string("evolution/") + server + "_" + id;
195         boost::shared_ptr<SyncConfig> config(new SyncConfig(string(server) + "_" + id));
196         boost::shared_ptr<SyncConfig> from = boost::shared_ptr<SyncConfig> ();
197
198         if (!config->exists()) {
199             // no configuration yet, create in different contexts because
200             // device ID is different
201             config.reset(new SyncConfig(string(server) + "_" + id + "@client-test-" + id));
202             config->setDefaults();
203             from = SyncConfig::createServerTemplate(server);
204             if(from) {
205                 set<string> filter;
206                 config->copy(*from, &filter);
207             }
208             config->setDevID(id == "1" ? "sc-api-nat" : "sc-pim-ppc");
209         }
210         BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) {
211             ClientTest::Config testconfig;
212             getSourceConfig(test, testconfig);
213             CPPUNIT_ASSERT(testconfig.type);
214
215             boost::shared_ptr<SyncSourceConfig> sc = config->getSyncSourceConfig(testconfig.sourceName);
216             if (!sc || !sc->exists()) {
217                 // no configuration yet
218                 config->setSourceDefaults(testconfig.sourceName);
219                 sc = config->getSyncSourceConfig(testconfig.sourceName);
220                 CPPUNIT_ASSERT(sc);
221                 sc->setURI(testconfig.uri);
222                 if(from && testconfig.sourceNameServerTemplate){
223                     boost::shared_ptr<SyncSourceConfig> scServerTemplate = from->getSyncSourceConfig(testconfig.sourceNameServerTemplate);
224                     sc->setURI(scServerTemplate->getURI());
225                 }
226                 sc->setSourceType(testconfig.type);
227             }
228
229             // always set these properties: they might have changed since the last run
230             string database = getDatabaseName(test->m_configName);
231             sc->setDatabaseID(database);
232             sc->setUser(m_evoUser);
233             sc->setPassword(m_evoPassword);
234         }
235         config->flush();
236     }
237
238     virtual LocalTests *createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co) {
239         return new EvolutionLocalTests(name, *this, sourceParam, co);
240     }
241
242     virtual int getNumSources() {
243         return m_source2Config.size();
244     }
245
246     virtual void getSourceConfig(int source, Config &config) {
247         getSourceConfig(m_configs[m_source2Config[source]], config);
248     }
249
250     static void getSourceConfig(const RegisterSyncSourceTest *test, Config &config) {
251         memset(&config, 0, sizeof(config));
252         ClientTest::getTestData(test->m_testCaseName.c_str(), config);
253         config.createSourceA = createSource;
254         config.createSourceB = createSource;
255         config.sourceName = test->m_configName.c_str();
256
257         test->updateConfig(config);
258     }
259
260     virtual ClientTest *getClientB() {
261         return m_clientB.get();
262     }
263
264     virtual bool isB64Enabled() {
265         return false;
266     }
267
268     virtual SyncMLStatus doSync(const int *sources,
269                                 const std::string &logbase,
270                                 const SyncOptions &options)
271     {
272         string server = getenv("CLIENT_TEST_SERVER") ? getenv("CLIENT_TEST_SERVER") : "funambol";
273         server += "_";
274         server += m_clientID;
275         
276         class ClientTest : public SyncContext {
277         public:
278             ClientTest(const string &server,
279                        const string &logbase,
280                        const SyncOptions &options) :
281                 SyncContext(server, false),
282                 m_logbase(logbase),
283                 m_options(options),
284                 m_started(false)
285                 {}
286
287         protected:
288             virtual void prepare() {
289                 setLogDir(m_logbase, true);
290                 setMaxLogDirs(0, true);
291                 setMaxObjSize(m_options.m_maxObjSize, true);
292                 setMaxMsgSize(m_options.m_maxMsgSize, true);
293                 setWBXML(m_options.m_isWBXML, true);
294                 SyncContext::prepare();
295             }
296
297             virtual void displaySyncProgress(sysync::TProgressEventEnum type,
298                                              int32_t extra1, int32_t extra2, int32_t extra3)
299             {
300                 if (!m_started) {
301                     m_started = true;
302                     if (m_options.m_startCallback(*this, m_options)) {
303                         m_options.m_isAborted = true;
304                     }
305                 }
306             }
307
308             virtual bool checkForAbort() { return m_options.m_isAborted; }
309             virtual bool checkForSuspend() {return m_options.m_isSuspended;}
310
311             virtual boost::shared_ptr<TransportAgent> createTransportAgent()
312             {
313                 boost::shared_ptr<TransportAgent>wrapper = m_options.m_transport;
314                 boost::shared_ptr<TransportAgent>agent =SyncContext::createTransportAgent();
315                 if (!wrapper.get())
316                     return agent;
317                 dynamic_cast<TransportWrapper*>(wrapper.get())->setAgent(agent);
318                 dynamic_cast<TransportWrapper*>(wrapper.get())->setSyncOptions(&m_options);
319                 return wrapper;
320             }
321
322         private:
323             const string m_logbase;
324             SyncOptions m_options;
325             bool m_started;
326         } client(server, logbase, options);
327
328         // configure active sources with the desired sync mode,
329         // disable the rest
330         FilterConfigNode::ConfigFilter filter;
331         filter[SyncSourceConfig::m_sourcePropSync.getName()] = "none";
332         client.setConfigFilter(false, "", filter);
333         filter[SyncSourceConfig::m_sourcePropSync.getName()] =
334             PrettyPrintSyncMode(options.m_syncMode);
335         for(int i = 0; sources[i] >= 0; i++) {
336             client.setConfigFilter(false, m_source2Config[sources[i]], filter);
337         }
338
339         SyncReport report;
340         SyncMLStatus status = client.sync(&report);
341         options.m_checkReport.check(status, report);
342         return status;
343     }
344   
345 private:
346     string m_clientID;
347     std::auto_ptr<TestEvolution> m_clientB;
348     const TestRegistry &m_configs;
349
350     /** prefix, username, password to be used for local databases */
351     string m_evoPrefix, m_evoUser, m_evoPassword;
352
353     /**
354      * The ClientTest framework identifies active configs with an integer.
355      * This is the mapping to the corresponding config name, created when
356      * constructing this instance.
357      */
358     vector<string> m_source2Config;
359
360     /** returns the name of the Evolution database */
361     string getDatabaseName(const string &configName) {
362         return m_evoPrefix + configName + "_" + m_clientID;
363     }
364     
365     static TestingSyncSource *createSource(ClientTest &client, int source, bool isSourceA) {
366         TestEvolution &evClient((TestEvolution &)client);
367         string name = evClient.m_source2Config[source];
368         string database = evClient.getDatabaseName(name);
369
370         SyncConfig config("client-test-changes");
371         SyncSourceNodes nodes = config.getSyncSourceNodes(name,
372                                                           string("_") + ((TestEvolution &)client).m_clientID +
373                                                           "_" + (isSourceA ? "A" : "B"));
374
375         // always set this property: the name might have changes since last test run
376         nodes.getProperties()->setProperty("evolutionsource", database.c_str());
377         nodes.getProperties()->setProperty("evolutionuser", evClient.m_evoUser.c_str());
378         nodes.getProperties()->setProperty("evolutionpassword", evClient.m_evoPassword.c_str());
379
380         SyncSourceParams params(name,
381                                 nodes);
382
383         const RegisterSyncSourceTest *test = evClient.m_configs[name];
384         ClientTestConfig testConfig;
385         getSourceConfig(test, testConfig);
386
387         PersistentSyncSourceConfig sourceConfig(params.m_name, params.m_nodes);
388         sourceConfig.setSourceType(testConfig.type);
389
390         // downcasting here: anyone who registers his sources for testing
391         // must ensure that they are indeed TestingSyncSource instances
392         SyncSource *ss = SyncSource::createSource(params);
393         return static_cast<TestingSyncSource *>(ss);
394     }
395 };
396
397 static void handler(int sig)
398 {
399     void *buffer[100];
400     int size;
401     
402     fprintf(stderr, "\ncaught signal %d\n", sig);
403     fflush(stderr);
404 #ifdef HAVE_EXECINFO_H
405     size = backtrace(buffer, sizeof(buffer)/sizeof(buffer[0]));
406     backtrace_symbols_fd(buffer, size, 2);
407 #endif
408 #ifdef HAVE_VALGRIND_VALGRIND_H
409     VALGRIND_PRINTF_BACKTRACE("\ncaught signal %d\n", sig);
410 #endif
411     /* system("objdump -l -C -d client-test >&2"); */
412     struct sigaction act;
413     memset(&act, 0, sizeof(act));
414     act.sa_handler = SIG_DFL;
415     sigaction(SIGABRT, &act, NULL);
416     abort();
417 }
418
419 static class RegisterTestEvolution {
420 public:
421     RegisterTestEvolution() :
422         testClient("1") {
423         struct sigaction act;
424
425         memset(&act, 0, sizeof(act));
426         act.sa_handler = handler;
427         sigaction(SIGABRT, &act, NULL);
428         sigaction(SIGSEGV, &act, NULL);
429         sigaction(SIGILL, &act, NULL);
430
431 #if defined(HAVE_GLIB)
432         // this is required when using glib directly or indirectly
433         g_type_init();
434         g_thread_init(NULL);
435 #endif
436         EDSAbiWrapperInit();
437         testClient.registerTests();
438     }
439
440 private:
441     TestEvolution testClient;
442     
443 } testEvolution;
444
445 SE_END_CXX