Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testmodule #32

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
*.db
.vscode
rmutil/test_vector
rmutil/test_periodic
*.dSYM
*.pyc
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 9 additions & 2 deletions rmutil/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
48 changes: 48 additions & 0 deletions rmutil/test_string.c
Original file line number Diff line number Diff line change
@@ -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));
}
}
4 changes: 4 additions & 0 deletions rmutil/test_util.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include "testmodule/testmodule.h"
#include "util.h"

TEST_CLASS(utils)
11 changes: 11 additions & 0 deletions rmutil/testmodule/Makefile
Original file line number Diff line number Diff line change
@@ -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
146 changes: 146 additions & 0 deletions rmutil/testmodule/_disposableredis.py
Original file line number Diff line number Diff line change
@@ -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)
62 changes: 62 additions & 0 deletions rmutil/testmodule/cunit.py
Original file line number Diff line number Diff line change
@@ -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
Loading