From 0872b43219475157356f174c7b7b022b0ff20e72 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 13 Jul 2017 14:41:44 +0300 Subject: [PATCH 1/3] gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 41c423b..b82e60a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ *.db .vscode rmutil/test_vector +rmutil/test_periodic +*.dSYM +*.pyc From eee6fd5e9e971625d31cc5157ac69419e32cc8e2 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 13 Jul 2017 11:08:34 +0300 Subject: [PATCH 2/3] Add new 'testmodule' code The testmodule may be used as a foundation for testing new code of your own. --- rmutil/Makefile | 11 +- rmutil/test_string.c | 48 +++++++ rmutil/test_util.c | 4 + rmutil/testmodule/Makefile | 11 ++ rmutil/testmodule/_disposableredis.py | 146 ++++++++++++++++++++ rmutil/testmodule/cunit.py | 62 +++++++++ rmutil/testmodule/testmodule-priv.h | 184 ++++++++++++++++++++++++++ rmutil/testmodule/testmodule.c | 135 +++++++++++++++++++ rmutil/testmodule/testmodule.h | 51 +++++++ 9 files changed, 650 insertions(+), 2 deletions(-) create mode 100644 rmutil/test_string.c create mode 100644 rmutil/test_util.c create mode 100644 rmutil/testmodule/Makefile create mode 100644 rmutil/testmodule/_disposableredis.py create mode 100644 rmutil/testmodule/cunit.py create mode 100644 rmutil/testmodule/testmodule-priv.h create mode 100644 rmutil/testmodule/testmodule.c create mode 100644 rmutil/testmodule/testmodule.h diff --git a/rmutil/Makefile b/rmutil/Makefile index 09e023b..e303ce1 100644 --- a/rmutil/Makefile +++ b/rmutil/Makefile @@ -26,6 +26,13 @@ test_periodic: test_periodic.o periodic.o $(CC) -Wall -o $@ $^ -lc -lpthread -O0 @(sh -c ./$@) .PHONY: test_periodic - -test: test_periodic test_vector + +export TESTMODULE_SO = rmodule_test.so +export TEST_OBJS = test_string.o test_util.o librmutil.a +export +test_rmodule: librmutil.a $(TEST_OBJS) + $(MAKE) -f testmodule/Makefile +.PHONY: test_rmodule + +test: test_periodic test_vector test_rmodule .PHONY: test diff --git a/rmutil/test_string.c b/rmutil/test_string.c new file mode 100644 index 0000000..9966794 --- /dev/null +++ b/rmutil/test_string.c @@ -0,0 +1,48 @@ +#include "testmodule/testmodule.h" +#include "strings.h" + +TEST_CLASS(string); + +TEST_F(testEquality)(RedisModuleCtx *ctx) { + const char *a_str = "Hello"; + const char *b_str = "World"; + RedisModuleString *a_rs = RedisModule_CreateString(ctx, a_str, strlen(a_str)); + RedisModuleString *b_rs = RedisModule_CreateString(ctx, b_str, strlen(b_str)); + + ASSERT_TRUE(RMUtil_StringEquals(a_rs, a_rs)); + ASSERT_FALSE(RMUtil_StringEquals(a_rs, b_rs)); + ASSERT_TRUE(RMUtil_StringEqualsC(a_rs, "Hello")); + ASSERT_FALSE(RMUtil_StringEqualsC(a_rs, "World")); + + ASSERT_TRUE(RMUtil_StringEqualsCaseC(a_rs, "HelLO")); + ASSERT_FALSE(RMUtil_StringEqualsCaseC(a_rs, "HELL")); +} + +TEST_F(testCaseConversion)(RedisModuleCtx *ctx) { + RedisModuleString *a_rs = RedisModule_CreateString(ctx, "Hello", strlen("Hello")); + RMUtil_StringToLower(a_rs); + ASSERT_STREQ_CR("hello", a_rs); + RMUtil_StringToUpper(a_rs); + ASSERT_STREQ_CR("HELLO", a_rs); +} + +TEST_F(testArgvConversion)(RedisModuleCtx *ctx) { + RedisModuleString **argv = RMTest_BuildArgs(ctx, "foo", "bar", "baz", NULL); + const char **cargs = RedisModule_PoolAlloc(ctx, sizeof(*cargs) * 3); + + const char *exps[] = {"foo", "bar", "baz"}; + + // No copying + RMUtil_StringConvert(argv, cargs, 3, 0); + for (size_t ii = 0; ii < 3; ++ii) { + ASSERT_STREQ_C(exps[ii], cargs[ii]); + ASSERT_EQ(cargs[ii], RedisModule_StringPtrLen(argv[ii], NULL)); + } + // Do the same thing, but copying + + RMUtil_StringConvert(argv, cargs, 3, RMUTIL_STRINGCONVERT_COPY); + for (size_t ii = 0; ii < 3; ++ii) { + ASSERT_STREQ_C(exps[ii], cargs[ii]); + ASSERT_NE(cargs[ii], RedisModule_StringPtrLen(argv[ii], NULL)); + } +} \ No newline at end of file diff --git a/rmutil/test_util.c b/rmutil/test_util.c new file mode 100644 index 0000000..f014740 --- /dev/null +++ b/rmutil/test_util.c @@ -0,0 +1,4 @@ +#include "testmodule/testmodule.h" +#include "util.h" + +TEST_CLASS(utils) \ No newline at end of file diff --git a/rmutil/testmodule/Makefile b/rmutil/testmodule/Makefile new file mode 100644 index 0000000..77183f2 --- /dev/null +++ b/rmutil/testmodule/Makefile @@ -0,0 +1,11 @@ +all: run + +SELFDIR := $(dir $(firstword $(MAKEFILE_LIST))) + +PYTHON ?= python +TEST_FILTER ?= * + +run: $(TEST_OBJS) + $(PYTHON) "$(SELFDIR)/cunit.py" -f "$(TEST_FILTER)" $(TEST_OBJS) + +.PHONY: run \ No newline at end of file diff --git a/rmutil/testmodule/_disposableredis.py b/rmutil/testmodule/_disposableredis.py new file mode 100644 index 0000000..8b7d286 --- /dev/null +++ b/rmutil/testmodule/_disposableredis.py @@ -0,0 +1,146 @@ +import subprocess +import socket +import redis +import time +import os +import sys +import itertools +from contextlib import contextmanager + + +REDIS_DEBUGGER = os.environ.get('REDIS_DEBUGGER', None) +REDIS_SHOW_OUTPUT = int(os.environ.get('REDIS_VERBOSE', 1 if REDIS_DEBUGGER else 0)) + + +def get_random_port(): + sock = socket.socket() + sock.listen(0) + _, port = sock.getsockname() + sock.close() + + return port + + +class Client(redis.StrictRedis): + def __init__(self, disposable_redis, port): + redis.StrictRedis.__init__(self, port=port) + self.dr = disposable_redis + + def retry_with_rdb_reload(self): + yield 1 + self.dr.dump_and_reload() + yield 2 + + +class DisposableRedis(object): + + def __init__(self, port=None, path='redis-server', **extra_args): + """ + :param port: port number to start the redis server on. + Specify none to automatically generate + :type port: int|None + :param extra_args: any extra arguments kwargs will + be passed to redis server as --key val + """ + self._port = port + + # this will hold the actual port the redis is listening on. + # It's equal to `_port` unless `_port` is None + # in that case `port` is randomly generated + self.port = None + self.extra_args = [] + for k, v in extra_args.items(): + self.extra_args.append('--%s' % k) + if isinstance(v, (list, tuple)): + self.extra_args += list(v) + else: + self.extra_args.append(v) + + self.path = path + self.dumped = False + self.errored = False + + def _get_output(self): + return '' if REDIS_SHOW_OUTPUT else self.process.stdout.read() + + def _start_process(self): + #print("Starting redis process: {}".format(' '.join(self.args))) + if REDIS_DEBUGGER: + debugger = REDIS_DEBUGGER.split() + args = debugger + self.args + else: + args = self.args + stdout = None if REDIS_SHOW_OUTPUT else subprocess.PIPE + self.process = subprocess.Popen( + args, + stdin=sys.stdin, + stdout=stdout, + stderr=sys.stderr + ) + + while True: + try: + self.client().ping() + break + except redis.ConnectionError: + self.process.poll() + if self.process.returncode is not None: + raise RuntimeError( + "Process has exited with code {}\n. Redis output: {}" + .format(self.process.returncode, self._get_output())) + + time.sleep(0.1) + + def start(self): + """ + Start the server. To stop the server you should call stop() + accordingly + """ + if self._port is None: + self.port = get_random_port() + else: + self.port = self._port + + self.dumpfile = 'dump.%s.rdb' % self.port + self.args = [self.path, + '--port', str(self.port), + '--save', '', + '--dbfilename', self.dumpfile] + self.extra_args + + self._start_process() + + def stop(self): + self.process.terminate() + if self.dumped: + try: + os.unlink(self.dumpfile) + except OSError: + pass + + def __enter__(self): + self.start() + return self.client() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.stop() + if exc_val or self.errored: + sys.stderr.write("Redis output: {}\n".format(self._get_output())) + + def dump_and_reload(self): + """ + Dump the rdb and reload it, to test for serialization errors + """ + conn = self.client() + conn.save() + self.dumped = True + try: + conn.execute_command('DEBUG', 'RELOAD') + except redis.RedisError as err: + self.errored = True + raise err + + def client(self): + """ + :rtype: redis.StrictRedis + """ + return Client(self, self.port) diff --git a/rmutil/testmodule/cunit.py b/rmutil/testmodule/cunit.py new file mode 100644 index 0000000..c9512dc --- /dev/null +++ b/rmutil/testmodule/cunit.py @@ -0,0 +1,62 @@ +from subprocess import Popen, PIPE +import os +import os.path +import sys +from argparse import ArgumentParser + +from _disposableredis import DisposableRedis + +TESTMODULE_SO = os.environ.get('TESTMODULE_SO', '__testmodule.so') +TESTMODULE_SRC = os.path.join(os.path.dirname(__file__), 'testmodule.c') +ap = ArgumentParser() +ap.add_argument('-i', '--source', help='C module source', default=TESTMODULE_SRC) +ap.add_argument('-m', '--module', help='C module name', default=TESTMODULE_SO) +ap.add_argument('-f', '--filter', help='Filter expression for tests', default='*') +ap.add_argument('-N', '--no-compile', help="Don't compile module. Just run tests.", + default=False, action='store_true') + + +def compile_module(opts, args): + # Flags passed to compiler/linker + flags = [ + '$(CC)', '$(CFLAGS)', '$(CPPFLAGS)', '$(SHOBJ_CFLAGS)', + '$(SHOBJ_CPPFLAGS)', '-o', '$@', '$^', + '$(LDFLAGS)', '$(SHOBJ_LDLFAGS)', + ] + + if sys.platform == 'darwin': + flags.append('-bundle -undefined dynamic_lookup') + + flags += ['-lc'] + + mktest = [ + 'all: {0}'.format(opts.module), + '', + '{0}: {1} '.format(opts.module, opts.source) + ' '.join(args), + "\t" + ' '.join(flags), + '' + ] + mktest = "\n".join(mktest) + + po = Popen(['make', '-f', '-'], + stdin=PIPE, stdout=sys.stdout, stderr=sys.stderr) + po.communicate(input=mktest) + if po.returncode != 0: + raise Exception("Couldn't compile module!") + + +if __name__ == '__main__': + opts, args = ap.parse_known_args() + + if not opts.no_compile: + compile_module(opts, args) + + r = DisposableRedis(loadmodule=opts.module, + path=os.environ.get('REDIS_PATH', 'redis-server')) + r.start() + try: + rv = r.client().execute_command('tm.runtests', opts.filter) + if rv != 0: + raise Exception('Tests failed!') + finally: + pass diff --git a/rmutil/testmodule/testmodule-priv.h b/rmutil/testmodule/testmodule-priv.h new file mode 100644 index 0000000..25becae --- /dev/null +++ b/rmutil/testmodule/testmodule-priv.h @@ -0,0 +1,184 @@ +#ifndef TESTMODULE_PRIV_H +#define TESTMODULE_PRIV_H + +#include +#include +#include +#include +#include "redismodule.h" + +static int test__abort_on_fail = 0; + +static inline void dump_int_value(const char *name, uint64_t value, FILE *out) { + fprintf(out, "\t%s: U=%llu I=%lld H=0x%llx\n", name, (unsigned long long)value, (long long)value, + (unsigned long long)value); +} + +extern int test__status_g; +extern RedisModuleCtx *test__curctx; + +#define TEST_DUMP_PREAMBLE(file, line) fprintf(stderr, "Assertion failed at %s:%d\n", file, line) +#define TEST_MARK_FAIL() \ + do { \ + test__status_g++; \ + if (test__abort_on_fail) { \ + abort(); \ + } \ + } while (0); + +typedef void (*test__function)(RedisModuleCtx *); + +typedef struct { + const char *testname; + test__function fn; +} test__entry; + +typedef struct { + const char *classname; + test__entry *entries; + size_t nentries; +} test__class; + +typedef struct { + test__class *classes; + size_t nclasses; +} test__master_list; + +extern test__master_list tests__alltests_g; + +static inline void test__addclass(const char *classname) { + test__master_list *master = &tests__alltests_g; + + if (!master->nclasses) { + // Technically waste an entry here. meh. + master->classes = calloc(1, sizeof(*master->classes)); + } + + master->nclasses++; + master->classes = realloc(master->classes, sizeof(*master->classes) * master->nclasses); + test__class *cls = master->classes + (master->nclasses - 1); + + cls->classname = classname; + cls->entries = calloc(1, sizeof(*cls->entries)); + cls->nentries = 0; +} + +static inline void test__addfn(const char *classname, const char *testname, test__function fn) { + // Find the class + test__master_list *master = &tests__alltests_g; + test__class *cls = NULL; + for (size_t ii = 0; ii < master->nclasses; ++ii) { + if (!strcmp(master->classes[ii].classname, classname)) { + cls = master->classes + ii; + break; + } + } + if (!cls) { + fprintf(stderr, "Adding test to class '%s' which was not yet created\n", classname); + abort(); + } + + cls->nentries++; + cls->entries = realloc(cls->entries, (sizeof *cls->entries) * cls->nentries); + + test__entry *newent = cls->entries + (cls->nentries - 1); + newent->fn = fn; + newent->testname = testname; +} + +static inline void test__runtests(RedisModuleCtx *ctx) { + test__master_list *master = &tests__alltests_g; + for (size_t ii = 0; ii < master->nclasses; ++ii) { + test__class *cls = master->classes + ii; + fprintf(stderr, "=== CLASS '%s' (%lu tests) ===\n", cls->classname, cls->nentries); + + for (size_t jj = 0; jj < cls->nentries; ++jj) { + test__entry *ent = cls->entries + jj; + fprintf(stderr, "--- TEST %s.%s (%lu/%lu) ---\n", cls->classname, ent->testname, jj + 1, + cls->nentries); + cls->entries[jj].fn(ctx); + } + + // Once we're done with the test class, free it to appease valgrind: + free(cls->entries); + } + free(master->classes); +} + +#define TEST_DEFINE_GLOBALS() \ + test__master_list tests__alltests_g = {.classes = NULL, .nclasses = 0}; \ + int test__status_g = 0; \ + RedisModuleCtx *test__curctx; + +#define TEST_CLASS(n) \ + static void __attribute__((constructor(100))) test__initclass_##n(void) { \ + test__addclass(#n); \ + } \ + static const char __attribute__((unused)) *test__cur_class = #n; + +#define TEST_F(name) \ + static void test__##name(RedisModuleCtx *); \ + static void __attribute__((constructor(200))) test__addentry__##name(void) { \ + test__addfn(test__cur_class, #name, test__##name); \ + } \ + static void test__##name + +#define TEST_RUN_ALL_TESTS(ctx) test__runtests(ctx) + +#define RMTEST_STRTYPE_CSTR 1 +#define RMTEST_STRTYPE_RSTR 2 +#define RMTEST_AUTOLEN ((size_t)-1) + +int RMTest__CheckKeyExistsC(RedisModuleCtx *ctx, const char *key); +int RMTest__CheckKeyExistsR(RedisModuleCtx *ctx, const RedisModuleString *key); + +static inline void assert_int_helper(const char *var_a, const char *var_b, uint64_t val_a, + uint64_t val_b, const char *oper, const char *file, int line) { + TEST_DUMP_PREAMBLE(file, line); + fprintf(stderr, "Expected: %s %s %s\n", var_a, oper, var_b); + dump_int_value(var_a, val_a, stderr); + dump_int_value(var_b, val_b, stderr); + TEST_MARK_FAIL(); +} + +static inline void assert_bool_helper(const char *var, int exp, const char *file, int line) { + TEST_DUMP_PREAMBLE(file, line); + fprintf(stderr, "Assertion failed at %s:%d\n", file, line); + fprintf(stderr, "Expected '%s' to be %s. Is %s\n", var, exp ? "true" : "false", + exp ? "false" : "true"); + TEST_MARK_FAIL(); +} + +static inline void assert_string_helper(const char *var_a, const char *var_b, const char *exp, + size_t explen, const char *got, size_t gotlen, + const char *file, int line) { + if (explen == gotlen && strncmp(exp, got, explen) == 0) { + return; + } + TEST_DUMP_PREAMBLE(file, line); + fprintf(stderr, "Expected '%s' = '%s': <<%.*s>>\n", var_a, var_b, (int)explen, exp); + fprintf(stderr, "\tGot: <<%.*s>>\n", (int)gotlen, got); + TEST_MARK_FAIL(); +} + +#define ASSERT_COMMON(a, b, oper) \ + do { \ + uint64_t test__rv_a = (uint64_t)(a); \ + uint64_t test__rv_b = (uint64_t)(b); \ + if (!(test__rv_a oper test__rv_b)) { \ + assert_int_helper(#a, #b, test__rv_a, test__rv_b, #oper, __FILE__, __LINE__); \ + } \ + } while (0) + +#define ASSERT_BOOL_COMMON(expr, want) \ + do { \ + int test__rv = !!expr; \ + if (test__rv != want) { \ + assert_bool_helper(#expr, want, __FILE__, __LINE__); \ + } \ + } while (0); + +#define ASSERT_STR_COMMON(v1, v2, s1, n1, s2, n2) \ + assert_string_helper(v1, v2, s1, n1, s2, n2, __FILE__, __LINE__) + +#endif \ No newline at end of file diff --git a/rmutil/testmodule/testmodule.c b/rmutil/testmodule/testmodule.c new file mode 100644 index 0000000..8196949 --- /dev/null +++ b/rmutil/testmodule/testmodule.c @@ -0,0 +1,135 @@ +#include "redismodule.h" +#include "testmodule.h" +#include + +TEST_DEFINE_GLOBALS(); + +static int isFilterMatch(const char *filter, size_t nfilter, const char *testname, int isPrefix) { + if (filter == NULL) { + return 1; + } else if (!isPrefix) { + if (nfilter != strlen(testname)) { + return 0; + } else { + return !strncmp(filter, testname, nfilter); + } + } else { + // Is a prefix: + return !strncmp(filter, testname, nfilter); + } +} + +static int TM_RunTests(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + RedisModule_AutoMemory(ctx); + const char *filter = NULL; + size_t nfilter = 0; + int isPrefix = 0; + + if (argc >= 2) { + filter = RedisModule_StringPtrLen(argv[1], &nfilter); + if (filter[nfilter - 1] == '*') { + isPrefix = 1; + nfilter--; + } + } + + for (size_t ii = 0; ii < tests__alltests_g.nclasses; ++ii) { + const test__class *cls = tests__alltests_g.classes + ii; + for (size_t jj = 0; jj < cls->nentries; ++jj) { + char fqName[128] = {0}; + const test__entry *ent = cls->entries + jj; + sprintf(fqName, "%s.%s", cls->classname, ent->testname); + if (!isFilterMatch(filter, nfilter, fqName, isPrefix)) { + fprintf(stderr, "Skipping: %s (filtered out)\n", ent->testname); + continue; + } + fprintf(stderr, "Running: %s\n", fqName); + test__curctx = ctx; + ent->fn(ctx); + } + } + + RedisModule_ReplyWithLongLong(ctx, test__status_g); + return REDISMODULE_OK; +} + +static int TM_ListTests(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + RedisModule_AutoMemory(ctx); + RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); + size_t arrlen = 0; + for (size_t ii = 0; ii < tests__alltests_g.nclasses; ++ii) { + const test__class *cls = tests__alltests_g.classes + ii; + for (size_t jj = 0; jj < cls->nentries; ++jj) { + const test__entry *ent = cls->entries + jj; + RedisModule_ReplyWithString( + ctx, RedisModule_CreateStringPrintf(ctx, "%s.%s", cls->classname, ent->testname)); + ++arrlen; + } + } + RedisModule_ReplySetArrayLength(ctx, arrlen); + return REDISMODULE_OK; +} + +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (RedisModule_Init(ctx, "tm", 1, REDISMODULE_APIVER_1) != REDISMODULE_OK) { + return REDISMODULE_ERR; + } + + if (RedisModule_CreateCommand(ctx, "TM.RUNTESTS", TM_RunTests, "readonly", 0, 0, 0) != + REDISMODULE_OK) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx, "TM.LIST", TM_ListTests, "readonly", 0, 0, 0) != + REDISMODULE_OK) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} + +static int RMTest__CheckKeyExistsCommon(RedisModuleCtx *ctx, const void *k, size_t n, int isCstr) { + RedisModuleString *s; + if (isCstr) { + s = RedisModule_CreateString(ctx, k, n); + } else { + s = (void *)k; + } + + RedisModuleKey *rkey = RedisModule_OpenKey(ctx, s, REDISMODULE_READ); + int rv = rkey != NULL; + if (rkey) { + RedisModule_CloseKey(rkey); + } + if (isCstr) { + RedisModule_FreeString(ctx, s); + } + return rv; +} + +int RMTest__CheckKeyExistsC(RedisModuleCtx *ctx, const char *key) { + return RMTest__CheckKeyExistsCommon(ctx, key, strlen(key), 1); +} +int RMTest__CheckKeyExistsR(RedisModuleCtx *ctx, const RedisModuleString *key) { + return RMTest__CheckKeyExistsCommon(ctx, key, 0, 0); +} + +RedisModuleString **RMTest_BuildArgs(RedisModuleCtx *ctx, ...) { + va_list ap; + va_start(ap, ctx); + + va_list ap2; + va_copy(ap2, ap); + + size_t nitems = 0; + const char *p; + while ((p = va_arg(ap2, const char *)) != NULL) { + ++nitems; + } + va_end(ap2); + + RedisModuleString **retrv = RedisModule_PoolAlloc(ctx, (sizeof *retrv) * nitems); + for (size_t ii = 0; ii < nitems; ++ii) { + p = va_arg(ap, const char *); + retrv[ii] = RedisModule_CreateString(ctx, p, strlen(p)); + } + va_end(ap); + return retrv; +} \ No newline at end of file diff --git a/rmutil/testmodule/testmodule.h b/rmutil/testmodule/testmodule.h new file mode 100644 index 0000000..72384e4 --- /dev/null +++ b/rmutil/testmodule/testmodule.h @@ -0,0 +1,51 @@ +#ifndef TESTMODULE_H +#define TESTMODULE_H + +#include "testmodule-priv.h" + +// == +#define ASSERT_EQ(a, b) ASSERT_COMMON(a, b, ==) +// != +#define ASSERT_NE(a, b) ASSERT_COMMON(a, b, !=) +// > +#define ASSERT_GT(a, b) ASSERT_COMMON(a, b, >) +// >= +#define ASSERT_GE(a, b) ASSERT_COMMON(a, b, >=) +// < +#define ASSERT_LT(a, b) ASSERT_COMMON(a, b, <) +// <= +#define ASSERT_LE(a, b) ASSERT_COMMON(a, b, <=) + +// Use these macros for better print-outs +#define ASSERT_TRUE(x) ASSERT_BOOL_COMMON(x, 1) +#define ASSERT_FALSE(x) ASSERT_BOOL_COMMON(x, 0) + +// Compare RedisModuleString s1 with char s2 +#define ASSERT_STREQ_C(s1, s2) ASSERT_STR_COMMON(#s1, #s2, s1, strlen(s1), s2, strlen(s2)) + +// Compare two RedisModuleString objects +#define ASSERT_STREQ_R(r1, r2) \ + do { \ + size_t test__n1; \ + size_t test__n2; \ + const char *test__s1 = RedisModule_StringPtrLen(r1, &test__n1); \ + const char *test__s2 = RedisModule_StringPtrLen(r2, &test__n2); \ + ASSERT_STR_COMMON(#r1, #r2, test__s1, test__n1, test__s2, test__n2); \ + } while (0); + +#define ASSERT_STREQ_CR(s, r) \ + do { \ + size_t test__n1; \ + const char *test__s1 = RedisModule_StringPtrLen(r, &test__n1); \ + ASSERT_STR_COMMON(#s, #r, s, strlen(s), test__s1, test__n1); \ + } while (0); + +#define ASSERT_KEY_EXISTS_C(key) ASSERT_TRUE(RMTest__CheckKeyExistsC(test__curctx, key)) +#define ASSERT_KEY_EXISTS_R(key) ASSERT_TRUE(RMTest__CheckKeyExistsR(test__curctx, key)) + +#define ASSERT_KEY_MISSING_C(key) ASSERT_FALSE(RMTest__CheckKeyExistsC(test__curctx, key)) +#define ASSERT_KEY_MISSING_R(key) ASSERT_FALSE(RMTest__CheckKeyExistsR(test__curctx, key)); + +/** Returns an array of RedisModuleString. No need to free */ +RedisModuleString **RMTest_BuildArgs(RedisModuleCtx *ctx, ...); +#endif // TESTMODULE_H \ No newline at end of file From e1f4f5d9a9d0fdf6892b9cdf8fcb649da30dd761 Mon Sep 17 00:00:00 2001 From: Mark Nunberg Date: Thu, 27 Jul 2017 07:32:51 -0700 Subject: [PATCH 3/3] add travis --- .travis.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d806d56 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: c + +compiler: + - gcc + +before_script: + - git clone --depth 1 https://github.com/antirez/redis.git + - sudo make -C redis install + +script: + - cd rmutil + - make test