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:
Patrick Ohly 2006-12-01 22:49:15 +00:00
parent 6f729542e6
commit 768464c846
3 changed files with 1021 additions and 0 deletions

547
test/ClientTest.cpp Normal file
View File

@ -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
}
}

252
test/ClientTest.h Normal file
View File

@ -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

222
test/client-test-main.cpp Normal file
View File

@ -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;
}
}