summaryrefslogtreecommitdiff
path: root/tests/libtap
diff options
context:
space:
mode:
Diffstat (limited to 'tests/libtap')
-rw-r--r--tests/libtap/Makefile.am4
-rw-r--r--tests/libtap/tap.c298
-rw-r--r--tests/libtap/tap.h85
-rw-r--r--tests/libtap/test.c183
-rw-r--r--tests/libtap/test.h84
5 files changed, 654 insertions, 0 deletions
diff --git a/tests/libtap/Makefile.am b/tests/libtap/Makefile.am
new file mode 100644
index 0000000..271ade2
--- /dev/null
+++ b/tests/libtap/Makefile.am
@@ -0,0 +1,4 @@
+check_LTLIBRARIES = libtap.la
+libtap_la_SOURCES = tap.c tap.h test.h test.c
+libtap_la_CFLAGS = -I$(top_srcdir)/src/ @GLIB_CFLAGS@
+libtap_la_LIBADD = $(top_builddir)/src/libmongo-client.la @GLIB_LIBS@
diff --git a/tests/libtap/tap.c b/tests/libtap/tap.c
new file mode 100644
index 0000000..e73ae4a
--- /dev/null
+++ b/tests/libtap/tap.c
@@ -0,0 +1,298 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <glib.h>
+#include "tap.h"
+
+static int expected_tests = NO_PLAN;
+static int failed_tests;
+static int current_test;
+static char *todo_mesg;
+
+void
+plan (int tests) {
+ expected_tests = tests;
+ if (tests != NO_PLAN)
+ printf("1..%d\n", tests);
+}
+
+static char *
+vstrdupf (const char *fmt, va_list args) {
+ char *str;
+ int size;
+ va_list args2;
+ va_copy(args2, args);
+ if (!fmt)
+ fmt = "";
+ size = g_vsnprintf(NULL, 0, fmt, args2) + 2;
+ str = malloc(size);
+ vsprintf(str, fmt, args);
+ va_end(args2);
+ return str;
+}
+
+int
+vok_at_loc (const char *file, int line, int test, const char *fmt,
+ va_list args)
+{
+ char *name = vstrdupf(fmt, args);
+ printf("%sok %d", test ? "" : "not ", ++current_test);
+ if (*name)
+ printf(" - %s", name);
+ if (todo_mesg) {
+ printf(" # TODO");
+ if (*todo_mesg)
+ printf(" %s", todo_mesg);
+ }
+ printf("\n");
+ if (!test) {
+ if (*name)
+ diag(" Failed%s test '%s'\n at %s line %d.",
+ todo_mesg ? " (TODO)" : "", name, file, line);
+ else
+ diag(" Failed%s test at %s line %d.",
+ todo_mesg ? " (TODO)" : "", file, line);
+ if (!todo_mesg)
+ failed_tests++;
+ }
+ free(name);
+ return test;
+}
+
+int
+ok_at_loc (const char *file, int line, int test, const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ vok_at_loc(file, line, test, fmt, args);
+ va_end(args);
+ return test;
+}
+
+static int
+mystrcmp (const char *a, const char *b) {
+ return a == b ? 0 : !a ? -1 : !b ? 1 : strcmp(a, b);
+}
+
+#define eq(a, b) (!mystrcmp(a, b))
+#define ne(a, b) (mystrcmp(a, b))
+
+int
+is_at_loc (const char *file, int line, const char *got, const char *expected,
+ const char *fmt, ...)
+{
+ int test = eq(got, expected);
+ va_list args;
+ va_start(args, fmt);
+ vok_at_loc(file, line, test, fmt, args);
+ va_end(args);
+ if (!test) {
+ diag(" got: '%s'", got);
+ diag(" expected: '%s'", expected);
+ }
+ return test;
+}
+
+int
+isnt_at_loc (const char *file, int line, const char *got, const char *expected,
+ const char *fmt, ...)
+{
+ int test = ne(got, expected);
+ va_list args;
+ va_start(args, fmt);
+ vok_at_loc(file, line, test, fmt, args);
+ va_end(args);
+ if (!test) {
+ diag(" got: '%s'", got);
+ diag(" expected: anything else");
+ }
+ return test;
+}
+
+int
+cmp_ok_at_loc (const char *file, int line, int a, const char *op, int b,
+ const char *fmt, ...)
+{
+ int test = eq(op, "||") ? a || b
+ : eq(op, "&&") ? a && b
+ : eq(op, "|") ? a | b
+ : eq(op, "^") ? a ^ b
+ : eq(op, "&") ? a & b
+ : eq(op, "==") ? a == b
+ : eq(op, "!=") ? a != b
+ : eq(op, "<") ? a < b
+ : eq(op, ">") ? a > b
+ : eq(op, "<=") ? a <= b
+ : eq(op, ">=") ? a >= b
+ : eq(op, "<<") ? a << b
+ : eq(op, ">>") ? a >> b
+ : eq(op, "+") ? a + b
+ : eq(op, "-") ? a - b
+ : eq(op, "*") ? a * b
+ : eq(op, "/") ? a / b
+ : eq(op, "%") ? a % b
+ : diag("unrecognized operator '%s'", op);
+ va_list args;
+ va_start(args, fmt);
+ vok_at_loc(file, line, test, fmt, args);
+ va_end(args);
+ if (!test) {
+ diag(" %d", a);
+ diag(" %s", op);
+ diag(" %d", b);
+ }
+ return test;
+}
+
+static void
+vdiag_to_fh (FILE *fh, const char *fmt, va_list args) {
+ char *mesg, *line;
+ int i;
+ if (!fmt)
+ return;
+ mesg = vstrdupf(fmt, args);
+ line = mesg;
+ for (i = 0; *line; i++) {
+ char c = mesg[i];
+ if (!c || c == '\n') {
+ mesg[i] = '\0';
+ fprintf(fh, "# %s\n", line);
+ if (!c) break;
+ mesg[i] = c;
+ line = &mesg[i+1];
+ }
+ }
+ free(mesg);
+ return;
+}
+
+int
+diag (const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ vdiag_to_fh(stderr, fmt, args);
+ va_end(args);
+ return 0;
+}
+
+int
+note (const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ vdiag_to_fh(stdout, fmt, args);
+ va_end(args);
+ return 0;
+}
+
+int
+exit_status () {
+ int retval = 0;
+ if (expected_tests == NO_PLAN) {
+ printf("1..%d\n", current_test);
+ }
+ else if (current_test != expected_tests) {
+ diag("Looks like you planned %d test%s but ran %d.",
+ expected_tests, expected_tests > 1 ? "s" : "", current_test);
+ retval = 255;
+ }
+ if (failed_tests) {
+ diag("Looks like you failed %d test%s of %d run.",
+ failed_tests, failed_tests > 1 ? "s" : "", current_test);
+ if (expected_tests == NO_PLAN)
+ retval = failed_tests;
+ else
+ retval = expected_tests - current_test + failed_tests;
+ }
+ return retval;
+}
+
+void
+skippy (int n, const char *fmt, ...) {
+ char *why;
+ va_list args;
+ va_start(args, fmt);
+ why = vstrdupf(fmt, args);
+ va_end(args);
+ while (n --> 0) {
+ printf("ok %d ", ++current_test);
+ note("skip %s\n", why);
+ }
+ free(why);
+}
+
+void
+ctodo (int ignore __attribute__((unused)), const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ todo_mesg = vstrdupf(fmt, args);
+ va_end(args);
+}
+
+void
+cendtodo () {
+ free(todo_mesg);
+ todo_mesg = NULL;
+}
+
+#ifndef _WIN32
+#include <sys/mman.h>
+#include <regex.h>
+
+#ifdef __APPLE__
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
+#ifndef MAP_ANON
+#define MAP_ANON MAP_ANONYMOUS
+#endif
+
+/* Create a shared memory int to keep track of whether a piece of code executed
+dies. to be used in the dies_ok and lives_ok macros */
+int
+tap_test_died (int status) {
+ static int *test_died = NULL;
+ int prev;
+ if (!test_died) {
+ test_died = (int *)mmap(0, sizeof (int), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANON, -1, 0);
+ *test_died = 0;
+ }
+ prev = *test_died;
+ *test_died = status;
+ return prev;
+}
+
+int
+like_at_loc (int for_match, const char *file, int line, const char *got,
+ const char *expected, const char *fmt, ...)
+{
+ int test;
+ regex_t re;
+ int err = regcomp(&re, expected, REG_EXTENDED);
+ if (err) {
+ char errbuf[256];
+ regerror(err, &re, errbuf, sizeof errbuf);
+ fprintf(stderr, "Unable to compile regex '%s': %s at %s line %d\n",
+ expected, errbuf, file, line);
+ exit(255);
+ }
+ err = regexec(&re, got, 0, NULL, 0);
+ regfree(&re);
+ test = for_match ? !err : err;
+ va_list args;
+ va_start(args, fmt);
+ vok_at_loc(file, line, test, fmt, args);
+ va_end(args);
+ if (!test) {
+ if (for_match) {
+ diag(" '%s'", got);
+ diag(" doesn't match: '%s'", expected);
+ }
+ else {
+ diag(" '%s'", got);
+ diag(" matches: '%s'", expected);
+ }
+ }
+ return test;
+}
+#endif
diff --git a/tests/libtap/tap.h b/tests/libtap/tap.h
new file mode 100644
index 0000000..3e841bc
--- /dev/null
+++ b/tests/libtap/tap.h
@@ -0,0 +1,85 @@
+#ifndef __TAP_H__
+#define __TAP_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#define NO_PLAN -1
+#define ok(...) ok_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL)
+#define pass(...) ok(1, ## __VA_ARGS__)
+#define fail(...) ok(0, ## __VA_ARGS__)
+#define is(...) is_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL)
+#define isnt(...) isnt_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL)
+#define cmp_ok(...) cmp_ok_at_loc(__FILE__, __LINE__, __VA_ARGS__, NULL)
+
+int vok_at_loc (const char *file, int line, int test, const char *fmt,
+ va_list args);
+void plan (int tests);
+int ok_at_loc (const char *file, int line, int test, const char *fmt,
+ ...);
+int diag (const char *fmt, ...);
+int note (const char *fmt, ...);
+int exit_status (void);
+void skippy (int n, const char *fmt, ...);
+void ctodo (int ignore, const char *fmt, ...);
+void cendtodo (void);
+int is_at_loc (const char *file, int line, const char *got,
+ const char *expected, const char *fmt, ...);
+int isnt_at_loc (const char *file, int line, const char *got,
+ const char *expected, const char *fmt, ...);
+int cmp_ok_at_loc (const char *file, int line, int a, const char *op,
+ int b, const char *fmt, ...);
+
+#ifdef _WIN32
+#define like(...) skippy(1, "like is not implemented on MSWin32")
+#define unlike(...) like()
+#else
+#define like(...) like_at_loc(1, __FILE__, __LINE__, __VA_ARGS__, NULL)
+#define unlike(...) like_at_loc(0, __FILE__, __LINE__, __VA_ARGS__, NULL)
+int like_at_loc (int for_match, const char *file, int line,
+ const char *got, const char *expected,
+ const char *fmt, ...);
+#endif
+
+#define skip(test, ...) do {if (test) {skippy(__VA_ARGS__, NULL); break;}
+#define endskip } while (0)
+
+#define todo(...) ctodo(0, ## __VA_ARGS__, NULL)
+#define endtodo cendtodo()
+
+#define dies_ok(code, ...) dies_ok_common(code, 1, ## __VA_ARGS__)
+#define lives_ok(code, ...) dies_ok_common(code, 0, ## __VA_ARGS__)
+
+#ifdef _WIN32
+#define dies_ok_common(...) \
+ skippy(1, "Death detection is not supported on MSWin32")
+#else
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+int tap_test_died (int status);
+#define dies_ok_common(code, for_death, ...) \
+ do { \
+ tap_test_died(1); \
+ int cpid = fork(); \
+ switch (cpid) { \
+ case -1: \
+ perror("fork error"); \
+ exit(EXIT_FAILURE); \
+ case 0: /* child */ \
+ close(1); close(2); \
+ code \
+ tap_test_died(0); \
+ exit(EXIT_SUCCESS); \
+ } \
+ if (waitpid(cpid, NULL, 0) < 0) { \
+ perror("waitpid error"); \
+ exit(EXIT_FAILURE); \
+ } \
+ int it_died = tap_test_died(0); \
+ if (!it_died) {code} \
+ ok(for_death ? it_died : !it_died, ## __VA_ARGS__); \
+ } while (0)
+#endif
+#endif
diff --git a/tests/libtap/test.c b/tests/libtap/test.c
new file mode 100644
index 0000000..979807f
--- /dev/null
+++ b/tests/libtap/test.c
@@ -0,0 +1,183 @@
+#include "test.h"
+#include "bson.h"
+#include "mongo-utils.h"
+
+#include <glib.h>
+#include <string.h>
+
+#ifndef HAVE_MSG_NOSIGNAL
+#include <signal.h>
+#endif
+
+
+func_config_t config;
+
+bson *
+test_bson_generate_full (void)
+{
+ bson *b, *d, *a, *scope;
+ guint8 oid[] = "1234567890ab";
+
+ a = bson_new ();
+ bson_append_int32 (a, "0", 32);
+ bson_append_int64 (a, "1", (gint64)-42);
+ bson_finish (a);
+
+ d = bson_new ();
+ bson_append_string (d, "name", "sub-document", -1);
+ bson_append_int32 (d, "answer", 42);
+ bson_finish (d);
+
+ scope = bson_new ();
+ bson_append_string (scope, "v", "hello world", -1);
+ bson_finish (scope);
+
+ b = bson_new ();
+ bson_append_double (b, "double", 3.14);
+ bson_append_string (b, "str", "hello world", -1);
+ bson_append_document (b, "doc", d);
+ bson_append_array (b, "array", a);
+ bson_append_binary (b, "binary0", BSON_BINARY_SUBTYPE_GENERIC,
+ (guint8 *)"foo\0bar", 7);
+ bson_append_oid (b, "_id", oid);
+ bson_append_boolean (b, "TRUE", FALSE);
+ bson_append_utc_datetime (b, "date", 1294860709000);
+ bson_append_timestamp (b, "ts", 1294860709000);
+ bson_append_null (b, "null");
+ bson_append_regex (b, "foobar", "s/foo.*bar/", "i");
+ bson_append_javascript (b, "alert", "alert (\"hello world!\");", -1);
+ bson_append_symbol (b, "sex", "Marilyn Monroe", -1);
+ bson_append_javascript_w_scope (b, "print", "alert (v);", -1, scope);
+ bson_append_int32 (b, "int32", 32);
+ bson_append_int64 (b, "int64", (gint64)-42);
+ bson_finish (b);
+
+ bson_free (d);
+ bson_free (a);
+ bson_free (scope);
+
+ return b;
+}
+
+mongo_packet *
+test_mongo_wire_generate_reply (gboolean valid, gint32 nreturn,
+ gboolean with_docs)
+{
+ mongo_reply_packet_header rh;
+ mongo_packet_header h;
+ mongo_packet *p;
+ guint8 *data;
+ gint data_size = sizeof (mongo_reply_packet_header);
+ bson *b1 = NULL, *b2 = NULL;
+
+ p = mongo_wire_packet_new ();
+
+ h.opcode = (valid) ? GINT32_TO_LE (1) : GINT32_TO_LE (42);
+ h.id = GINT32_TO_LE (1984);
+ h.resp_to = GINT32_TO_LE (42);
+ if (with_docs)
+ {
+ b1 = test_bson_generate_full ();
+ b2 = test_bson_generate_full ();
+ data_size += bson_size (b1) + bson_size (b2);
+ }
+ h.length = GINT32_TO_LE (sizeof (mongo_packet_header) + data_size);
+
+ mongo_wire_packet_set_header (p, &h);
+
+ data = g_try_malloc (data_size);
+
+ rh.flags = 0;
+ rh.cursor_id = GINT64_TO_LE ((gint64)12345);
+ rh.start = 0;
+ rh.returned = GINT32_TO_LE (nreturn);
+
+ memcpy (data, &rh, sizeof (mongo_reply_packet_header));
+ if (with_docs)
+ {
+ memcpy (data + sizeof (mongo_reply_packet_header),
+ bson_data (b1), bson_size (b1));
+ memcpy (data + sizeof (mongo_reply_packet_header) + bson_size (b1),
+ bson_data (b2), bson_size (b2));
+ }
+
+ mongo_wire_packet_set_data (p, data, data_size);
+ g_free (data);
+ bson_free (b1);
+ bson_free (b2);
+
+ return p;
+}
+
+mongo_sync_connection *
+test_make_fake_sync_conn (gint fd, gboolean slaveok)
+{
+ mongo_sync_connection *c;
+
+ c = g_try_new0 (mongo_sync_connection, 1);
+ if (!c)
+ return NULL;
+
+ c->super.fd = fd;
+ c->slaveok = slaveok;
+ c->safe_mode = FALSE;
+ c->auto_reconnect = FALSE;
+ c->max_insert_size = MONGO_SYNC_DEFAULT_MAX_INSERT_SIZE;
+
+ return c;
+}
+
+gboolean
+test_env_setup (void)
+{
+ config.primary_host = config.secondary_host = NULL;
+ config.primary_port = config.secondary_port = 27017;
+ config.db = g_strdup ("test");
+ config.coll = g_strdup ("libmongo");
+
+ if (getenv ("TEST_DB"))
+ {
+ g_free (config.db);
+ config.db = g_strdup (getenv ("TEST_DB"));
+ }
+ if (getenv ("TEST_COLLECTION"))
+ {
+ g_free (config.coll);
+ config.coll = g_strdup (getenv ("TEST_COLLECTION"));
+ }
+ config.ns = g_strconcat (config.db, ".", config.coll, NULL);
+
+ config.gfs_prefix = g_strconcat (config.ns, ".", "grid", NULL);
+
+ if (!getenv ("TEST_PRIMARY") || strlen (getenv ("TEST_PRIMARY")) == 0)
+ return FALSE;
+
+ if (!mongo_util_parse_addr (getenv ("TEST_PRIMARY"), &config.primary_host,
+ &config.primary_port))
+ return FALSE;
+
+ if (getenv ("TEST_SECONDARY") && strlen (getenv ("TEST_SECONDARY")) > 0)
+ mongo_util_parse_addr (getenv ("TEST_SECONDARY"), &config.secondary_host,
+ &config.secondary_port);
+
+ return TRUE;
+}
+
+void
+test_env_free (void)
+{
+ g_free (config.primary_host);
+ g_free (config.secondary_host);
+ g_free (config.db);
+ g_free (config.coll);
+ g_free (config.ns);
+ g_free (config.gfs_prefix);
+}
+
+void
+test_main_setup (void)
+{
+ #ifndef HAVE_MSG_NOSIGNAL
+ signal(SIGPIPE, SIG_IGN);
+ #endif
+}
diff --git a/tests/libtap/test.h b/tests/libtap/test.h
new file mode 100644
index 0000000..1c442f5
--- /dev/null
+++ b/tests/libtap/test.h
@@ -0,0 +1,84 @@
+#ifndef LIBMONGO_CLIENT_TEST_H
+#define LIBMONGO_CLIENT_TEST_H 1
+
+#include "tap.h"
+#include "bson.h"
+#include "mongo-wire.h"
+#include "mongo-sync.h"
+#include "libmongo-private.h"
+
+#include <dlfcn.h>
+
+typedef struct
+{
+ gchar *primary_host;
+ gint primary_port;
+
+ gchar *secondary_host;
+ gint secondary_port;
+
+ gchar *db;
+ gchar *coll;
+ gchar *ns;
+
+ gchar *gfs_prefix;
+} func_config_t;
+
+extern func_config_t config;
+
+#define begin_network_tests(n) \
+ do \
+ { \
+ skip(!test_env_setup (), n, "Environment not set up for network tests")
+
+#define end_network_tests() \
+ endskip; \
+ test_env_free(); \
+ } while (0)
+
+#define RUN_TEST(n, t) \
+ int \
+ main (void) \
+ { \
+ test_main_setup(); \
+ plan (n); \
+ test_##t (); \
+ return 0; \
+ }
+
+gboolean test_env_setup (void);
+void test_env_free (void);
+void test_main_setup (void);
+
+#define RUN_NET_TEST(n, t) \
+ int \
+ main (void) \
+ { \
+ test_main_setup(); \
+ if (!test_env_setup ()) \
+ printf ("1..0 # skip, Environment not set up for network tests"); \
+ else \
+ { \
+ plan (n); \
+ test_##t (); \
+ } \
+ test_env_free (); \
+ return 0; \
+ }
+
+bson *test_bson_generate_full (void);
+mongo_packet *test_mongo_wire_generate_reply (gboolean valid,
+ gint32 nreturn,
+ gboolean with_docs);
+mongo_sync_connection *test_make_fake_sync_conn (gint fd,
+ gboolean slaveok);
+
+#define SAVE_OLD_FUNC(n) \
+ static void *(*func_##n)(); \
+ if (!func_##n) \
+ func_##n = (void *(*)())dlsym (RTLD_NEXT, #n);
+
+#define CALL_OLD_FUNC(n, ...) \
+ func_##n (__VA_ARGS__)
+
+#endif