diff --git a/src/common/test_util.h b/src/common/test_util.h
index 33007e07f4999a5b6b3aacf16a1bdca13e9eed09..0e009c33a49e38af79f6e8cd1e222ee07b02c207 100644
--- a/src/common/test_util.h
+++ b/src/common/test_util.h
@@ -27,7 +27,7 @@
 extern "C" {
 #endif
 
-extern int num_tests;
+extern int g_num_tests;
 
 #ifdef MG_TEST_ABORT_ON_FAIL
 #define MG_TEST_ABORT abort()
@@ -48,7 +48,7 @@ void _strfail(const char *a, const char *e, int len);
 
 #define ASSERT(expr)                    \
   do {                                  \
-    num_tests++;                        \
+    g_num_tests++;                      \
     if (!(expr)) FAIL(#expr, __LINE__); \
   } while (0)
 #define ASSERT_TRUE(expr) ASSERT(expr)
@@ -71,7 +71,6 @@ void _strfail(const char *a, const char *e, int len);
       elapsed = cs_time() - elapsed;               \
       printf("  [%.3f] %s\n", elapsed, test_name); \
       fflush(stdout);                              \
-      *total_elapsed += elapsed;                   \
     }                                              \
     if (msg) return msg;                           \
   } while (0)
@@ -89,7 +88,7 @@ void _strfail(const char *a, const char *e, int len);
  */
 #define ASSERT_EQ(actual, expected)                                 \
   do {                                                              \
-    num_tests++;                                                    \
+    g_num_tests++;                                                  \
     if (!((actual) == (expected))) {                                \
       printf("%f != %f\n", AS_DOUBLE(actual), AS_DOUBLE(expected)); \
       FAIL(#actual " == " #expected, __LINE__);                     \
@@ -99,7 +98,7 @@ void _strfail(const char *a, const char *e, int len);
 /* "Less than" assertion. */
 #define ASSERT_LT(a, b)                                 \
   do {                                                  \
-    num_tests++;                                        \
+    g_num_tests++;                                      \
     if (!((a) < (b))) {                                 \
       printf("%f >= %f\n", AS_DOUBLE(a), AS_DOUBLE(b)); \
       FAIL(#a " < " #b, __LINE__);                      \
@@ -109,7 +108,7 @@ void _strfail(const char *a, const char *e, int len);
 /* "Greater than" assertion. */
 #define ASSERT_GT(a, b)                                 \
   do {                                                  \
-    num_tests++;                                        \
+    g_num_tests++;                                      \
     if (!((a) > (b))) {                                 \
       printf("%f <= %f\n", AS_DOUBLE(a), AS_DOUBLE(b)); \
       FAIL(#a " > " #b, __LINE__);                      \
@@ -119,7 +118,7 @@ void _strfail(const char *a, const char *e, int len);
 /* Assert that actual == expected, where both are NUL-terminated. */
 #define ASSERT_STREQ(actual, expected)                            \
   do {                                                            \
-    num_tests++;                                                  \
+    g_num_tests++;                                                \
     if (!_assert_streq(actual, expected)) {                       \
       FAIL("ASSERT_STREQ(" #actual ", " #expected ")", __LINE__); \
     }                                                             \
@@ -128,7 +127,7 @@ void _strfail(const char *a, const char *e, int len);
 /* Assert that actual == expected, where both are pointers */
 #define ASSERT_PTREQ(actual, expected)                            \
   do {                                                            \
-    num_tests++;                                                  \
+    g_num_tests++;                                                \
     if (actual != expected) {                                     \
       printf("%p != %p\n", actual, expected);                     \
       FAIL("ASSERT_PTREQ(" #actual ", " #expected ")", __LINE__); \
@@ -138,7 +137,7 @@ void _strfail(const char *a, const char *e, int len);
 /* Assert that actual != expected, where both are pointers */
 #define ASSERT_PTRNEQ(actual, expected)                            \
   do {                                                             \
-    num_tests++;                                                   \
+    g_num_tests++;                                                 \
     if (actual == expected) {                                      \
       printf("%p == %p\n", actual, expected);                      \
       FAIL("ASSERT_PTRNEQ(" #actual ", " #expected ")", __LINE__); \
@@ -148,7 +147,7 @@ void _strfail(const char *a, const char *e, int len);
 /* Same as STREQ, but only expected is NUL-terminated. */
 #define ASSERT_STREQ_NZ(actual, expected)                            \
   do {                                                               \
-    num_tests++;                                                     \
+    g_num_tests++;                                                   \
     if (!_assert_streq_nz(actual, expected)) {                       \
       FAIL("ASSERT_STREQ_NZ(" #actual ", " #expected ")", __LINE__); \
     }                                                                \
@@ -156,7 +155,7 @@ void _strfail(const char *a, const char *e, int len);
 
 #define ASSERT_MG_STREQ(actual, expected)                            \
   do {                                                               \
-    num_tests++;                                                     \
+    g_num_tests++;                                                   \
     if ((actual).len != strlen(expected) ||                          \
         memcmp((actual).p, expected, (actual).len) != 0) {           \
       printf("'%.*s' (%d) != '%s'\n", (int)(actual).len, (actual).p, \
diff --git a/test/Makefile b/test/Makefile
index d4d3bc60e7f0224cb992e43ca04867dcd19008ec..4795243d01635c37f83b88b799df511c2b6cf539 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -15,7 +15,7 @@ else
 COMMON_PARENT = $(SRC_DIR)
 endif
 
-TEST_SOURCES = unit_test.c test_util.c
+TEST_SOURCES = unit_test.c test_util.c test_main.c
 AMALGAMATED_SOURCES = ../mongoose.c
 KRYPTON_PATH = $(REPO_ROOT)/krypton
 
diff --git a/test/unit_test.c b/test/unit_test.c
index 801ecae69bbb82f739feaa75b164e005c7007e1f..9c7f284cd00000bddcc39d25d65cf8bcf6e3a6ca 100644
--- a/test/unit_test.c
+++ b/test/unit_test.c
@@ -18,13 +18,10 @@
 #include "mongoose.h"
 #include "src/mg_internal.h"
 #include "unit_test.h"
+#include "common/test_main.h"
 #include "common/test_util.h"
 #include "common/cs_md5.h"
 
-#if defined(_MSC_VER) && _MSC_VER >= 1900
-#include <crtdbg.h>
-#endif
-
 #if defined(__STDC_VERSION__) && __STDC_VERSION__ < 199901L && !defined(WIN32)
 #define __func__ ""
 #endif
@@ -35,7 +32,6 @@
 #define LOOPBACK_IP "127.0.0.1"
 #define LISTENING_ADDR LOOPBACK_IP ":" HTTP_PORT
 
-static const char *s_argv_0 = NULL;
 static struct mg_serve_http_opts s_http_server_opts;
 static int s_listening_port = 23456;
 
@@ -1648,8 +1644,8 @@ static void cb7(struct mg_connection *nc, int ev, void *ev_data) {
 
   if (ev == MG_EV_HTTP_REPLY) {
     /* Make sure that we've downloaded this executable, byte-to-byte */
-    data = read_file(s_argv_0, &size);
-    DBG(("file %s, size %d; got %d", s_argv_0, (int) size, (int) hm->body.len));
+    data = read_file(g_argv_0, &size);
+    DBG(("file %s, size %d; got %d", g_argv_0, (int) size, (int) hm->body.len));
     if (data != NULL && size == hm->body.len &&
         memcmp(hm->body.p, data, size) == 0) {
       strcpy(user_data, "success");
@@ -2025,12 +2021,12 @@ static const char *test_http(void) {
   nc->user_data = status;
 
   /* Wine and GDB set argv0 to full path: strip the dir component */
-  if ((this_binary = strrchr(s_argv_0, '\\')) != NULL) {
+  if ((this_binary = strrchr(g_argv_0, '\\')) != NULL) {
     this_binary++;
-  } else if ((this_binary = strrchr(s_argv_0, '/')) != NULL) {
+  } else if ((this_binary = strrchr(g_argv_0, '/')) != NULL) {
     this_binary++;
   } else {
-    this_binary = s_argv_0;
+    this_binary = g_argv_0;
   }
   mg_printf(nc, "GET /%s HTTP/1.0\n\n", this_binary);
   /* Test mime type for static file */
@@ -5394,12 +5390,12 @@ static const char *test_socks(void) {
   mg_set_protocol_http_websocket(c);
   c->user_data = status;
   /* Wine and GDB set argv0 to full path: strip the dir component */
-  if ((this_binary = strrchr(s_argv_0, '\\')) != NULL) {
+  if ((this_binary = strrchr(g_argv_0, '\\')) != NULL) {
     this_binary++;
-  } else if ((this_binary = strrchr(s_argv_0, '/')) != NULL) {
+  } else if ((this_binary = strrchr(g_argv_0, '/')) != NULL) {
     this_binary++;
   } else {
-    this_binary = s_argv_0;
+    this_binary = g_argv_0;
   }
   mg_printf(c, "GET /%s HTTP/1.0\n\n", this_binary);
 
@@ -5412,7 +5408,11 @@ static const char *test_socks(void) {
 }
 #endif
 
-static const char *run_tests(const char *filter, double *total_elapsed) {
+void tests_setup(void) {
+  test_iface = mg_if_create_iface(mg_ifaces[MG_MAIN_IFACE], NULL);
+}
+
+const char *tests_run(const char *filter) {
   RUN_TEST(test_mbuf);
   RUN_TEST(test_parse_uri);
   RUN_TEST(test_assemble_uri);
@@ -5525,51 +5525,6 @@ static const char *run_tests(const char *filter, double *total_elapsed) {
   return NULL;
 }
 
-#if defined(_MSC_VER) && _MSC_VER >= 1900
-int __cdecl CrtDbgHook(int nReportType, char *szMsg, int *pnRet) {
-  (void) nReportType;
-  (void) szMsg;
-  (void) pnRet;
-
-  fprintf(stderr, "CRT debug hook: type: %d, msg: %s\n", nReportType, szMsg);
-  /* Return true - Abort,Retry,Ignore dialog will *not* be displayed */
-  return 1;
-}
-#endif
-
-void setup() {
-  test_iface = mg_if_create_iface(mg_ifaces[MG_MAIN_IFACE], NULL);
-}
-
-void teardown() {
+void tests_teardown(void) {
   free(test_iface);
 }
-
-int __cdecl main(int argc, char *argv[]) {
-  const char *fail_msg;
-  const char *filter = argc > 1 ? argv[1] : "";
-  double total_elapsed = 0.0;
-
-  setvbuf(stdout, NULL, _IONBF, 0);
-  setvbuf(stderr, NULL, _IONBF, 0);
-
-#if defined(_MSC_VER) && _MSC_VER >= 1900
-  /* NOTE: not available on wine/vc6 */
-  _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, CrtDbgHook);
-#endif
-
-  setup();
-
-  s_argv_0 = argv[0];
-  fail_msg = run_tests(filter, &total_elapsed);
-  printf("%s, run %d in %.3lfs\n", fail_msg ? "FAIL" : "PASS", num_tests,
-         total_elapsed);
-
-  teardown();
-  if (fail_msg != NULL) {
-    /* Prevent leak analyzer from running: there will be "leaks" because of
-     * premature return from the test, and in this case we don't care. */
-    _exit(EXIT_FAILURE);
-  }
-  return EXIT_SUCCESS;
-}