diff options
author | Tom Smeding <tom.smeding@gmail.com> | 2020-08-07 22:47:46 +0200 |
---|---|---|
committer | Tom Smeding <tom.smeding@gmail.com> | 2020-08-07 22:47:46 +0200 |
commit | 8f17684810718973c8ea0f1150e46d6c1b8c0ef1 (patch) | |
tree | d03b30c0a14e75e6c3374af3744cf7d7b1789f39 | |
parent | a260f02eac411171a827c9e0aeb31b8de397a5b5 (diff) |
server: Add basic unit test framework
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 46 | ||||
-rw-r--r-- | TODO.txt | 4 | ||||
-rw-r--r-- | test/hashtable.c | 8 | ||||
-rw-r--r-- | test/main.c | 49 | ||||
-rw-r--r-- | test/test_framework.c | 14 | ||||
-rw-r--r-- | test/test_framework.h | 25 |
7 files changed, 135 insertions, 13 deletions
@@ -1,7 +1,9 @@ *.o *.dSYM *.sql.h +test/test_declarations.h tomsg_server +test/main db.db *.dylib *.so @@ -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' >$@ @@ -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_); \ + } \ + } |