summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2023-01-01 14:53:05 +0100
committerRoland Reichwein <mail@reichwein.it>2023-01-01 14:53:05 +0100
commitcbf1ba38794ab6a323441dcc3b0e5e942f7ab386 (patch)
treec20619b90a1f9ca512aa5e1db9354178e2c2726d
parent1f679124bba936e7905c7f4c83186d0c961dca61 (diff)
Added CompiledSQL class, Test coverage
-rwxr-xr-xMakefile2
-rw-r--r--common.mk14
-rw-r--r--compiledsql.cpp32
-rw-r--r--compiledsql.h34
-rw-r--r--storage.cpp130
-rw-r--r--storage.h27
-rw-r--r--tests/Makefile45
7 files changed, 192 insertions, 92 deletions
diff --git a/Makefile b/Makefile
index 85a5ed4..140b1ba 100755
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ DISTROS=base debian11 ubuntu2204
VERSION=$(shell dpkg-parsechangelog --show-field Version)
INCLUDES=-I.
-HEADERS=file.h config.h qrcode.h storage.h whiteboard.h
+HEADERS=file.h config.h qrcode.h storage.h whiteboard.h compiledsql.h
SOURCES=$(HEADERS:.h=.cpp)
OBJECTS=$(HEADERS:.h=.o)
TARGETS=whiteboard.fcgi
diff --git a/common.mk b/common.mk
index 9da602e..ccacd6c 100644
--- a/common.mk
+++ b/common.mk
@@ -34,17 +34,11 @@ CXXFLAGS+=-std=c++20
endif
ifeq ($(CXX),clang++-10)
-LIBS+= \
--fuse-ld=lld-10 \
--lstdc++
-#-lc++ \
-#-lc++abi
-#-lc++fs
-#-lstdc++fs
+LIBS+=-fuse-ld=lld-10 -lstdc++
+else ifeq ($(CXX),clang++-14)
+LIBS+=-fuse-ld=lld-14 -lc++ -lc++abi
else
-LIBS+= \
--lstdc++ \
--lstdc++fs
+LIBS+=-lstdc++ -lstdc++fs
endif
CXXFLAGS+=$(shell pkg-config --cflags qrcodegencpp Magick++ fmt sqlite3)
diff --git a/compiledsql.cpp b/compiledsql.cpp
new file mode 100644
index 0000000..a3503af
--- /dev/null
+++ b/compiledsql.cpp
@@ -0,0 +1,32 @@
+#include "compiledsql.h"
+
+CompiledSQL::CompiledSQL(SQLite::Database& db):
+ m_stmt{},
+ m_db{db},
+ m_isSelect{}
+{
+}
+
+void CompiledSQL::init(const std::string& stmt)
+{
+ if (m_stmt) {
+ m_stmt->reset();
+ } else {
+ if (stmt.starts_with("SELECT ")) {
+ m_isSelect = true;
+ } else {
+ m_isSelect = false;
+ }
+ m_stmt = std::make_shared<SQLite::Statement>(m_db, stmt);
+ }
+}
+
+bool CompiledSQL::execute()
+{
+ if (m_isSelect) {
+ return m_stmt->executeStep();
+ } else {
+ return m_stmt->exec();
+ }
+}
+
diff --git a/compiledsql.h b/compiledsql.h
new file mode 100644
index 0000000..923be83
--- /dev/null
+++ b/compiledsql.h
@@ -0,0 +1,34 @@
+// Helper Class for SQLite backed storage
+
+#pragma once
+
+#include <memory>
+
+#include <SQLiteCpp/SQLiteCpp.h>
+
+class CompiledSQL
+{
+public:
+ CompiledSQL(SQLite::Database& db);
+
+ void init(const std::string& stmt);
+
+ template<typename T>
+ void bind(int index, T value)
+ {
+ m_stmt->bind(index, value);
+ }
+
+ bool execute();
+
+ template<typename T>
+ T getColumn(const int index)
+ {
+ return m_stmt->getColumn(index);
+ }
+
+private:
+ std::shared_ptr<SQLite::Statement> m_stmt;
+ SQLite::Database& m_db;
+ bool m_isSelect; // In SQLite, SELECT statements will be handled w/ executeStep(), others w/ exec()
+};
diff --git a/storage.cpp b/storage.cpp
index 392f06c..ffdb2f4 100644
--- a/storage.cpp
+++ b/storage.cpp
@@ -11,23 +11,23 @@ using namespace std::string_literals;
Storage::Storage(const Config& config):
m_db(config.getDataPath() + "/whiteboard.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE),
- m_maxage(config.getMaxage())
+ m_maxage(config.getMaxage()),
+ m_stmt_create(m_db),
+ m_stmt_getNumberOfDocuments(m_db),
+ m_stmt_cleanup(m_db),
+ m_stmt_exists(m_db),
+ m_stmt_getDocument(m_db),
+ m_stmt_getRevision(m_db),
+ m_stmt_getCursorPos(m_db),
+ m_stmt_getRow(m_db),
+ m_stmt_setDocument(m_db),
+ m_stmt_setDocument_new(m_db),
+ m_stmt_setRevision(m_db),
+ m_stmt_setCursorPos(m_db),
+ m_stmt_setRow(m_db)
{
- m_stmt_create = std::make_shared<SQLite::Statement>(m_db, "CREATE TABLE IF NOT EXISTS documents (id VARCHAR(16) PRIMARY KEY, value BLOB, rev INTEGER, cursorpos INTEGER, timestamp BIGINT)");
- m_stmt_create->exec();
-
- m_stmt_getNumberOfDocuments = std::make_shared<SQLite::Statement>(m_db, "SELECT COUNT(*) FROM documents");
- m_stmt_cleanup = std::make_shared<SQLite::Statement>(m_db, "DELETE FROM documents WHERE timestamp + " + std::to_string(static_cast<int64_t>(m_maxage)) + " < unixepoch()");
- m_stmt_exists = std::make_shared<SQLite::Statement>(m_db, "SELECT id FROM documents WHERE id = ?");
- m_stmt_getDocument = std::make_shared<SQLite::Statement>(m_db, "SELECT value FROM documents WHERE id = ?");
- m_stmt_getRevision = std::make_shared<SQLite::Statement>(m_db, "SELECT rev FROM documents WHERE id = ?");
- m_stmt_getCursorPos = std::make_shared<SQLite::Statement>(m_db, "SELECT cursorpos FROM documents WHERE id = ?");
- m_stmt_getRow = std::make_shared<SQLite::Statement>(m_db, "SELECT value, rev, cursorpos FROM documents WHERE id = ?");
- m_stmt_setDocument = std::make_shared<SQLite::Statement>(m_db, "UPDATE documents SET value = ? WHERE id = ?");
- m_stmt_setDocument_new = std::make_shared<SQLite::Statement>(m_db, "INSERT INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, unixepoch())");
- m_stmt_setRevision = std::make_shared<SQLite::Statement>(m_db, "UPDATE documents SET rev = ? WHERE id = ?");
- m_stmt_setCursorPos = std::make_shared<SQLite::Statement>(m_db, "UPDATE documents SET cursorpos = ? WHERE id = ?");
- m_stmt_setRow = std::make_shared<SQLite::Statement>(m_db, "INSERT OR REPLACE INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, unixepoch())");
+ m_stmt_create.init("CREATE TABLE IF NOT EXISTS documents (id VARCHAR(16) PRIMARY KEY, value BLOB, rev INTEGER, cursorpos INTEGER, timestamp BIGINT)");
+ m_stmt_create.execute();
}
Storage::~Storage()
@@ -36,11 +36,11 @@ Storage::~Storage()
uint64_t Storage::getNumberOfDocuments()
{
- m_stmt_getNumberOfDocuments->reset();
- if (!m_stmt_getNumberOfDocuments->executeStep())
+ m_stmt_getNumberOfDocuments.init("SELECT COUNT(*) FROM documents");
+ if (!m_stmt_getNumberOfDocuments.execute())
throw std::runtime_error("Count not possible");
- return static_cast<int64_t>(m_stmt_getNumberOfDocuments->getColumn(0));
+ return m_stmt_getNumberOfDocuments.getColumn<int64_t>(0);
}
void Storage::cleanup()
@@ -48,106 +48,106 @@ void Storage::cleanup()
if (m_maxage == 0)
return;
- m_stmt_cleanup->reset();
- m_stmt_cleanup->exec();
+ m_stmt_cleanup.init("DELETE FROM documents WHERE timestamp + "s + std::to_string(static_cast<int64_t>(m_maxage)) + " < unixepoch()"s);
+ m_stmt_cleanup.execute();
}
bool Storage::exists(const std::string& id)
{
- m_stmt_exists->reset();
- m_stmt_exists->bind(1, id);
+ m_stmt_exists.init("SELECT id FROM documents WHERE id = ?");
+ m_stmt_exists.bind(1, id);
- return m_stmt_exists->executeStep();
+ return m_stmt_exists.execute();
}
std::string Storage::getDocument(const std::string& id)
{
- m_stmt_getDocument->reset();
- m_stmt_getDocument->bind(1, id);
+ m_stmt_getDocument.init("SELECT value FROM documents WHERE id = ?");
+ m_stmt_getDocument.bind(1, id);
- if (!m_stmt_getDocument->executeStep())
+ if (!m_stmt_getDocument.execute())
throw std::runtime_error("id "s + id + " not found"s);
- return m_stmt_getDocument->getColumn(0);
+ return m_stmt_getDocument.getColumn<std::string>(0);
}
int Storage::getRevision(const std::string& id)
{
- m_stmt_getRevision->reset();
- m_stmt_getRevision->bind(1, id);
+ m_stmt_getRevision.init("SELECT rev FROM documents WHERE id = ?");
+ m_stmt_getRevision.bind(1, id);
- if (!m_stmt_getRevision->executeStep())
+ if (!m_stmt_getRevision.execute())
throw std::runtime_error("id "s + id + " not found"s);
- return m_stmt_getRevision->getColumn(0);
+ return m_stmt_getRevision.getColumn<int>(0);
}
int Storage::getCursorPos(const std::string& id)
{
- m_stmt_getCursorPos->reset();
- m_stmt_getCursorPos->bind(1, id);
+ m_stmt_getCursorPos.init("SELECT cursorpos FROM documents WHERE id = ?");
+ m_stmt_getCursorPos.bind(1, id);
- if (!m_stmt_getCursorPos->executeStep())
+ if (!m_stmt_getCursorPos.execute())
throw std::runtime_error("id "s + id + " not found"s);
- return m_stmt_getCursorPos->getColumn(0);
+ return m_stmt_getCursorPos.getColumn<int>(0);
}
std::tuple<std::string, int, int> Storage::getRow(const std::string& id)
{
- m_stmt_getRow->reset();
- m_stmt_getRow->bind(1, id);
+ m_stmt_getRow.init("SELECT value, rev, cursorpos FROM documents WHERE id = ?");
+ m_stmt_getRow.bind(1, id);
- if (!m_stmt_getRow->executeStep())
+ if (!m_stmt_getRow.execute())
throw std::runtime_error("id "s + id + " not found"s);
- return {m_stmt_getRow->getColumn(0), m_stmt_getRow->getColumn(1), m_stmt_getRow->getColumn(2)};
+ return {m_stmt_getRow.getColumn<std::string>(0), m_stmt_getRow.getColumn<int>(1), m_stmt_getRow.getColumn<int>(2)};
}
void Storage::setDocument(const std::string& id, const std::string& document)
{
- m_stmt_setDocument->reset();
- m_stmt_setDocument->bind(1, document);
- m_stmt_setDocument->bind(2, id);
-
- if (!m_stmt_setDocument->exec()) {
- m_stmt_setDocument_new->reset();
- m_stmt_setDocument_new->bind(1, id);
- m_stmt_setDocument_new->bind(2, document);
- m_stmt_setDocument_new->bind(3, 0);
- m_stmt_setDocument_new->bind(4, 0);
- m_stmt_setDocument_new->exec();
+ m_stmt_setDocument.init("UPDATE documents SET value = ? WHERE id = ?");
+ m_stmt_setDocument.bind(1, document);
+ m_stmt_setDocument.bind(2, id);
+
+ if (!m_stmt_setDocument.execute()) {
+ m_stmt_setDocument_new.init("INSERT INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, unixepoch())");
+ m_stmt_setDocument_new.bind(1, id);
+ m_stmt_setDocument_new.bind(2, document);
+ m_stmt_setDocument_new.bind(3, 0);
+ m_stmt_setDocument_new.bind(4, 0);
+ m_stmt_setDocument_new.execute();
}
}
void Storage::setRevision(const std::string& id, int rev)
{
- m_stmt_setRevision->reset();
- m_stmt_setRevision->bind(1, rev);
- m_stmt_setRevision->bind(2, id);
+ m_stmt_setRevision.init("UPDATE documents SET rev = ? WHERE id = ?");
+ m_stmt_setRevision.bind(1, rev);
+ m_stmt_setRevision.bind(2, id);
- if (!m_stmt_setRevision->exec())
+ if (!m_stmt_setRevision.execute())
throw std::runtime_error("Unable to insert row with id "s + id);
}
void Storage::setCursorPos(const std::string& id, int cursorPos)
{
- m_stmt_setCursorPos->reset();
- m_stmt_setCursorPos->bind(1, cursorPos);
- m_stmt_setCursorPos->bind(2, id);
+ m_stmt_setCursorPos.init("UPDATE documents SET cursorpos = ? WHERE id = ?");
+ m_stmt_setCursorPos.bind(1, cursorPos);
+ m_stmt_setCursorPos.bind(2, id);
- if (!m_stmt_setCursorPos->exec())
+ if (!m_stmt_setCursorPos.execute())
throw std::runtime_error("Unable to insert row with id "s + id);
}
void Storage::setRow(const std::string& id, const std::string& document, int rev, int cursorPos)
{
- m_stmt_setRow->reset();
- m_stmt_setRow->bind(1, id);
- m_stmt_setRow->bind(2, document);
- m_stmt_setRow->bind(3, rev);
- m_stmt_setRow->bind(4, cursorPos);
- if (!m_stmt_setRow->exec())
+ m_stmt_setRow.init("INSERT OR REPLACE INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, unixepoch())");
+ m_stmt_setRow.bind(1, id);
+ m_stmt_setRow.bind(2, document);
+ m_stmt_setRow.bind(3, rev);
+ m_stmt_setRow.bind(4, cursorPos);
+ if (!m_stmt_setRow.execute())
throw std::runtime_error("Unable to insert row with id "s + id);
}
diff --git a/storage.h b/storage.h
index 24f2961..09514c3 100644
--- a/storage.h
+++ b/storage.h
@@ -7,6 +7,7 @@
#include <SQLiteCpp/SQLiteCpp.h>
#include "config.h"
+#include "compiledsql.h"
class Storage
{
@@ -34,18 +35,18 @@ private:
uint64_t m_maxage;
// shared_ptr to work around initialization in constructor
- std::shared_ptr<SQLite::Statement> m_stmt_create;
- std::shared_ptr<SQLite::Statement> m_stmt_getNumberOfDocuments;
- std::shared_ptr<SQLite::Statement> m_stmt_cleanup;
- std::shared_ptr<SQLite::Statement> m_stmt_exists;
- std::shared_ptr<SQLite::Statement> m_stmt_getDocument;
- std::shared_ptr<SQLite::Statement> m_stmt_getRevision;
- std::shared_ptr<SQLite::Statement> m_stmt_getCursorPos;
- std::shared_ptr<SQLite::Statement> m_stmt_getRow;
- std::shared_ptr<SQLite::Statement> m_stmt_setDocument;
- std::shared_ptr<SQLite::Statement> m_stmt_setDocument_new;
- std::shared_ptr<SQLite::Statement> m_stmt_setRevision;
- std::shared_ptr<SQLite::Statement> m_stmt_setCursorPos;
- std::shared_ptr<SQLite::Statement> m_stmt_setRow;
+ CompiledSQL m_stmt_create;
+ CompiledSQL m_stmt_getNumberOfDocuments;
+ CompiledSQL m_stmt_cleanup;
+ CompiledSQL m_stmt_exists;
+ CompiledSQL m_stmt_getDocument;
+ CompiledSQL m_stmt_getRevision;
+ CompiledSQL m_stmt_getCursorPos;
+ CompiledSQL m_stmt_getRow;
+ CompiledSQL m_stmt_setDocument;
+ CompiledSQL m_stmt_setDocument_new;
+ CompiledSQL m_stmt_setRevision;
+ CompiledSQL m_stmt_setCursorPos;
+ CompiledSQL m_stmt_setRow;
};
diff --git a/tests/Makefile b/tests/Makefile
index 1f912c3..e36198b 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -1,5 +1,18 @@
+CXXFLAGS=-g -O0
+
+ifeq ($(CXX),clang++-14)
+CXXFLAGS+=-fprofile-instr-generate -fcoverage-mapping
+else
+# GCC
+CXXFLAGS+=--coverage
+endif
+
include ../common.mk
+LDFLAGS+=-fprofile-instr-generate -fcoverage-mapping
+
+UNITS=storage.cpp config.cpp file.cpp compiledsql.cpp qrcode.cpp whiteboard.cpp
+
UNITTESTS=test-config.cpp \
test-storage.cpp
@@ -11,14 +24,40 @@ CXXFLAGS+=\
-I..
test: unittests
- ./unittests
+ # https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
+ifeq ($(CXX),clang++-14)
+ LLVM_PROFILE_FILE="unittests.profraw" ./unittests
+ llvm-profdata-14 merge -sparse unittests.profraw -o unittests.profdata
+ llvm-cov-14 report --ignore-filename-regex='google' --ignore-filename-regex='test-' --show-region-summary=0 -instr-profile unittests.profdata unittests
+endif
+
+coverage:
+ llvm-cov-14 show -instr-profile unittests.profdata $(UNITS:.cpp=.o)
-unittests: libgmock.a $(UNITTESTS:.cpp=.o) ../config.o ../file.o ../storage.o
+unittests: libgmock.a $(UNITTESTS:.cpp=.o) $(UNITS:.cpp=.o)
$(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LIBS) -o $@
%.o: %.cpp
$(CXX) $(CXXFLAGS) -o $@ -c $<
+config.o: ../config.cpp
+ $(CXX) $(CXXFLAGS) -o $@ -c $<
+
+file.o: ../file.cpp
+ $(CXX) $(CXXFLAGS) -o $@ -c $<
+
+storage.o: ../storage.cpp
+ $(CXX) $(CXXFLAGS) -o $@ -c $<
+
+compiledsql.o: ../compiledsql.cpp
+ $(CXX) $(CXXFLAGS) -o $@ -c $<
+
+whiteboard.o: ../whiteboard.cpp
+ $(CXX) $(CXXFLAGS) -o $@ -c $<
+
+qrcode.o: ../qrcode.cpp
+ $(CXX) $(CXXFLAGS) -o $@ -c $<
+
libgmock.a:
$(CXX) $(CXXFLAGS) -c /usr/src/googletest/googletest/src/gtest-all.cc
$(CXX) $(CXXFLAGS) -c /usr/src/googletest/googlemock/src/gmock-all.cc
@@ -26,4 +65,4 @@ libgmock.a:
ar -rv libgmock.a gmock-all.o gtest-all.o gmock_main.o
clean:
- -rm -f *.o *.a unittests
+ -rm -f *.o *.a unittests *.gcno *.profraw *.profdata