aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile46
-rw-r--r--TODO.txt4
-rw-r--r--test/hashtable.c8
-rw-r--r--test/main.c49
-rw-r--r--test/test_framework.c14
-rw-r--r--test/test_framework.h25
7 files changed, 135 insertions, 13 deletions
diff --git a/.gitignore b/.gitignore
index 8d78be1..a33cbb5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,9 @@
*.o
*.dSYM
*.sql.h
+test/test_declarations.h
tomsg_server
+test/main
db.db
*.dylib
*.so
diff --git a/Makefile b/Makefile
index 41621f9..fe01fb6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,16 +1,24 @@
-CC = gcc
-CFLAGS = -Wall -Wextra -std=c11 -g -O2 -fwrapv -D_DEFAULT_SOURCE
-LDFLAGS = -ldl
+CC := gcc
+CFLAGS := -Wall -Wextra -std=c11 -g -O2 -fwrapv -D_DEFAULT_SOURCE
+LDFLAGS := -ldl
CFLAGS += $(shell pkg-config --cflags sqlite3 libsodium)
LDFLAGS += $(shell pkg-config --libs sqlite3 libsodium)
-TARGETS = tomsg_server
+TARGETS := tomsg_server
+TEST_TARGET := test/main
-PLUGINDIR = plugins
+PLUGINDIR := plugins
-PLUGINS = $(filter-out _%,$(patsubst $(PLUGINDIR)/%,%,$(shell find $(PLUGINDIR)/ -maxdepth 1 -type d)))
+PLUGINS := $(filter-out _%,$(patsubst $(PLUGINDIR)/%,%,$(shell find $(PLUGINDIR)/ -maxdepth 1 -type d)))
-.PHONY: all clean
+SOURCES := $(wildcard *.c)
+OBJECTS := $(SOURCES:.c=.o)
+TEST_ONLY_SOURCES := $(wildcard test/*.c)
+TEST_ONLY_OBJECTS := $(TEST_ONLY_SOURCES:.c=.o)
+TEST_SOURCES := $(TEST_ONLY_SOURCES) $(filter-out main.c,$(SOURCES))
+TEST_OBJECTS := $(TEST_SOURCES:.c=.o)
+
+.PHONY: all clean test_build test
# Clear all implicit suffix rules
.SUFFIXES:
@@ -19,18 +27,32 @@ PLUGINS = $(filter-out _%,$(patsubst $(PLUGINDIR)/%,%,$(shell find $(PLUGINDIR)/
.SECONDARY:
all: $(TARGETS)
- for pl in $(PLUGINS); do make --no-print-directory -C $(PLUGINDIR) $@ PLUGINNAME=$$pl; done
+ for pl in $(PLUGINS); do $(MAKE) --no-print-directory -C $(PLUGINDIR) $@ PLUGINNAME=$$pl; done
clean:
- rm -f $(TARGETS) *.o *.sql.h
- for pl in $(PLUGINS); do make --no-print-directory -C $(PLUGINDIR) $@ PLUGINNAME=$$pl; done
+ rm -f $(TARGETS) $(TEST_TARGET) *.o *.sql.h test/test_declarations.h
+ for pl in $(PLUGINS); do $(MAKE) --no-print-directory -C $(PLUGINDIR) $@ PLUGINNAME=$$pl; done
+
+test_build: $(TEST_TARGET)
+
+test: test_build
+ $(TEST_TARGET)
-$(TARGETS): $(patsubst %.c,%.o,$(wildcard *.c))
+$(TARGETS): $(OBJECTS)
$(CC) -o $@ $^ $(LDFLAGS)
-%.o: %.c $(wildcard *.h) $(patsubst %.sql,%.sql.h,$(wildcard *.sql))
+$(OBJECTS) $(TEST_ONLY_OBJECTS): $(wildcard *.h) $(patsubst %.sql,%.sql.h,$(wildcard *.sql))
+$(TEST_ONLY_OBJECTS): test/test_declarations.h
+%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
%.sql.h: %.sql
xxd -i $^ $@
+
+
+$(TEST_TARGET): $(TEST_OBJECTS) $(wildcard *.h test/*.h)
+ $(CC) -o $@ $(filter %.o,$^) $(LDFLAGS)
+
+test/test_declarations.h: $(wildcard test/*.c)
+ $(CC) -E -D TEST_HEADER_GENERATION=1 test/*.c | sed -n 's/^XXTEST_DECLARATION(\([^)]*\)).*/int \1(void);/p' >$@
diff --git a/TODO.txt b/TODO.txt
index 9efde47..f180d0a 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,8 +1,10 @@
+- Server crashed with 'DIE userdata_unregister called while nonexistent'; fix!
+- Unit tests for hashtable
+
- Room leave command
- Store message as UTF8 text, not blob
- Somehow communicate (push? poll?) to room members whether a particular user is currently _active_, not just online.
- A session is marked inactive 2 minutes after the latest activity on that session. Make the number "2" configurable and not a hard-coded constant.
- Use poll(2), not select(2)
- Fix OOM dos vector
-- Unit tests for hashtable
- Public rooms, and public room join command
diff --git a/test/hashtable.c b/test/hashtable.c
new file mode 100644
index 0000000..cfb2d9d
--- /dev/null
+++ b/test/hashtable.c
@@ -0,0 +1,8 @@
+#include "test_framework.h"
+
+
+DEFINE_TEST(hashtable) {
+ EXPECT(1);
+ EXPECT(0);
+ return 0;
+}
diff --git a/test/main.c b/test/main.c
new file mode 100644
index 0000000..ebfe3da
--- /dev/null
+++ b/test/main.c
@@ -0,0 +1,49 @@
+#include <stdio.h>
+#include <stdatomic.h>
+#include <time.h>
+#include "test_framework.h"
+#include "test_declarations.h"
+
+#define STR_(x) #x
+#define STR(x) STR_(x)
+
+
+// Referred to via extern in 'test_framework.c'
+atomic_flag test_framework_assertion_failed = ATOMIC_FLAG_INIT;
+
+
+static void report(const char *clr, const char *label, const char *name, clock_t taken) {
+ printf("\x1B[%sm[%s] %s (%lfs)\n",
+ clr, label, name, (double)taken / CLOCKS_PER_SEC);
+}
+
+static void report_success(const char *name, clock_t taken) {
+ report("32", " OK ", name, taken);
+}
+
+static void report_failure(const char *name, clock_t taken) {
+ report("31", "FAILED", name, taken);
+}
+
+#define RUN_TEST(name) do { \
+ atomic_flag_clear(&test_framework_assertion_failed); \
+ clock_t start_ = clock(); \
+ int ret_ = TEST(name)(); \
+ clock_t diff_ = clock() - start_; \
+ if (ret_ == 0 && !atomic_flag_test_and_set(&test_framework_assertion_failed)) { \
+ report_success(STR(name), diff_); \
+ } else { \
+ report_failure(STR(name), diff_); \
+ return 1; \
+ } \
+ } while (0)
+
+static int run_tests(void) {
+ RUN_TEST(hashtable);
+ return 0;
+}
+
+
+int main(void) {
+ return run_tests();
+}
diff --git a/test/test_framework.c b/test/test_framework.c
new file mode 100644
index 0000000..8cd53ac
--- /dev/null
+++ b/test/test_framework.c
@@ -0,0 +1,14 @@
+#include <stdio.h>
+#include <stdatomic.h>
+#include "test_framework.h"
+
+
+extern atomic_flag test_framework_assertion_failed;
+
+
+void test_report_error(
+ const char *type, const char *condition, const char *fname, int lineno) {
+ printf("\x1B[31;1m%s:%d: Assertion failed:\n %s(%s)\x1B[0m\n",
+ fname, lineno, type, condition);
+ atomic_flag_test_and_set(&test_framework_assertion_failed);
+}
diff --git a/test/test_framework.h b/test/test_framework.h
new file mode 100644
index 0000000..7ab4816
--- /dev/null
+++ b/test/test_framework.h
@@ -0,0 +1,25 @@
+#pragma once
+
+
+#define TEST(name) testfn__ ## name
+
+// Test function should return 0 on success, nonzero value on error
+#ifdef TEST_HEADER_GENERATION
+#define DEFINE_TEST(name) XXTEST_DECLARATION(TEST(name))
+#else
+#define DEFINE_TEST(name) int TEST(name)(void)
+#endif
+
+void test_report_error(
+ const char *type, const char *condition, const char *fname, int lineno);
+
+#define EXPECT(cond_) do { \
+ if (!(cond_)) test_report_error("EXPECT", #cond_, __FILE__, __LINE__); \
+ } while (0)
+
+#define EXPECTRET(cond_, ret_) do { \
+ if (!(cond_)) { \
+ test_report_error("EXPECT", #cond_, __FILE__, __LINE__); \
+ return (ret_); \
+ } \
+ }