Browse Source

Merge branch 'LIB-12' into LIB-11 - "Add List Manager Backend: Dummy (SQLite3)"

LIB-11
heck 4 years ago
parent
commit
e19e3a42a4
  1. 5
      .clang-format
  2. 6
      .gitignore
  3. 48
      src/ListManagerDummy.cc
  4. 29
      src/ListManagerDummy.hh
  5. 33
      src/ListManagerInterface.hh
  6. 9
      src/Makefile
  7. 12264
      src/internal/sqlite3.h
  8. 354
      src/listmanager_dummy.cc
  9. 59
      src/listmanager_dummy.hh
  10. 82
      src/pEpLog.cc
  11. 121
      src/pEpLog.hh
  12. 166
      src/pEpSQLite.cc
  13. 64
      src/pEpSQLite.hh
  14. 234229
      src/sqlite3.c
  15. 73
      test/framework/utils.cc
  16. 30
      test/framework/utils.hh
  17. 24
      test/framework/utils.hxx
  18. 16
      test/test_group.cc
  19. 276
      test/test_listmanager_dummy.cc
  20. 162
      test/test_lm_dummy.cc
  21. 703
      test/test_pEpSQLite.cc
  22. 115
      test/test_pEpSQLite.hh

5
.clang-format

@ -1,6 +1,6 @@
BasedOnStyle: LLVM
Language: Cpp
Standard: c++14
Standard: c++11
DerivePointerAlignment: true
SortIncludes: false
ReflowComments: false
@ -38,4 +38,5 @@ SpaceAfterTemplateKeyword: false
AccessModifierOffset: -4
AllowShortBlocksOnASingleLine: Always
IndentPPDirectives: BeforeHash
Cpp11BracedListStyle: false
Cpp11BracedListStyle: false
BreakStringLiterals: false

6
.gitignore

@ -2,6 +2,7 @@
*.a
*.d
*.swp
.DS_Store
ws
test_adapter
.gnupg
@ -24,3 +25,8 @@ test_library
test_message_cache
test_passphrase_cache
test_semaphore
test_lm_dummy
test_sqlite3
test_pEpsqlite
test_listmanager_dummy
*.db

48
src/ListManagerDummy.cc

@ -0,0 +1,48 @@
#include "ListManagerDummy.hh"
//#include "status_to_string.hh"
#include "pEpLog.hh"
namespace pEp {
void ListManagerDummy::create(
pEp_identity *group_identity,
pEp_identity *manager,
identity_list *memberlist)
{
pEpLog("called");
}
void ListManagerDummy::dissolve(pEp_identity *group_identity)
{
pEpLog("called");
}
void ListManagerDummy::join(pEp_identity *group_identity, pEp_identity *as_member)
{
pEpLog("called");
}
void ListManagerDummy::remove_member(pEp_identity *group_identity, pEp_identity *group_member)
{
pEpLog("called");
}
identity_list *ListManagerDummy::groups()
{
pEpLog("called");
return NULL;
}
pEp_identity *ListManagerDummy::manager(const pEp_identity *const group)
{
pEpLog("called");
return NULL;
}
identity_list *ListManagerDummy::members(const pEp_identity *const group)
{
pEpLog("called");
return NULL;
}
} // namespace pEp

29
src/ListManagerDummy.hh

@ -0,0 +1,29 @@
// This file is under GNU General Public License 3.0
// see LICENSE.txt
#ifndef LIBPEPADAPTER_LISTMANAGERDUMMY_HH
#define LIBPEPADAPTER_LISTMANAGERDUMMY_HH
#include "ListManagerInterface.hh"
namespace pEp {
class ListManagerDummy : public ListManagerInterface {
public:
// update functions
// Group create/destroy
void create(pEp_identity *group_identity, pEp_identity *manager, identity_list *memberlist) override;
void dissolve(pEp_identity *group_identity) override;
// Members create/destroy
void join(pEp_identity *group_identity, pEp_identity *as_member) override;
void remove_member(pEp_identity *group_identity, pEp_identity *group_member) override;
// query functions
identity_list *groups() override;
pEp_identity *manager(const pEp_identity *const group) override;
identity_list *members(const pEp_identity *const group) override;
private:
};
} // namespace pEp
#endif // LIBPEPADAPTER_LISTMANAGERDUMMY_HH

33
src/ListManagerInterface.hh

@ -0,0 +1,33 @@
// This file is under GNU General Public License 3.0
// see LICENSE.txt
#ifndef LIBPEPADAPTER_LISTMANAGERINTERFACE_HH
#define LIBPEPADAPTER_LISTMANAGERINTERFACE_HH
#include <pEp/message_api.h>
namespace pEp {
class ListManagerInterface {
public:
// update functions
// Group create/destroy
virtual void create(
pEp_identity *group_identity,
pEp_identity *manager,
identity_list *memberlist) = 0;
virtual void dissolve(pEp_identity *group_identity) = 0;
// Members create/destroy
virtual void join(pEp_identity *group_identity, pEp_identity *as_member) = 0;
virtual void remove_member(pEp_identity *group_identity, pEp_identity *group_member) = 0;
// query functions
virtual identity_list *groups() = 0;
virtual pEp_identity *manager(const pEp_identity *const group) = 0;
virtual identity_list *members(const pEp_identity *const group) = 0;
private:
};
} // namespace pEp
#endif // LIBPEPADAPTER_LISTMANAGERINTERFACE_HH

9
src/Makefile

@ -5,10 +5,13 @@
include ../Makefile.conf
SOURCE=$(wildcard *.cc)
SRC_CXX=$(wildcard *.cc)
SRC_C=$(wildcard *.c)
HEADERS=$(wildcard *.hh *.hxx *.h)
OBJECTS=$(subst .cc,.o,$(SOURCE))
DEPENDS=$(subst .cc,.d,$(SOURCE))
OBJ_CXX=$(subst .cc,.o,$(SRC_CXX))
OBJ_C=$(subst .c,.o,$(SRC_C))
OBJECTS=$(OBJ_CXX) $(OBJ_C)
DEPENDS=$(subst .cc,.d,$(SRC_CXX))
CXXFLAGS+= -MMD -MP
ifneq ($(MAKECMDGOALS),clean)

12264
src/internal/sqlite3.h

File diff suppressed because it is too large

354
src/listmanager_dummy.cc

@ -0,0 +1,354 @@
#include "listmanager_dummy.hh"
#include "pEpSQLite.hh"
#include <exception>
using namespace std;
namespace pEp {
bool ListManagerDummy::log_enabled = false;
// public
ListManagerDummy::ListManagerDummy(const string& db_path) : db(pEpSQLite(db_path))
{
pEpLogClass("called");
}
// private
void ListManagerDummy::ensure_db_initialized()
{
if (!db.is_open()) {
is_db_initialized = false;
try {
db.create_or_open_db();
} catch (...) {
db.close_db();
DBException e{ "ListManagerDummy - error opening db" };
throw_with_nested(e);
}
}
if (!is_db_initialized) {
try {
db_config();
create_tables();
} catch (...) {
db.close_db();
DBException e{ "ListManagerDummy - db init failed" };
throw_with_nested(e);
}
is_db_initialized = true;
}
}
// private
void ListManagerDummy::db_config()
{
try {
string sql;
sql = "PRAGMA foreign_keys=ON";
db.execute(sql);
} catch (...) {
DBException e{ "ListManagerDummy - db config failed" };
throw_with_nested(e);
}
}
// private
void ListManagerDummy::create_tables()
{
try {
string sql;
sql = "CREATE TABLE IF NOT EXISTS lists("
"address TEXT NOT NULL,"
"moderator_address TEXT NOT NULL,"
"PRIMARY KEY(address));";
db.execute(sql);
sql = "CREATE TABLE IF NOT EXISTS member_of("
"address TEXT NOT NULL,"
"list_address TEXT NOT NULL,"
"PRIMARY KEY (address, list_address),"
"FOREIGN KEY(list_address) REFERENCES lists(address) ON DELETE CASCADE);";
db.execute(sql);
} catch (...) {
DBException e("ListManagerDummy - create tables failed");
throw_with_nested(e);
}
}
// public
void ListManagerDummy::close_db()
{
pEpLogClass("called");
db.close_db();
}
// public
void ListManagerDummy::delete_db()
{
pEpLogClass("called");
try {
db.delete_db();
} catch (...) {
DBException e{ "ListManagerDummy: delete_db() failed" };
throw_with_nested(e);
}
}
// public
void ListManagerDummy::list_add(const std::string& addr_list, const std::string& addr_mgr)
{
pEpLogClass("list_add(addr_list: \"" + addr_list + "\", addr_mgr: \"" + addr_mgr + "\")");
if (list_exists(addr_list)) {
AlreadyExistsException e{ "list_add(addr_list: \"" + addr_list + "\", addr_mgr: \"" +
addr_mgr + "\") - List already exists" };
throw e;
}
try {
string sql = "INSERT INTO lists(address, moderator_address) VALUES ('" + addr_list +
"','" + addr_mgr + "');";
db.execute(sql);
} catch (...) {
DBException e{ "ListManagerDummy: list_add(addr_list: \"" + addr_list +
"\"\taddr_mgr: \"" + addr_mgr + "\") - failed with exception" };
throw_with_nested(e);
}
}
// public
void ListManagerDummy::list_delete(const std::string& addr_list)
{
pEpLogClass("list_delete(addr_list: \"" + addr_list + "\")");
if (!list_exists(addr_list)) {
DoesNotExistException e{ "list_delete(addr_list: \"" + addr_list +
"\") - List does not exist" };
throw e;
}
try {
string sql;
sql = "DELETE FROM lists WHERE lists.address = '" + addr_list + "';";
db.execute(sql);
} catch (...) {
DBException e{ "ListManagerDummy: list_delete(addr_list: \"" + addr_list +
"\") - failed with exception" };
throw_with_nested(e);
}
}
// public
void ListManagerDummy::member_add(const std::string& addr_list, const std::string& addr_member)
{
pEpLogClass(
"member_add(addr_list: \"" + addr_list + "\", addr_member: \"" + addr_member + "\")");
if (member_exists(addr_list, addr_member)) {
AlreadyExistsException e{ "member_add(addr_list: \"" + addr_list + "\", addr_member: \"" +
addr_member + "\") - member already exists" };
throw e;
}
try {
string sql = "INSERT INTO member_of(address, list_address) VALUES ('" + addr_member +
"', '" + addr_list + "');";
db.execute(sql);
} catch (...) {
DBException e{ "ListManagerDummy: member_add(addr_list: \"" + addr_list +
"\", addr_member: \"" + addr_member + "\") - failed with exception" };
throw_with_nested(e);
}
}
// public
void ListManagerDummy::member_remove(const std::string& addr_list, const std::string& addr_member)
{
pEpLogClass(
"member_remove(addr_list: \"" + addr_list + "\", addr_member: '\"" + addr_member + "\")");
if (!member_exists(addr_list, addr_member)) {
DoesNotExistException e{ "member_remove(addr_list: \"" + addr_list +
"\", addr_member: '\"" + addr_member +
"\") - member does not exist" };
throw e;
}
try {
string sql;
sql = "DELETE FROM member_of WHERE (member_of.address = '" + addr_member +
"') AND (member_of.list_address = '" + addr_list + "');";
db.execute(sql);
} catch (...) {
DBException e{ "ListManagerDummy: member_remove(" + addr_list + ", " + addr_member +
") - failed with exception" };
throw_with_nested(e);
}
}
// public
std::vector<std::string> ListManagerDummy::lists()
{
pEpLogClass("called");
ensure_db_initialized();
vector<string> ret;
ResultSet rs;
try {
string sql;
sql = "SELECT address FROM lists";
rs = db.execute(sql);
} catch (...) {
DBException e{ "ListManagerDummy: lists() failed" };
throw_with_nested(e);
}
for (const RSRecord& rec : rs) {
ret.push_back(rec.at("address"));
}
return ret;
}
// public
std::string ListManagerDummy::moderator(const std::string& addr_list)
{
pEpLogClass("moderator(list_address:\"" + addr_list + "\")");
if (!list_exists(addr_list)) {
DoesNotExistException e{ "moderator(list_address:\"" + addr_list +
"\") - List does not exist" };
throw e;
}
string ret;
ResultSet rs;
try {
string sql;
sql = "SELECT moderator_address FROM lists "
"WHERE lists.address = '" +
addr_list + "';";
rs = db.execute(sql);
} catch (...) {
DBException e{ "ListManagerDummy: moderator(list_address:\"" + addr_list +
"\") - failed" };
throw_with_nested(e);
}
if (!rs.empty()) {
for (const RSRecord& rec : rs) {
ret = rec.at("moderator_address");
}
}
return ret;
}
// public
std::vector<std::string> ListManagerDummy::members(const std::string& addr_list)
{
pEpLogClass("members(list_address:\"" + addr_list + "\")");
if (!list_exists(addr_list)) {
DoesNotExistException e{ "members(list_address:\"" + addr_list +
"\") - List does not exist" };
throw e;
}
vector<string> ret;
ResultSet rs;
try {
string sql;
sql = "SELECT address FROM member_of "
"WHERE list_address = '" +
addr_list + "'";
rs = db.execute(sql);
} catch (...) {
DBException e{ "ListManagerDummy: members(list_address:\"" + addr_list + "\")" };
throw_with_nested(e);
}
if (!rs.empty()) {
for (const RSRecord& rec : rs) {
ret.push_back(rec.at("address"));
}
}
return ret;
}
bool ListManagerDummy::list_exists(const std::string& addr_list)
{
pEpLogClass("list_exists(addr_list:\"" + addr_list + "\")");
bool ret{ false };
ensure_db_initialized();
ResultSet rs;
int rescount = 0;
try {
string sql;
sql = "SELECT COUNT(address) AS rescount "
"FROM lists "
"WHERE address = '" +
addr_list + "';";
rs = db.execute(sql);
rescount = pEpSQLite::eval_sql_count(rs, "rescount");
} catch (...) {
DBException e{ "ListManagerDummy: list_exists(addr_list:\"" + addr_list + "\")" };
throw_with_nested(e);
}
// Check FATAL inconsistency
if (rescount > 1) {
DBException e{ "ListManagerDummy: list_exists(addr_list:\"" + addr_list +
"\") - FATAL DB CONSTRAINT ERROR: list exists more than once" };
throw_with_nested(e);
}
if (rescount == 1) {
ret = true;
}
return ret;
}
bool ListManagerDummy::member_exists(const std::string& addr_list, const std::string& addr_member)
{
pEpLogClass(
"member_exists(addr_list:\"" + addr_list + "\", addr_member:\"" + addr_member + "\")");
bool ret{ false };
ensure_db_initialized();
ResultSet rs;
int rescount = 0;
try {
string sql;
sql = "SELECT COUNT(address) AS rescount "
"FROM member_of "
"WHERE (address = '" +
addr_member + "' AND list_address = '" + addr_list + "');";
rs = db.execute(sql);
rescount = pEpSQLite::eval_sql_count(rs, "rescount");
} catch (...) {
DBException e{ "member_exists(addr_list:\"" + addr_list + "\", addr_member:\"" +
addr_member + "\")" };
throw_with_nested(e);
}
// Check FATAL inconsistency
if (rescount > 1) {
DBException e{ "member_exists(addr_list:\"" + addr_list + "\", addr_member:\"" +
addr_member +
"\") - FATAL DB CONSTRAINT ERROR: list exists more than once" };
throw_with_nested(e);
}
if (rescount == 1) {
ret = true;
}
return ret;
}
// public
ListManagerDummy::~ListManagerDummy()
{
pEpLogClass("called");
db.close_db();
}
} // namespace pEp

59
src/listmanager_dummy.hh

@ -0,0 +1,59 @@
// This file is under GNU General Public License 3.0
// see LICENSE.txt
#ifndef LIBPEPADAPTER_LISTMANAGER_DUMMY_HH
#define LIBPEPADAPTER_LISTMANAGER_DUMMY_HH
#include "pEpSQLite.hh"
#include "pEpLog.hh"
#include <vector>
#include <string>
namespace pEp {
class ListManagerDummy {
public:
ListManagerDummy() = delete;
explicit ListManagerDummy(const std::string& db_path);
// Update
void list_add(const std::string& addr_list, const std::string& addr_mgr);
void list_delete(const std::string& addr_list);
void member_add(const std::string& addr_list, const std::string& addr_member);
void member_remove(const std::string& addr_list, const std::string& addr_member);
// Query
std::vector<std::string> lists();
std::string moderator(const std::string& addr_list);
std::vector<std::string> members(const std::string& addr_list);
bool list_exists(const std::string& addr_list);
bool member_exists(const std::string& addr_list, const std::string& addr_member);
// db
void close_db();
void delete_db();
// Logging
static bool log_enabled;
Adapter::pEpLog::pEpLogger logger{ "ListManagerDummy", log_enabled };
~ListManagerDummy();
private:
pEpSQLite db;
bool is_db_initialized{ false };
void ensure_db_initialized();
void db_config();
void create_tables();
Adapter::pEpLog::pEpLogger& m4gic_logger_n4ame = logger;
};
class DBException : public std::runtime_error {
public:
DBException(const std::string& string) : runtime_error(string) {}
};
class DoesNotExistException : public std::runtime_error {
public:
DoesNotExistException(const std::string& string) : runtime_error(string) {}
};
class AlreadyExistsException : public std::runtime_error {
public:
AlreadyExistsException(const std::string& string) : runtime_error(string) {}
};
} // namespace pEp
#endif // LIBPEPADAPTER_LISTMANAGER_DUMMY_HH

82
src/pEpLog.cc

@ -11,16 +11,17 @@
#include <android/log.h>
#endif
using namespace std;
namespace pEp {
namespace Adapter {
namespace pEpLog {
std::mutex mtx;
// NON CLASS
mutex mtx;
atomic_bool is_enabled{ false };
std::atomic_bool is_enabled{ false };
void set_enabled(bool enabled)
void set_enabled(const bool& enabled)
{
is_enabled.store(enabled);
}
@ -30,18 +31,81 @@ namespace pEp {
return is_enabled.load();
}
void log(std::string msg)
// Common "print" function implementing the actual "backends"
void _log(const string& msg)
{
if (is_enabled.load()) {
std::lock_guard<std::mutex> l(mtx);
lock_guard<mutex> l(mtx);
#ifdef ANDROID
__android_log_print(ANDROID_LOG_DEBUG, "pEpDebugLog", "%s", msg.c_str());
__android_log_print(ANDROID_LOG_DEBUG, "pEpDebugLog", "%s", msg.c_str());
#else
std::cout << msg << std::endl; //std::endl also flushes
cerr << msg << endl; //endl also flushes, but cerr is unbuffered anyways
#endif
}
void log(const string& msg)
{
if (is_enabled.load()) {
_log(msg);
}
}
} // namespace pEpLog
} // namespace Adapter
} // namespace pEp
namespace pEp {
namespace Adapter {
namespace pEpLog {
// Class pEpLogger
int pEpLogger::auto_instance_nr = 0;
pEpLogger::pEpLogger(const string& classname, const bool& enabled) :
classname(classname), is_enabled(enabled)
{
auto_instance_nr++;
this->set_instancename(to_string(auto_instance_nr));
}
void pEpLogger::log(const string& msg) const
{
std::stringstream msg_;
msg_ << std::this_thread::get_id();
msg_ << " - ";
msg_ << this->get_classname() << "[" << this->get_instancename() << "]";
msg_ << " - " << msg;
this->logRaw(msg_.str());
}
void pEpLogger::logRaw(const string& msg) const
{
if (this->is_enabled) {
_log(msg);
}
}
void pEpLogger::set_enabled(const bool& enabled)
{
this->is_enabled = enabled;
}
bool pEpLogger::get_enabled() const
{
return this->is_enabled;
}
string pEpLogger::get_classname() const
{
return this->classname;
}
void pEpLogger::set_instancename(const string& name)
{
this->instancename = name;
}
string pEpLogger::get_instancename() const
{
return this->instancename;
}
} // namespace pEpLog
} // namespace Adapter
} // namespace pEp

121
src/pEpLog.hh

@ -10,23 +10,22 @@
// ======
// a "to be kept ultra small and simple" logging unit.
// featuring:
// * pEpLog macro that will be eliminated in release-builds (-DNDEBUG=1)
// * thread safe (no interleave when logging from diff threads)
// * Logging macros that completely eliminate any logging calls in release-builds (NDEBUG)
// * thread safe (no interleave when logging from diff threads) TODO: pEpLogger: REALLY?
// * OS dependent backend switches:
// * android: __android_log_print
// * all other OS: cout
// * runtime enabled/disabled switch (global)
// * all other OS: cerr
// * Logging without any class/object (pEpLog / pEpLogRaw macros)
// * runtime switchable only on a global level
// * Class backed Logging macros (pEpLogClass / pEpLogRawClass)
// * * runtime switchable logging on a class and object level
//
// You might want more and more features, but the feature-policy is very restrictive, and there is a
// primary design goal to keep it simple, maintainable and portable.
// There are already too mnay features and you might want even more and more,
// but the feature-policy of this logging unit is very restrictive, and there is a
// primary design goal to keep it very simple, maintainable and portable.
//
// How to use:
// include <pEpLog.hh>
// use the macro pEpLog(msg) to do logging
// use NDEBUG=1 to turn logging on/off at compile-time
// use set_enabled(bool) to turn logging on/off at runtime
// use set_enabled_<backend>(bool) to turn logging on/off per backend
// pEpLog is to be used in a non-class/object context
#ifdef NDEBUG
#define pEpLog(msg) \
do { \
@ -34,23 +33,107 @@
#else
#define pEpLog(msg) \
do { \
std::stringstream msg_ss; \
msg_ss << std::this_thread::get_id() << " - " << __FILE__ << "::" << __FUNCTION__ \
<< " - " << msg; \
pEp::Adapter::pEpLog::log(msg_ss.str()); \
std::stringstream msg_; \
msg_ << std::this_thread::get_id(); \
msg_ << " - " << __FILE__ << "::" << __FUNCTION__; \
msg_ << " - " << msg; \
pEp::Adapter::pEpLog::log(msg_.str()); \
} while (0)
#endif // NDEBUG
// pEpLogRaw the same as pEpLog, but does not print anything except the supplied msg
#ifdef NDEBUG
#define pEpLogRaw(msg) \
do { \
} while (0)
#else
#define pEpLogRaw(msg) \
do { \
pEp::Adapter::pEpLog::log(msg_.str()); \
} while (0)
#endif // NDEBUG
namespace pEp {
namespace Adapter {
namespace pEpLog {
// Logging functions to control pEpLog() macro
void log(const std::string& msg);
void set_enabled(const bool& is_enabled);
bool get_enabled();
} // namespace pEpLog
} // namespace Adapter
} // namespace pEp
void log(std::string msg);
// --------------------------------------------------------------------------------------------------
void set_enabled(bool is_enabled);
bool get_enabled();
// pEpLogClass is to be used in a class
// pEpLogger can only print the "thread - file::class::function - <message>" format using this macro
// WARNING: Some magic is needed
// Usage:
// create your logger obj in your class as a public member (usually)
// Adapter::pEpLog::pEpLogger logger{"<CLASSNAME>", enabled: true|false};
// then, create an alias for your logger called "m4gic_logger_n4ame" as a private member
// Adapter::pEpLog::pEpLogger& m4gic_logger_n4ame = logger;
// Thats all.
// Now in your implementation, to log a message you just write:
// pEpLogClass("my great logging message");
#ifdef NDEBUG
#define pEpLogClass(msg) \
do { \
} while (0)
#else
#define pEpLogClass(msg) \
do { \
std::stringstream msg_; \
msg_ << std::this_thread::get_id(); \
msg_ << " - " << this->m4gic_logger_n4ame.get_classname(); \
msg_ << "[" << this->m4gic_logger_n4ame.get_instancename() << "]"; \
msg_ << "::" << __FUNCTION__; \
msg_ << " - " << (msg); \
this->m4gic_logger_n4ame.logRaw(msg_.str()); \
} while (0)
#endif // NDEBUG
// pEpLogRawClass is the same as pEpLogClass, but does not print anything except the supplied msg
// This can also be achieved without this macro, just use the log method of pEpLogger
// You also need to set up the logger in your class as for pEpLogClass
// The only advantage of this macro is that is compiled away to nothing with NDEBUG
#ifdef NDEBUG
#define pEpLogRawClass(msg) \
do { \
} while (0)
#else
#define pEpLogRawClass(msg) \
do { \
this->m4gic_logger_n4ame.logRaw(msg_.str()); \
} while (0)
#endif // NDEBUG
namespace pEp {
namespace Adapter {
namespace pEpLog {
class pEpLogger {
public:
pEpLogger() = delete;
pEpLogger(const std::string& classname, const bool& enabled);
// Print a logging message in the format "thread - classname[instancename] - <msg>"
void log(const std::string& msg) const;
// Prints just "<msg>"
void logRaw(const std::string& msg) const;
void set_enabled(const bool& enabled);
bool get_enabled() const;
std::string get_classname() const;
// If never set, the default instancename is a unique number
void set_instancename(const std::string& name);
std::string get_instancename() const;
private:
static int auto_instance_nr;
bool is_enabled;
std::string classname;
std::string instancename;
};
} // namespace pEpLog
} // namespace Adapter
} // namespace pEp

166
src/pEpSQLite.cc

@ -0,0 +1,166 @@
#include "pEpSQLite.hh"
#include "pEpLog.hh"
#include <iostream>
#include <cstdio>
#include <stdexcept>
#include <string>
using namespace std;
namespace pEp {
bool pEpSQLite::log_enabled = false;
pEpSQLite::pEpSQLite(const std::string &db_path) : db_path(db_path)
{
pEpLogClass("called with db_path: " + db_path + "");
}
void pEpSQLite::create_or_open_db()
{
pEpLogClass("called");
int rc{ ::sqlite3_open(db_path.c_str(), &db) };
if (rc) {
runtime_error e{ "pEpSQLite: create_or_open_db(\"" + db_path +
"\") - failed with sqlite3 error: " + std::to_string(rc) + " - " +
::sqlite3_errmsg(db) };
close_db();
throw(e);
}
}
string pEpSQLite::get_db_path() const
{
pEpLogClass("called");
return db_path;
}
void pEpSQLite::close_db()
{
pEpLogClass("called");
if (db != nullptr) {
::sqlite3_close(db);
db = nullptr;
}
}
bool pEpSQLite::is_open() const
{
if (db == nullptr) {
return false;
} else {
return true;
}
}
void pEpSQLite::delete_db()
{
pEpLogClass("called");
close_db();
int status = remove(db_path.c_str());
if (status) {
runtime_error e{ "pEpSQLite: delete_db(\"" + db_path + "\"): failed with error: " +
std::to_string(status) + " - " + strerror(errno) };
throw(e);
}
}
int pEpSQLite::callback(void *obj, int argc, char **argv, char **azColName)
{
RSRecord record;
for (int col = 0; col < argc; col++) {
const string key = string{ azColName[col] };
// TODO: NULL is not correct, could be a valid value
const string val = string{ argv[col] ? argv[col] : "NULL" };
record.insert({ key, val });
}
(static_cast<pEpSQLite *>(obj))->resultset.push_back(record);
return 0;
}
ResultSet pEpSQLite::execute(const string &stmt)
{
if (!is_open()) {
DBNotOpenException e{ "pEpSQLite: execute() failed - db is not open:" };
throw(e);
} else {
pEpLogClass("called");
this->resultset.clear();
char *zErrMsg = nullptr;
int rc = ::sqlite3_exec(
db,
stmt.c_str(),
(int (*)(void *, int, char **, char **)) & callback,
this,
&zErrMsg);
if (rc != SQLITE_OK) {
if (rc == SQLITE_CONSTRAINT) {
ConstraintException e{ "pEpSQLite: execute() failed with sqlite error: " +
std::to_string(rc) + " - " + string(zErrMsg) };
::sqlite3_free(zErrMsg);
throw(e);
}
runtime_error e{ "pEpSQLite: execute() failed with sqlite error: " +
std::to_string(rc) + " - " + string(zErrMsg) };
::sqlite3_free(zErrMsg);
throw(e);
}
}
return resultset;
}
// Utils
string pEpSQLite::to_string(const RSRecord &rec)
{
stringstream ss;
for (const auto &col : rec) {
ss << "[\"" << col.first << "\"] = \"" << col.second << "\"" << endl;
}
return ss.str();
}
string pEpSQLite::to_string(const ResultSet &rs)
{
stringstream ss;
ss << "ROWCOUNT: " << rs.size() << endl;
int i = 0;
for (const RSRecord &rec : rs) {
ss << "ROW[" << i << "]" << endl << to_string(rec);
i++;
}
return ss.str();
}
//Helper
int pEpSQLite::eval_sql_count(const ResultSet& rs, const string& countfieldname)
{
int rescount = 0;
// Get row
RSRecord rec{};
if (rs.size() != 1) {
runtime_error e{ "ListManagerDummy: eval_sql_count() - row count != 1" };
throw_with_nested(e);
}
try {
rec = rs.at(0);
} catch (...) {
runtime_error e{ "ListManagerDummy: eval_sql_count() - cant get row nr 0" };
throw_with_nested(e);
}
// Get field
try {
rescount = stoi(rec.at(countfieldname));
} catch (...) {
runtime_error e{ "ListManagerDummy: eval_sql_count() - field not existing" };
throw_with_nested(e);
}
return rescount;
}
pEpSQLite::~pEpSQLite()
{
pEpLogClass("called");
close_db();
}
} // namespace pEp

64
src/pEpSQLite.hh

@ -0,0 +1,64 @@
// This file is under GNU General Public License 3.0
// see LICENSE.txt
#ifndef LIBPEPADAPTER_PEPSQLITE_HH
#define LIBPEPADAPTER_PEPSQLITE_HH
#include "internal/sqlite3.h"
#include "pEpLog.hh"
#include <iostream>
#include <vector>
#include <map>
using ResultSet = std::vector<std::map<std::string, std::string>>;
using RSRecord = std::map<std::string, std::string>;
namespace pEp {
class pEpSQLite {
public:
pEpSQLite() = delete;
// The database file as a constant for the obj lifetime
explicit pEpSQLite(const std::string& db_path);
std::string get_db_path() const;
// Creates the database file not existsing
// Will not create any dirs
void create_or_open_db();
void close_db();
bool is_open() const;
// Delete the database file
void delete_db();
ResultSet execute(const std::string& stmt);
// Utils
static std::string to_string(const RSRecord& rec);
static std::string to_string(const ResultSet& rs);
static int eval_sql_count(const ResultSet& rs, const std::string& countfieldname);
// Logging
static bool log_enabled;
Adapter::pEpLog::pEpLogger logger{ "pEpSQLite", log_enabled };
~pEpSQLite();
private:
::sqlite3* db = nullptr;
std::string db_path;
ResultSet resultset;
Adapter::pEpLog::pEpLogger& m4gic_logger_n4ame = logger;
static int callback(void* obj, int argc, char** argv, char** azColName);
};
class DBNotOpenException : public std::runtime_error {
public:
DBNotOpenException(const std::string& string) : runtime_error(string) {}
};
class ConstraintException : public std::runtime_error {
public:
ConstraintException(const std::string& string) : runtime_error(string) {}
};
} // namespace pEp
#endif // LIBPEPADAPTER_PEPSQLITE_HH

234229
src/sqlite3.c

File diff suppressed because it is too large

73
test/framework/utils.cc

@ -4,6 +4,8 @@
#include "utils.hh"
#include <iostream>
#include <iostream>
#include <fstream>
#include <pEp/identity_list.h>
@ -35,9 +37,9 @@ namespace pEp {
} // namespace Log
namespace Utils {
string identity_to_string(::pEp_identity* ident, bool full, int indent)
{
string to_string(::pEp_identity *ident, bool full, int indent)
{
stringstream builder;
if (ident != nullptr) {
if (full) {
@ -79,15 +81,15 @@ namespace pEp {
return builder.str();
}
std::string identitylist_to_string(::identity_list* idl, bool full, int indent)
std::string to_string(::identity_list *idl, bool full, int indent)
{
stringstream builder;
if (idl != nullptr) {
builder << endl;
builder << std::string(indent, '\t') << "[" << endl;
indent++;
for (::identity_list* curr = idl; curr != nullptr; curr = curr->next) {
builder << identity_to_string(curr->ident, full, indent) << endl;
for (::identity_list *curr = idl; curr != nullptr; curr = curr->next) {
builder << to_string(curr->ident, full, indent) << endl;
}
indent--;
builder << std::string(indent, '\t') << "]";
@ -98,14 +100,14 @@ namespace pEp {
return builder.str();
}
string member_to_string(::pEp_member* member, bool full, int indent)
string to_string(::pEp_member *member, bool full, int indent)
{
stringstream builder;
if (member != nullptr) {
builder << std::string(indent, '\t') << "{" << endl;
indent++;
builder << std::string(indent, '\t')
<< "ident: " << identity_to_string(member->ident, full, indent) << endl;
<< "ident: " << to_string(member->ident, full, indent) << endl;
builder << std::string(indent, '\t') << "joined: " << member->joined << endl;
indent--;
builder << std::string(indent, '\t') << "}";
@ -116,16 +118,16 @@ namespace pEp {
return builder.str();
}
string memberlist_to_string(::member_list* mbl, bool full, int indent)
string to_string(::member_list *mbl, bool full, int indent)
{
stringstream builder;
if (mbl != nullptr) {
builder << endl;
builder << std::string(indent, '\t') << "[" << endl;
indent++;
for (member_list* curr_member = mbl; curr_member != nullptr;
for (member_list *curr_member = mbl; curr_member != nullptr;
curr_member = curr_member->next) {
builder << member_to_string(curr_member->member, full, indent) << endl;
builder << to_string(curr_member->member, full, indent) << endl;
}
indent--;
builder << std::string(indent, '\t') << "]";
@ -136,7 +138,7 @@ namespace pEp {
return builder.str();
}
string group_to_string(::pEp_group* group, bool full, int indent)
string to_string(::pEp_group *group, bool full, int indent)
{
stringstream builder;
if (group != nullptr) {
@ -144,13 +146,13 @@ namespace pEp {
builder << std::string(indent, '\t') << "{" << endl;
indent++;
builder << std::string(indent, '\t') << "group_identity: "
<< identity_to_string(group->group_identity, full, indent) << endl;
<< to_string(group->group_identity, full, indent) << endl;
builder << std::string(indent, '\t')
<< "manager: " << identity_to_string(group->manager, full, indent)
<< "manager: " << to_string(group->manager, full, indent)
<< endl;
builder << std::string(indent, '\t') << "active: " << group->active << endl;
builder << std::string(indent, '\t')
<< "members: " << memberlist_to_string(group->members, full, indent)
<< "members: " << to_string(group->members, full, indent)
<< endl;
indent--;
builder << std::string(indent, '\t') << "]";
@ -160,6 +162,49 @@ namespace pEp {
return builder.str();
}
void print_exception(const exception &e, int level)
{
cerr << string(level, ' ') << "exception: " << e.what() << endl;
try {
rethrow_if_nested(e);
} catch (const exception &e) {
print_exception(e, level + 1);
} catch (...) {
}
}
// File utils
ofstream file_create(const string &filename)
{
ofstream outfile{ filename };
return outfile;
}
bool file_exists(const string &filename)
{
ifstream ifile(filename.c_str());
return (bool)ifile;
}
void file_delete(const string &filename)
{
int status = remove(filename.c_str());
if (status) {
runtime_error e{ string(
"file_delete(\"" + filename + "\") - " + strerror(errno)) };
throw(e);
}
}
void file_ensure_not_existing(string path)
{
while (file_exists(path)) {
file_delete(path);
}
}
} // namespace Utils
} // namespace Test
} // namespace pEp

30
test/framework/utils.hh

@ -8,6 +8,7 @@
#include <pEp/message.h>
#include <pEp/identity_list.h>
#include <pEp/group.h>
#include <exception>
namespace pEp {
namespace Test {
@ -16,12 +17,29 @@ namespace pEp {
void logH2(std::string msg);
}
namespace Utils {
std::string identity_to_string(::pEp_identity* ident, bool full = true, int indent = 0);
std::string identitylist_to_string(::identity_list * idl, bool full = true, int indent = 0);
std::string member_to_string(::pEp_member* member, bool full = true, int indent = 0);
std::string memberlist_to_string(::member_list* mbl, bool full = true, int indent = 0);
std::string group_to_string(::pEp_group* group, bool full = true, int indent = 0);
// pEpEngine datatypes to string
std::string to_string(::pEp_identity *ident, bool full = true, int indent = 0);
std::string to_string(::identity_list *idl, bool full = true, int indent = 0);
std::string to_string(::pEp_member *member, bool full = true, int indent = 0);
std::string to_string(::member_list *mbl, bool full = true, int indent = 0);
std::string to_string(::pEp_group *group, bool full = true, int indent = 0);
// C++/STL data types to string
template<typename T>
std::string to_string(std::vector<T> v);
// exception utils
void print_exception(const std::exception& e, int level = 0);
// file utils
std::ofstream file_create(const std::string &filename);
bool file_exists(const std::string &filename);
void file_delete(const std::string &filename);
void file_ensure_not_existing(std::string path);
}
} // namespace Test
} // namespace pEp
#endif
#include "utils.hxx"
#endif // LIBPEPADAPTER_UTILS_HH

24
test/framework/utils.hxx

@ -0,0 +1,24 @@
// This file is under GNU General Public License 3.0
// see LICENSE.txt
#ifndef LIBPEPADAPTER_UTILS_HXX
#define LIBPEPADAPTER_UTILS_HXX
#include <sstream>
namespace pEp {
namespace Test {
namespace Utils {
template<typename T>
std::string to_string(std::vector<T> v)
{
std::stringstream ss;
for (const T& elem : v) {
ss << elem << std::endl;
}
return ss.str();
}
}
} // namespace Test
} // namespace pEp
#endif // LIBPEPADAPTER_UTILS_HXX

16
test/test_group.cc

@ -35,8 +35,8 @@ bool debug_info_full = true;
::PEP_STATUS test_notifyHandshake(::pEp_identity* _me, ::pEp_identity* _partner, sync_handshake_signal signal)
{
cout << "called" << endl;
cout << "me: " << Test::Utils::identity_to_string(_me, false) << endl;
cout << "partner: " << Test::Utils::identity_to_string(_partner, false) << endl;
cout << "me: " << Test::Utils::to_string(_me, false) << endl;
cout << "partner: " << Test::Utils::to_string(_partner, false) << endl;
cout << "Signal: " << signal << endl;
return PEP_STATUS_OK;
@ -56,7 +56,7 @@ void test_create_alice_me()
status = ::myself(Adapter::session(), alice);
cout << "STATUS: " << status_to_string(status) << endl;
assert(!status);
cout << "Alice:" << Test::Utils::identity_to_string(alice, debug_info_full) << endl;
cout << "Alice:" << Test::Utils::to_string(alice, debug_info_full) << endl;
}
void test_create_bob_partner()
@ -69,7 +69,7 @@ void test_create_bob_partner()
status = ::update_identity(Adapter::session(), bob);
cout << "STATUS: " << status_to_string(status) << endl;
assert(!status);
cout << "Bob:" << Test::Utils::identity_to_string(bob, debug_info_full) << endl;
cout << "Bob:" << Test::Utils::to_string(bob, debug_info_full) << endl;
}
void test_create_carol_partner()
@ -82,7 +82,7 @@ void test_create_carol_partner()
status = ::update_identity(Adapter::session(), carol);
cout << "STATUS: " << status_to_string(status) << endl;
assert(!status);
cout << "Carol:" << Test::Utils::identity_to_string(carol, debug_info_full) << endl;
cout << "Carol:" << Test::Utils::to_string(carol, debug_info_full) << endl;
}
void test_setup_and_start_sync()
@ -94,7 +94,7 @@ void test_setup_and_start_sync()
void test_group_create(::identity_list* idl)
{
logH2("test_group_create");
cout << "IDL: " << Test::Utils::identitylist_to_string(idl, debug_info_full) << endl;
cout << "IDL: " << Test::Utils::to_string(idl, debug_info_full) << endl;
cout << "create group identity" << endl;
grp_ident = ::new_identity("group1@peptest.ch", NULL, "432", "group1");
@ -102,7 +102,7 @@ void test_group_create(::identity_list* idl)
status = ::myself(Adapter::session(), grp_ident);
cout << "STATUS: " << status_to_string(status) << endl;
assert(!status);
cout << "grp_ident:" << Test::Utils::identity_to_string(grp_ident, debug_info_full) << endl;
cout << "grp_ident:" << Test::Utils::to_string(grp_ident, debug_info_full) << endl;
cout << "adapter_group_create()" << endl;
::pEp_group* pep_grp1 = nullptr;
@ -110,7 +110,7 @@ void test_group_create(::identity_list* idl)
cout << "STATUS: " << status_to_string(status) << endl;
assert(!status);
assert(pep_grp1);
cout << "GRP: " << Test::Utils::group_to_string(pep_grp1, debug_info_full) << endl;
cout << "GRP: " << Test::Utils::to_string(pep_grp1, debug_info_full) << endl;
}
void test_group_invite_member(::pEp_identity* ident)

276
test/test_listmanager_dummy.cc

@ -0,0 +1,276 @@
#include "../src/listmanager_dummy.hh"
#include "framework/utils.hh"
#include <iostream>
#include <exception>
#include <map>
#ifndef ASSERT_EXCEPT
#define ASSERT_EXCEPT(func) \
do { \
try { \
(func); \
assert(false); \
} catch (const exception& e) { \
Test::Utils::print_exception(e); \
} \
} while (0)
#endif
using namespace std;
using namespace pEp;
using namespace Test::Log;
using namespace Test::Utils;
struct lm_list {
string addr;
string mod;
vector<string> members;
};
struct model_test_lmd {
string alice;
string bob;
string carol;
string joe;
string db_path;
vector<lm_list> lists;
vector<string> lists_addr() const
{
vector<string> ret;
for (const lm_list& l : this->lists) {
ret.push_back(l.addr);
}
return ret;
}
};
void apply_model(ListManagerDummy& lmd, const model_test_lmd& model)
{
cout << "apply_model()" << endl;
for (const lm_list& l : model.lists) {
lmd.list_add(l.addr, l.mod);
for (const string& m : l.members) {
lmd.member_add(l.addr, m);
}
}
}
void verify_model(ListManagerDummy& lmd, const model_test_lmd& model)
{
cout << "verify_model()" << endl;
assert((model.lists_addr()) == lmd.lists());
for (const lm_list& l : model.lists) {
assert(l.members == lmd.members(l.addr));
}
for (const lm_list& l : model.lists) {
assert(l.mod == lmd.moderator(l.addr));
}
}
void recreate_apply_and_verify_model(ListManagerDummy& lmd, const model_test_lmd& model)
{
try {
lmd.delete_db();
} catch (const exception& e) {
}
assert(!file_exists(model.db_path));
apply_model(lmd, model);
verify_model(lmd, model);
}
model_test_lmd create_default_model()
{
model_test_lmd model;
model.db_path = "test_lmd.db";
model.alice = "alice@peptest.org";
model.bob = "bob@peptest.org";
model.carol = "carol@peptest.org";
model.joe = "joe@peptest.org";
lm_list l1;
l1.addr = "list1@peptest.org";
l1.mod = model.alice;
l1.members.push_back(model.bob);
l1.members.push_back(model.carol);
lm_list l2;
l2.addr = "list2@peptest.org";
l2.mod = model.alice;
l2.members.push_back(model.bob);
l2.members.push_back(model.carol);
l2.members.push_back(model.joe);
lm_list l3;
l3.addr = "list3@peptest.org";
l3.mod = model.bob;
l3.members.push_back(model.carol);
l3.members.push_back(model.joe);
model.lists.push_back(l1);
model.lists.push_back(l2);
model.lists.push_back(l3);
return model;
}
model_test_lmd create_model_bad_path()
{
model_test_lmd model = create_default_model();
model.db_path = "/wont_create_dirs/bad.db";
return model;
}
int main(int argc, char* argv[])
{
// pEpSQLite::log_enabled = true;
ListManagerDummy::log_enabled = false;
logH1("Testing SUCCESS conditions");
{
logH2("Test create new db");
model_test_lmd model = create_default_model();
ListManagerDummy lmd(model.db_path);
recreate_apply_and_verify_model(lmd, model);
}
{
logH2("Test re-open db");
model_test_lmd model = create_default_model();
assert(file_exists(model.db_path));
ListManagerDummy lmd(model.db_path);
verify_model(lmd, model);
logH2("Test list_delete");
lmd.list_delete(model.lists.at(2).addr);
model.lists.pop_back();
verify_model(lmd, model);
logH2("Test auto reopen after close()");
lmd.close_db();
logH2("Test member_remove");
lmd.member_remove(model.lists.at(0).addr, model.lists.at(0).members.at(1));
model.lists.at(0).members.pop_back();
verify_model(lmd, model);
logH2("Test list_exists() - true");
assert(lmd.list_exists(model.lists.at(0).addr));
logH2("Test list_exists() - false");
assert(!lmd.list_exists("does_not_exist_for_sure"));
logH2("Test member_exists() - true");
assert(lmd.member_exists(model.lists.at(0).addr, model.lists.at(0).members.at(0)));
logH2("Test member_exists() - false");
assert(!lmd.member_exists(model.lists.at(0).addr, "does_not_exist_for_sure"));
logH2("Test delete_db");
lmd.delete_db();
assert(!file_exists(model.db_path));
}
logH1("Testing ERROR conditions");
{
logH2("Testing success on close_db() on model_bad_path");
model_test_lmd model = create_model_bad_path();
ListManagerDummy lmd(model.db_path);
lmd.close_db();
}
{
logH2("Testing exception on delete_db() on model_bad_path");
model_test_lmd model = create_model_bad_path();
ListManagerDummy lmd(model.db_path);
ASSERT_EXCEPT(lmd.delete_db());
}
{
logH2("Testing exception on lists() on: on model_bad_path");
model_test_lmd model = create_default_model();
model.db_path = "/wont_create_dirs/bad.db";
ListManagerDummy lmd(model.db_path);
ASSERT_EXCEPT(lmd.lists());
}
{
logH2("Testing list_add() AlreadyExistsException");
model_test_lmd model = create_default_model();
ListManagerDummy lmd(model.db_path);
recreate_apply_and_verify_model(lmd, model);
try {
lmd.list_add(model.lists.at(0).addr, "any");
assert(false);
} catch (const AlreadyExistsException& e) {
print_exception(e);
} catch (...) {
cout << "NEVER SEEEE" << endl;
assert(false);
}
}
{
logH2("Testing list_delete() DoesNotExistException");
model_test_lmd model = create_default_model();
ListManagerDummy lmd(model.db_path);
recreate_apply_and_verify_model(lmd, model);
try {
lmd.list_delete("does_not_exist_for_sure");
assert(false);
} catch (const DoesNotExistException& e) {
print_exception(e);
} catch (...) {
assert(false);
}
}
{
logH2("Testing member_add() AlreadyExistsException");
model_test_lmd model = create_default_model();
ListManagerDummy lmd(model.db_path);
recreate_apply_and_verify_model(lmd, model);
try {
lmd.member_add(model.lists.at(0).addr, model.lists.at(0).members.at(0));
assert(false);
} catch (const AlreadyExistsException& e) {
print_exception(e);
} catch (...) {
assert(false);
}
}
{
logH2("Testing member_remove() DoesNotExistException");
model_test_lmd model = create_default_model();
ListManagerDummy lmd(model.db_path);
recreate_apply_and_verify_model(lmd, model);
try {
lmd.member_remove(model.lists.at(0).addr, "does_not_exist_for_sure");
assert(false);
} catch (const DoesNotExistException& e) {
print_exception(e);
} catch (...) {
assert(false);
}
}
{
logH2("Testing moderator() DoesNotExistException");
model_test_lmd model = create_default_model();
ListManagerDummy lmd(model.db_path);
recreate_apply_and_verify_model(lmd, model);
try {
lmd.moderator("does_not_exist_for_sure");
assert(false);
} catch (const DoesNotExistException& e) {
print_exception(e);
} catch (...) {
assert(false);
}
}
{
logH2("Testing members() DoesNotExistException");
model_test_lmd model = create_default_model();
ListManagerDummy lmd(model.db_path);
recreate_apply_and_verify_model(lmd, model);
try {
lmd.members("does_not_exist_for_sure");
assert(false);
} catch (const DoesNotExistException& e) {
print_exception(e);
} catch (...) {
assert(false);
}
}
}

162
test/test_lm_dummy.cc

@ -0,0 +1,162 @@
// This file is under GNU General Public License 3.0
// see LICENSE.txt
#include "framework/framework.hh"
#include "framework/utils.hh"
#include <iostream>
#include "../src/Adapter.hh"
#include "../src/status_to_string.hh"
#include "../src/ListManagerDummy.hh"
using namespace std;
using namespace pEp;
using namespace pEp::Test::Log;
bool debug_info_full = true;
::pEp_identity* alice = nullptr;
::pEp_identity* bob = nullptr;
::pEp_identity* carol = nullptr;
::pEp_identity* grp_ident = nullptr;
::PEP_STATUS status;
ListManagerInterface* lm_backend;
/*
* Test Units
*/
void test_create_alice_me()
{
logH2("test_create_alice_me");
alice = ::new_identity("alice@peptest.ch", NULL, "23", "Alice");
assert(alice);
alice->lang[0] = 'e';
alice->lang[1] = 'n';
status = ::myself(Adapter::session(), alice);
cout << "STATUS: " << status_to_string(status) << endl;
assert(!status);
cout << "Alice:" << Test::Utils::to_string(alice, debug_info_full) << endl;
}
void test_create_bob_partner()
{
logH2("test_create_bob_partner");
bob = ::new_identity("bob@peptest.ch", NULL, PEP_OWN_USERID, "Bob");
assert(bob);
bob->lang[0] = 'c';
bob->lang[1] = 'r';
status = ::update_identity(Adapter::session(), bob);
cout << "STATUS: " << status_to_string(status) << endl;
assert(!status);
cout << "Bob:" << Test::Utils::to_string(bob, debug_info_full) << endl;
}
void test_create_carol_partner()
{
logH2("test_create_carol_partner");
carol = ::new_identity("carol@peptest.ch", NULL, PEP_OWN_USERID, "Carol");
assert(carol);
carol->lang[0] = 'f';
carol->lang[1] = 'n';
status = ::update_identity(Adapter::session(), carol);
cout << "STATUS: " << status_to_string(status) << endl;
assert(!status);
cout << "Carol:" << Test::Utils::to_string(carol, debug_info_full) << endl;
}
void test_group_create(::identity_list* idl)
{
logH2("test_group_create");
cout << "IDL: " << Test::Utils::to_string(idl, debug_info_full) << endl;
cout << "create group identity" << endl;
grp_ident = ::new_identity("group1@peptest.ch", NULL, "432", "group1");
assert(grp_ident);
status = ::myself(Adapter::session(), grp_ident);
cout << "STATUS: " << status_to_string(status) << endl;
assert(!status);
cout << "grp_ident:" << Test::Utils::to_string(grp_ident, debug_info_full) << endl;
lm_backend->create(grp_ident, alice, idl);
}
void test_group_join(::pEp_identity* ident)
{
logH2("test_group_join");
lm_backend->join(grp_ident, ident);
}
void test_group_remove_member(::pEp_identity* ident)
{
logH2("test_group_remove_member");
lm_backend->remove_member(grp_ident, ident);
}
void test_group_dissolve()
{
logH2("test_group_dissolve");
lm_backend->dissolve(grp_ident);
}
/*
* Update functions
* ----------------
* Test procedure:
* 1. Create group
* - group_create(Alice)
* 2. Add Bob
* - group_join(Bob)
* 3. Add Carol
* - group_join(Carol)
* 4. Remove Carol
* - group_remove_member(Carol)
* 6. Dissolve
* - group_dissolve()
*
* Query functions
* ---------------
* Always test all the query functions for correctness between every step above.
* group_query_groups()
* group_query_manager()
* group_query_members
*/
int main(int argc, char** argv)
{
Test::setup(argc, argv);
Adapter::pEpLog::set_enabled(true);
debug_info_full = false;
// Setup Test Context
test_create_alice_me();
test_create_bob_partner();
test_create_carol_partner();
// Setup list manager backend
ListManagerDummy lm_dummy{};
lm_backend = &lm_dummy;
logH1("1. Create group");
::identity_list* initial_memberlist = nullptr;
initial_memberlist = new_identity_list(bob);
::identity_list_add(initial_memberlist, carol);
test_group_create(initial_memberlist);
logH1("2. Add Bob");
test_group_join(bob); // Fails
logH1("3. Add Carol");
test_group_join(carol);
logH1("4. Remove Carol");
test_group_remove_member(carol);
logH1("6. Dissolve");
test_group_dissolve();
Adapter::shutdown();
return 0;
}

703
test/test_pEpSQLite.cc

@ -0,0 +1,703 @@
#include "test_pEpSQLite.hh"
#include "../src/pEpSQLite.hh"
#include "../src/pEpLog.hh"
#include "framework/utils.hh"
#include <fstream>
using namespace std;
using namespace pEp;
using namespace pEp::Test;
using namespace pEp::Test::Log;
using namespace pEp::Test::Utils;
namespace pEp {
namespace Test {
// --------------------------------- FIXTURES ---------------------------------
// filenames
string fixture_db_filename_new()
{
// pEpLog("called");
string path = "new.db";
return path;
}
string fixture_db_filename_bad()
{
// pEpLog("called");
string db_path_bad = "/will_not_create_dirs/bad.db";
return db_path_bad;
}
string fixture_db_filename_existing_and_verified()
{
// pEpLog("called");
string path = "new.db";
return path;
}
string fixture_db_filename_corrupt()
{
// pEpLog("called");
string path = "corrupt.db";
return path;
}
// prepared db's
string fixture_init_db_new()
{
// pEpLog("called");
string path = fixture_db_filename_new();
// cout << "fixture: \"" << path << "\" not existing" << endl;
file_ensure_not_existing(path);
return path;
}
string fixture_init_db_existing_and_verified()
{
// pEpLog("called");
string path = "existing.db";
// cout << "fixture: \"" << path << "\" not existing" << endl;
file_ensure_not_existing(path);
return path;
}
string fixture_init_db_corrupt()
{
// pEpLog("called");
// cout << "creating corrupt db" << endl;
string path = fixture_db_filename_corrupt();
file_ensure_not_existing(path);
ofstream db_corrupt = file_create(path);
db_corrupt << "G4rbage" << endl;
db_corrupt.close();
return path;
}
// instance
pEpSQLite fixture_instance_of_new()
{
// pEpLog("called");
return test_create_instance_on_new();
}
pEpSQLite fixture_instance_of_existing_and_verified()
{
// pEpLog("called");
return test_create_instance_on_existing();
}
pEpSQLite fixture_instance_of_bad()
{
// pEpLog("called");
return test_create_instance_on_path_bad();
}
pEpSQLite fixture_instance_of_corrupt()
{
// pEpLog("called");
return test_create_instance_on_path_corrupt();
}
// open
pEpSQLite fixture_db_open_of_new()
{
// pEpLog("called");
return test_createopen_db_new();
}
pEpSQLite fixture_db_open_of_existing_and_verified()
{
// pEpLog("called");
return test_db_verify_content_after_insert_on_tables_exist();
}
pEpSQLite fixture_db_open_of_bad()
{
// pEpLog("called");
return test_createopen_db_bad();
}
pEpSQLite fixture_db_open_of_corrupt()
{
// pEpLog("called");
return test_createopen_db_corrupt();
}
pEpSQLite fixture_db_open_after_close()
{
// pEpLog("called");
return test_close_after_open();
}
// tables
pEpSQLite fixture_db_open_with_tables_of_new()
{
// pEpLog("called");
return test_db_create_tables_on_open_new();
}
pEpSQLite fixture_db_open_with_tables_of_corrupt()
{
// pEpLog("called");
return test_db_create_tables_on_open_corrupt();
}
// content
pEpSQLite fixture_db_open_with_tables_and_content()
{
// pEpLog("called");
return test_db_insert_on_tables_exist();
}
// delete
pEpSQLite fixture_db_open_after_delete()
{
// pEpLog("called");
return test_delete_file_gone_after_open_existing();
}
pEpSQLite fixture_db_open_after_close_after_delete()
{
// pEpLog("called");
return test_delete_file_gone_after_close_new();
}
// --------------------------------- TESTS ---------------------------------
// instance creation
// OK
pEpSQLite test_create_instance_on_new()
{
pEpLog("called");
pEpSQLite db(fixture_init_db_new());
return db;
}
// OK
pEpSQLite test_create_instance_on_existing()
{
pEpLog("called");
pEpSQLite db(fixture_init_db_existing_and_verified());
return db;
}
// OK
pEpSQLite test_create_instance_on_path_bad()
{
pEpLog("called");
pEpSQLite db(fixture_db_filename_bad());
return db;
}
// OK
pEpSQLite test_create_instance_on_path_corrupt()
{
pEpLog("called");
pEpSQLite db(fixture_init_db_corrupt());
return db;
}
// db create_open (create_or_open())
// OK, new db
pEpSQLite test_createopen_db_new()
{
pEpLog("called");
pEpSQLite db = fixture_instance_of_new();
assert(!file_exists(fixture_db_filename_new()));
db.create_or_open_db();
assert(file_exists(fixture_db_filename_new()));
return db;
}
// OK, open db
pEpSQLite test_createopen_db_existing()
{
pEpLog("called");
pEpSQLite db = fixture_instance_of_existing_and_verified();
assert(file_exists(fixture_db_filename_existing_and_verified()));
db.create_or_open_db();
return db;
}
// ERR, cant create
pEpSQLite test_createopen_db_bad()
{
pEpLog("called");
assert(!file_exists(fixture_db_filename_bad()));
pEpSQLite db = fixture_instance_of_bad();
assert(!file_exists(fixture_db_filename_bad()));
try {
db.create_or_open_db();
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
assert(!file_exists(fixture_db_filename_bad()));
return db;
}
// OK(cant detect corruption)
pEpSQLite test_createopen_db_corrupt()
{
pEpLog("called");
pEpSQLite db = fixture_instance_of_corrupt();
assert(file_exists(fixture_db_filename_corrupt()));
db.create_or_open_db();
assert(file_exists(fixture_db_filename_corrupt()));
return db;
}
// close()
// OK
pEpSQLite test_close_before_open()
{
pEpLog("called");
pEpSQLite db = fixture_instance_of_new();
db.close_db();
return db;
}
// OK
pEpSQLite test_close_after_open()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_new();
db.close_db();
return db;
}
// OK
pEpSQLite test_close_idempotent()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_new();
db.close_db();
db.close_db();
db.close_db();
db.close_db();
return db;
}
// create tables (execute())
// OK
pEpSQLite test_db_create_tables_on_open_new()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_new();
db.execute("CREATE TABLE test(i,i_squared);");
return db;
}
// ERR, Tables already exist
pEpSQLite test_db_create_tables_open_existing()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_existing_and_verified();
try {
db.execute("CREATE TABLE test(i,i_squared);");
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
return db;
}
// ERR, db closed
pEpSQLite test_db_create_tables_on_open_bad()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_bad();
try {
db.execute("CREATE TABLE test(i,i_squared);");
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
return db;
}
// ERR, db corrupt
pEpSQLite test_db_create_tables_on_open_corrupt()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_corrupt();
try {
db.execute("CREATE TABLE test(i,i_squared);");
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
return db;
}
// insert (execute())
void insert_operation(pEpSQLite& db)
{
pEpLog("called");
for (int i = 0; i < 9; i = (i + 2)) {
db.execute(
"INSERT INTO test(i,i_squared) VALUES ('" + to_string(i) + "', '" +
to_string(i * i) + "');");
}
}
// OK
pEpSQLite test_db_insert_on_tables_exist()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_with_tables_of_new();
insert_operation(db);
return db;
}
// ERR, Tables missing
pEpSQLite test_db_insert_on_tables_dont_exist()
{
pEpSQLite db = fixture_db_open_of_new();
try {
insert_operation(db);
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
return db;
}
// ERR, Tables missing
pEpSQLite test_db_insert_before_open()
{
pEpLog("called");
pEpSQLite db = fixture_instance_of_new();
try {
insert_operation(db);
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
return db;
}
// ERR, Tables missing
pEpSQLite test_db_insert_after_close()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_after_close();
try {
insert_operation(db);
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
return db;
}
// verify contents (execute())
void verify_operation(pEpSQLite& db)
{
pEpLog("called");
for (int i = 0; i < 9; i++) {
ResultSet rs = db.execute(
"SELECT i, i_squared FROM test "
"WHERE (test.i == '" +
to_string(i) + "');");
// cout << "RESULT: " << endl << pEpSQLite::to_string(rs) << endl;
if (i % 2) {
if (!rs.empty()) {
runtime_error e{ "Exception verifying database content" };
throw(e);
}
} else {
if(rs.size() != 1) {
runtime_error e{ "Exception verifying database content" };
throw(e);
}
for (const RSRecord& r : rs) {
const int x = stoi(r.at("i"));
const int x_squared = stoi(r.at("i_squared"));
if((x * x) != x_squared) {
runtime_error e{ "Exception verifying database content" };
throw(e);
}
}
}
}
}
// OK
pEpSQLite test_db_verify_content_existing_open_db()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_existing_and_verified();
verify_operation(db);
return db;
}
// OK
pEpSQLite test_db_verify_content_after_insert_on_tables_exist()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_with_tables_and_content();
verify_operation(db);
return db;
}
// ERR - no tables
pEpSQLite test_db_verify_content_no_tables()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_new();
try {
verify_operation(db);
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
return db;
}
// ERR - err no data
pEpSQLite test_db_verify_content_after_create_tables()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_with_tables_of_new();
try {
verify_operation(db);
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
return db;
}
// get_path()
// OK
pEpSQLite test_get_path_on_instance_good()
{
pEpLog("called");
pEpSQLite db = fixture_instance_of_new();
assert(db.get_db_path() == fixture_db_filename_new());
return db;
}
// OK
pEpSQLite test_get_path_on_instance_bad()
{
pEpLog("called");
pEpSQLite db = fixture_instance_of_bad();
assert(db.get_db_path() == fixture_db_filename_bad());
return db;
}
// delete_db()
// ERR, file not found
pEpSQLite test_delete_file_gone_before_open_new()
{
pEpLog("called");
pEpSQLite db = fixture_instance_of_new();
try {
db.delete_db();
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
assert(!file_exists(fixture_db_filename_new()));
return db;
}
// ERR, file not found
pEpSQLite test_delete_file_gone_before_open_existing()
{
pEpLog("called");
pEpSQLite db = fixture_instance_of_existing_and_verified();
try {
db.delete_db();
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
assert(!file_exists(fixture_db_filename_existing_and_verified()));
return db;
}
// OK
pEpSQLite test_delete_file_gone_after_close_new()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_after_close();
db.delete_db();
assert(!file_exists(fixture_db_filename_new()));
return db;
}
// OK
pEpSQLite test_delete_file_gone_after_open_existing()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_existing_and_verified();
db.delete_db();
assert(!file_exists(fixture_db_filename_existing_and_verified()));
return db;
}
// OK
pEpSQLite test_delete_file_gone_after_open_corrupt()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_corrupt();
db.delete_db();
assert(!file_exists(fixture_db_filename_corrupt()));
return db;
}
// ERR
pEpSQLite test_delete_file_gone_after_open_bad()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_bad();
try {
db.delete_db();
assert(false);
} catch (const exception& e) {
pEp::Test::Utils::print_exception(e);
}
assert(!file_exists(fixture_db_filename_bad()));
return db;
}
// is_open()
// false
pEpSQLite test_is_open_before_open_new()
{
pEpLog("called");
pEpSQLite db = fixture_instance_of_new();
assert(!db.is_open());
return db;
}
// true
pEpSQLite test_is_open_after_open_new()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_new();
assert(db.is_open());
return db;
}
// true
pEpSQLite test_is_open_after_open_existing()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_existing_and_verified();
assert(db.is_open());
return db;
}
// false
pEpSQLite test_is_open_after_open_bad()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_bad();
assert(!db.is_open());
return db;
}
// true (cant detect corruption)
pEpSQLite test_is_open_after_open_corrupt()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_of_corrupt();
assert(db.is_open());
return db;
}
// false
pEpSQLite test_is_open_after_close()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_after_close();
assert(!db.is_open());
return db;
}
// false
pEpSQLite test_is_open_after_delete_on_open()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_after_delete();
assert(!db.is_open());
return db;
}
// false
pEpSQLite test_is_open_after_delete_on_closed()
{
pEpLog("called");
pEpSQLite db = fixture_db_open_after_close_after_delete();
assert(!db.is_open());
return db;
}
} // namespace Test
} // namespace pEp
int main(int argc, char* argv[])
{
// Enable logging for all instances of pEpSQLite
pEpSQLite::log_enabled = true;
Adapter::pEpLog::set_enabled(true);
// instance creation
test_create_instance_on_new();
test_create_instance_on_existing();
test_create_instance_on_path_bad();
test_create_instance_on_path_corrupt();
// db create_open (create_or_open())
test_createopen_db_new();
test_createopen_db_existing();
test_createopen_db_bad();
test_createopen_db_corrupt();
// close()
test_close_before_open();
test_close_after_open();
test_close_idempotent();
// create tables (execute())
test_db_create_tables_on_open_new();
test_db_create_tables_open_existing();
test_db_create_tables_on_open_bad();
test_db_create_tables_on_open_corrupt();
// insert (execute())
test_db_insert_on_tables_exist();
test_db_insert_on_tables_dont_exist();
test_db_insert_before_open();
test_db_insert_after_close();
// verify contents (execute())
test_db_verify_content_existing_open_db();
test_db_verify_content_after_insert_on_tables_exist();
test_db_verify_content_no_tables();
test_db_verify_content_after_create_tables();
// get_path()
test_get_path_on_instance_good();
test_get_path_on_instance_bad();
// delete_db()
test_delete_file_gone_before_open_new();
test_delete_file_gone_before_open_existing();
test_delete_file_gone_after_close_new();
test_delete_file_gone_after_open_existing();
test_delete_file_gone_after_open_corrupt();
test_delete_file_gone_after_open_bad();
// is_open()
test_is_open_before_open_new();
test_is_open_after_open_new();
test_is_open_after_open_existing();
test_is_open_after_open_bad();
test_is_open_after_open_corrupt();
test_is_open_after_close();
test_is_open_after_delete_on_open();
test_is_open_after_delete_on_closed();
file_ensure_not_existing(fixture_db_filename_new());
file_ensure_not_existing(fixture_db_filename_corrupt());
file_ensure_not_existing(fixture_db_filename_existing_and_verified());
return 0;
}

115
test/test_pEpSQLite.hh

@ -0,0 +1,115 @@
// This file is under GNU General Public License 3.0
// see LICENSE.txt
#ifndef LIBPEPADAPTER_TEST_PEPSQLITE_HH
#define LIBPEPADAPTER_TEST_PEPSQLITE_HH
#include <string>
#include "../src/pEpSQLite.hh"
// ATTENTION ATTENTION ATTENTION ATTENTION ATTENTION ATTENTION ATTENTION
//
// THIS IS TERRIBLE, JUST TERRIBLE, THIS IS A ANTI-PATTERN
// Thats the single most stupid way of writing unit test ever!!!
// DONT EVER DO THIS!
//
// The idea is as follows:
// * Tests start simple and become increasingly complex
// * There are fixtures and tests, as usual
// * You start with basic fixtures to write the first test
// * A test becomes a fixture, if the operation is needed for another test
// * You systematically cover all the states possible in which a test can be executed
//
// THATS JUST TERRIBLE!!!
// I WILL NEVER DO THAT AGAIN!
//
// ATTENTION ATTENTION ATTENTION ATTENTION ATTENTION ATTENTION ATTENTION
namespace pEp {
namespace Test {
// FIXTURES
// --------
// filenames
std::string fixture_db_filename_new();
std::string fixture_db_filename_existing_and_verified();
std::string fixture_db_filename_bad();
std::string fixture_db_filename_corrupt();
// prepared db's
std::string fixture_init_db_new();
std::string fixture_init_db_existing_and_verified();
std::string fixture_init_db_corrupt();
// instance
pEpSQLite fixture_instance_of_new();
pEpSQLite fixture_instance_of_existing_and_verified();
pEpSQLite fixture_instance_of_bad();
pEpSQLite fixture_instance_of_corrupt();
// open
pEpSQLite fixture_db_open_of_new();
pEpSQLite fixture_db_open_of_existing_and_verified();
pEpSQLite fixture_db_open_of_bad();
pEpSQLite fixture_db_open_of_corrupt();
pEpSQLite fixture_db_open_after_close();
// tables
pEpSQLite fixture_db_open_with_tables_of_new();
// content
pEpSQLite fixture_db_open_with_tables_and_content();
// delete
pEpSQLite fixture_db_open_after_delete();
pEpSQLite fixture_db_open_after_close_after_delete();
// TESTS
// -----
// instance creation
pEpSQLite test_create_instance_on_new(); // OK
pEpSQLite test_create_instance_on_existing(); // OK
pEpSQLite test_create_instance_on_path_bad(); // OK
pEpSQLite test_create_instance_on_path_corrupt(); // OK
// db create_open (create_or_open())
pEpSQLite test_createopen_db_new(); // OK, new db
pEpSQLite test_createopen_db_existing(); // OK, open db
pEpSQLite test_createopen_db_bad(); // ERR, cant create
pEpSQLite test_createopen_db_corrupt(); // OK (cant detect corruption)
// close()
pEpSQLite test_close_before_open(); // OK
pEpSQLite test_close_after_open(); // OK
pEpSQLite test_close_idempotent(); // OK
// create tables (execute())
pEpSQLite test_db_create_tables_on_open_new(); // OK
pEpSQLite test_db_create_tables_open_existing(); // ERR, Tables already exist
pEpSQLite test_db_create_tables_on_open_bad(); // ERR, db closed
pEpSQLite test_db_create_tables_on_open_corrupt(); // ERR, db corrupt
// insert (execute())
pEpSQLite test_db_insert_on_tables_exist(); // OK
pEpSQLite test_db_insert_on_tables_dont_exist(); // ERR, Tables missing
pEpSQLite test_db_insert_before_open(); // ERR, Tables missing
pEpSQLite test_db_insert_after_close(); // ERR, Tables missing
// verify contents (execute())
pEpSQLite test_db_verify_content_existing_open_db(); // OK
pEpSQLite test_db_verify_content_after_insert_on_tables_exist(); // OK
pEpSQLite test_db_verify_content_no_tables(); // ERR - no tables
pEpSQLite test_db_verify_content_after_create_tables(); // ERR - err no data
// get_path()
pEpSQLite test_get_path_on_instance_good(); // OK
pEpSQLite test_get_path_on_instance_bad(); // OK
// delete_db()
pEpSQLite test_delete_file_gone_before_open_new(); // OK?
pEpSQLite test_delete_file_gone_before_open_existing(); // OK
pEpSQLite test_delete_file_gone_after_close_new(); // OK
pEpSQLite test_delete_file_gone_after_open_existing(); // OK
pEpSQLite test_delete_file_gone_after_open_corrupt(); // OK
pEpSQLite test_delete_file_gone_after_open_bad(); // ERR
// is_open()
pEpSQLite test_is_open_before_open_new(); // false
pEpSQLite test_is_open_after_open_new(); // true
pEpSQLite test_is_open_after_open_existing(); // true
pEpSQLite test_is_open_after_open_bad(); // false
pEpSQLite test_is_open_after_open_corrupt(); // true (cant detect corruption)
pEpSQLite test_is_open_after_close(); // false
pEpSQLite test_is_open_after_delete_on_open(); // false
pEpSQLite test_is_open_after_delete_on_closed(); // false
} // namespace Test
} // end of namespace pEp
#endif // LIBPEPADAPTER_TEST_PEPSQLITE_HH
Loading…
Cancel
Save