added a C++ client test framework, moved test runner to new 'test' directory
git-svn-id: https://core.forge.funambol.org/svn/core/top-level/trunk/3x/client-api/native@11726 e8e8ed6c-164c-0410-afcf-9e9a7c7d8c10
This commit is contained in:
parent
6f729542e6
commit
768464c846
|
@ -0,0 +1,547 @@
|
|||
/*
|
||||
* Copyright (C) 2003-2006 Funambol
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include "ClientTest.h"
|
||||
#include "base/test.h"
|
||||
|
||||
#ifdef ENABLE_INTEGRATION_TESTS
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
/** execute _x and the status of _source afterwards */
|
||||
#define SOURCE_ASSERT_NO_FAILURE(_source, _x) \
|
||||
{ \
|
||||
CPPUNIT_ASSERT_NO_THROW(_x); \
|
||||
/* TODO: check _source */ \
|
||||
}
|
||||
|
||||
/** check _x and the status of _source afterwards */
|
||||
#define SOURCE_ASSERT(_source, _x) \
|
||||
{ \
|
||||
CPPUNIT_ASSERT(_x); \
|
||||
/* TODO: check _source */ \
|
||||
}
|
||||
|
||||
/** same as SOURCE_ASSERT() with a specific failure message */
|
||||
#define SOURCE_ASSERT_MESSAGE(_message, _source, _x) \
|
||||
{ \
|
||||
CPPUNIT_ASSERT((_message), (_x)); \
|
||||
/* TODO: check _source */ \
|
||||
}
|
||||
|
||||
/** utility function to iterate over different kinds of items in a sync source */
|
||||
static int countAnyItems(
|
||||
SyncSource &source,
|
||||
SyncItem * (SyncSource::*first)(),
|
||||
SyncItem * (SyncSource::*next)() )
|
||||
{
|
||||
SyncItem *item;
|
||||
int count = 0;
|
||||
#if 0
|
||||
CPPUNIT_ASSERT( !source.hasFailed() );
|
||||
#endif
|
||||
SOURCE_ASSERT_NO_FAILURE( source, item = (source.*first)() );
|
||||
while ( item ) {
|
||||
count++;
|
||||
delete item;
|
||||
SOURCE_ASSERT_NO_FAILURE( source, item = (source.*next)() );
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int countNewItems( SyncSource &source )
|
||||
{
|
||||
int res = countAnyItems(
|
||||
source,
|
||||
&SyncSource::getFirstNewItem,
|
||||
&SyncSource::getNextNewItem );
|
||||
return res;
|
||||
}
|
||||
|
||||
static int countUpdatedItems( SyncSource &source )
|
||||
{
|
||||
int res = countAnyItems(
|
||||
source,
|
||||
&SyncSource::getFirstUpdatedItem,
|
||||
&SyncSource::getNextUpdatedItem );
|
||||
return res;
|
||||
}
|
||||
|
||||
static int countDeletedItems( SyncSource &source )
|
||||
{
|
||||
int res = countAnyItems(
|
||||
source,
|
||||
&SyncSource::getFirstDeletedItem,
|
||||
&SyncSource::getNextDeletedItem );
|
||||
return res;
|
||||
}
|
||||
|
||||
static int countItems( SyncSource &source )
|
||||
{
|
||||
int res = countAnyItems(
|
||||
source,
|
||||
&SyncSource::getFirstItem,
|
||||
&SyncSource::getNextItem );
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* convenience macro for adding a test name like a function,
|
||||
* to be used inside an instance of that class
|
||||
*
|
||||
* @param _class class which contains the function
|
||||
* @param _function a function without parameters in that class
|
||||
*/
|
||||
#define ADD_TEST(_class, _function) \
|
||||
addTest(new CppUnit::TestCaller<_class>(getName() + "::" #_function, &_class::_function, *this))
|
||||
|
||||
/**
|
||||
* local test of one sync source and utility functions also used by
|
||||
* sync tests
|
||||
*/
|
||||
class LocalTests : public CppUnit::TestSuite, public CppUnit::TestFixture {
|
||||
public:
|
||||
/** the client we are testing */
|
||||
ClientTest &client;
|
||||
|
||||
/** configuration for the source that we are testing in that client */
|
||||
ClientTest::Config config;
|
||||
|
||||
LocalTests(const std::string &name, ClientTest &cl, ClientTest::Config &co) :
|
||||
CppUnit::TestSuite(name),
|
||||
client(cl),
|
||||
config(co) {}
|
||||
|
||||
/** adds the supported tests to the instance itself */
|
||||
void addTests() {
|
||||
if (config.createSourceA) {
|
||||
ADD_TEST(LocalTests, testOpen);
|
||||
if (config.insertItem) {
|
||||
ADD_TEST(LocalTests, testSimpleInsert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* opens source and inserts the given item; can be called
|
||||
* regardless whether the data source already contains items or not
|
||||
*/
|
||||
void insert(ClientTest::Config::createsource_t createSource, const char *data) {
|
||||
CPPUNIT_ASSERT(createSource);
|
||||
std::auto_ptr<SyncSource> source((*createSource)(client));
|
||||
CPPUNIT_ASSERT(source.get() != NULL);
|
||||
SOURCE_ASSERT(source, source->beginSync() == 0);
|
||||
int numItems;
|
||||
CPPUNIT_ASSERT_NO_THROW(numItems = countItems(*source));
|
||||
SyncItem item;
|
||||
item.setData(data, strlen(data));
|
||||
int status;
|
||||
SOURCE_ASSERT_NO_FAILURE(source, status = source->addItem(item));
|
||||
CPPUNIT_ASSERT(item.getKey() != NULL);
|
||||
CPPUNIT_ASSERT(strlen(item.getKey()) > 0);
|
||||
CPPUNIT_ASSERT_NO_THROW(source.reset());
|
||||
|
||||
// two possible results:
|
||||
// - a new item was added
|
||||
// - the item was matched against an existing one
|
||||
CPPUNIT_ASSERT_NO_THROW(source.reset((*createSource)(client)));
|
||||
CPPUNIT_ASSERT(source.get() != NULL);
|
||||
SOURCE_ASSERT(source, source->beginSync() == 0);
|
||||
CPPUNIT_ASSERT(status == STC_OK || status == STC_CONFLICT_RESOLVED_WITH_MERGE);
|
||||
CPPUNIT_ASSERT(countItems(*source) == numItems +
|
||||
(status == STC_CONFLICT_RESOLVED_WITH_MERGE ? 0 : 1));
|
||||
CPPUNIT_ASSERT(countNewItems(*source.get()) == 0);
|
||||
CPPUNIT_ASSERT(countUpdatedItems(*source.get()) == 0);
|
||||
CPPUNIT_ASSERT(countDeletedItems(*source.get()) == 0);
|
||||
CPPUNIT_ASSERT_NO_THROW(source.reset());
|
||||
#if 0
|
||||
SyncItem *sameItem;
|
||||
SOURCE_ASSERT_NO_FAILURE(
|
||||
source,
|
||||
sameItem = source.createItem(item.getKey(), item.getState()));
|
||||
CPPUNIT_ASSERT(sameItem != NULL);
|
||||
CPPUNIT_ASSERT(!strcmp( sameItem->getKey(), item.getKey()));
|
||||
delete sameItem;
|
||||
#endif
|
||||
}
|
||||
|
||||
/** assumes that one element is currently inserted and updates it with the given item */
|
||||
void update(SyncSource &source, const char *data) {
|
||||
}
|
||||
|
||||
/** deletes all items locally via sync source */
|
||||
void deleteAllLocal(SyncSource &source);
|
||||
|
||||
// creating sync source
|
||||
void testOpen() {
|
||||
// check requirements
|
||||
CPPUNIT_ASSERT(config.createSourceA);
|
||||
|
||||
std::auto_ptr<SyncSource> source((*config.createSourceA)(client));
|
||||
CPPUNIT_ASSERT(source.get() != NULL);
|
||||
CPPUNIT_ASSERT_NO_THROW(source.reset());
|
||||
}
|
||||
|
||||
// insert one contact without clearing the source first
|
||||
void testSimpleInsert() {
|
||||
// check requirements
|
||||
CPPUNIT_ASSERT(config.insertItem);
|
||||
CPPUNIT_ASSERT(config.createSourceA);
|
||||
|
||||
insert(config.createSourceA, config.insertItem);
|
||||
}
|
||||
|
||||
// delete all items
|
||||
void testLocalDeleteAll();
|
||||
// restart scanning of items
|
||||
void testIterateTwice();
|
||||
// clean database, then insert
|
||||
void testComplexInsert();
|
||||
// clean database, insert item, update it
|
||||
void testLocalUpdate();
|
||||
// complex sequence of changes
|
||||
void testChanges();
|
||||
// clean database, import file, then export again and compare
|
||||
void testImport();
|
||||
// same as testImport() with immediate delete
|
||||
void testImportDelete();
|
||||
// test change tracking with large number of items
|
||||
void testManyChanges();
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests synchronization with one or more sync sources enabled.
|
||||
* When testing multiple sources at once only the first config
|
||||
* is checked to see which tests can be executed.
|
||||
*/
|
||||
class SyncTests : public CppUnit::TestSuite, public CppUnit::TestFixture {
|
||||
public:
|
||||
/** the client we are testing */
|
||||
ClientTest &client;
|
||||
|
||||
SyncTests(const std::string &name, ClientTest &cl, std::vector<int> sourceIndices) :
|
||||
CppUnit::TestSuite(name),
|
||||
client(cl) {
|
||||
sourceArray = new int[sourceIndices.size() + 1];
|
||||
for (std::vector<int>::iterator it = sourceIndices.begin();
|
||||
it != sourceIndices.end();
|
||||
++it) {
|
||||
ClientTest::Config config;
|
||||
client.getSourceConfig(*it, config);
|
||||
|
||||
if (config.sourceName) {
|
||||
sourceArray[sources.size()] = *it;
|
||||
sources.push_back(std::pair<int,LocalTests *>(*it, new LocalTests(config.sourceName, cl, config)));
|
||||
}
|
||||
}
|
||||
sourceArray[sources.size()] = -1;
|
||||
}
|
||||
|
||||
~SyncTests() {
|
||||
for (source_it it = sources.begin();
|
||||
it != sources.end();
|
||||
++it) {
|
||||
delete it->second;
|
||||
}
|
||||
delete [] sourceArray;
|
||||
}
|
||||
|
||||
/** adds the supported tests to the instance itself */
|
||||
void addTests() {
|
||||
if (sources.size()) {
|
||||
ClientTest::Config &config(sources[0].second->config);
|
||||
|
||||
ADD_TEST(SyncTests, testTwoWaySync);
|
||||
ADD_TEST(SyncTests, testSlowSync);
|
||||
ADD_TEST(SyncTests, testRefreshFromServerSync);
|
||||
ADD_TEST(SyncTests, testRefreshFromClientSync);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/** list with all local test classes for manipulating the sources and their index in the client */
|
||||
std::vector< std::pair<int, LocalTests *> > sources;
|
||||
typedef std::vector< std::pair<int, LocalTests *> >::iterator source_it;
|
||||
|
||||
/** the indices from sources, terminated by -1 (for sync()) */
|
||||
int *sourceArray;
|
||||
|
||||
enum DeleteAllMode {
|
||||
DELETE_ALL_SYNC, /**< make sure client and server are in sync,
|
||||
delete locally,
|
||||
sync again */
|
||||
DELETE_ALL_REFRESH /**< delete locally, refresh server */
|
||||
};
|
||||
|
||||
/** deletes all items locally and on server */
|
||||
void deleteAllSync(ClientTest &client, SyncSource &source, DeleteAllMode mode = DELETE_ALL_SYNC);
|
||||
|
||||
// do a two-way sync without additional checks
|
||||
void testTwoWaySync() {
|
||||
sync(SYNC_TWO_WAY);
|
||||
}
|
||||
// do a slow sync without additional checks
|
||||
void testSlowSync() {
|
||||
sync(SYNC_SLOW);
|
||||
}
|
||||
// do a refresh from server sync without additional checks
|
||||
void testRefreshFromServerSync() {
|
||||
sync(SYNC_REFRESH_FROM_SERVER);
|
||||
}
|
||||
// do a refresh from client sync without additional checks
|
||||
void testRefreshFromClientSync() {
|
||||
sync(SYNC_REFRESH_FROM_CLIENT);
|
||||
}
|
||||
|
||||
// delete all items, locally and on server using two-way sync
|
||||
void testDeleteAllSync();
|
||||
// delete all items, locally and on server using refresh-from-client sync
|
||||
void testDeleteAllRefresh();
|
||||
// test that a refresh sync of an empty server leads to an empty datatbase
|
||||
void testRefreshSemantic();
|
||||
// tests the following sequence of events:
|
||||
// - insert item
|
||||
// - delete all items
|
||||
// - insert one other item
|
||||
// - refresh from client
|
||||
// => no items should now be listed as new, updated or deleted for this client
|
||||
void testRefreshStatus();
|
||||
// test that a two-way sync copies an item from one address book into the other
|
||||
void testCopy();
|
||||
// test that a two-way sync copies updates from database to the other client,
|
||||
// using simple data commonly supported by servers
|
||||
void testUpdate();
|
||||
// test that a two-way sync copies updates from database to the other client,
|
||||
// using data that some, but not all servers support, like adding a second
|
||||
// phone number to a contact
|
||||
void testComplexUpdate();
|
||||
// test that a two-way sync deletes the copy of an item in the other database
|
||||
void testDelete();
|
||||
// test what the server does when it finds that different
|
||||
// fields of the same item have been modified
|
||||
void testMerge();
|
||||
// test what the server does when it has to execute a slow sync
|
||||
// with identical data on client and server:
|
||||
// expected behaviour is that nothing changes
|
||||
void testTwinning();
|
||||
// tests one-way sync from server:
|
||||
// - get both clients and server in sync with no items anywhere
|
||||
// - add one item on first client, copy to server
|
||||
// - add a different item on second client, one-way-from-server
|
||||
// - two-way sync with first client
|
||||
// => one item on first client, two on second
|
||||
// - delete on first client, sync that to second client
|
||||
// via two-way sync + one-way-from-server
|
||||
// => one item left on second client (the one inserted locally)
|
||||
void testOneWayFromServer();
|
||||
// tests one-way sync from client:
|
||||
// - get both clients and server in sync with no items anywhere
|
||||
// - add one item on first client, copy to server
|
||||
// - add a different item on second client, one-way-from-client
|
||||
// - two-way sync with first client
|
||||
// => two items on first client, one on second
|
||||
// - delete on second client, sync that to first client
|
||||
// via one-way-from-client, two-way
|
||||
// => one item left on first client (the one inserted locally)
|
||||
void testOneWayFromClient();
|
||||
// creates several items, transmits them back and forth and
|
||||
// then compares which of them have been preserved
|
||||
void testItems();
|
||||
// tests the following sequence of events:
|
||||
// - both clients in sync with server
|
||||
// - client 1 adds item
|
||||
// - client 1 updates the same item
|
||||
// - client 2 gets item (depending on server, might be flagged as update)
|
||||
// See http://forge.objectweb.org/tracker/?func=detail&atid=100096&aid=305018&group_id=96
|
||||
void testAddUpdate();
|
||||
// test copying with maxMsg and no large object support
|
||||
void testMaxMsg() {
|
||||
doVarSizes(true, false, NULL);
|
||||
}
|
||||
// test copying with maxMsg and large object support
|
||||
void testLargeObject() {
|
||||
doVarSizes(true, true, NULL);
|
||||
}
|
||||
// test copying with maxMsg and large object support using explicit "bin" encoding
|
||||
void testLargeObjectBin() {
|
||||
doVarSizes(true, true, "bin");
|
||||
}
|
||||
// test copying with maxMsg and large object support using B64 encoding
|
||||
void testLargeObjectEncoded() {
|
||||
doVarSizes(true, true, "b64");
|
||||
}
|
||||
|
||||
//
|
||||
// stress tests: execute some of the normal operations,
|
||||
// but with large number of artificially generated items
|
||||
//
|
||||
|
||||
// two-way sync with clean client/server,
|
||||
// followed by slow sync and comparison
|
||||
// via second client
|
||||
void testManyItems();
|
||||
|
||||
|
||||
/**
|
||||
* implements testMaxMsg(), testLargeObject(), testLargeObjectEncoded()
|
||||
* using a sequence of items with varying sizes
|
||||
*/
|
||||
void doVarSizes(bool withMaxMsgSize,
|
||||
bool withLargeObject,
|
||||
const char *encoding);
|
||||
|
||||
|
||||
void sync(SyncMode syncMode,
|
||||
long maxMsgSize = 0,
|
||||
long maxObjSize = 0,
|
||||
bool loSupport = false,
|
||||
const char *encoding = NULL) {
|
||||
int res = 0;
|
||||
std::string logfile = getCurrentTest() + ".client.log";
|
||||
simplifyFilename(logfile);
|
||||
|
||||
remove(logfile.c_str());
|
||||
LOG.setLogName(logfile.c_str());
|
||||
LOG.reset();
|
||||
|
||||
try {
|
||||
res = client.sync(sourceArray,
|
||||
syncMode,
|
||||
maxMsgSize,
|
||||
loSupport,
|
||||
encoding);
|
||||
} catch(...) {
|
||||
/* TODO: EvolutionSyncSource::handleException(); */
|
||||
res = 1;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// make a copy of the server's log (if found), then truncate it
|
||||
if (m_serverLog.size()) {
|
||||
int fd = open( m_serverLog.c_str(), O_RDWR );
|
||||
|
||||
if (fd >= 0) {
|
||||
// let the server finish
|
||||
sleep(m_syncDelay);
|
||||
|
||||
string serverLog = logfile;
|
||||
size_t pos = serverLog.find( "client" );
|
||||
if (pos != serverLog.npos ) {
|
||||
serverLog.replace( pos, 6, "server" );
|
||||
} else {
|
||||
serverLog += ".server.log";
|
||||
}
|
||||
string cmd = string("cp ") + m_serverLog + " " + serverLog;
|
||||
system( cmd.c_str() );
|
||||
ftruncate( fd, 0 );
|
||||
} else {
|
||||
perror( m_serverLog.c_str() );
|
||||
}
|
||||
} else {
|
||||
// let the server finish
|
||||
sleep(m_syncDelay);
|
||||
}
|
||||
#endif
|
||||
|
||||
CPPUNIT_ASSERT( !res );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** generates tests on demand based on what the client supports */
|
||||
class ClientTestFactory : public CppUnit::TestFactory {
|
||||
public:
|
||||
ClientTestFactory(ClientTest &c) :
|
||||
client(c) {}
|
||||
|
||||
virtual CppUnit::Test *makeTest() {
|
||||
int source;
|
||||
CppUnit::TestSuite *alltests = new CppUnit::TestSuite("Client");
|
||||
CppUnit::TestSuite *tests;
|
||||
|
||||
// create local source tests
|
||||
tests = new CppUnit::TestSuite(alltests->getName() + "::Source");
|
||||
for (source=0; source < client.getNumSources(); source++) {
|
||||
ClientTest::Config config;
|
||||
client.getSourceConfig(source, config);
|
||||
if (config.sourceName) {
|
||||
LocalTests *sourcetests =
|
||||
new LocalTests(tests->getName() + "::" + config.sourceName, client, config);
|
||||
sourcetests->addTests();
|
||||
tests->addTest(sourcetests);
|
||||
}
|
||||
}
|
||||
alltests->addTest(tests);
|
||||
tests = NULL;
|
||||
|
||||
// create sync tests with just one source
|
||||
tests = new CppUnit::TestSuite(alltests->getName() + "::Sync");
|
||||
for (source=0; source < client.getNumSources(); source++) {
|
||||
ClientTest::Config config;
|
||||
client.getSourceConfig(source, config);
|
||||
if (config.sourceName) {
|
||||
std::vector<int> sources;
|
||||
sources.push_back(source);
|
||||
SyncTests *synctests =
|
||||
new SyncTests(tests->getName() + "::" + config.sourceName, client, sources);
|
||||
synctests->addTests();
|
||||
tests->addTest(synctests);
|
||||
}
|
||||
}
|
||||
alltests->addTest(tests);
|
||||
tests = NULL;
|
||||
|
||||
return alltests;
|
||||
}
|
||||
|
||||
private:
|
||||
ClientTest &client;
|
||||
};
|
||||
|
||||
#endif // ENABLE_INTEGRATION_TESTS
|
||||
|
||||
void ClientTest::registerTests()
|
||||
{
|
||||
#ifdef ENABLE_INTEGRATION_TESTS
|
||||
factory = (void *)new ClientTestFactory(*this);
|
||||
CppUnit::TestFactoryRegistry::getRegistry().registerFactory((CppUnit::TestFactory *)factory);
|
||||
#endif
|
||||
}
|
||||
|
||||
ClientTest::ClientTest() :
|
||||
factory(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
ClientTest::~ClientTest()
|
||||
{
|
||||
if(factory) {
|
||||
#ifdef ENABLE_INTEGRATION_TESTS
|
||||
CppUnit::TestFactoryRegistry::getRegistry().unregisterFactory((CppUnit::TestFactory *)factory);
|
||||
delete (CppUnit::TestFactory *)factory;
|
||||
factory = NULL;
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* Copyright (C) 2005-2006 Funambol
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef INCL_TESTSYNCCLIENT
|
||||
#define INCL_TESTSYNCCLIENT
|
||||
|
||||
#include "spds/SyncSource.h"
|
||||
|
||||
/**
|
||||
* This is the interface expected by the testing framework for sync
|
||||
* clients. It defines several methods that a derived class must
|
||||
* implement if it wants to use that framework. Note that this class
|
||||
* itself is not derived from SyncClient. This gives a user of this
|
||||
* framework the freedom to implement it in two different ways:
|
||||
* - implement a class derived from both SyncClient and ClientTest
|
||||
* - add testing to an existing subclass of SyncClient by implementing
|
||||
* a ClientTest which uses that subclass
|
||||
*
|
||||
* The client is expected to support change tracking for multiple
|
||||
* servers. Although the framework always always tests against the
|
||||
* same server, for most tests it is necessary to access the database
|
||||
* without affecting the next synchronization with the server. This is
|
||||
* done by asking the client for another sync source which it must
|
||||
* create in a suitable way - pretty much as if the client was
|
||||
* synchronized against different server.
|
||||
*
|
||||
* Furthermore the client is expected to support multiple data sources
|
||||
* of the same kind, f.i. two different address books. This is used to
|
||||
* test full client A <-> server <-> client B synchronizations in some
|
||||
* tests or to check server modifications done by client A with a
|
||||
* synchronization against client B. In those tests client A is mapped
|
||||
* to the first data source and client B to the second one.
|
||||
*
|
||||
* Handling configuration and creating classes is entirely done by the
|
||||
* subclass of ClientTest, the frameworks makes no assumptions
|
||||
* about how this is done. Instead it queries the ClientTest for
|
||||
* properties (like available sync sources) and then creates several
|
||||
* tests.
|
||||
*/
|
||||
class ClientTest {
|
||||
public:
|
||||
ClientTest();
|
||||
virtual ~ClientTest();
|
||||
|
||||
/**
|
||||
* This is the only function provided by ClientTest itself:
|
||||
* it registers tests using this instance of ClientTest for
|
||||
* later use during a test run.
|
||||
*
|
||||
* Theinstance must remain valid until after the tests were
|
||||
* run. To run them use a separate test runner, like the one from
|
||||
* client-test-main.cpp.
|
||||
*/
|
||||
virtual void registerTests();
|
||||
|
||||
/**
|
||||
* Information about a data source. For the sake of simplicity all
|
||||
* items pointed to are owned by the ClientTest and must
|
||||
* remain valid throughout a test session. Not setting a pointer
|
||||
* is okay, but it will disable all tests that need the
|
||||
* information.
|
||||
*/
|
||||
struct Config {
|
||||
/**
|
||||
* The name is used in test names and has to be set.
|
||||
*/
|
||||
const char *sourceName;
|
||||
|
||||
/**
|
||||
* A member function of a subclass which is called to create a
|
||||
* sync source referencing the data. This is used in tests of
|
||||
* the SyncSource API itself as well as in tests which need to
|
||||
* modify or check the data sources used during synchronization.
|
||||
*
|
||||
* The test framework will call beginSync() and then some of
|
||||
* the functions it wants to test. After a successful test it
|
||||
* will call endSync() which is then expected to store all
|
||||
* changes persistently. Creating a sync source again
|
||||
* with the same call should not report any
|
||||
* new/updated/deleted items until such changes are made via
|
||||
* another sync source.
|
||||
*
|
||||
* The instance will be deleted by the caller. Because this
|
||||
* may be in the error case or in an exception handler,
|
||||
* the sync source's desctructor should not thow exceptions.
|
||||
*
|
||||
* @param client the same instance to which this config belongs
|
||||
*/
|
||||
typedef SyncSource *(*createsource_t)(ClientTest &client);
|
||||
|
||||
/**
|
||||
* Creates a sync source which references the primary database;
|
||||
* it may report the same changes as the sync source used during
|
||||
* sync tests.
|
||||
*/
|
||||
createsource_t createSourceA;
|
||||
|
||||
/**
|
||||
* A second sync source also referencing the primary data
|
||||
* source, but configured so that it tracks changes
|
||||
* independently from the the primary sync source.
|
||||
*
|
||||
* In local tests the usage is like this:
|
||||
* - add item via first SyncSource
|
||||
* - iterate over new items in second SyncSource
|
||||
* - check that it lists the added item
|
||||
*
|
||||
* In tests with a server the usage is:
|
||||
* - do a synchronization with the server
|
||||
* - iterate over items in second SyncSource
|
||||
* - check that the total number and number of
|
||||
* added/updated/deleted items is as expected
|
||||
*/
|
||||
createsource_t createSourceB;
|
||||
|
||||
/**
|
||||
* Time the server needs before it correctly handles another
|
||||
* connection. Some servers use time stamps to track modifications
|
||||
* and get confused when connections are made to quickly after
|
||||
* another.
|
||||
*/
|
||||
int serverDelaySeconds;
|
||||
|
||||
/**
|
||||
* The framework can generate vCard and vCalendar/iCalendar items
|
||||
* automatically by copying a template item and modifying certain
|
||||
* properties.
|
||||
*
|
||||
* This is the template for these automatically generated items.
|
||||
*/
|
||||
const char *templateItem;
|
||||
|
||||
/**
|
||||
* This is a colon (:) separated list of properties which need
|
||||
* to be modified in templateItem.
|
||||
*/
|
||||
const char *uniqueProperties;
|
||||
|
||||
/**
|
||||
* This is a single property in templateItem which can be extended
|
||||
* to increase the size of generated items.
|
||||
*/
|
||||
const char *sizeProperty;
|
||||
|
||||
/**
|
||||
* A very simple item that is inserted during basic tests. Ideally
|
||||
* it only contains properties supported by all servers.
|
||||
*/
|
||||
const char *insertItem;
|
||||
|
||||
/**
|
||||
* A slightly modified version of insertItem. If the source has UIDs
|
||||
* embedded into the item data, then both must have the same UID.
|
||||
* Again all servers should better support these modified properties.
|
||||
*/
|
||||
const char *updateItem;
|
||||
|
||||
/**
|
||||
* A more heavily modified version of insertItem. Same UID if necessary,
|
||||
* but can test changes to items only supported by more advanced
|
||||
* servers.
|
||||
*/
|
||||
const char *complexUpdateItem;
|
||||
|
||||
/**
|
||||
* To test merge conflicts two different updates of insertItem are
|
||||
* needed. This is the first such update.
|
||||
*/
|
||||
const char *mergeItem1;
|
||||
|
||||
/**
|
||||
* The second merge update item. To avoid true conflicts it should
|
||||
* update different properties than mergeItem1, but even then servers
|
||||
* usually have problems perfectly merging items. Therefore the
|
||||
* test is run without expecting a certain merge result.
|
||||
*/
|
||||
const char *mergeItem2;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data sources are enumbered from 0 to n-1 for the purpose of
|
||||
* testing. This call returns n.
|
||||
*/
|
||||
virtual int getNumSources() = 0;
|
||||
|
||||
/**
|
||||
* Called to fill the given test source config with information
|
||||
* about a sync source identified by its index. It's okay to only
|
||||
* fill in the available pieces of information and set everything
|
||||
* else to zero.
|
||||
*/
|
||||
virtual void getSourceConfig(int source, Config &config) = 0;
|
||||
|
||||
/**
|
||||
* The instance to use as second client. Returning NULL disables
|
||||
* all checks which require a second client. The returned pointer
|
||||
* must remain valid throughout the life time of the tests.
|
||||
*
|
||||
* The second client must be configured to access the same server
|
||||
* and have data sources which match the ones from the primary
|
||||
* client.
|
||||
*/
|
||||
virtual ClientTest *getClientB() = 0;
|
||||
|
||||
/**
|
||||
* Execute a synchronization with the selected sync sources
|
||||
* and the selected synchronization options. The log file
|
||||
* in LOG has been set up already for the synchronization run
|
||||
* and should not be changed by the client.
|
||||
*
|
||||
* @param sources a -1 terminated array of sync source indices
|
||||
* @param syncMode the synchronization mode to be used
|
||||
* @param maxMsgSize >0: enable the maximum message size, else disable it
|
||||
* @param maxObjSize same as maxMsgSize for maximum object size
|
||||
* @param encoding if non-NULL, then let client library transform all items
|
||||
* into this format
|
||||
*
|
||||
* @return - 0 on success, an error otherwise
|
||||
*/
|
||||
virtual int sync(
|
||||
const int *sources,
|
||||
SyncMode syncMode,
|
||||
long maxMsgSize = 0,
|
||||
long maxObjSize = 0,
|
||||
bool loSupport = false,
|
||||
const char *encoding = NULL) = 0;
|
||||
|
||||
private:
|
||||
/**
|
||||
* really a CppUnit::TestFactory, but declared as void * to avoid
|
||||
* dependencies on the CPPUnit header files: created by
|
||||
* registerTests() and remains valid until the client is deleted
|
||||
*/
|
||||
void *factory;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* Copyright (C) 2005 Patrick Ohly
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include "base/test.h"
|
||||
|
||||
#include <cppunit/CompilerOutputter.h>
|
||||
#include <cppunit/ui/text/TestRunner.h>
|
||||
#include <cppunit/TestListener.h>
|
||||
#include <cppunit/TestResult.h>
|
||||
#include <cppunit/TestFailure.h>
|
||||
#include <cppunit/extensions/TestFactoryRegistry.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
#include <base/Log.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#ifdef HAVE_SIGNAL_H
|
||||
# include <signal.h>
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
using namespace std;
|
||||
|
||||
void simplifyFilename(string &filename)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while (true) {
|
||||
pos = filename.find(":", pos);
|
||||
if (pos == filename.npos ) {
|
||||
break;
|
||||
}
|
||||
filename.replace(pos, 1, "_");
|
||||
}
|
||||
}
|
||||
|
||||
class ClientOutputter : public CppUnit::CompilerOutputter {
|
||||
public:
|
||||
ClientOutputter(CppUnit::TestResultCollector *result, std::ostream &stream) :
|
||||
CompilerOutputter(result, stream) {}
|
||||
void write() {
|
||||
// ensure that output goes to console again
|
||||
LOG.setLogName("test.log");
|
||||
CompilerOutputter::write();
|
||||
}
|
||||
};
|
||||
|
||||
class ClientListener : public CppUnit::TestListener {
|
||||
public:
|
||||
ClientListener() :
|
||||
m_failed(false) {
|
||||
#ifdef HAVE_SIGNAL_H
|
||||
// install signal handler which turns an alarm signal into a runtime exception
|
||||
// to abort tests which run too long
|
||||
const char *alarm = getenv("CLIENT_TEST_ALARM");
|
||||
m_alarmSeconds = alarm ? atoi(alarm) : -1;
|
||||
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_handler = alarmTriggered;
|
||||
action.sa_flags = SA_NOMASK;
|
||||
sigaction(SIGALRM, &action, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void addAllowedFailures(string allowedFailures) {
|
||||
size_t start = 0, end;
|
||||
while ((end = allowedFailures.find(',', start)) != allowedFailures.npos) {
|
||||
size_t len = end - start;
|
||||
if (len) {
|
||||
m_allowedFailures.insert(allowedFailures.substr(start, len));
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
if (allowedFailures.size() > start) {
|
||||
m_allowedFailures.insert(allowedFailures.substr(start));
|
||||
}
|
||||
}
|
||||
|
||||
void startTest (CppUnit::Test *test) {
|
||||
m_currentTest = test->getName();
|
||||
LOG.setLogName("test.log");
|
||||
cerr << m_currentTest;
|
||||
string logfile = m_currentTest + ".log";
|
||||
simplifyFilename(logfile);
|
||||
remove(logfile.c_str());
|
||||
LOG.setLogName(logfile.c_str());
|
||||
m_testFailed = false;
|
||||
|
||||
#ifdef HAVE_SIGNAL_H
|
||||
if (m_alarmSeconds > 0) {
|
||||
alarm(m_alarmSeconds);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void addFailure(const CppUnit::TestFailure &failure) {
|
||||
m_testFailed = true;
|
||||
}
|
||||
|
||||
void endTest (CppUnit::Test *test) {
|
||||
#ifdef HAVE_SIGNAL_H
|
||||
if (m_alarmSeconds > 0) {
|
||||
alarm(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
LOG.setLogName("test.log");
|
||||
if (m_testFailed) {
|
||||
if (m_allowedFailures.find(m_currentTest) == m_allowedFailures.end()) {
|
||||
cerr << " *** failed ***";
|
||||
m_failed = true;
|
||||
} else {
|
||||
cerr << " *** failure ignored ***";
|
||||
}
|
||||
}
|
||||
cerr << "\n";
|
||||
}
|
||||
|
||||
bool hasFailed() { return m_failed; }
|
||||
const string &getCurrentTest() const { return m_currentTest; }
|
||||
|
||||
private:
|
||||
set<string> m_allowedFailures;
|
||||
bool m_failed, m_testFailed;
|
||||
string m_currentTest;
|
||||
int m_alarmSeconds;
|
||||
|
||||
static void alarmTriggered(int signal) {
|
||||
CPPUNIT_ASSERT_MESSAGE(false, "test timed out");
|
||||
}
|
||||
} syncListener;
|
||||
|
||||
const string &getCurrentTest() {
|
||||
return syncListener.getCurrentTest();
|
||||
}
|
||||
|
||||
static void printTests(CppUnit::Test *test, int indention)
|
||||
{
|
||||
if (!test) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name = test->getName();
|
||||
printf("%*s%s\n", indention * 3, "", name.c_str());
|
||||
for (int i = 0; i < test->getChildTestCount(); i++) {
|
||||
printTests(test->getChildTestAt(i), indention+1);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
// Get the top level suite from the registry
|
||||
CppUnit::Test *suite = CppUnit::TestFactoryRegistry::getRegistry().makeTest();
|
||||
|
||||
if (argc >= 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
|
||||
printf("usage: %s [test name]+\n\n"
|
||||
"Without arguments all available tests are run.\n"
|
||||
"Otherwise only the tests or group of tests listed are run.\n"
|
||||
"Here is the test hierarchy of this test program:\n",
|
||||
argv[0]);
|
||||
printTests(suite, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Adds the test to the list of test to run
|
||||
CppUnit::TextUi::TestRunner runner;
|
||||
runner.addTest( suite );
|
||||
|
||||
// Change the default outputter to a compiler error format outputter
|
||||
runner.setOutputter( new ClientOutputter( &runner.result(),
|
||||
std::cerr ) );
|
||||
|
||||
// track current test and failure state
|
||||
const char *allowedFailures = getenv("CLIENT_TEST_FAILURES");
|
||||
if (allowedFailures) {
|
||||
syncListener.addAllowedFailures(allowedFailures);
|
||||
}
|
||||
runner.eventManager().addListener(&syncListener);
|
||||
|
||||
try {
|
||||
// Run the tests.
|
||||
if (argc <= 1) {
|
||||
// all tests
|
||||
runner.run("", false, true, false);
|
||||
} else {
|
||||
// run selected tests individually
|
||||
for (int test = 1; test < argc; test++) {
|
||||
runner.run(argv[test], false, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Return error code 1 if the one of test failed.
|
||||
return syncListener.hasFailed() ? 1 : 0;
|
||||
} catch (invalid_argument e) {
|
||||
// Test path not resolved
|
||||
std::cerr << std::endl
|
||||
<< "ERROR: " << e.what()
|
||||
<< std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue