From f92dd279a1e26dad7507d5d6944567c23834d440 Mon Sep 17 00:00:00 2001 From: deva Date: Thu, 27 May 2010 09:45:12 +0000 Subject: A lot of session handling. A lot of new unit tests. Add of a more structured commit/discard handling. Fix of some wierd line break bugs in journalwriter --- server/src/Makefile.am | 4 + server/src/artefact.cc | 59 +++++++--- server/src/artefact.h | 4 + server/src/configuration.cc | 2 + server/src/configuration.h | 2 + server/src/configurationparser.cc | 6 + server/src/connection.cc | 242 +++++++++++++++++++++++++++++++++++--- server/src/connection.h | 10 +- server/src/connectionpool.cc | 69 +++++++++-- server/src/connectionpool.h | 79 ++++++++++--- server/src/environment.cc | 26 ++-- server/src/journal_commit.cc | 2 +- server/src/journalwriter.cc | 15 ++- server/src/journalwriter.h | 5 + server/src/server.cc | 14 ++- server/src/session.cc | 108 +++++++++++++++-- server/src/session.h | 7 ++ server/src/sessionparser.cc | 110 +++++++++++++++++ server/src/sessionparser.h | 64 ++++++++++ server/src/sessionserialiser.cc | 200 +++++++++++++++++++++++++++++++ server/src/sessionserialiser.h | 53 +++++++++ server/src/transactionhandler.cc | 2 +- server/src/transactionparser.cc | 73 +++++------- 23 files changed, 1025 insertions(+), 131 deletions(-) create mode 100644 server/src/sessionparser.cc create mode 100644 server/src/sessionparser.h create mode 100644 server/src/sessionserialiser.cc create mode 100644 server/src/sessionserialiser.h diff --git a/server/src/Makefile.am b/server/src/Makefile.am index 0a3569f..942ced5 100644 --- a/server/src/Makefile.am +++ b/server/src/Makefile.am @@ -44,6 +44,8 @@ pracrod_SOURCES = \ semaphore.cc \ server.cc \ session.cc \ + sessionparser.cc \ + sessionserialiser.cc \ templatelist.cc \ templateheaderparser.cc \ templateparser.cc \ @@ -126,6 +128,8 @@ EXTRA_DIST = \ semaphore.h \ server.h \ session.h \ + sessionparser.h \ + sessionserialiser.h \ templatelist.h \ templateheaderparser.h \ templateparser.h \ diff --git a/server/src/artefact.cc b/server/src/artefact.cc index 24114f9..f3e739d 100644 --- a/server/src/artefact.cc +++ b/server/src/artefact.cc @@ -34,10 +34,10 @@ Artefact::Artefact() { -#ifndef WITHOUT_PENTOMINOS +#ifndef WITHOUT_ARTEFACT + PRACRO_DEBUG(artefact, "Creating artefact connection %s : %d\n", Conf::artefact_addr.c_str(), Conf::artefact_port); -#endif/*WITHOUT_PENTOMINOS*/ atfh = atf_init(); if(!atfh) PRACRO_ERR(artefact, "Out of memory!\n"); @@ -46,19 +46,26 @@ Artefact::Artefact() Conf::artefact_addr.c_str(), Conf::artefact_port, Conf::artefact_use_ssl); + +#endif/*WITHOUT_ARTEFACT*/ } Artefact::~Artefact() { +#ifndef WITHOUT_ARTEFACT + atf_disconnect(conn); atf_close(atfh); + +#endif/*WITHOUT_ARTEFACT*/ } +#ifndef WITHOUT_ARTEFACT static QueryResult node2result(atf_result_node_t *node, time_t timestamp) { QueryResult rnode; rnode.timestamp = timestamp; - rnode.source = "pentominos"; + rnode.source = "artefact"; if(!node) return rnode; @@ -74,11 +81,18 @@ static QueryResult node2result(atf_result_node_t *node, time_t timestamp) return rnode; } +#endif/*WITHOUT_ARTEFACT*/ QueryResult Artefact::exec(Query &query, std::string patientid, std::string user) { + QueryResult rroot; + rroot.timestamp = 0; + rroot.source = "pentominos"; + +#ifndef WITHOUT_ARTEFACT + atf_transaction_t* atft = NULL; atf_reply_t *reply = NULL; atf_result_t *result = NULL; @@ -87,10 +101,6 @@ QueryResult Artefact::exec(Query &query, time_t timestamp; atf_id id; - QueryResult rroot; - rroot.timestamp = 0; - rroot.source = "pentominos"; - if(query.attributes.find("class") == query.attributes.end()) { PRACRO_ERR(artefact, "Missing 'class' attribute!\n"); goto aaarg; @@ -135,22 +145,43 @@ QueryResult Artefact::exec(Query &query, if(reply) atf_free_reply(reply); if(atft) atf_free_transaction(atft); +#endif/*WITHOUT_ARTEFACT*/ + return rroot; } #ifdef TEST_ARTEFACT -//Additional dependency files -//deps: -//Required cflags (autoconf vars may be used) -//cflags: -//Required link options (autoconf vars may be used) -//libs: +//deps: configuration.cc debug.cc +//cflags: $(ATF_CFLAGS) -I.. +//libs: $(ATF_LIBS) #include "test.h" TEST_BEGIN; -// TODO: Put some testcode here (see test.h for usable macros). +pracro_debug_init(); +pracro_debug_parse("+all"); + +{ + Artefact atf; + Query q; + q.attributes["class"] = "echo"; + QueryResult res = atf.exec(q, "1505050505", "me"); + + res.print(); +} + +{ + Conf::artefact_addr = "nowhere_at_all.com"; + Conf::artefact_port = 10000; + + Artefact atf; + Query q; + q.attributes["class"] = "echo"; + QueryResult res = atf.exec(q, "1505050505", "me"); + + res.print(); +} TEST_END; diff --git a/server/src/artefact.h b/server/src/artefact.h index 7eb1a6b..fa1602d 100644 --- a/server/src/artefact.h +++ b/server/src/artefact.h @@ -31,7 +31,9 @@ #include "template.h" #include "queryresult.h" +#ifndef WITHOUT_ARTEFACT #include +#endif/*WITHOUT_ARTEFACT*/ class Artefact { public: @@ -43,8 +45,10 @@ public: std::string user); private: +#ifndef WITHOUT_ARTEFACT atf_handle_t *atfh; atf_connection_t *conn; +#endif/*WITHOUT_ARTEFACT*/ }; #endif/*__PRACRO_ARTEFACT_H__*/ diff --git a/server/src/configuration.cc b/server/src/configuration.cc index ace07e7..b222639 100644 --- a/server/src/configuration.cc +++ b/server/src/configuration.cc @@ -59,3 +59,5 @@ std::string Conf::ssl_cert = ""; int Conf::connection_limit = 42; int Conf::connection_timeout = 0; + +std::string Conf::session_path = "/tmp"; diff --git a/server/src/configuration.h b/server/src/configuration.h index d077806..8980bfd 100644 --- a/server/src/configuration.h +++ b/server/src/configuration.h @@ -66,6 +66,8 @@ namespace Conf { extern int connection_limit; extern int connection_timeout; + + extern std::string session_path; }; #endif/*__ARTEFACT_CONFIGURATION_H__*/ diff --git a/server/src/configurationparser.cc b/server/src/configurationparser.cc index 3823ee9..bc10e2b 100644 --- a/server/src/configurationparser.cc +++ b/server/src/configurationparser.cc @@ -181,6 +181,12 @@ void ConfigurationParser::reload() Conf::connection_timeout = i; } catch( ... ) { } + + try { + std::string s = lookup("session_path"); + Conf::session_path = s; + } catch( ... ) { + } } #ifdef TEST_CONFIGURATIONPARSER diff --git a/server/src/connection.cc b/server/src/connection.cc index 36c030e..ed5a7a9 100644 --- a/server/src/connection.cc +++ b/server/src/connection.cc @@ -40,13 +40,23 @@ static std::string error_box(std::string message) return errorbox; } -Connection::Connection(Environment &e, std::string sid, bool c) +#ifdef TEST_CONNECTION +static bool did_commit = false; +#endif + +Connection::Connection(Environment &e, std::string sid, bool c, bool d) : env(e), parser(&transaction) { PRACRO_DEBUG(connection, "[%p] CREATE\n", this); sessionid = sid; - commit = c; + docommit = c; + dodiscard = d; + +#ifdef TEST_CONNECTION + did_commit = false; +#endif + } Connection::~Connection() @@ -54,6 +64,27 @@ Connection::~Connection() PRACRO_DEBUG(connection, "[%p] DESTROY\n", this); } +void Connection::commit(Session *session) +{ + if(docommit) { + session->commit(); + env.sessions.deleteSession(session->id()); + sessionid = ""; +#ifdef TEST_CONNECTION + did_commit = true; +#endif + } +} + +void Connection::discard(Session *session) +{ + if(dodiscard) { + session->discard(); + env.sessions.deleteSession(session->id()); + sessionid = ""; + } +} + bool Connection::handle(const char *data, size_t size) { Session *session = NULL; @@ -76,20 +107,22 @@ bool Connection::handle(const char *data, size_t size) sessionid = session->id(); - if(!data || !size) return true; - try { + + if(!data || !size) { + commit(session); + discard(session); + return true; + } + if(parser.parse(data, size)) { { SessionAutolock lock(*session); response = handleTransaction(transaction, env, *session); } - if(commit) { - session->commit(); - env.sessions.deleteSession(session->id()); - } - + commit(session); + discard(session); return true; } } catch(...) { @@ -112,17 +145,194 @@ std::string Connection::getSessionID() } #ifdef TEST_CONNECTION -//Additional dependency files -//deps: -//Required cflags (autoconf vars may be used) -//cflags: -//Required link options (autoconf vars may be used) -//libs: +//deps: debug.cc transactionparser.cc session.cc xml_encode_decode.cc saxparser.cc transactionhandler.cc journalwriter.cc mutex.cc templateparser.cc exception.cc configuration.cc macroparser.cc semaphore.cc entitylist.cc luaquerymapper.cc inotify.cc log.cc queryhandlerpentominos.cc widgetgenerator.cc queryhandlerpracro.cc resumeparser.cc journal_commit.cc versionstr.cc luaresume.cc luautil.cc artefact.cc environment.cc database.cc macrolist.cc templatelist.cc pracrodao.cc templateheaderparser.cc macroheaderparser.cc pracrodaotest.cc pracrodaopgsql.cc +//cflags: -DWITHOUT_DATABASE -DWITHOUT_ARTEFACT -I.. $(LUA_CFLAGS) $(EXPAT_CFLAGS) $(PTHREAD_CFLAGS) $(PQXX_CXXFLAGS) +//libs: $(LUA_LIBS) $(EXPAT_LIBS) $(PTHREAD_LIBS) $(PQXX_LIBS) #include "test.h" +static char xml_request[] = +"\n" +"\n" +" \n" +"\n" + ; + +static char xml_commit[] = +"\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" + ; + +static char xml_commit_p1[] = +"\n" +"\n" +" \n" +" \n" +" \n" +" \n" +"\n" + ; + TEST_BEGIN; -// TODO: Put some testcode here (see test.h for usable macros). +Environment env; +std::string sid; + +// Without data +{ + Connection con(env, "", false); + TEST_TRUE(con.handle("", 0), "Test handler return value."); + TEST_EQUAL_STR(con.getResponse(), "", "Test response value."); + sid = con.getSessionID(); + TEST_NOTEQUAL_STR(sid, "", "Test new session id."); + TEST_FALSE(did_commit, "No commit."); +} + +{ + Connection con(env, sid, false); + TEST_TRUE(con.handle("", 0), "Test handler return value."); + TEST_EQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_NOTEQUAL_STR(con.getSessionID(), "", "Test existing session id."); + TEST_EQUAL_STR(con.getSessionID(), sid, "Test existing session id."); + TEST_FALSE(did_commit, "No commit."); +} + +{ + Connection con(env, sid, true); + TEST_TRUE(con.handle("", 0), "Test handler return value."); + TEST_EQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_EQUAL_STR(con.getSessionID(), "", "Test existing session id."); + TEST_TRUE(did_commit, "Commit."); +} + +{ + Connection con(env, sid, false); + TEST_TRUE(con.handle("", 0), "Test handler return value."); + TEST_EQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_NOTEQUAL_STR(con.getSessionID(), "", "Test existing session id."); + TEST_NOTEQUAL_STR(con.getSessionID(), sid, "Test new session id."); + TEST_FALSE(did_commit, "No commit."); +} + +// With commit partial data +{ + Connection con(env, "", false); + TEST_FALSE(con.handle(xml_commit_p1, sizeof(xml_commit_p1) - 1), + "Test handler return value."); + sid = con.getSessionID(); + TEST_NOTEQUAL_STR(sid, "", "Test new session id."); + TEST_FALSE(did_commit, "No commit."); + TEST_EQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_TRUE(con.handle(xml_commit_p2, sizeof(xml_commit_p2) - 1), + "Test handler return value."); + TEST_EQUAL_STR(con.getSessionID(), sid, "Test session id."); + TEST_NOTEQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_FALSE(did_commit, "No commit."); +} + +// With commit partial data and journal commit +{ + Connection con(env, "", true); + TEST_FALSE(con.handle(xml_commit_p1, sizeof(xml_commit_p1) - 1), + "Test handler return value."); + sid = con.getSessionID(); + TEST_NOTEQUAL_STR(sid, "", "Test new session id."); + TEST_EQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_FALSE(did_commit, "No commit."); + TEST_TRUE(con.handle(xml_commit_p2, sizeof(xml_commit_p2) - 1), + "Test handler return value."); + TEST_EQUAL_STR(con.getSessionID(), "", "Test session id."); + TEST_TRUE(did_commit, "No commit."); + TEST_NOTEQUAL_STR(con.getResponse(), "", "Test response value."); +} + +// With commit data +{ + Connection con(env, "", false); + TEST_TRUE(con.handle(xml_commit, sizeof(xml_commit) - 1), + "Test handler return value."); + sid = con.getSessionID(); + TEST_NOTEQUAL_STR(sid, "", "Test new session id."); + TEST_NOTEQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_FALSE(did_commit, "No commit."); +} + +{ + Connection con(env, sid, false); + TEST_TRUE(con.handle(xml_commit, sizeof(xml_commit) - 1), + "Test handler return value."); + TEST_NOTEQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_NOTEQUAL_STR(con.getSessionID(), "", "Test existing session id."); + TEST_EQUAL_STR(con.getSessionID(), sid, "Test existing session id."); + TEST_FALSE(did_commit, "No commit."); +} + +{ + Connection con(env, sid, true); + TEST_TRUE(con.handle(xml_commit, sizeof(xml_commit) - 1), + "Test handler return value."); + TEST_NOTEQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_EQUAL_STR(con.getSessionID(), "", "Test existing session id."); + TEST_TRUE(did_commit, "Commit."); +} + +{ + Connection con(env, sid, false); + TEST_TRUE(con.handle(xml_commit, sizeof(xml_commit) - 1), + "Test handler return value."); + TEST_NOTEQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_NOTEQUAL_STR(con.getSessionID(), "", "Test existing session id."); + TEST_NOTEQUAL_STR(con.getSessionID(), sid, "Test new session id."); + TEST_FALSE(did_commit, "No commit."); +} + +// With request data +{ + Connection con(env, "", false); + TEST_TRUE(con.handle(xml_request, sizeof(xml_request) - 1), + "Test handler return value."); + TEST_NOTEQUAL_STR(con.getResponse(), "", "Test response value."); + sid = con.getSessionID(); + TEST_NOTEQUAL_STR(sid, "", "Test new session id."); + TEST_FALSE(did_commit, "No commit."); +} + +{ + Connection con(env, sid, false); + TEST_TRUE(con.handle(xml_request, sizeof(xml_request) - 1), + "Test handler return value."); + TEST_NOTEQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_NOTEQUAL_STR(con.getSessionID(), "", "Test existing session id."); + TEST_EQUAL_STR(con.getSessionID(), sid, "Test existing session id."); + TEST_FALSE(did_commit, "No commit."); +} + +{ + Connection con(env, sid, true); + TEST_TRUE(con.handle(xml_request, sizeof(xml_request) - 1), + "Test handler return value."); + TEST_NOTEQUAL_STR(con.getResponse(), "", "Test response value."); + TEST_EQUAL_STR(con.getSessionID(), "", "Test existing session id."); + TEST_TRUE(did_commit, "Commit."); +} + +{ + Connection con(env, sid, false); + TEST_TRUE(con.handle(xml_request, sizeof(xml_request) - 1), + "Test handler return value."); + TEST_NOTEQUAL_STR(con.getSessionID(), "", "Test existing session id."); + TEST_NOTEQUAL_STR(con.getSessionID(), sid, "Test new session id."); + TEST_FALSE(did_commit, "No commit."); +} TEST_END; diff --git a/server/src/connection.h b/server/src/connection.h index f91ddc1..61997da 100644 --- a/server/src/connection.h +++ b/server/src/connection.h @@ -33,9 +33,11 @@ #include "transaction.h" #include "transactionparser.h" +class Session; + class Connection { public: - Connection(Environment &e, std::string sessionid, bool commit); + Connection(Environment &e, std::string sessionid, bool commit, bool discard); ~Connection(); bool handle(const char *data, size_t size); @@ -44,8 +46,12 @@ public: std::string getSessionID(); private: + void commit(Session *session); + void discard(Session *session); + std::string sessionid; - bool commit; + bool docommit; + bool dodiscard; Environment &env; Transaction transaction; diff --git a/server/src/connectionpool.cc b/server/src/connectionpool.cc index 07e6aa2..e2b6bfc 100644 --- a/server/src/connectionpool.cc +++ b/server/src/connectionpool.cc @@ -58,6 +58,20 @@ static void* thread_run(void *data) return NULL; } +static void* thread_run_clear_test(void *data) +{ + ConnectionPool *pool = (ConnectionPool*)data; + pool->giveBack(1); + pool->giveBack(2); + + sleep(1); + + pool->giveBack(3); + pool->giveBack(4); + + return NULL; +} + TEST_BEGIN; ConnectionPool pool; @@ -90,19 +104,21 @@ pool.giveBack(b_db2); TEST_EQUAL(pool.numFree(), 4, "Testing number of free databases."); -pthread_attr_t attr; -pthread_t tid; -pthread_attr_init(&attr); -pthread_create(&tid, &attr, thread_run, &pool); - -while(pool.numFree() > 0) { usleep(10); } - -int b_db3 = pool.borrow(); -TEST_FALSE(pool.testFree(b_db3), "Testing if returned db is free (semaphore test)."); -pool.giveBack(db3); - -pthread_join(tid, NULL); -pthread_attr_destroy(&attr); +{ + pthread_attr_t attr; + pthread_t tid; + pthread_attr_init(&attr); + pthread_create(&tid, &attr, thread_run, &pool); + + while(pool.numFree() > 0) { usleep(10); } + + int b_db3 = pool.borrow(); + TEST_FALSE(pool.testFree(b_db3), "Testing if returned db is free (semaphore test)."); + pool.giveBack(db3); + + pthread_join(tid, NULL); + pthread_attr_destroy(&attr); +} TEST_EQUAL(pool.numFree(), 4, "Testing if all database are now available again"); @@ -114,6 +130,33 @@ TEST_EQUAL(pool.numFree(), 4, "Testing if all database are now available again") } TEST_EQUAL(pool.numFree(), 4, "Testing if autoborrower has returned the db."); +// Force remove all elements. +pool.borrow(); +pool.clear(true); +TEST_EQUAL(pool.numFree(), 0, "Testing number of free databases."); + +// Add them again. +pool.add(db1); +pool.add(db2); +pool.add(db3); +pool.add(db4); +TEST_EQUAL(pool.numFree(), 4, "Testing number of free databases."); + +pool.borrow(); +pool.borrow(); +pool.borrow(); +pool.borrow(); + +{ + pthread_attr_t attr; + pthread_t tid; + pthread_attr_init(&attr); + pthread_create(&tid, &attr, thread_run_clear_test, &pool); + pool.clear(false); + pthread_join(tid, NULL); + pthread_attr_destroy(&attr); +} + TEST_END; #endif/*TEST_CONNECTIONPOOL*/ diff --git a/server/src/connectionpool.h b/server/src/connectionpool.h index 77fc33b..33473e5 100644 --- a/server/src/connectionpool.h +++ b/server/src/connectionpool.h @@ -45,6 +45,8 @@ public: T borrow(); void giveBack(T t); + std::list clear(bool force = true); + private: bool contains(std::list &list, T t); @@ -75,10 +77,11 @@ private: template void ConnectionPool::add(T t) { - mutex.lock(); + MutexAutolock lock(mutex); + passive.push_back(t); semaphore.post(); - mutex.unlock(); + } template @@ -96,12 +99,13 @@ bool ConnectionPool::contains(std::list &list, T t) template void ConnectionPool::remove(T t) { - mutex.lock(); + MutexAutolock lock(mutex); + if(contains(passive, t)) { semaphore.post(); passive.remove(t); } - mutex.unlock(); + } template @@ -109,9 +113,8 @@ bool ConnectionPool::testFree(T t) { bool testfree = false; - mutex.lock(); + MutexAutolock lock(mutex); testfree = contains(passive, t); - mutex.unlock(); return testfree; } @@ -120,9 +123,10 @@ template int ConnectionPool::numFree() { int num; - mutex.lock(); + + MutexAutolock lock(mutex); num = passive.size(); - mutex.unlock(); + return num; } @@ -133,13 +137,13 @@ T ConnectionPool::borrow() semaphore.wait(); - mutex.lock(); + { + MutexAutolock lock(mutex); - t = passive.front(); - passive.remove(t); - active.push_back(t); - - mutex.unlock(); + t = passive.front(); + passive.remove(t); + active.push_back(t); + } return t; } @@ -147,17 +151,58 @@ T ConnectionPool::borrow() template void ConnectionPool::giveBack(T t) { - mutex.lock(); + MutexAutolock lock(mutex); if(contains(active, t)) { active.remove(t); passive.push_back(t); semaphore.post(); } - - mutex.unlock(); } +template +std::list ConnectionPool::clear(bool force) +{ + typename std::list lst; + + if(force == false) { + size_t num = 0; + { + MutexAutolock lock(mutex); + num = active.size() + passive.size(); + } + + while(num) { + borrow(); + num--; + } + } + + { + MutexAutolock lock(mutex); + + typename std::list::iterator i; + + i = active.begin(); + while(i != active.end()) { + lst.push_back(*i); + // i = active.erase(i); + semaphore.post(); + i++; + } + active.clear(); + + i = passive.begin(); + while(i != passive.end()) { + lst.push_back(*i); + // i = passive.erase(i); + i++; + } + passive.clear(); + } + + return lst; +} template AutoBorrower::AutoBorrower(ConnectionPool &p) diff --git a/server/src/environment.cc b/server/src/environment.cc index 14dbd79..9904afc 100644 --- a/server/src/environment.cc +++ b/server/src/environment.cc @@ -46,18 +46,26 @@ Environment::Environment() Environment::~Environment() { -} + // Remove, but wait until resources are released + std::list dblst = dbpool.clear(false); + std::list::iterator i = dblst.begin(); + while(i != dblst.end()) { + delete *i; + i++; + } -/* - TCPSocket pentominos_socket; -#ifndef WITHOUT_PENTOMINOS - pentominos_socket.connect(Conf::pentominos_addr, Conf::pentominos_port); -#endif -*/ + // Remove, but wait until resources are released + std::list atflst = atfpool.clear(false); + std::list::iterator j = atflst.begin(); + while(j != atflst.end()) { + delete *j; + j++; + } +} #ifdef TEST_ENVIRONMENT //deps: configuration.cc database.cc artefact.cc pracrodao.cc session.cc mutex.cc semaphore.cc debug.cc pracrodaotest.cc pracrodaopgsql.cc journalwriter.cc journal_commit.cc entitylist.cc inotify.cc exception.cc versionstr.cc tcpsocket.cc macrolist.cc templatelist.cc saxparser.cc log.cc macroheaderparser.cc templateheaderparser.cc -//cflags: -I.. $(PQXX_CXXFLAGS) $(PTHREAD_CFLAGS) $(EXPAT_CFLAGS) +//cflags: -DWITHOUT_ARTEFACT -I.. $(PQXX_CXXFLAGS) $(PTHREAD_CFLAGS) $(EXPAT_CFLAGS) //libs: $(PQXX_LIBS) -lpthread $(EXPAT_LIBS) $(PTHREAD_LIBS) #include "test.h" @@ -68,6 +76,8 @@ Conf::database_poolsize = 1; Conf::artefact_poolsize = 1; +Conf::xml_basedir = "/tmp"; + TEST_NOEXCEPTION(Environment env, "Check if the Enviroment can be created."); TEST_END; diff --git a/server/src/journal_commit.cc b/server/src/journal_commit.cc index bc47fce..56e7baf 100644 --- a/server/src/journal_commit.cc +++ b/server/src/journal_commit.cc @@ -192,7 +192,7 @@ int journal_commit(const char *cpr, const char *user, if(sock != -1 && write(sock, resume.c_str(), resume.size()) != (ssize_t)resume.size()) { PRACRO_ERR_LOG(journal, "write did not write all the bytes in the buffer.\n"); } - PRACRO_DEBUG(journal, "%s", buf); + PRACRO_DEBUG(journal, "%s\n", buf); #ifndef WITHOUT_UPLOADSERVER // close socket diff --git a/server/src/journalwriter.cc b/server/src/journalwriter.cc index c73a841..4b2b4be 100644 --- a/server/src/journalwriter.cc +++ b/server/src/journalwriter.cc @@ -187,7 +187,9 @@ void JournalWriter::addEntry(Transaction &transaction, Commit &commit, PRACRO_DEBUG(journal, "addEntry: template(%s)\n", templ->attributes["name"].c_str()); - // Add the template resume as the header (ie. first entry) of the journal entry. +#if 0 // this feature is no longer nessecary... + // Add the template resume as the header (ie. first entry) + // of the journal entry. if(entrylist.size() == 0 && templ->attributes["name"] != "") { std::string template_resume = templ->attributes["resume"]; @@ -200,16 +202,23 @@ void JournalWriter::addEntry(Transaction &transaction, Commit &commit, entrylist[-1] = re; // Make sure it comes first. } } +#endif - // Test if the username or the cpr has changed... if so, commit and clear the list. + // Test if the username or the cpr has changed... + // if so, commit and clear the list. if(currentuser != transaction.user || currentcpr != transaction.cpr) { this->commit(); entrylist.clear(); } + addEntry(resume, commit.macro, index); +} + +void JournalWriter::addEntry(std::string resume, std::string macro, int index) +{ // Strip trailing whitespace, and add newlines. std::string r = stripTrailingWhitepace(addNewlines(resume, 60)); - std::string m = commit.macro; + std::string m = macro; ResumeEntry re; re.resume = r; diff --git a/server/src/journalwriter.h b/server/src/journalwriter.h index 5bde707..2cd191d 100644 --- a/server/src/journalwriter.h +++ b/server/src/journalwriter.h @@ -34,13 +34,18 @@ #include "transaction.h" #include "template.h" +class SessionSerialiser; + class JournalWriter { + friend class SessionSerialiser; public: JournalWriter(std::string host, unsigned short int port); void addEntry(Transaction &transaction, Commit &commit, std::string resume, Template *templ); + void addEntry(std::string resume, std::string macro, int index); + void commit(); private: diff --git a/server/src/server.cc b/server/src/server.cc index 118db85..1f85479 100644 --- a/server/src/server.cc +++ b/server/src/server.cc @@ -69,8 +69,12 @@ static int handle_request_callback(void *cls, const char *scm = MHD_lookup_connection_value(con, MHD_HEADER_KIND, "SessionCommit"); + + const char *sdc = MHD_lookup_connection_value(con, MHD_HEADER_KIND, + "SessionDiscard"); + Environment *env = (Environment *)cls; - connection = new Connection(*env, sessionid, scm != NULL); + connection = new Connection(*env, sessionid, scm != NULL, sdc != NULL); *con_cls = connection; } @@ -130,7 +134,7 @@ void server() { srand(time(NULL)); - bool forceshutdown = false; + // bool forceshutdown = false; port_t port = Conf::server_port; int flags = MHD_USE_DEBUG | MHD_USE_SELECT_INTERNALLY; @@ -161,9 +165,9 @@ void server() PRACRO_ERR(server, "Failed to initialise MHD_start_daemon!\n"); return; } - again: + // again: while(pracro_is_running) sleep(1); - + /* if(!forceshutdown && env.sessions.size() != 0) { char *errbuf; if(asprintf(&errbuf, "There are %d live sessions." @@ -177,6 +181,8 @@ void server() forceshutdown = true; goto again; } + */ + env.sessions.store(); MHD_stop_daemon(d); diff --git a/server/src/session.cc b/server/src/session.cc index 74adb32..803a515 100644 --- a/server/src/session.cc +++ b/server/src/session.cc @@ -29,9 +29,16 @@ #include +// for stat +#include +#include +#include +#include + #include "journalwriter.h" #include "configuration.h" #include "connectionpool.h" +#include "sessionserialiser.h" Session::Session(std::string sessionid) { @@ -39,6 +46,11 @@ Session::Session(std::string sessionid) _journal = NULL; } +Session::~Session() +{ + if(_journal) delete _journal; +} + std::string Session::id() { return _id; @@ -63,6 +75,14 @@ void Session::commit() } } +void Session::discard() +{ + if(_journal) { + delete _journal; + _journal = NULL; + } +} + JournalWriter *Session::journal() { if(_journal == NULL) { @@ -76,12 +96,32 @@ Sessions::Sessions() { } +static bool fexists(const std::string &f) +{ + bool ret; + +/* + struct stat sbuf; + int n = stat(f.c_str(), &sbuf); + if(n != -1) ret = true; + ret = errno != ENOENT; +*/ + + FILE *fp = fopen(f.c_str(), "r"); + ret = fp != NULL; + if(fp) fclose(fp); + + return ret; +} + Session *Sessions::newSession() { char sessionid[32]; + std::string filename; do { snprintf(sessionid, sizeof(sessionid)-1, "%d", rand()); - } while(sessions.find(sessionid) != sessions.end()); + filename = getSessionFilename(Conf::session_path, sessionid); + } while(sessions.find(sessionid) != sessions.end() || fexists(filename)); Session *session = new Session(sessionid); sessions[session->id()] = session; @@ -90,16 +130,35 @@ Session *Sessions::newSession() Session *Sessions::session(std::string sessionid) { - if(sessions.find(sessionid) == sessions.end()) return NULL; - return sessions[sessionid]; + if(sessions.find(sessionid) != sessions.end()) + return sessions[sessionid]; + + std::string filename = getSessionFilename(Conf::session_path, sessionid); + if(fexists(filename)) { + Session *s = new Session(sessionid); + SessionSerialiser ser(Conf::session_path, s); + ser.load(); + sessions[s->id()] = s; + + fprintf(stderr, "s: %p\n",s); + + return s; + } + + return NULL; } Session *Sessions::takeSession(std::string sessionid) { - Session *s = session(sessionid); + Session *s = NULL; + if(sessions.find(sessionid) != sessions.end()) { + s = sessions[sessionid]; + } + if(s) { sessions.erase(sessionid); } + return s; } @@ -114,6 +173,19 @@ size_t Sessions::size() return sessions.size(); } +void Sessions::store() +{ + std::map::iterator i = sessions.begin(); + while(i != sessions.end()) { + SessionSerialiser ser(Conf::session_path, i->second); + ser.save(); + delete i->second; + sessions.erase(i); + i++; + } + sessions.clear(); +} + SessionAutolock::SessionAutolock(Session &s) : session(s) { @@ -126,23 +198,41 @@ SessionAutolock::~SessionAutolock() } #ifdef TEST_SESSION -//deps: configuration.cc journalwriter.cc journal_commit.cc mutex.cc debug.cc -//cflags: -I.. $(PTHREAD_CFLAGS) -//libs: $(PTHREAD_LIBS) +//deps: configuration.cc journalwriter.cc journal_commit.cc mutex.cc debug.cc sessionserialiser.cc sessionparser.cc saxparser.cc +//cflags: -I.. $(PTHREAD_CFLAGS) $(EXPAT_CFLAGS) +//libs: $(PTHREAD_LIBS) $(EXPAT_LIBS) #include TEST_BEGIN; Sessions sessions; -srand(0); +Conf::session_path = "/tmp"; + +srand(0); // force seed Session *s1 = sessions.newSession(); -srand(0); +srand(0); // force seed Session *s2 = sessions.newSession(); TEST_NOTEQUAL(s1->id(), s2->id(), "Testing if IDs are unique."); TEST_EQUAL(sessions.size(), 2, "Testing if size match."); +std::string sessionid = s1->id(); +SessionSerialiser ser(Conf::session_path, s1); +ser.save(); + +sessions.deleteSession(sessionid); +TEST_EQUAL(sessions.size(), 1, "Testing if size match."); + +s1 = sessions.session(sessionid); +TEST_NOTEQUAL(s1, NULL, "Did we reload the session from disk?"); + +sessions.store(); +TEST_EQUAL(sessions.size(), 0, "Testing if size match."); + +s1 = sessions.session(sessionid); +TEST_NOTEQUAL(s1, NULL, "Did we reload the session from disk?"); + TEST_END; #endif/*TEST_SESSION*/ diff --git a/server/src/session.h b/server/src/session.h index 48c01d7..6c614c7 100644 --- a/server/src/session.h +++ b/server/src/session.h @@ -38,6 +38,7 @@ class JournalWriter; class Session { public: Session(std::string sessionid); + ~Session(); std::string id(); @@ -45,6 +46,7 @@ public: void unlock(); void commit(); + void discard(); JournalWriter *journal(); @@ -86,6 +88,11 @@ public: */ size_t size(); + /** + * Write all active sessions to disc. + */ + void store(); + private: std::map sessions; }; diff --git a/server/src/sessionparser.cc b/server/src/sessionparser.cc new file mode 100644 index 0000000..ba3693d --- /dev/null +++ b/server/src/sessionparser.cc @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set et sw=2 ts=2: */ +/*************************************************************************** + * sessionparser.cc + * + * Thu May 20 14:30:23 CEST 2010 + * Copyright 2010 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of Pracro. + * + * Pracro 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. + * + * Pracro 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 Pracro; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "sessionparser.h" + +#include "debug.h" + +SessionParser::SessionParser() +{ + done = false; + totalbytes = 0; + inresume = false; +} + +SessionParser::~SessionParser() +{ +} + +void SessionParser::characterData(std::string &data) +{ + if(inresume) { + entries[entries.size()-1].resume += data; + } +} + +void SessionParser::startTag(std::string name, + std::map attributes) +{ + PRACRO_DEBUG(sessionparser, "<%s>\n", name.c_str()); + + if(name == "session") { + sessionid = attributes["id"]; + } + + if(name == "journal") { + patientid = attributes["patientid"]; + userid = attributes["userid"]; + } + + if(name == "entry") { + Entry e; + e.index = atoi(attributes["index"].c_str()); + e.macro = attributes["macro"]; + entries.push_back(e); + } + + if(name == "resume") { + inresume = true; + } +} + +void SessionParser::endTag(std::string name) +{ + if(name == "resume") { + inresume = false; + } +} + +void SessionParser::parseError(const char *buf, size_t len, + std::string error, int lineno) +{ + PRACRO_ERR(sessionnparser, "SessionParser error at line %d: %s\n", + lineno, error.c_str()); + + std::string xml; + xml.append(buf, len); + + PRACRO_ERR(sessionparser, "\tBuffer %u bytes: [%s]\n", + len, xml.c_str()); + + throw std::exception(); +} + +#ifdef TEST_SESSIONPARSER +//deps: +//cflags: +//libs: +#include "test.h" + +TEST_BEGIN; + +#error "Put some testcode here (see test.h for usable macros)." + +TEST_END; + +#endif/*TEST_SESSIONPARSER*/ diff --git a/server/src/sessionparser.h b/server/src/sessionparser.h new file mode 100644 index 0000000..37899aa --- /dev/null +++ b/server/src/sessionparser.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set et sw=2 ts=2: */ +/*************************************************************************** + * sessionparser.h + * + * Thu May 20 14:30:23 CEST 2010 + * Copyright 2010 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of Pracro. + * + * Pracro 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. + * + * Pracro 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 Pracro; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#ifndef __PRACRO_SESSIONPARSER_H__ +#define __PRACRO_SESSIONPARSER_H__ + +#include "saxparser.h" + +#include +#include + +class SessionParser : public SAXParser { +public: + SessionParser(); + ~SessionParser(); + + void characterData(std::string &data); + void startTag(std::string name, + std::map< std::string, std::string> attributes); + void endTag(std::string name); + void parseError(const char *buf, size_t len, std::string error, int lineno); + + std::string sessionid; + std::string patientid; + std::string userid; + + class Entry { + public: + int index; + std::string macro; + std::string resume; + }; + + std::vector entries; + +private: + bool inresume; +}; + +#endif/*__PRACRO_SESSIONPARSER_H__*/ diff --git a/server/src/sessionserialiser.cc b/server/src/sessionserialiser.cc new file mode 100644 index 0000000..10d449d --- /dev/null +++ b/server/src/sessionserialiser.cc @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set et sw=2 ts=2: */ +/*************************************************************************** + * sessionserialiser.cc + * + * Thu May 20 11:26:18 CEST 2010 + * Copyright 2010 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of Pracro. + * + * Pracro 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. + * + * Pracro 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 Pracro; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include "sessionserialiser.h" + +#include "journalwriter.h" + +#include "sessionparser.h" + +#include +#include + +std::string getSessionFilename(const std::string &path, + const std::string &sessionid) +{ + return path + "/pracro_session." + sessionid; +} + + +static std::string itostr(int i) +{ + char sid[32]; + snprintf(sid, sizeof(sid), "%d", i); + return sid; +} + +SessionSerialiser::SessionSerialiser(std::string path, Session *session) +{ + this->session = session; + this->path = path; +} + +void SessionSerialiser::loadStr(const std::string &xml) +{ + // SessionAutolock lock(*session); + SessionParser parser; + parser.parse(xml.data(), xml.length()); + JournalWriter *j = session->journal(); + j->currentuser = parser.userid; + j->currentcpr = parser.patientid; + std::vector::iterator i = parser.entries.begin(); + while(i != parser.entries.end()) { + j->addEntry(i->resume, i->macro, i->index); + i++; + } +} + +std::string SessionSerialiser::saveStr() +{ + // SessionAutolock lock(*session); + + std::string xml; + + xml += "\n"; + xml += "id()+"\">\n"; + + JournalWriter *journal = session->journal(); + + xml += " currentcpr + + "\" userid=\"" + journal->currentuser + "\">\n"; + + std::map< int, JournalWriter::ResumeEntry >::iterator i = + journal->entrylist.begin(); + while(i != journal->entrylist.end()) { + + xml += " first) + "\"" + " macro=\"" + i->second.macro + "\">\n"; + xml += " " + i->second.resume + "\n"; + xml += " \n"; + + i++; + } + + xml += " \n"; + xml += "\n"; + + return xml; +} + +void SessionSerialiser::load() +{ + // read xml from file + std::string filename = getSessionFilename(path, session->id()); + + FILE *fp = fopen(filename.c_str(), "r"); + char xml[2048]; + memset(xml, 0, sizeof(xml)); + fread(xml, sizeof(xml), 1, fp); + fclose(fp); + + loadStr(xml); + + // delete file + unlink(filename.c_str()); + +} + +void SessionSerialiser::save() +{ + std::string filename = getSessionFilename(path, session->id()); + + std::string xml = saveStr(); + + // write xml to file + FILE *fp = fopen(filename.c_str(), "w"); + fwrite(xml.data(), xml.size(), 1, fp); + fclose(fp); +} + +#ifdef TEST_SESSIONSERIALISER +//deps: session.cc journalwriter.cc debug.cc configuration.cc mutex.cc journal_commit.cc sessionparser.cc saxparser.cc +//cflags: -I.. $(PTHREAD_CFLAGS) $(EXPAT_CFLAGS) +//libs: $(PTHREAD_LIBS) $(EXPAT_LIBS) +#include "test.h" + +#define SID "42" +#define SPATH "/tmp" + +TEST_BEGIN; + +std::string xml; + +{ + Session session(SID); + JournalWriter *j = session.journal(); + j->addEntry("some text", "macro1", 0); + j->addEntry("some more text", "macro2", 2); + j->addEntry("yet some more text", "macro3", 1); + SessionSerialiser s(SPATH, &session); + xml = s.saveStr(); + s.loadStr(xml); + std::string xml2 = s.saveStr(); + TEST_EQUAL_STR(xml, xml2, "Compare"); +} + +{ + Session session(SID); + JournalWriter *j = session.journal(); + j->addEntry("some text", "macro1", 0); + j->addEntry("some more text", "macro2", 2); + j->addEntry("yet some more text", "macro3", 1); + SessionSerialiser s(SPATH, &session); + xml = s.saveStr(); +} + +{ + Session session(SID); + SessionSerialiser s(SPATH, &session); + s.loadStr(xml); + std::string xml2 = s.saveStr(); + TEST_EQUAL_STR(xml, xml2, "Compare"); +} + +{ + Session session(SID); + JournalWriter *j = session.journal(); + j->addEntry("some text", "macro1", 0); + j->addEntry("some more text", "macro2", 2); + j->addEntry("yet some more text", "macro3", 1); + SessionSerialiser s(SPATH, &session); + s.save(); +} + +{ + Session session(SID); + SessionSerialiser s(SPATH, &session); + s.load(); + std::string xml2 = s.saveStr(); + TEST_EQUAL_STR(xml, xml2, "Compare"); +} + + +TEST_END; + +#endif/*TEST_SESSIONSERIALISER*/ diff --git a/server/src/sessionserialiser.h b/server/src/sessionserialiser.h new file mode 100644 index 0000000..4002760 --- /dev/null +++ b/server/src/sessionserialiser.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set et sw=2 ts=2: */ +/*************************************************************************** + * sessionserialiser.h + * + * Thu May 20 11:26:18 CEST 2010 + * Copyright 2010 Bent Bisballe Nyeng + * deva@aasimon.org + ****************************************************************************/ + +/* + * This file is part of Pracro. + * + * Pracro 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. + * + * Pracro 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 Pracro; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#ifndef __PRACRO_SESSIONSERIALISER_H__ +#define __PRACRO_SESSIONSERIALISER_H__ + +#include + +#include "session.h" + +class SessionSerialiser { +public: + SessionSerialiser(std::string path, Session *session); + + void loadStr(const std::string &xml); + std::string saveStr(); + + void load(); + void save(); + +private: + Session *session; + std::string path; +}; + +std::string getSessionFilename(const std::string &path, + const std::string &sessionid); + +#endif/*__PRACRO_SESSIONSERIALISER_H__*/ diff --git a/server/src/transactionhandler.cc b/server/src/transactionhandler.cc index 662896b..c9c58b6 100644 --- a/server/src/transactionhandler.cc +++ b/server/src/transactionhandler.cc @@ -280,7 +280,7 @@ std::string handleTransaction(Transaction &transaction, Environment &env, TEST_BEGIN; -// TODO: Put some testcode here (see test.h for usable macros). +#error TODO: Put some testcode here (see test.h for usable macros). TEST_END; diff --git a/server/src/transactionparser.cc b/server/src/transactionparser.cc index 7595bb2..b33cac6 100644 --- a/server/src/transactionparser.cc +++ b/server/src/transactionparser.cc @@ -106,6 +106,10 @@ void TransactionParser::parseError(const char *buf, size_t len, } #ifdef TEST_TRANSACTIONPARSER +//deps: saxparser.cc debug.cc exception.cc log.cc +//cflags: -I.. $(EXPAT_CFLAGS) +//libs: $(EXPAT_LIBS) +#include "test.h" static char xml_minimal[] = "\n" @@ -137,53 +141,36 @@ static char xml_fail[] = "\n" ; -int main() -{ - // Test minimal - try { - Transaction transaction; - TransactionParser parser(&transaction); - parser.parse(xml_minimal, strlen(xml_minimal)); - } catch(Exception &e) { - printf("ERROR: %s\n", e.what()); - return 1; - } - - // Test request - try { - Transaction transaction; - TransactionParser parser(&transaction); - parser.parse(xml_request, strlen(xml_request)); - } catch(Exception &e) { - printf("ERROR: %s\n", e.what()); - return 1; - } +TEST_BEGIN; - // Test commit - try { - Transaction transaction; - TransactionParser parser(&transaction); - parser.parse(xml_commit, strlen(xml_commit)); - } catch(Exception &e) { - printf("ERROR: %s\n", e.what()); - return 1; - } +// Test minimal +{ + Transaction transaction; + TransactionParser parser(&transaction); + TEST_NOEXCEPTION(parser.parse(xml_minimal, sizeof(xml_minimal)-1), "minimal"); +} - // Test parse error (should throw an exception) - try { - Transaction transaction; - TransactionParser parser(&transaction); - parser.parse(xml_fail, strlen(xml_fail)); - } catch(Exception &e) { - printf("ERROR: %s\n", e.what()); - goto onandon; - } - printf("We should fail here...\n"); - return 1; +// Test request +{ + Transaction transaction; + TransactionParser parser(&transaction); + TEST_NOEXCEPTION(parser.parse(xml_request, sizeof(xml_request)-1), "request"); +} - onandon: +// Test commit +{ + Transaction transaction; + TransactionParser parser(&transaction); + TEST_NOEXCEPTION(parser.parse(xml_commit, sizeof(xml_commit)-1), "commit"); +} - return 0; +// Test parse error (should throw an exception) +{ + Transaction transaction; + TransactionParser parser(&transaction); + TEST_EXCEPTION(parser.parse(xml_fail, sizeof(xml_fail)-1), std::exception, "parse error"); } +TEST_END; + #endif/*TEST_TRANSACTIONPARSER*/ -- cgit v1.2.3