/* * Copyright (C) 2005-2008 Patrick Ohly */ #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_VALGRIND_VALGRIND_H # include #endif #ifdef HAVE_EXECINFO_H # include #endif #include "EvolutionSyncClient.h" #include "EvolutionSyncSource.h" #include "SyncEvolutionUtil.h" /* * always provide this test class, even if not used: * that way the test scripts can unconditionally * invoke "client-test SyncEvolution" */ CPPUNIT_REGISTRY_ADD_TO_DEFAULT("SyncEvolution"); /** * a wrapper class which automatically does an open() in the constructor and a close() in the destructor * and ensures that the sync mode is "none" = testing mode */ class TestEvolutionSyncSource : public EvolutionSyncSource { public: TestEvolutionSyncSource(const string &type, const EvolutionSyncSourceParams ¶ms) : EvolutionSyncSource(params) { PersistentEvolutionSyncSourceConfig config(params.m_name, params.m_nodes); config.setSourceType(type); m_source.reset(EvolutionSyncSource::createSource(params)); CPPUNIT_ASSERT(m_source.get()); m_source->setSyncMode(SYNC_NONE); } virtual int beginSync() throw () { CPPUNIT_ASSERT_NO_THROW(m_source->open()); CPPUNIT_ASSERT(!m_source->hasFailed()); return m_source->beginSync(); } virtual int endSync() throw () { int res = m_source->endSync(); CPPUNIT_ASSERT_NO_THROW(m_source->close()); CPPUNIT_ASSERT(!m_source->hasFailed()); return res; } virtual SyncItem* getFirstItem() throw () { return m_source->getFirstItem(); } virtual SyncItem* getNextItem() throw () { return m_source->getNextItem(); } virtual SyncItem* getFirstNewItem() throw () { return m_source->getFirstNewItem(); } virtual SyncItem* getNextNewItem() throw () { return m_source->getNextNewItem(); } virtual SyncItem* getFirstUpdatedItem() throw () { return m_source->getFirstUpdatedItem(); } virtual SyncItem* getNextUpdatedItem() throw () { return m_source->getNextUpdatedItem(); } virtual SyncItem* getFirstDeletedItem() throw () { return m_source->getFirstDeletedItem(); } virtual SyncItem* getNextDeletedItem() throw () { return m_source->getNextDeletedItem(); } virtual SyncItem* getFirstItemKey() throw () { return m_source->getFirstItemKey(); } virtual SyncItem* getNextItemKey() throw () { return m_source->getNextItemKey(); } virtual void setItemStatus(const char *key, int status) throw () { m_source->setItemStatus(key, status); } virtual int addItem(SyncItem& item) throw () { return m_source->addItem(item); } virtual int updateItem(SyncItem& item) throw () { return m_source->updateItem(item); } virtual int deleteItem(SyncItem& item) throw () { return m_source->deleteItem(item); } virtual int removeAllItems() throw () { return m_source->removeAllItems(); } const char *getName() throw () { return m_source->getName(); } virtual Databases getDatabases() { return m_source->getDatabases(); } virtual void open() { m_source->open(); } virtual SyncItem *createItem(const string &uid) { return m_source->createItem(uid); } virtual void close() { m_source->close(); } virtual void exportData(ostream &out) { m_source->exportData(out); } virtual string fileSuffix() const { return m_source->fileSuffix(); } virtual const char *getMimeType() const { return m_source->getMimeType(); } virtual const char *getMimeVersion() const { return m_source->getMimeVersion(); } virtual const char* getSupportedTypes() const { return m_source->getSupportedTypes(); } virtual void beginSyncThrow(bool needAll, bool needPartial, bool deleteLocal) { m_source->beginSyncThrow(needAll, needPartial, deleteLocal); } virtual void endSyncThrow() { m_source->endSyncThrow(); } virtual int addItemThrow(SyncItem& item) { return m_source->addItemThrow(item); } virtual int updateItemThrow(SyncItem& item) { return m_source->updateItemThrow(item); } virtual int deleteItemThrow(SyncItem& item) { return m_source->deleteItemThrow(item); } virtual void logItem(const string &uid, const string &info, bool debug = false) { m_source->logItem(uid, info, debug); } virtual void logItem(const SyncItem &item, const string &info, bool debug = false) { m_source->logItem(item, info, debug); } auto_ptr m_source; }; class EvolutionLocalTests : public LocalTests { public: EvolutionLocalTests(const std::string &name, ClientTest &cl, int sourceParam, ClientTest::Config &co) : LocalTests(name, cl, sourceParam, co) {} virtual void addTests() { LocalTests::addTests(); #ifdef ENABLE_MAEMO if (config.createSourceA && config.createSourceB && config.templateItem && strstr(config.templateItem, "BEGIN:VCARD") && config.uniqueProperties) { ADD_TEST(EvolutionLocalTests, testOssoDelete); } #endif } private: // insert am item, // overwrite it with an additional X-OSSO-CONTACT-STATE:DELETED as Maemoe address book does, // iterate again and check that our own code deleted the item void testOssoDelete() { // get into clean state with one template item added deleteAll(createSourceA); insert(createSourceA, config.templateItem); // add X-OSSO-CONTACT-STATE:DELETED string item = config.templateItem; const char *comma = strchr(config.uniqueProperties, ':'); size_t offset = item.find(config.uniqueProperties, 0, comma ? comma - config.uniqueProperties : strlen(config.uniqueProperties)); CPPUNIT_ASSERT(offset != item.npos); item.insert(offset, "X-OSSO-CONTACT-STATE:DELETED\n"); update(createSourceA, item.c_str(), false); // opening and preparing the source should delete the item std::auto_ptr source; SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA())); SOURCE_ASSERT(source.get(), source->beginSync() == 0 ); CPPUNIT_ASSERT_EQUAL(0, countItemsOfType(source.get(), TOTAL_ITEMS)); CPPUNIT_ASSERT_EQUAL(0, countItemsOfType(source.get(), NEW_ITEMS)); CPPUNIT_ASSERT_EQUAL(0, countItemsOfType(source.get(), UPDATED_ITEMS)); CPPUNIT_ASSERT_EQUAL(1, countItemsOfType(source.get(), DELETED_ITEMS)); } }; /** * This code uses the ClientTest and and information provided by * the backends in their RegisterSyncSourceTest instances to test * real synchronization with a server. * * Configuration is done by environment variables which indicate which * part below the root node "client-test" of the the configuration tree to use; * beyond that everything needed for synchronization is read from the * configuration tree. * * - CLIENT_TEST_SERVER = maps to name of root node in configuration tree * - CLIENT_TEST_EVOLUTION_PREFIX = a common "evolutionsource" prefix for *all* * sources; the source name followed by "_[12]" * is appended to get unique names * - CLIENT_TEST_EVOLUTION_USER = sets the "evolutionuser" property of all sources * - CLIENT_TEST_EVOLUTION_PASSWORD = sets the "evolutionpassword" property of all sources * - CLIENT_TEST_SOURCES = comma separated list of active sources, * names as selected in their RegisterSyncSourceTest * instances * - CLIENT_TEST_DELAY = number of seconds to sleep between syncs, required * by some servers * - CLIENT_TEST_LOG = logfile name of a server, can be empty: * if given, then the content of that file will be * copied and stored together with the client log * (only works on Unix) * - CLIENT_TEST_NUM_ITEMS = numbers of contacts/events/... to use during * local and sync tests which create artificial * items * * The CLIENT_TEST_SERVER also has another meaning: it is used as hint * by the synccompare.pl script and causes it to automatically ignore * known, acceptable data modifications caused by sending an item to * a server and back again. Currently the script recognizes "funambol", * "scheduleworld", "synthesis" and "egroupware" as special server * names. */ class TestEvolution : public ClientTest { public: /** * can be instantiated as client A with id == "1" and client B with id == "2" */ TestEvolution(const string &id) : ClientTest(getenv("CLIENT_TEST_DELAY") ? atoi(getenv("CLIENT_TEST_DELAY")) : 0, getenv("CLIENT_TEST_LOG") ? getenv("CLIENT_TEST_LOG") : ""), m_clientID(id), m_configs(EvolutionSyncSource::getTestRegistry()) { const char *server = getenv("CLIENT_TEST_SERVER"); if (id == "1") { m_clientB.reset(new TestEvolution("2")); } /* check server */ if (!server) { server = "funambol"; setenv("CLIENT_TEST_SERVER", "funambol", 1); } /* override Evolution database names? */ const char *evoprefix = getenv("CLIENT_TEST_EVOLUTION_PREFIX"); m_evoPrefix = evoprefix ? evoprefix : "SyncEvolution_Test_"; const char *evouser = getenv("CLIENT_TEST_EVOLUTION_USER"); if (evouser) { m_evoUser = evouser; } const char *evopasswd = getenv("CLIENT_TEST_EVOLUTION_PASSWORD"); if (evopasswd) { m_evoPassword = evopasswd; } /* check sources */ const char *sourcelist = getenv("CLIENT_TEST_SOURCES"); set sources; if (sourcelist) { boost::split(sources, sourcelist, boost::is_any_of(",")); } else { BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) { sources.insert(test->m_configName); } } BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) { if (sources.find(test->m_configName) != sources.end()) { m_source2Config.push_back(test->m_configName); } } // get configuration and set obligatory fields LOG.setLevel(LOG_LEVEL_DEBUG); std::string root = std::string("evolution/") + server + "_" + id; EvolutionSyncConfig config(string(server) + "_" + id); if (!config.exists()) { // no configuration yet config.setDefaults(); config.setDevID(id == "1" ? "sc-api-nat" : "sc-pim-ppc"); } BOOST_FOREACH(const RegisterSyncSourceTest *test, m_configs) { ClientTest::Config testconfig; getSourceConfig(test, testconfig); CPPUNIT_ASSERT(testconfig.type); boost::shared_ptr sc = config.getSyncSourceConfig(testconfig.sourceName); if (!sc || !sc->exists()) { // no configuration yet config.setSourceDefaults(testconfig.sourceName); sc = config.getSyncSourceConfig(testconfig.sourceName); CPPUNIT_ASSERT(sc); sc->setURI(testconfig.uri); sc->setSourceType(testconfig.type); } // always set these properties: they might have changed since the last run string database = getDatabaseName(test->m_configName); sc->setDatabaseID(database); sc->setUser(m_evoUser); sc->setPassword(m_evoPassword); } config.flush(); } virtual LocalTests *createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co) { return new EvolutionLocalTests(name, *this, sourceParam, co); } virtual int getNumSources() { return m_source2Config.size(); } virtual void getSourceConfig(int source, Config &config) { getSourceConfig(m_configs[m_source2Config[source]], config); } static void getSourceConfig(const RegisterSyncSourceTest *test, Config &config) { memset(&config, 0, sizeof(config)); ClientTest::getTestData(test->m_testCaseName.c_str(), config); config.createSourceA = createSource; config.createSourceB = createSource; config.compare = compare; config.sourceName = test->m_configName.c_str(); test->updateConfig(config); } virtual ClientTest *getClientB() { return m_clientB.get(); } virtual bool isB64Enabled() { return false; } virtual int sync( const int *sources, SyncMode syncMode, const CheckSyncReport &checkReport, long maxMsgSize = 0, long maxObjSize = 0, bool loSupport = false, const char *encoding = NULL) { set activeSources; for(int i = 0; sources[i] >= 0; i++) { activeSources.insert(m_source2Config[sources[i]]); } string server = getenv("CLIENT_TEST_SERVER") ? getenv("CLIENT_TEST_SERVER") : "funambol"; server += "_"; server += m_clientID; class ClientTest : public EvolutionSyncClient { public: ClientTest(const string &server, const set &activeSources, SyncMode syncMode, long maxMsgSize, long maxObjSize, bool loSupport, const char *encoding) : EvolutionSyncClient(server, false, activeSources), m_syncMode(syncMode), m_maxMsgSize(maxMsgSize), m_maxObjSize(maxObjSize), m_loSupport(loSupport), m_encoding(encoding) {} protected: virtual void prepare(SyncSource **sources) { for (SyncSource **source = sources; *source; source++) { ((EvolutionSyncSource *)*source)->setEncoding(m_encoding ? m_encoding : "", true); (*source)->setPreferredSyncMode(m_syncMode); } setLoSupport(m_loSupport, true); setMaxObjSize(m_maxObjSize, true); setMaxMsgSize(m_maxMsgSize, true); EvolutionSyncClient::prepare(sources); } private: const SyncMode m_syncMode; const long m_maxMsgSize; const long m_maxObjSize; const bool m_loSupport; const char *m_encoding; } client(server, activeSources, syncMode, maxMsgSize, maxObjSize, loSupport, encoding); int res = client.sync(); CPPUNIT_ASSERT(client.getSyncReport()); checkReport.check(res, *client.getSyncReport()); return res; } static bool compare(ClientTest &client, const char *fileA, const char *fileB) { std::string cmdstr = std::string("./synccompare ") + fileA + " " + fileB; return system(cmdstr.c_str()) == 0; } private: string m_clientID; std::auto_ptr m_clientB; const TestRegistry &m_configs; /** prefix, username, password to be used for local databases */ string m_evoPrefix, m_evoUser, m_evoPassword; /** * The ClientTest framework identifies active configs with an integer. * This is the mapping to the corresponding config name, created when * constructing this instance. */ vector m_source2Config; /** returns the name of the Evolution database */ string getDatabaseName(const string &configName) { return m_evoPrefix + configName + "_" + m_clientID; } static SyncSource *createSource(ClientTest &client, int source, bool isSourceA) { TestEvolution &evClient((TestEvolution &)client); string changeID = "SyncEvolution Change ID #"; string name = evClient.m_source2Config[source]; changeID += isSourceA ? "1" : "2"; string database = evClient.getDatabaseName(name); EvolutionSyncConfig config("client-test-changes"); SyncSourceNodes nodes = config.getSyncSourceNodes(name, string("_") + ((TestEvolution &)client).m_clientID + "_" + (isSourceA ? "A" : "B")); // always set this property: the name might have changes since last test run nodes.m_configNode->setProperty("evolutionsource", database.c_str()); nodes.m_configNode->setProperty("evolutionuser", evClient.m_evoUser.c_str()); nodes.m_configNode->setProperty("evolutionpassword", evClient.m_evoPassword.c_str()); EvolutionSyncSourceParams params(name, nodes, changeID); const RegisterSyncSourceTest *test = evClient.m_configs[name]; ClientTestConfig testConfig; getSourceConfig(test, testConfig); SyncSource *ss = new TestEvolutionSyncSource(testConfig.type, params); return ss; } }; static void handler(int sig) { void *buffer[100]; int size; fprintf(stderr, "\ncaught signal %d\n", sig); fflush(stderr); #ifdef HAVE_EXECINFO_H size = backtrace(buffer, sizeof(buffer)/sizeof(buffer[0])); backtrace_symbols_fd(buffer, size, 2); #endif #ifdef HAVE_VALGRIND_VALGRIND_H VALGRIND_PRINTF_BACKTRACE("\ncaught signal %d\n", sig); #endif /* system("objdump -l -C -d client-test >&2"); */ struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = SIG_DFL; sigaction(SIGABRT, &act, NULL); abort(); } static class RegisterTestEvolution { public: RegisterTestEvolution() : testClient("1") { struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = handler; sigaction(SIGABRT, &act, NULL); sigaction(SIGSEGV, &act, NULL); sigaction(SIGILL, &act, NULL); #if defined(HAVE_GLIB) && defined(HAVE_EDS) // this is required on Maemo and does not harm either on a normal // desktop system with Evolution g_type_init(); #endif EDSAbiWrapperInit(); testClient.registerTests(); } private: TestEvolution testClient; } testEvolution; int RegisterSyncSourceTest::dump(ClientTest &client, SyncSource &source, const char *file) { std::ofstream out(file); ((EvolutionSyncSource &)source).exportData(out); out.close(); return out.bad(); }