73cd1fd91923b95fca01ee1b716c27f127662dfc
[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_syncSource2Config.push_back(test->m_configName);
189             }
190         }
191
192         /* Local Test SyncSource : remove all virtual datastores, inserting the
193          * sub datastores*/
194         ClientTest::Config conf;
195         BOOST_FOREACH (string source, sources) {
196             getSourceConfig (source, conf);
197             if (conf.subConfigs) {
198                 vector<string> subs;
199                 boost::split (subs, conf.subConfigs, boost::is_any_of(","));
200                 BOOST_FOREACH (string sub, subs) {
201                     m_localSource2Config.push_back (sub);
202                 }
203             } else {
204                 m_localSource2Config.push_back (source);
205             }
206         }
207         // get configuration and set obligatory fields
208         LoggerBase::instance().setLevel(Logger::DEBUG);
209         std::string root = std::string("evolution/") + server + "_" + id;
210         boost::shared_ptr<SyncConfig> config(new SyncConfig(string(server) + "_" + id));
211         boost::shared_ptr<SyncConfig> from = boost::shared_ptr<SyncConfig> ();
212
213         if (!config->exists()) {
214             // no configuration yet, create in different contexts because
215             // device ID is different
216             config.reset(new SyncConfig(string(server) + "_" + id + "@client-test-" + id));
217             config->setDefaults();
218             from = SyncConfig::createPeerTemplate(server);
219             if(from) {
220                 set<string> filter;
221                 config->copy(*from, &filter);
222             }
223             config->setDevID(id == "1" ? "sc-api-nat" : "sc-pim-ppc");
224         }
225         BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) {
226             ClientTest::Config testconfig;
227             getSourceConfig(test, testconfig);
228             CPPUNIT_ASSERT(testconfig.type);
229
230             boost::shared_ptr<SyncSourceConfig> sc = config->getSyncSourceConfig(testconfig.sourceName);
231             if (!sc || !sc->exists()) {
232                 // no configuration yet
233                 config->setSourceDefaults(testconfig.sourceName);
234                 sc = config->getSyncSourceConfig(testconfig.sourceName);
235                 CPPUNIT_ASSERT(sc);
236                 sc->setURI(testconfig.uri);
237                 if(from && testconfig.sourceNameServerTemplate){
238                     boost::shared_ptr<SyncSourceConfig> scServerTemplate = from->getSyncSourceConfig(testconfig.sourceNameServerTemplate);
239                     sc->setURI(scServerTemplate->getURI());
240                 }
241                 sc->setSourceType(testconfig.type);
242             }
243
244             // always set these properties: they might have changed since the last run
245             string database = getDatabaseName(test->m_configName);
246             if (test->m_configName!="super"){
247                 sc->setDatabaseID(database);
248             }
249             sc->setUser(m_evoUser);
250             sc->setPassword(m_evoPassword);
251         }
252         config->flush();
253     }
254
255     virtual LocalTests *createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co) {
256         return new EvolutionLocalTests(name, *this, sourceParam, co);
257     }
258
259     virtual int getNumLocalSources() {
260         return m_localSource2Config.size();
261     }
262
263     virtual int getNumSyncSources() {
264         return m_syncSource2Config.size();
265     }
266
267     virtual void getLocalSourceConfig(int source, Config &config) {
268         getSourceConfig(m_configs[m_localSource2Config[source]], config);
269     }
270
271     virtual void getSyncSourceConfig(int source, Config &config) {
272         getSourceConfig(m_configs[m_syncSource2Config[source]], config);
273     }
274
275     virtual int getLocalSourcePosition(const string &configName) {
276         for (size_t i=0; i< m_localSource2Config.size(); i++) {
277             if(m_localSource2Config[i] == configName) {
278                 return i;
279                 break;
280             }
281         }
282         return -1;
283     }
284
285     virtual void getSourceConfig (const string &configName, Config &config) {
286         return getSourceConfig (m_configs[configName], config);
287     }
288
289     static void getSourceConfig(const RegisterSyncSourceTest *test, Config &config) {
290         memset(&config, 0, sizeof(config));
291         ClientTest::getTestData(test->m_testCaseName.c_str(), config);
292         config.createSourceA = createSource;
293         config.createSourceB = createSource;
294         config.sourceName = test->m_configName.c_str();
295
296         test->updateConfig(config);
297     }
298
299     virtual ClientTest *getClientB() {
300         return m_clientB.get();
301     }
302
303     virtual bool isB64Enabled() {
304         return false;
305     }
306
307     virtual SyncMLStatus doSync(const int *sources,
308                                 const std::string &logbase,
309                                 const SyncOptions &options)
310     {
311         string server = getenv("CLIENT_TEST_SERVER") ? getenv("CLIENT_TEST_SERVER") : "funambol";
312         server += "_";
313         server += m_clientID;
314         
315         class ClientTest : public SyncContext {
316         public:
317             ClientTest(const string &server,
318                        const string &logbase,
319                        const SyncOptions &options) :
320                 SyncContext(server, false),
321                 m_logbase(logbase),
322                 m_options(options),
323                 m_started(false)
324                 {}
325
326         protected:
327             virtual void prepare() {
328                 setLogDir(m_logbase, true);
329                 setMaxLogDirs(0, true);
330                 setMaxObjSize(m_options.m_maxObjSize, true);
331                 setMaxMsgSize(m_options.m_maxMsgSize, true);
332                 setWBXML(m_options.m_isWBXML, true);
333                 SyncContext::prepare();
334             }
335
336             virtual void displaySyncProgress(sysync::TProgressEventEnum type,
337                                              int32_t extra1, int32_t extra2, int32_t extra3)
338             {
339                 if (!m_started) {
340                     m_started = true;
341                     if (m_options.m_startCallback(*this, m_options)) {
342                         m_options.m_isAborted = true;
343                     }
344                 }
345             }
346
347             virtual bool checkForAbort() { return m_options.m_isAborted; }
348             virtual bool checkForSuspend() {return m_options.m_isSuspended;}
349
350             virtual boost::shared_ptr<TransportAgent> createTransportAgent()
351             {
352                 boost::shared_ptr<TransportAgent>wrapper = m_options.m_transport;
353                 boost::shared_ptr<TransportAgent>agent =SyncContext::createTransportAgent();
354                 if (!wrapper.get())
355                     return agent;
356                 dynamic_cast<TransportWrapper*>(wrapper.get())->setAgent(agent);
357                 dynamic_cast<TransportWrapper*>(wrapper.get())->setSyncOptions(&m_options);
358                 return wrapper;
359             }
360
361         private:
362             const string m_logbase;
363             SyncOptions m_options;
364             bool m_started;
365         } client(server, logbase, options);
366
367         // configure active sources with the desired sync mode,
368         // disable the rest
369         FilterConfigNode::ConfigFilter filter;
370         filter[SyncSourceConfig::m_sourcePropSync.getName()] = "none";
371         client.setConfigFilter(false, "", filter);
372         filter[SyncSourceConfig::m_sourcePropSync.getName()] =
373             PrettyPrintSyncMode(options.m_syncMode);
374         for(int i = 0; sources[i] >= 0; i++) {
375             client.setConfigFilter(false, m_syncSource2Config[sources[i]], filter);
376         }
377
378         SyncReport report;
379         SyncMLStatus status = client.sync(&report);
380         options.m_checkReport.check(status, report);
381         return status;
382     }
383   
384 private:
385     string m_clientID;
386     std::auto_ptr<TestEvolution> m_clientB;
387     const TestRegistry &m_configs;
388
389     /** prefix, username, password to be used for local databases */
390     string m_evoPrefix, m_evoUser, m_evoPassword;
391
392     /**
393      * The ClientTest framework identifies active configs with an integer.
394      * This is the mapping to the corresponding config name, created when
395      * constructing this instance.
396      */
397     vector<string> m_localSource2Config;
398     vector<string> m_syncSource2Config;
399
400     /** returns the name of the Evolution database */
401     string getDatabaseName(const string &configName) {
402         return m_evoPrefix + configName + "_" + m_clientID;
403     }
404     
405     static TestingSyncSource *createSource(ClientTest &client, int source, bool isSourceA) {
406         TestEvolution &evClient((TestEvolution &)client);
407         string name = evClient.m_localSource2Config[source];
408         string database = evClient.getDatabaseName(name);
409
410         SyncConfig config("client-test-changes");
411         SyncSourceNodes nodes = config.getSyncSourceNodes(name,
412                                                           string("_") + ((TestEvolution &)client).m_clientID +
413                                                           "_" + (isSourceA ? "A" : "B"));
414
415         // always set this property: the name might have changes since last test run
416         if (name != "super") {
417             nodes.getProperties()->setProperty("evolutionsource", database.c_str());
418         }
419         nodes.getProperties()->setProperty("evolutionuser", evClient.m_evoUser.c_str());
420         nodes.getProperties()->setProperty("evolutionpassword", evClient.m_evoPassword.c_str());
421
422         SyncSourceParams params(name,
423                                 nodes);
424
425         const RegisterSyncSourceTest *test = evClient.m_configs[name];
426         ClientTestConfig testConfig;
427         getSourceConfig(test, testConfig);
428
429         PersistentSyncSourceConfig sourceConfig(params.m_name, params.m_nodes);
430         sourceConfig.setSourceType(testConfig.type);
431
432         // downcasting here: anyone who registers his sources for testing
433         // must ensure that they are indeed TestingSyncSource instances
434         SyncSource *ss = SyncSource::createSource(params);
435         return static_cast<TestingSyncSource *>(ss);
436     }
437 };
438
439 static void handler(int sig)
440 {
441     void *buffer[100];
442     int size;
443     
444     fprintf(stderr, "\ncaught signal %d\n", sig);
445     fflush(stderr);
446 #ifdef HAVE_EXECINFO_H
447     size = backtrace(buffer, sizeof(buffer)/sizeof(buffer[0]));
448     backtrace_symbols_fd(buffer, size, 2);
449 #endif
450 #ifdef HAVE_VALGRIND_VALGRIND_H
451     VALGRIND_PRINTF_BACKTRACE("\ncaught signal %d\n", sig);
452 #endif
453     /* system("objdump -l -C -d client-test >&2"); */
454     struct sigaction act;
455     memset(&act, 0, sizeof(act));
456     act.sa_handler = SIG_DFL;
457     sigaction(SIGABRT, &act, NULL);
458     abort();
459 }
460
461 static class RegisterTestEvolution {
462 public:
463     RegisterTestEvolution() :
464         testClient("1") {
465         struct sigaction act;
466
467         memset(&act, 0, sizeof(act));
468         act.sa_handler = handler;
469         sigaction(SIGABRT, &act, NULL);
470         sigaction(SIGSEGV, &act, NULL);
471         sigaction(SIGILL, &act, NULL);
472
473 #if defined(HAVE_GLIB)
474         // this is required when using glib directly or indirectly
475         g_type_init();
476         g_thread_init(NULL);
477 #endif
478         EDSAbiWrapperInit();
479         testClient.registerTests();
480     }
481
482 private:
483     TestEvolution testClient;
484     
485 } testEvolution;
486
487 SE_END_CXX