buteo testing: enable buteo testing in client-test

To enable it in configuration, use '--enable-buteo-tests'.
To test client-test with buteo to do sync, make sure the
environment variable 'CLIENT_TEST_BUTEO=1' is set.

Three main steps to support buteo testing in client-test,
1) pre-run: storage preparation, clean up buteo logging
files and set corresponding keys in meego-sync-conf.xml.
For qtcontacts, switch backend database files for each
client; For calendar/todo/note, specify different notebook
names for each client.
2) run: re-implement ClientTest.doSync function, which
dbus-calls 'msyncd', the dbus daemon of buteo to do sync.
Listen to needed signals to track its status.
3) post-run: collect its status and summarize its statistics
and convert to internal sync report.

Buteo doesn't support explicit slow-sync mode and refresh-
from-server mode. Add two workarounds for them:
For slow-sync mode, wipe out anchors;
For refresh-from-server, wipe out anchors and clean all local
data and do two-way sync.

To enable buteo testing in your environment, you have to:
1) Add read/write privilege for the directory /etc/sync for
current testing user
2) To test one remote server, make sure the profile xml is put
in $HOME/.sync/profiles/sync. The name of the profile xml should
be set as the server name.
This commit is contained in:
Zhu, Yongsheng 2010-12-07 08:03:01 -05:00 committed by Patrick Ohly
parent d3284d72a8
commit c7e47d8d3c
7 changed files with 918 additions and 10 deletions

View File

@ -80,6 +80,17 @@ AC_ARG_ENABLE(integration-tests,
[enables tests outside of the library (can be used together with normal builds of the library)]),
enable_integration_tests="$enableval", enable_integration_tests="no")
AC_ARG_ENABLE(buteo-tests,
AS_HELP_STRING([--enable-buteo-tests],
[enables tests for Buteo sync framework. Not enabled when neither '--enable-unit-tests' nor '--enable-integration-tests' is specified]),
[ if test "$enable_unit_tests" = "yes" || test "$enable_integration_tests" = "yes" ; then
enable_buteo_tests="$enableval"
else
enable_buteo_tests="no"
fi], enable_buteo_tests="no")
PKG_CHECK_MODULES(BUTEOSYNCPROFILE, [syncprofile], HAVE_SYNCPROFILE=yes, HAVE_SYNCPROFILE=no)
PKG_CHECK_MODULES(BUTEOSYNCCOMMON, [synccommon], HAVE_SYNCCOMMON=yes, HAVE_SYNCCOMMON=no)
AC_ARG_ENABLE(static-cxx,
AS_HELP_STRING([--enable-static-cxx],
[build executables which contain libstdc++ instead of requiring suitable libstdc++.so to run]),
@ -122,8 +133,17 @@ fi
if test "$enable_integration_tests" = "yes"; then
AC_DEFINE(ENABLE_INTEGRATION_TESTS, 1, [enable integration tests inside the final library])
fi
if test "$enable_buteo_tests" = "yes"; then
AC_DEFINE(ENABLE_BUTEO_TESTS, 1, [enable buteo tests])
need_qt_modules="$need_qt_modules dbus xml"
AC_PATH_PROG(SQLITE3, sqlite3)
if test -z "$SQLITE3"; then
AC_ERROR([sqlite3 not found, is required for buteo testing])
fi
fi
AM_CONDITIONAL([ENABLE_UNIT_TESTS], [test "$enable_unit_tests" = "yes"])
AM_CONDITIONAL([ENABLE_TESTING], [test "$enable_unit_tests" = "yes" || test "$enable_integration_tests" = "yes" ])
AM_CONDITIONAL([ENABLE_BUTEO_TESTS], [test "$enable_buteo_tests" = "yes"])
if test $enable_static_cxx == "yes"; then
LIBS="$LIBS -L."
@ -413,6 +433,10 @@ AC_SUBST(KEYRING_LIBS)
AC_SUBST(LIBNOTIFY_CFLAGS)
AC_SUBST(LIBNOTIFY_LIBS)
AC_SUBST(LIBEXECDIR)
AC_SUBST(BUTEOSYNCPROFILE_LIBS)
AC_SUBST(BUTEOSYNCPROFILE_CFLAGS)
AC_SUBST(BUTEOSYNCCOMMON_LIBS)
AC_SUBST(BUTEOSYNCCOMMON_CFLAGS)
DBUS_SERVICES_DIR="${datadir}/dbus-1/services"
AC_SUBST(DBUS_SERVICES_DIR)

View File

@ -1,3 +1,5 @@
include $(top_srcdir)/m4-repo/autotroll.mk
BACKENDS = @BACKENDS@
# start with building nothing and add more targets below
@ -209,6 +211,12 @@ client_test_SOURCES = \
$(CORE_SOURCES)
nodist_client_test_SOURCES = ../test/test.cpp
if ENABLE_BUTEO_TESTS
client_test_SOURCES += client-test-buteo.h
client_test_SOURCES += client-test-buteo.cpp
nodist_client_test_SOURCES += client-test-buteo.moc.cpp
endif
# list of test file base files
#
# Generated files (testcases/ical20.ics.funambol.tem) are derived from
@ -235,10 +243,10 @@ TEST_FILES_PATCHED = $(wildcard testcases/*.tem)
# add files created via patches
CLIENT_LIB_TEST_FILES += $(TEST_FILES_GENERATED)
client_test_CPPFLAGS = -DHAVE_CONFIG_H -DENABLE_INTEGRATION_TESTS -DENABLE_UNIT_TESTS $(AM_CPPFLAGS)
client_test_CXXFLAGS = `cppunit-config --cflags` $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(KEYRING_CFLAGS)
client_test_LDFLAGS = `cppunit-config --libs` `nm syncevo/.libs/libsyncevolution.a | grep funambolAutoRegisterRegistry | sed -e 's/.* /-u /'` $(CORE_LD_FLAGS) $(KEYRING_LIBS)
client_test_LDADD = $(CORE_LDADD) $(SYNTHESIS_ENGINE)
client_test_CPPFLAGS = -DHAVE_CONFIG_H -DENABLE_INTEGRATION_TESTS -DENABLE_UNIT_TESTS $(AM_CPPFLAGS) $(BUTEOSYNCPROFILE_CFLAGS) $(BUTEOSYNCCOMMON_CFLAGS) $(QT_CPPFLAGS)
client_test_CXXFLAGS = `cppunit-config --cflags` $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(KEYRING_CFLAGS) $(BUTEOSYNCPROFILE_CFLAGS) $(BUTEOSYNCCOMMON_CFLAGS) $(filter-out -O2 -g -W -Wall, $(QT_CXXFLAGS))
client_test_LDFLAGS = `cppunit-config --libs` `nm syncevo/.libs/libsyncevolution.a | grep funambolAutoRegisterRegistry | sed -e 's/.* /-u /'` $(CORE_LD_FLAGS) $(KEYRING_LIBS) $(BUTEOSYNCPROFILE_LIBS) $(BUTEOSYNCCOMMON_LIBS) $(QT_LDFLAGS)
client_test_LDADD = $(CORE_LDADD) $(SYNTHESIS_ENGINE) $(BUTEOSYNCPROFILE_LIBS) $(BUTEOSYNCCOMMON_LIBS) $(QT_LIBS)
# These dependencies are intentionally a bit too broad:
# they ensure that all files are in place to *run* client-test.
@ -288,6 +296,8 @@ $(filter-out %.tem, $(filter testcases/%, $(subst $(srcdir)/../test/,,$(CLIENT_L
# runs the binary with out-dated auxiliary files.
client_test_DEPENDENCIES = $(EXTRA_LTLIBRARIES) $(CORE_DEP) $(CLIENT_LIB_TEST_FILES) testcase2patch synccompare templates
CLEANFILES += client-test-buteo.moc.cpp
# copy template directory into current working directory, if not there
# yet
.PHONY: templates

View File

@ -41,6 +41,11 @@
#include <syncevo/VolatileConfigNode.h>
#include <syncevo/declarations.h>
#ifdef ENABLE_BUTEO_TESTS
#include "client-test-buteo.h"
#endif
SE_BEGIN_CXX
/*
@ -319,14 +324,41 @@ public:
return false;
}
#ifdef ENABLE_BUTEO_TESTS
virtual void setup() {
QtContactsSwitcher::prepare(*this);
}
#endif
virtual SyncMLStatus doSync(const int *sources,
const std::string &logbase,
const SyncOptions &options)
{
// check whether using buteo to do sync
const char *buteo = getenv("CLIENT_TEST_BUTEO");
bool useButeo = false;
if (buteo &&
(boost::equals(buteo, "1") || boost::iequals(buteo, "t"))) {
useButeo = true;
}
string server = getenv("CLIENT_TEST_SERVER") ? getenv("CLIENT_TEST_SERVER") : "funambol";
server += "_";
server += m_clientID;
if (useButeo) {
#ifdef ENABLE_BUTEO_TESTS
ButeoTest buteo(*this, server, logbase, options);
buteo.prepareSources(sources, m_syncSource2Config);
SyncReport report;
SyncMLStatus status = buteo.doSync(&report);
options.m_checkReport.check(status, report);
return status;
#else
throw runtime_error("This client-test was built without enabling buteo testing.");
#endif
}
class ClientTest : public CmdlineSyncClient {
public:
ClientTest(const string &server,

624
src/client-test-buteo.cpp Normal file
View File

@ -0,0 +1,624 @@
/*
* Copyright (C) 2010 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "syncevo/util.h"
#include "client-test-buteo.h"
#include <libsyncprofile/SyncResults.h>
#include <libsyncprofile/ProfileEngineDefs.h>
#include <libsyncprofile/Profile.h>
#include <libsyncprofile/SyncProfile.h>
#include <syncmlcommon/SyncMLCommon.h>
#include <QDomDocument>
#include <QtDBus>
using namespace Buteo;
using namespace SyncEvo;
// execute a command. If 'check' is true, throw an exception when
// execution encounters error(s)
static void execCmd(const std::string &cmd, bool check = true)
{
int result = Execute(cmd, ExecuteFlags(EXECUTE_NO_STDERR | EXECUTE_NO_STDOUT));
if (result < 0 && check) {
throw runtime_error("failed to excute command: " + cmd);
}
}
bool ButeoTest::m_inited = false;
QString ButeoTest::m_deviceIds[2];
map<string, string> ButeoTest::m_source2storage;
ButeoTest::ButeoTest(ClientTest &client,
const string &server,
const string &logbase,
const SyncEvo::SyncOptions &options) :
m_client(client), m_server(server), m_logbase(logbase), m_options(options)
{
init();
}
void ButeoTest::init()
{
if (!m_inited) {
m_inited = true;
// generate device ids
for(int i = 0; i < sizeof(m_deviceIds)/sizeof(m_deviceIds[0]); i++) {
QString id;
UUID uuid;
QTextStream(&id) << "sc-pim-" << uuid.c_str();
m_deviceIds[i] = id;
}
// insert source -> storage mappings
m_source2storage.insert(std::make_pair("qt_vcard30", "hcontacts"));
m_source2storage.insert(std::make_pair("kcal_ical20", "hcalendar"));
m_source2storage.insert(std::make_pair("kcal_itodo20", "htodo"));
m_source2storage.insert(std::make_pair("kcal_text", "hnotes"));
//init qcoreapplication to use qt
static const char *argv[] = { "SyncEvolution" };
static int argc = 1;
new QCoreApplication(argc, (char **)argv);
}
}
void ButeoTest::prepareSources(const int *sources,
const vector<string> &source2Config)
{
for(int i = 0; sources[i] >= 0; i++) {
string source = source2Config[sources[i]];
map<string, string>::iterator it = m_source2storage.find(source);
if (it != m_source2storage.end()) {
m_configedSources.insert(it->second);
} else {
throw runtime_error("unsupported source '" + source + "'");
}
}
}
SyncMLStatus ButeoTest::doSync(SyncReport *report)
{
SyncMLStatus status = STATUS_OK;
// kill msyncd
killAllMsyncd();
//set sync options
setupOptions();
// restore qtcontacts if needed
if (inclContacts()) {
QtContactsSwitcher::restoreStorage(m_client);
}
//start msyncd
int pid = startMsyncd();
//kill 'sh' process which is the parent of 'msyncd'
stringstream cmd;
cmd << "kill -9 " << pid;
//run sync
if (!run()) {
execCmd(cmd.str(), false);
killAllMsyncd();
return STATUS_FATAL;
}
execCmd(cmd.str(), false);
killAllMsyncd();
// save qtcontacts if needed
if (inclContacts()) {
QtContactsSwitcher::backupStorage(m_client);
}
//get sync results
genSyncResults(m_syncResults, report);
return report->getStatus();
}
void ButeoTest::setupOptions()
{
// 1. set deviceid, max-message-size options to /etc/sync/meego-sync-conf.xml
QString meegoSyncmlConf = "/etc/sync/meego-syncml-conf.xml";
QFile syncmlFile(meegoSyncmlConf);
if (!syncmlFile.open(QIODevice::ReadOnly)) {
throw runtime_error("can't open syncml config");
}
// don't invoke buteo-syncml API for it doesn't support flushing
QString syncmlContent(syncmlFile.readAll());
syncmlFile.close();
int id = 0;
if (!boost::ends_with(m_server, "_1")) {
id = 1;
}
//specify the db path which saves anchors related info, then we can wipe
//out it if want to slow sync.
replaceElement(syncmlContent, "dbpath", QString((m_server + ".db").c_str()));
replaceElement(syncmlContent, "local-device-name", m_deviceIds[id]);
QString msgSize;
QTextStream(&msgSize) << m_options.m_maxMsgSize;
replaceElement(syncmlContent, "max-message-size", msgSize);
writeToFile(meegoSyncmlConf, syncmlContent);
// 2. set storage 'Notebook Name' for calendar, todo and notes
// for contacts, we have to set corresponding tracker db
string storageDir = getHome() + "/.sync/profiles/storage/";
BOOST_FOREACH(const string &source, m_configedSources) {
if (boost::iequals(source, "hcalendar") ||
boost::iequals(source, "htodo") ||
boost::iequals(source, "hnotes")) {
string filePath = storageDir + source + ".xml";
QDomDocument doc(m_server.c_str());
buildDomFromFile(doc, filePath.c_str());
QString notebookName;
QTextStream(&notebookName) << "client_test_" << id;
Profile profile(doc.documentElement());
profile.setKey("Notebook Name", notebookName);
writeToFile(filePath.c_str(), profile.toString());
}
}
// 3. set wbxml option, sync mode, enabled selected sources and disable other sources
QDomDocument doc(m_server.c_str());
//copy profile
string profileDir = getHome() + "/.sync/profiles/sync/";
string profilePath = profileDir + m_server + ".xml";
size_t pos = m_server.rfind('_');
if (pos != m_server.npos) {
string prefix = m_server.substr(0, pos);
stringstream cmd;
cmd << "cp " << profileDir
<< prefix << ".xml "
<< profilePath;
execCmd(cmd.str());
}
buildDomFromFile(doc, profilePath.c_str());
SyncProfile syncProfile(doc.documentElement());
syncProfile.setName(m_server.c_str());
QList<Profile *> storages = syncProfile.storageProfilesNonConst();
QListIterator<Profile *> it(storages);
while (it.hasNext()) {
Profile * profile = it.next();
set<string>::iterator configedIt = m_configedSources.find(profile->name().toStdString());
if (configedIt != m_configedSources.end()) {
profile->setKey(KEY_ENABLED, "true");
} else {
profile->setKey(KEY_ENABLED, "false");
}
}
// set syncml client
Profile * syncml = syncProfile.subProfile("syncml", "client");
if (syncml) {
// set whether using wbxml
syncml->setBoolKey(PROF_USE_WBXML, m_options.m_isWBXML);
// set sync mode
QString syncMode;
switch(m_options.m_syncMode) {
case SYNC_NONE:
break;
case SYNC_TWO_WAY:
syncMode = VALUE_TWO_WAY;
break;
case SYNC_ONE_WAY_FROM_CLIENT:
// work around here since buteo doesn't support refresh mode now
syncMode = VALUE_TO_REMOTE;
break;
case SYNC_REFRESH_FROM_CLIENT:
// don't support, no workaround here
throw runtime_error("Buteo doesn't support refresh mode");
case SYNC_ONE_WAY_FROM_SERVER:
syncMode = VALUE_FROM_REMOTE;
break;
case SYNC_REFRESH_FROM_SERVER: {
//workaround here since buteo doesn't support refresh-from-server
//wipe out anchors and remove tracker database
//so we will do refresh-from-server by slow sync
stringstream cmd1;
cmd1 << "rm -f " << m_server << ".db";
execCmd(cmd1.str(), false);
if (inclContacts()) {
execCmd("tracker-control -r", false);
stringstream cmd2;
cmd2 << "rm -f "
<< getHome() << "/.cache/tracker/*.db "
<< getHome() << "/.cache/tracker/*.db_"
<< m_client.getClientB() ? "1" : "2";
execCmd(cmd2.str(), false);
}
syncMode = VALUE_TWO_WAY;
break;
}
case SYNC_SLOW: {
//workaround here since buteo doesn't support explicite slow-sync
//wipe out anchors so we will do slow sync
stringstream cmd;
cmd << "rm -f " << m_server << ".db";
execCmd(cmd.str(), false);
syncMode = VALUE_TWO_WAY;
break;
}
default:
break;
}
syncml->setKey(KEY_SYNC_DIRECTION, syncMode);
}
writeToFile(profilePath.c_str(), syncProfile.toString());
}
void ButeoTest::killAllMsyncd()
{
execCmd("killall -9 msyncd", false);
}
int ButeoTest::startMsyncd()
{
int pid = fork();
if (pid == 0) {
//child
stringstream cmd;
cmd << "msyncd >" << m_logbase << ".log 2>&1";
if (execlp("sh", "sh", "-c", cmd.str().c_str(), (char *)0) < 0 ) {
exit(1);
}
} else if (pid < 0) {
throw runtime_error("can't fork process");
}
// wait for msyncd get prepared
execCmd("sleep 2", false);
return pid;
}
bool ButeoTest::run()
{
static const QString msyncdService = "com.meego.msyncd";
static const QString msyncdObject = "/synchronizer";
static const QString msyncdInterface = "com.meego.msyncd";
QDBusConnection conn = QDBusConnection::sessionBus();
std::auto_ptr<QDBusInterface> interface(new QDBusInterface(msyncdService, msyncdObject, msyncdInterface, conn));
if (!interface->isValid()) {
QString error = interface->lastError().message();
return false;
}
// add watcher for watching unregistering service
std::auto_ptr<QDBusServiceWatcher> dbusWatcher(new QDBusServiceWatcher(msyncdService, conn, QDBusServiceWatcher::WatchForUnregistration));
dbusWatcher->connect(dbusWatcher.get(), SIGNAL(serviceUnregistered(QString)),
this, SLOT(serviceUnregistered(QString)));
//connect signals
interface->connect(interface.get(), SIGNAL(syncStatus(QString, int, QString, int)),
this, SLOT(syncStatus(QString, int, QString, int)));
interface->connect(interface.get(), SIGNAL(resultsAvailable(QString, QString)),
this, SLOT(resultsAvailable(QString, QString)));
// start sync
QDBusReply<bool> reply = interface->call(QString("startSync"), m_server.c_str());
if (reply.isValid() && !reply.value()) {
return false;
}
// wait sync completed
return QCoreApplication::exec() == 0;
}
void ButeoTest::genSyncResults(const QString &text, SyncReport *report)
{
QDomDocument domResults;
if (domResults.setContent(text, true)) {
SyncResults syncResults(domResults.documentElement());
switch(syncResults.majorCode()) {
case SyncResults::SYNC_RESULT_SUCCESS:
report->setStatus(STATUS_OK);
break;
case SyncResults::SYNC_RESULT_FAILED:
report->setStatus(STATUS_FATAL);
break;
case SyncResults::SYNC_RESULT_CANCELLED:
report->setStatus(STATUS_FATAL);
break;
};
QList<TargetResults> targetResults = syncResults.targetResults();
QListIterator<TargetResults> it(targetResults);
while (it.hasNext()) {
// get item sync info
TargetResults target = it.next();
SyncSourceReport targetReport;
// temporary set this mode due to no this information in report
targetReport.recordFinalSyncMode(m_options.m_syncMode);
ItemCounts itemCounts = target.localItems();
targetReport.setItemStat(SyncSourceReport::ITEM_LOCAL,
SyncSourceReport::ITEM_ADDED,
SyncSourceReport::ITEM_TOTAL,
itemCounts.added);
targetReport.setItemStat(SyncSourceReport::ITEM_LOCAL,
SyncSourceReport::ITEM_UPDATED,
SyncSourceReport::ITEM_TOTAL,
itemCounts.modified);
targetReport.setItemStat(SyncSourceReport::ITEM_LOCAL,
SyncSourceReport::ITEM_REMOVED,
SyncSourceReport::ITEM_TOTAL,
itemCounts.deleted);
// get item info for remote
itemCounts = target.remoteItems();
targetReport.setItemStat(SyncSourceReport::ITEM_REMOTE,
SyncSourceReport::ITEM_ADDED,
SyncSourceReport::ITEM_TOTAL,
itemCounts.added);
targetReport.setItemStat(SyncSourceReport::ITEM_REMOTE,
SyncSourceReport::ITEM_UPDATED,
SyncSourceReport::ITEM_TOTAL,
itemCounts.modified);
targetReport.setItemStat(SyncSourceReport::ITEM_REMOTE,
SyncSourceReport::ITEM_REMOVED,
SyncSourceReport::ITEM_TOTAL,
itemCounts.deleted);
// set to sync report
report->addSyncSourceReport(target.targetName().toStdString(), targetReport);
}
} else {
report->setStatus(STATUS_FATAL);
}
}
void ButeoTest::syncStatus(QString profile, int status, QString message, int moreDetails)
{
if (profile == m_server.c_str()) {
switch(status) {
case 0: // QUEUED
case 1: // STARTED
case 2: // PROGRESS
break;
case 3: // ERROR
case 5: // ABORTED
QCoreApplication::exit(1);
break;
case 4: // DONE
QCoreApplication::exit(0);
break;
default:
;
}
}
}
void ButeoTest::resultsAvailable(QString profile, QString syncResults)
{
if (profile == m_server.c_str()) {
m_syncResults = syncResults;
}
}
void ButeoTest::serviceUnregistered(QString service)
{
QCoreApplication::exit(1);
}
bool ButeoTest::inclContacts()
{
set<string>::iterator sit = m_configedSources.find("hcontacts");
if (sit != m_configedSources.end()) {
return true;
}
return false;
}
void ButeoTest::writeToFile(const QString &filePath, const QString &content)
{
// clear tempoary file firstly
stringstream rmCmd;
rmCmd << "rm -f " << filePath.toStdString() << "_tmp";
execCmd(rmCmd.str(), false);
// open temporary file and serialize dom to the file
QFile file(filePath + "_tmp");
if (!file.open(QIODevice::WriteOnly)) {
stringstream msg;
msg << "can't open file '" << filePath.toStdString() << "' with 'write' mode";
throw runtime_error(msg.str());
}
if (file.write(content.toUtf8()) == -1) {
file.close();
stringstream msg;
msg << "can't write file '" << filePath.toStdString() << "'";
throw runtime_error(msg.str());
}
file.close();
// move temp file to destination file
stringstream mvCmd;
mvCmd << "mv " << filePath.toStdString() << "_tmp "
<< filePath.toStdString();
execCmd(mvCmd.str());
}
void ButeoTest::replaceElement(QString &xml, const QString &elem, const QString &value)
{
// TODO: use DOM to parse xml
// currently this could work
QString startTag = "<" + elem +">";
QString endTag = "</" + elem +">";
int start = xml.indexOf(startTag);
if ( start == -1) {
return;
}
int end = xml.indexOf(endTag, start);
int pos = start + startTag.size();
xml.replace(pos, end - pos, value);
}
void ButeoTest::buildDomFromFile(QDomDocument &doc, const QString &filePath)
{
// open it
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
stringstream msg;
msg << "can't open profile file '" << filePath.toStdString() << "'";
throw runtime_error(msg.str());
}
// parse it
if (!doc.setContent(&file)) {
file.close();
stringstream msg;
msg << "can't parse profile file '" << filePath.toStdString() << "'";
throw runtime_error(msg.str());
}
file.close();
}
static bool isButeo()
{
static bool checked = false;
static bool useButeo = false;
if (!checked) {
const char *buteo = getenv("CLIENT_TEST_BUTEO");
if (buteo &&
(boost::equals(buteo, "1") || boost::iequals(buteo, "t"))) {
useButeo = true;
}
checked = true;
}
return useButeo;
}
// 3 databases used by tracker to store contacts
// empty string is used as separator
string QtContactsSwitcher::m_databases[] = {"meta.db",
"contents.db",
"fulltext.db", // 3 databases used by tracker
"", // separator
"hcontacts.db" // database to record deleted contact items
};
string QtContactsSwitcher::m_dirs[] = {"/.cache/tracker/",
"/.sync/sync-app/"};
string QtContactsSwitcher::getId(ClientTest &client) {
if (client.getClientB()) {
return "1";
}
return "2";
}
void QtContactsSwitcher::prepare(ClientTest &client) {
int index = 0;
for (int i = 0; i < sizeof(m_databases)/sizeof(m_databases[0]); i++) {
if (m_databases[i].empty()) {
index++;
continue;
}
stringstream cmd;
cmd << "rm -f " << getDatabasePath(index) << m_databases[i]
<< "_";
execCmd(cmd.str() + "1", false);
execCmd(cmd.str() + "2", false);
}
execCmd("tracker-control -r", false);
}
void QtContactsSwitcher::restoreStorage(ClientTest &client)
{
// if CLIENT_TEST_BUTEO is not enabled, skip it for LocalTests may also use it
if (!isButeo()) {
return;
}
string id = getId(client);
terminate();
copyDatabases(client, false);
start();
}
void QtContactsSwitcher::backupStorage(ClientTest &client)
{
// if CLIENT_TEST_BUTEO is not enabled, skip it for LocalTests may also use it
if (!isButeo()) {
return;
}
string id = getId(client);
terminate();
copyDatabases(client);
start();
}
string QtContactsSwitcher::getDatabasePath(int index)
{
string m_path = getHome() + m_dirs[index];
return m_path;
}
void QtContactsSwitcher::copyDatabases(ClientTest &client, bool fromDefault)
{
static string m_cmds[] = {"",
"",
"",
"",
"\"delete from deleteditems;\""};
string id = getId(client);
int index = 0;
for (int i = 0; i < sizeof(m_databases)/sizeof(m_databases[0]); i++) {
if (m_databases[i].empty()) {
index++;
continue;
}
string src = getDatabasePath(index) + m_databases[i];
string dest = src + "_" + id;
if (!fromDefault) {
// in this case, we copy *_1/2.db to default db
// if *_1/2.db doesn't exist, we copy default with initial commands
if (access(dest.c_str(), F_OK) < 0) {
if (access(src.c_str(), F_OK) >= 0) {
if (!m_cmds[i].empty()) {
stringstream temp;
temp << "sqlite3 " << src << " " << m_cmds[i];
execCmd(temp.str(), false);
}
}
} else {
string tmp = src;
src = dest;
dest = tmp;
}
}
stringstream cmd;
cmd << "cp -f " << src << " " << dest;
execCmd(cmd.str(), false);
}
}
void QtContactsSwitcher::terminate()
{
execCmd("tracker-control -t");
}
void QtContactsSwitcher::start()
{
// sleep one second to let tracker daemon get prepared
execCmd("tracker-control -s");
execCmd("sleep 2");
}

159
src/client-test-buteo.h Normal file
View File

@ -0,0 +1,159 @@
/*
* Copyright (C) 2010 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef INCL_SYNC_BUTEOTEST
#define INCL_SYNC_BUTEOTEST
#include <syncevo/util.h>
#include <syncevo/declarations.h>
#include <QString>
#include <QObject>
#include <QtDBus>
#include <QDomDocument>
#include "ClientTest.h"
using namespace SyncEvo;
/**
* ButeoTest is used to invoke buteo to do client test with the help
* of client test framework. The basic idea is to implement doSync and
* replace with invocation of buteo's dbus server - 'msyncd'.
* The main steps are:
* 1) pre-run: This may include set up sync options for local client
* and target server, prepare local databases
* 2) run: run sync by sending dbus calls to 'msyncd' and wait until
* it finishes
* 3) post-run: collect sync result and statistics
*/
class ButeoTest : public QObject {
Q_OBJECT
public:
ButeoTest(ClientTest &client,
const string &server,
const string &logbase,
const SyncEvo::SyncOptions &options);
// prepare sync sources
void prepareSources(const int *sources, const vector<string> &source2Config);
// do actually sync
SyncEvo::SyncMLStatus doSync(SyncEvo::SyncReport *report);
private slots:
void syncStatus(QString, int, QString, int);
void resultsAvailable(QString, QString);
void serviceUnregistered(QString);
private:
/** initialize
*/
static void init();
/**
* 1. set deviceid, max-message-size options to /etc/sync/meego-sync-conf.xml
* 2. set wbxml option, sync mode, enabled selected sources and disable other sources
*/
void setupOptions();
// kill all msyncd
void killAllMsyncd();
// start msyncd
int startMsyncd();
// do actually running
bool run();
// get sync results from buteo and set them to sync report
void genSyncResults(const QString &text, SyncEvo::SyncReport *report);
// whether configured sources include contacts
bool inclContacts();
// truncate file and write content to file
static void writeToFile(const QString &filePath, const QString &content );
//replace the element value with a new value
static void replaceElement(QString &xml, const QString &elem, const QString &value);
// build a dom tree from file
static void buildDomFromFile(QDomDocument &doc, const QString &filePath);
ClientTest &m_client;
string m_server;
string m_logbase;
SyncEvo::SyncOptions m_options;
std::set<string> m_configedSources;
QString m_syncResults;
//device ids
static QString m_deviceIds[2];
//mappings for syncevolution source and buteo storage
static map<string, string> m_source2storage;
//flag for initialization
static bool m_inited;
};
/**
* Qtcontacts use tracker to store data. However, it can't specify
* the place where to store them. Since we have to separate client A
* and B's data, restore and backup their databases
*/
class QtContactsSwitcher {
public:
/** do preparation */
static void prepare(ClientTest &client);
static string getId(ClientTest &client);
/**
* prepare storage:
* 1. terminate tracker
* 2. copy tracker databases from backup to its default place
* according to id
* 3. restart tracker
*/
static void restoreStorage(ClientTest &client);
/**
* backup storage:
* 1. terminate tracker
* 2. copy tracker databases from default place to backup
* 3. restart tracker
*/
static void backupStorage(ClientTest &client);
private:
// get the file path of databases
static std::string getDatabasePath(int index = 0);
//terminate tracker daemons
static void terminate();
//start tracker daemons
static void start();
//copy databases between default place and backup place
static void copyDatabases(ClientTest &client, bool fromDefault = true);
//databases used by tracker
static std::string m_databases[];
static std::string m_dirs[];
};
#endif

View File

@ -55,6 +55,11 @@
#include <boost/bind.hpp>
#include <syncevo/declarations.h>
#ifdef ENABLE_BUTEO_TESTS
#include "client-test-buteo.h"
#endif
SE_BEGIN_CXX
static set<ClientTest::Cleanup_t> cleanupSet;
@ -170,6 +175,24 @@ static std::string importItem(TestingSyncSource *source, const ClientTestConfig
}
}
static void restoreStorage(const ClientTest::Config &config, ClientTest &client)
{
#ifdef ENABLE_BUTEO_TESTS
if (boost::iequals(config.sourceName,"qt_vcard30")) {
QtContactsSwitcher::restoreStorage(client);
}
#endif
}
static void backupStorage(const ClientTest::Config &config, ClientTest &client)
{
#ifdef ENABLE_BUTEO_TESTS
if (boost::iequals(config.sourceName,"qt_vcard30")) {
QtContactsSwitcher::backupStorage(client);
}
#endif
}
/** adds the supported tests to the instance itself */
void LocalTests::addTests() {
if (config.createSourceA) {
@ -236,6 +259,8 @@ void LocalTests::addTests() {
}
std::string LocalTests::insert(CreateSource createSource, const char *data, bool relaxed, std::string *inserted) {
restoreStorage(config, client);
// create source
TestingSyncSourcePtr source(createSource());
@ -264,6 +289,7 @@ std::string LocalTests::insert(CreateSource createSource, const char *data, bool
CPPUNIT_ASSERT(countUpdatedItems(source.get()) == 0);
CPPUNIT_ASSERT(countDeletedItems(source.get()) == 0);
}
backupStorage(config, client);
return res.m_luid;
}
@ -305,6 +331,8 @@ void LocalTests::update(CreateSource createSource, const char *data, bool check)
CPPUNIT_ASSERT(createSource.createSource);
CPPUNIT_ASSERT(data);
restoreStorage(config, client);
// create source
TestingSyncSourcePtr source(createSource());
@ -330,23 +358,29 @@ void LocalTests::update(CreateSource createSource, const char *data, bool check)
SOURCE_ASSERT_NO_FAILURE(source.get(), it = source->getAllItems().begin());
CPPUNIT_ASSERT(it != source->getAllItems().end());
CPPUNIT_ASSERT_EQUAL(luid, *it);
backupStorage(config, client);
}
void LocalTests::update(CreateSource createSource, const char *data, const std::string &luid) {
CPPUNIT_ASSERT(createSource.createSource);
CPPUNIT_ASSERT(data);
restoreStorage(config, client);
// create source
TestingSyncSourcePtr source(createSource());
// update it
SOURCE_ASSERT_NO_FAILURE(source.get(), source->insertItemRaw(luid, config.mangleItem(data).c_str()));
backupStorage(config, client);
}
/** deletes all items locally via sync source */
void LocalTests::deleteAll(CreateSource createSource) {
CPPUNIT_ASSERT(createSource.createSource);
restoreStorage(config, client);
// create source
TestingSyncSourcePtr source(createSource());
@ -363,6 +397,7 @@ void LocalTests::deleteAll(CreateSource createSource) {
CPPUNIT_ASSERT_EQUAL( 0, countNewItems(source.get()) );
CPPUNIT_ASSERT_EQUAL( 0, countUpdatedItems(source.get()) );
CPPUNIT_ASSERT_EQUAL( 0, countDeletedItems(source.get()) );
backupStorage(config, client);
}
/** deletes specific item locally via sync source */
@ -543,6 +578,7 @@ std::list<std::string> LocalTests::insertManyItems(CreateSource createSource, in
CPPUNIT_ASSERT(config.templateItem);
CPPUNIT_ASSERT(config.uniqueProperties);
restoreStorage(config, client);
TestingSyncSourcePtr source;
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
CPPUNIT_ASSERT(startIndex > 1 || !countItems(source.get()));
@ -556,6 +592,7 @@ std::list<std::string> LocalTests::insertManyItems(CreateSource createSource, in
std::string data = createItem(item, "", size);
luids.push_back(importItem(source.get(), config, data));
}
backupStorage(config, client);
return luids;
}
@ -753,8 +790,10 @@ void LocalTests::testImport() {
// import via sync source A
TestingSyncSourcePtr source;
SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
restoreStorage(config, client);
std::string testcases;
SOURCE_ASSERT_EQUAL(source.get(), 0, config.import(client, *source.get(), config, config.testcases, testcases));
backupStorage(config, client);
CPPUNIT_ASSERT_NO_THROW(source.reset());
// export again and compare against original file
@ -2648,9 +2687,16 @@ void SyncTests::testComplexRefreshFromServerSemantic()
testCopy();
// check refresh with one item on server
accessClientB->doSync("refresh-one",
SyncOptions(SYNC_REFRESH_FROM_SERVER,
CheckSyncReport(1,0,1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
const char *value = getenv ("CLIENT_TEST_NOREFRESH");
// If refresh_from_server or refresh_from_client (depending on this is a
// server or client) is not supported, we can still test via slow sync.
if (value) {
accessClientB->refreshClient();
} else {
accessClientB->doSync("refresh-one",
SyncOptions(SYNC_REFRESH_FROM_SERVER,
CheckSyncReport(1,0,1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
}
// delete that item via A, check again
BOOST_FOREACH(source_array_t::value_type &source_pair, sources) {
@ -2659,9 +2705,13 @@ void SyncTests::testComplexRefreshFromServerSemantic()
doSync("delete-item",
SyncOptions(SYNC_TWO_WAY,
CheckSyncReport(0,0,0, 0,0,1, true, SYNC_TWO_WAY)));
accessClientB->doSync("refresh-none",
SyncOptions(SYNC_REFRESH_FROM_SERVER,
CheckSyncReport(0,0,1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
if (value) {
accessClientB->refreshClient();
} else {
accessClientB->doSync("refresh-none",
SyncOptions(SYNC_REFRESH_FROM_SERVER,
CheckSyncReport(0,0,1, 0,0,0, true, SYNC_REFRESH_FROM_SERVER)));
}
}
/**

View File

@ -225,6 +225,9 @@ class ClientTest {
ClientTest(int serverSleepSec = 0, const std::string &serverLog= "");
virtual ~ClientTest();
/** set up before running a test */
virtual void setup() { }
/** cleanup function to be called when shutting down testing */
typedef void (*Cleanup_t)(void);
@ -462,6 +465,9 @@ public:
createSourceB(co.createSourceB, cl, sourceParam, false)
{}
/** set up before running a test */
virtual void setUp() { client.setup(); }
/**
* adds the supported tests to the instance itself;
* this is the function that a derived class can override
@ -587,6 +593,9 @@ public:
/** adds the supported tests to the instance itself */
virtual void addTests();
/** set up before running a test */
virtual void setUp() { client.setup(); }
protected:
/** list with all local test classes for manipulating the sources and their index in the client */
typedef std::vector< std::pair<int, LocalTests *> > source_array_t;