diff --git a/libol-tests.vcxproj b/libol-tests.vcxproj
new file mode 100644
index 00000000..911a266f
--- /dev/null
+++ b/libol-tests.vcxproj
@@ -0,0 +1,95 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+
+
+ {301fe650-cd34-14e5-6b63-42e383fa02bc}
+ false
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ test
+ {B30D70EF-2678-4393-B322-74E1476757DC}
+ ManagedCProj
+ liboltests
+
+
+
+ Application
+ Unicode
+
+
+ Application
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+ if exist app.config copy app.config "$(OutDir)app.config"
+ true
+ false
+ .exe
+ .exe
+
+
+
+ Level3
+ Disabled
+ WIN32;_DEBUG;%(PreprocessorDefinitions)
+ NotUsing
+
+
+ true
+ ws2_32.lib
+ Default
+
+
+
+
+ Level3
+ WIN32;NDEBUG;%(PreprocessorDefinitions)
+ NotUsing
+
+
+ true
+ ws2_32.lib
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libol.sln b/libol.sln
index b9144123..45cef918 100644
--- a/libol.sln
+++ b/libol.sln
@@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libol", "libol.vcxproj", "{301FE650-CD34-14E5-6B63-42E383FA02BC}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libol-tests", "libol-tests.vcxproj", "{B30D70EF-2678-4393-B322-74E1476757DC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -13,6 +15,10 @@ Global
{301FE650-CD34-14E5-6B63-42E383FA02BC}.Debug|Win32.Build.0 = Debug|Win32
{301FE650-CD34-14E5-6B63-42E383FA02BC}.Release|Win32.ActiveCfg = Release|Win32
{301FE650-CD34-14E5-6B63-42E383FA02BC}.Release|Win32.Build.0 = Release|Win32
+ {B30D70EF-2678-4393-B322-74E1476757DC}.Debug|Win32.ActiveCfg = Debug|Win32
+ {B30D70EF-2678-4393-B322-74E1476757DC}.Debug|Win32.Build.0 = Debug|Win32
+ {B30D70EF-2678-4393-B322-74E1476757DC}.Release|Win32.ActiveCfg = Release|Win32
+ {B30D70EF-2678-4393-B322-74E1476757DC}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/libol.vcxproj b/libol.vcxproj
index 7a68a3df..30861fdb 100644
--- a/libol.vcxproj
+++ b/libol.vcxproj
@@ -15,11 +15,11 @@
- Application
+ StaticLibrary
true
- Application
+ StaticLibrary
false
@@ -40,7 +40,7 @@
- WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ WIN32;_DEBUG;%(PreprocessorDefinitions)
MultiThreadedDebugDLL
Level3
ProgramDatabase
@@ -55,7 +55,7 @@
- WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ WIN32;NDEBUG;%(PreprocessorDefinitions)
MultiThreadedDLL
Level3
ProgramDatabase
@@ -70,20 +70,11 @@
-
-
-
- true
-
-
-
-
-
diff --git a/test/echo.c b/test/echo-server.c
similarity index 91%
rename from test/echo.c
rename to test/echo-server.c
index 5d8d406a..6e72ad82 100644
--- a/test/echo.c
+++ b/test/echo-server.c
@@ -1,7 +1,7 @@
#include "../ol.h"
+#include "test.h"
#include
#include
-#include
#define BUFSIZE 1024
@@ -111,7 +111,19 @@ int echo_start(int port) {
return 0;
}
+
int echo_stop() {
assert(server != NULL);
- ol_close(server);
+ return ol_close(server);
+}
+
+
+TEST_IMPL(echo_server) {
+ ol_init();
+ if (echo_start(TEST_PORT))
+ return 1;
+
+ fprintf(stderr, "Listening!\n");
+ ol_run();
+ return 0;
}
\ No newline at end of file
diff --git a/test/echo.h b/test/echo.h
deleted file mode 100644
index 52246a2c..00000000
--- a/test/echo.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#include "../ol.h"
-
-int echo_start(int port);
-int echo_stop();
\ No newline at end of file
diff --git a/test/test-fail-always.c b/test/test-fail-always.c
new file mode 100644
index 00000000..5780a67c
--- /dev/null
+++ b/test/test-fail-always.c
@@ -0,0 +1,7 @@
+#include "test.h"
+
+TEST_IMPL(fail_always) {
+ /* This test always fails. It is used to test the test runner. */
+ assert("Yes, it always fails" && 0);
+ return 1;
+}
\ No newline at end of file
diff --git a/test/test-list.h b/test/test-list.h
new file mode 100644
index 00000000..28714fd3
--- /dev/null
+++ b/test/test-list.h
@@ -0,0 +1,13 @@
+TEST_DECLARE (echo_server)
+TEST_DECLARE (ping_pong)
+TEST_DECLARE (pass_always)
+TEST_DECLARE (fail_always)
+
+TEST_LIST_START
+ TEST_ENTRY (ping_pong)
+ TEST_HELPER (ping_pong, echo_server)
+
+ TEST_ENTRY (fail_always)
+
+ TEST_ENTRY (pass_always)
+TEST_LIST_END
\ No newline at end of file
diff --git a/test/test-pass-always.c b/test/test-pass-always.c
new file mode 100644
index 00000000..975913a7
--- /dev/null
+++ b/test/test-pass-always.c
@@ -0,0 +1,6 @@
+#include "test.h"
+
+TEST_IMPL(pass_always) {
+ /* This test always passes. It is used to test the test runner. */
+ return 0;
+}
\ No newline at end of file
diff --git a/test/test-ping-pong.c b/test/test-ping-pong.c
index 7b6f49ee..9f25d555 100644
--- a/test/test-ping-pong.c
+++ b/test/test-ping-pong.c
@@ -1,11 +1,14 @@
#include "../ol.h"
-#include "echo.h"
+#include "test.h"
#include
#include
+#include
static int completed_pingers = 0;
static ol_req connect_req;
+#define NUM_PINGS 50
+
/* 64 bytes is enough for a pinger */
#define BUFSIZE 64
@@ -27,11 +30,10 @@ void pinger_on_close(ol_handle* handle, ol_err err) {
assert(!err);
p = (pinger*)handle->data;
- assert(1000 == p->pongs);
+ assert(NUM_PINGS == p->pongs);
free(p);
ol_free(handle);
completed_pingers++;
- echo_stop();
}
void pinger_after_read(ol_req* req, size_t nread) {
@@ -53,7 +55,7 @@ void pinger_after_read(ol_req* req, size_t nread) {
p->state = (p->state + 1) % 5;
if (p->state == 0) {
p->pongs++;
- if (p->pongs < 1000) {
+ if (p->pongs < NUM_PINGS) {
r = ol_write2(p->handle, PING);
assert(!r);
} else {
@@ -75,10 +77,16 @@ void pinger_try_read(pinger* pinger) {
void pinger_on_connect(ol_req *req, ol_err err) {
- int r;
ol_handle *handle = req->handle;
+ pinger *p;
+ int r;
- pinger *p = calloc(sizeof(pinger), 1);
+ if (err) {
+ /* error */
+ assert(0);
+ }
+
+ p = calloc(sizeof(pinger), 1);
p->handle = handle;
p->buf.base = p->read_buffer;
p->buf.len = BUFSIZE;
@@ -97,10 +105,10 @@ void pinger_on_connect(ol_req *req, ol_err err) {
int pinger_connect(int port) {
- /* Try to connec to the server and do 1000 ping-pongs. */
+ /* Try to connec to the server and do NUM_PINGS ping-pongs. */
ol_handle* handle = ol_tcp_handle_new(pinger_on_close, NULL);
struct sockaddr_in client_addr = ol_ip4_addr("0.0.0.0", 0);
- struct sockaddr_in server_addr = ol_ip4_addr("127.0.0.1", port);
+ struct sockaddr_in server_addr = ol_ip4_addr("127.0.0.1", TEST_PORT);
ol_bind(handle, (struct sockaddr*)&client_addr);
ol_req_init(&connect_req, &pinger_on_connect);
@@ -108,13 +116,9 @@ int pinger_connect(int port) {
}
-int main(int argc, char** argv) {
+TEST_IMPL(ping_pong) {
ol_init();
- if (echo_start(8000)) {
- return 1;
- }
-
if (pinger_connect(8000)) {
return 2;
}
diff --git a/test/test-runner-win32.c b/test/test-runner-win32.c
new file mode 100644
index 00000000..8236bff1
--- /dev/null
+++ b/test/test-runner-win32.c
@@ -0,0 +1,210 @@
+
+#include
+#include
+#include
+#include
+
+#include "test-runner.h"
+
+
+int process_start(char *name, process_info_t *p) {
+ HANDLE file = INVALID_HANDLE_VALUE;
+ HANDLE nul = INVALID_HANDLE_VALUE;
+ WCHAR path[MAX_PATH], filename[MAX_PATH];
+ WCHAR image[MAX_PATH + 1];
+ WCHAR args[MAX_PATH * 2];
+ STARTUPINFOW si;
+ PROCESS_INFORMATION pi;
+ DWORD result;
+
+ if (GetTempPathW(sizeof(path), (WCHAR*)&path) == 0)
+ goto error;
+ if (GetTempFileNameW((WCHAR*)&path, L"ol_", 0, (WCHAR*)&filename) == 0)
+ goto error;
+
+ file = CreateFileW((WCHAR*)filename,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ NULL,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
+ NULL);
+ if (file == INVALID_HANDLE_VALUE)
+ goto error;
+
+ if (!SetHandleInformation(file, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
+ goto error;
+
+ nul = CreateFileA("nul",
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ if (nul == INVALID_HANDLE_VALUE)
+ goto error;
+
+ if (!SetHandleInformation(nul, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
+ goto error;
+
+ result = GetModuleFileName(NULL, (WCHAR*)&image, sizeof(image));
+ if (result == 0 || result == sizeof(image))
+ goto error;
+
+ if (_snwprintf_s((wchar_t*)&args,
+ sizeof(args) / sizeof(wchar_t),
+ _TRUNCATE,
+ L"\"%s\" %S meh",
+ image,
+ name) < 0)
+ goto error;
+
+ memset((void*)&si, 0, sizeof(si));
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.hStdInput = nul;
+ si.hStdOutput = file;
+ si.hStdError = file;
+
+ if (!CreateProcessW(image, args, NULL, NULL, TRUE,
+ 0, NULL, NULL, &si, &pi))
+ goto error;
+
+ CloseHandle(pi.hThread);
+
+ SetHandleInformation(nul, HANDLE_FLAG_INHERIT, 0);
+ SetHandleInformation(file, HANDLE_FLAG_INHERIT, 0);
+
+ p->stdio_in = nul;
+ p->stdio_out = file;
+ p->process = pi.hProcess;
+ p->name = name;
+
+ return 0;
+
+
+error:
+ if (file != INVALID_HANDLE_VALUE)
+ CloseHandle(file);
+ if (nul != INVALID_HANDLE_VALUE)
+ CloseHandle(file);
+
+ return -1;
+}
+
+
+/* Timeout is is msecs. Set timeout < 0 to never time out. */
+/* Returns 0 when all processes are terminated, -1 on timeout. */
+int process_wait(process_info_t *vec, int n, int timeout) {
+ int i;
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS];
+ DWORD timeout_api, result;
+
+ /* If there's nothing to wait for, return immedately. */
+ if (n == 0)
+ return 0;
+
+ assert(n <= MAXIMUM_WAIT_OBJECTS);
+
+ for (i = 0; i < n; i++)
+ handles[i] = vec[i].process;
+
+ if (timeout >= 0) {
+ timeout_api = (DWORD)timeout;
+ } else {
+ timeout_api = INFINITE;
+ }
+
+ result = WaitForMultipleObjects(n, handles, TRUE, timeout_api);
+
+ if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + n) {
+ /* All processes are terminated. */
+ return 0;
+ }
+ if (result == WAIT_TIMEOUT) {
+ return -2;
+ }
+ return -1;
+}
+
+
+long int process_output_size(process_info_t *p) {
+ LARGE_INTEGER size;
+ if (!GetFileSizeEx(p->stdio_out, &size))
+ return -1;
+ return (long int)size.QuadPart;
+}
+
+
+int process_copy_output(process_info_t *p, int fd) {
+ /* Any errors in this function are ignored */
+ DWORD read;
+ char buf[1024];
+
+ if (SetFilePointer(p->stdio_out, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
+ return -1;
+
+ while (ReadFile(p->stdio_out, (void*)&buf, sizeof(buf), &read, NULL) &&
+ read > 0)
+ write(fd, buf, read);
+
+ if (GetLastError() != ERROR_HANDLE_EOF)
+ return -1;
+
+ return 0;
+}
+
+
+char* process_get_name(process_info_t *p) {
+ return p->name;
+}
+
+
+int process_terminate(process_info_t *p) {
+ /* If it fails the process is probably already closed. */
+ if (!TerminateProcess(p->process, 1))
+ return -1;
+ return 0;
+}
+
+
+int process_reap(process_info_t *p) {
+ DWORD exitCode;
+ if (!GetExitCodeProcess(p->process, &exitCode))
+ return -1;
+ return (int)exitCode;
+}
+
+
+void process_cleanup(process_info_t *p) {
+ CloseHandle(p->process);
+ CloseHandle(p->stdio_in);
+ CloseHandle(p->stdio_out);
+}
+
+
+int rewind_cursor() {
+ HANDLE handle;
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ COORD coord;
+
+ handle = (HANDLE)_get_osfhandle(fileno(stdout));
+ if (handle == INVALID_HANDLE_VALUE)
+ return -1;
+
+ if (!GetConsoleScreenBufferInfo(handle, &info))
+ return -1;
+
+ coord = info.dwCursorPosition;
+ if (coord.Y <= 0)
+ return -1;
+
+ coord.Y--;
+ coord.X = 0;
+
+ if (!SetConsoleCursorPosition(handle, coord))
+ return -1;
+
+ return 0;
+}
\ No newline at end of file
diff --git a/test/test-runner-win32.h b/test/test-runner-win32.h
new file mode 100644
index 00000000..620468ca
--- /dev/null
+++ b/test/test-runner-win32.h
@@ -0,0 +1,10 @@
+
+#include
+
+
+typedef struct {
+ HANDLE process;
+ HANDLE stdio_in;
+ HANDLE stdio_out;
+ char *name;
+} process_info_t;
diff --git a/test/test-runner.c b/test/test-runner.c
new file mode 100644
index 00000000..6d73a4b8
--- /dev/null
+++ b/test/test-runner.c
@@ -0,0 +1,178 @@
+
+#include "test-runner.h"
+
+#include
+#include
+#include
+#include
+
+/* Actual tests and helpers are defined in test-list.h */
+#include "test-list.h"
+
+/* The maximum number of processes (main + helpers) that a test can have. */
+#define TEST_MAX_PROCESSES 8
+
+/* The time in milliseconds after which a single test times out, */
+#define TEST_TIMEOUT 20000
+
+/* Die with fatal error. */
+#define FATAL(msg) assert(msg && 0);
+
+/* Log to stderr. */
+#define LOG(...) fprintf(stderr, "%s", __VA_ARGS__)
+#define LOGF(...) fprintf(stderr, __VA_ARGS__)
+
+
+/*
+ * Runs an individual test; returns 1 if the test succeeded, 0 if it failed.
+ * If the test fails it prints diagnostic information.
+ */
+int run_test(test_entry_t *test) {
+ int i, result, success;
+ char errmsg[256];
+ test_entry_t *helper;
+ int process_count;
+ process_info_t processes[TEST_MAX_PROCESSES];
+ process_info_t *main_process;
+
+ success = 0;
+
+ process_count = 0;
+
+ /* Start all helpers for this test first */
+ for (helper = (test_entry_t*)&TESTS; helper->main; helper++) {
+ if (helper->is_helper &&
+ strcmp(test->test_name, helper->test_name) == 0) {
+ if (process_start(helper->process_name, &processes[process_count]) == -1) {
+ sprintf_s((char*)&errmsg, sizeof(errmsg), "process `%s` failed to start.", helper->process_name);
+ goto finalize;
+ }
+ process_count++;
+ }
+ }
+
+ /* Start the main test process. */
+ if (process_start(test->process_name, &processes[process_count]) == -1) {
+ sprintf_s((char*)&errmsg, sizeof(errmsg), "process `%s` failed to start.", test->process_name);
+ goto finalize;
+ }
+ main_process = &processes[process_count];
+ process_count++;
+
+ /* Wait for the main process to terminate. */
+ result = process_wait(main_process, 1, TEST_TIMEOUT);
+ if (result == -1) {
+ FATAL("process_wait failed\n");
+ } else if (result == -2) {
+ sprintf_s((char*)&errmsg, sizeof(errmsg), "timeout.");
+ goto finalize;
+ }
+
+ /* Reap main process */
+ result = process_reap(main_process);
+ if (result != 0) {
+ sprintf_s((char*)&errmsg, sizeof(errmsg), "exit code %d.", result);
+ goto finalize;
+ }
+
+ /* Yes! did it. */
+ success = 1;
+
+finalize:
+ /* Kill all (helper) processes that are still running. */
+ for (i = 0; i < process_count; i++)
+ process_terminate(&processes[i]);
+
+ /* Wait until all processes have really terminated. */
+ if (process_wait((process_info_t*)&processes, process_count, -1) < 0)
+ FATAL("process_wait failed\n");
+
+ /* Show error and output from processes if the test failed. */
+ if (!success) {
+ LOG("===============================================================================\n");
+ LOGF("Test `%s` failed: %s\n", test->test_name, errmsg);
+ for (i = 0; i < process_count; i++) {
+ switch (process_output_size(&processes[i])) {
+ case -1:
+ LOGF("Output from process `%s`: << unavailable >>\n", process_get_name(&processes[i]));
+ break;
+
+ case 0:
+ LOGF("Output from process `%s`: << no output >>\n", process_get_name(&processes[i]));
+ break;
+
+ default:
+ LOGF("Output from process `%s`:\n", process_get_name(&processes[i]));
+ process_copy_output(&processes[i], fileno(stderr));
+ break;
+ }
+ }
+ LOG("\n");
+ }
+
+ /* Clean up all process handles. */
+ for (i = 0; i < process_count; i++)
+ process_cleanup(&processes[i]);
+
+ return success;
+}
+
+
+void log_progress(int total, int passed, int failed, char *name) {
+ LOGF("[%% %3d|+ %3d|- %3d]: %-50s\n", (passed + failed) / total * 100, passed, failed, name);
+}
+
+
+int main(int argc, char **argv) {
+ int total, passed, failed;
+ test_entry_t *test;
+
+#ifdef _WIN32
+ /* On windows disable the "application crashed" popup */
+ SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
+#endif
+
+ /* Disable output buffering */
+ setvbuf(stdout, NULL, _IONBF, 0);
+ setvbuf(stderr, NULL, _IONBF, 0);
+
+ if (argc > 1) {
+ /* A specific test process is being started. */
+ for (test = (test_entry_t*)&TESTS; test->main; test++) {
+ if (strcmp(argv[1], test->process_name) == 0)
+ return test->main();
+ }
+ LOGF("Test process %s not found!\n", argv[1]);
+ return 255;
+
+ } else {
+ /* Count the number of tests */
+ total = 0;
+ test = (test_entry_t*)&TESTS;
+ for (test = (test_entry_t*)&TESTS; test->main; test++) {
+ if (!test->is_helper)
+ total++;
+ }
+
+ /* Run all tests */
+ passed = 0;
+ failed = 0;
+ test = (test_entry_t*)&TESTS;
+ for (test = (test_entry_t*)&TESTS; test->main; test++) {
+ if (test->is_helper)
+ continue;
+
+ log_progress(total, passed, failed, test->test_name);
+ rewind_cursor();
+
+ if (run_test(test)) {
+ passed++;
+ } else {
+ failed++;
+ }
+ }
+ log_progress(total, passed, failed, "Done.");
+
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/test/test-runner.h b/test/test-runner.h
new file mode 100644
index 00000000..be76b7ba
--- /dev/null
+++ b/test/test-runner.h
@@ -0,0 +1,83 @@
+
+#ifndef TEST_RUNNER_H_
+#define TEST_RUNNER_H_
+
+/*
+ * Struct to store both tests and to define helper processes for tests.
+ */
+typedef struct {
+ char *test_name;
+ char *process_name;
+ int (*main)();
+ int is_helper;
+} test_entry_t;
+
+
+/*
+ * Macros used by test-list.h
+ */
+#define TEST_DECLARE(name) \
+ int run_##name();
+
+#define TEST_LIST_START \
+ test_entry_t TESTS[] = {
+
+#define TEST_LIST_END \
+ { 0, 0, 0, 0 } \
+ };
+
+#define TEST_ENTRY(name) \
+ { #name, #name, &run_##name, 0 },
+
+#define TEST_HELPER(name, proc) \
+ { #name, #proc, &run_##proc, 1 },
+
+
+/*
+ * Include platform-dependent definitions
+ */
+#ifdef _WIN32
+# include "test-runner-win32.h"
+#else
+# include "test-runner-unix.h"
+#endif
+
+
+/*
+ * Stuff that should be implemented by test-runner-.h
+ * All functions return 0 on success, -1 on failure, unless specified
+ * otherwise.
+ */
+
+/* Invoke "arv[0] test-name". Store process info in *p. */
+/* Make sure that all stdio output of the processes is buffered up. */
+int process_start(char *name, process_info_t *p);
+
+/* Wait for all `n` processes in `vec` to terminate. */
+/* Time out after `timeout` msec, or never if timeout == -1 */
+/* Return 0 if all processes are terminated, -1 on error, -2 on timeout. */
+int process_wait(process_info_t *vec, int n, int timeout);
+
+/* Returns the number of bytes in the stdio output buffer for process `p`. */
+long int process_output_size(process_info_t *p);
+
+/* Copy the contents of the stdio output buffer to `fd`. */
+int process_copy_output(process_info_t *p, int fd);
+
+/* Return the name that was specified when `p` was started by process_start */
+char* process_get_name(process_info_t *p);
+
+/* Terminate process `p`. */
+int process_terminate(process_info_t *p);
+
+/* Return the return value of process p. */
+/* On error, return -1. */
+int process_reap(process_info_t *p);
+
+/* Clean up after terminating process `p` (e.g. free the output buffer etc.) */
+void process_cleanup(process_info_t *p);
+
+/* Move the console cursor one line up and back to the first column. */
+int rewind_cursor();
+
+#endif /* TEST_RUNNER_H_ */
\ No newline at end of file
diff --git a/test/test.h b/test/test.h
new file mode 100644
index 00000000..f65b0bb7
--- /dev/null
+++ b/test/test.h
@@ -0,0 +1,12 @@
+#ifndef TEST_H_
+#define TEST_H_
+
+#include
+
+
+#define TEST_IMPL(name) \
+ int run_##name()
+
+#define TEST_PORT 8123
+
+#endif /* TEST_H_ */
\ No newline at end of file