diff --git a/.gitignore b/.gitignore index 4a268da39..ace9dc100 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ cscope.out # CLion /.idea lcov + +# python compiled files +*.pyc diff --git a/.travis.yml b/.travis.yml index 47c18dabf..508112c09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -93,3 +93,4 @@ script: - cmake .. - make -j - make check + - cd ../test && python -m unittest discover diff --git a/test/integration/__init__.py b/test/integration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration/base.py b/test/integration/base.py new file mode 100644 index 000000000..05261445a --- /dev/null +++ b/test/integration/base.py @@ -0,0 +1,50 @@ +import unittest + +from .pelikan_client import PelikanClient +from .pelikan_server import PelikanServer + +SERVER_PORT = 12345 +ADMIN_PORT = 12346 + + +class PelikanTest(unittest.TestCase): + reserved_ports = set() + + def setUp(self): + self.server = self.getServer( + server_port=SERVER_PORT, + admin_port=ADMIN_PORT + ) + self.client = PelikanClient( + server_port=SERVER_PORT, + admin_port=ADMIN_PORT + ) + + def tearDown(self): + self.server.stop() + + def assertRead(self, expected): + read = self.client.read(len(expected)) + self.assertEqual(expected, read) + + + def assertMetrics(self, *args): + stats = self.client.getStats() + for (k, v) in args: + self.assertEqual( + stats.get(k, None), + str(v), + 'Expected {} to be {}, got {} instead'.format( + k, + v, + stats.get(k, None), + ) + ) + + +class TwemcacheTest(PelikanTest): + def getServer(self, **kwargs): + server = PelikanServer('pelikan_twemcache') + server.start(**kwargs) + server.wait_ready() + return server diff --git a/test/integration/pelikan_client.py b/test/integration/pelikan_client.py new file mode 100644 index 000000000..e70ecd54e --- /dev/null +++ b/test/integration/pelikan_client.py @@ -0,0 +1,38 @@ +import errno +import socket + + +class PelikanClient(object): + def __init__(self, server_port, admin_port): + self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.client.connect(('localhost', server_port)) + self.admin = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.admin.connect(('localhost', admin_port)) + + def getStats(self): + self.admin.sendall('stats\r\n') + data = '' + self.admin.setblocking(False) + while True: + try: + data += self.admin.recv(1024) + except socket.error, e: + err = e.args[0] + if err == errno.EAGAIN or err == errno.EWOULDBLOCK: + if len(data) == 0: + continue + else: + break + else: + raise + self.admin.setblocking(True) + # this NULL is kinda unexpected for me + if data[-6:] != 'END\r\n\0': + raise Exception('Invalid data while fetching stats: {}'.format(data)) + return dict(line.split(' ')[1:] for line in data[:-6].strip().split('\r\n')) + + def read(self, length): + return self.client.recv(length) + + def write(self, data): + self.client.sendall(data) diff --git a/test/integration/pelikan_server.py b/test/integration/pelikan_server.py new file mode 100644 index 000000000..66b65adcf --- /dev/null +++ b/test/integration/pelikan_server.py @@ -0,0 +1,39 @@ +import os +import subprocess +import tempfile + + +class PelikanServer(object): + @staticmethod + def default_path(): + return os.path.realpath(os.path.join( + os.path.dirname(os.path.abspath(__file__)), + '../../_build/_bin' + )) + + def __init__(self, executable): + self.executable = executable + + def start(self, **kwargs): + self.config_file = tempfile.NamedTemporaryFile() + self.config_file.write('\n'.join(('{}: {}'.format(k, v) for (k, v) in kwargs.items()))) + self.config_file.flush() + + executable = os.path.join( + os.getenv('PELIKAN_BIN_PATH', PelikanServer.default_path()), + self.executable + ) + self.server = subprocess.Popen((executable, self.config_file.name), + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + def wait_ready(self): + # wait for the port to be listening; no great "ready" output present + # but it lists all configs, so this must exist + while 'name: server_port' not in self.server.stdout.readline(): + pass + + def stop(self): + self.server.kill() diff --git a/test/integration/test_twemcache_basic.py b/test/integration/test_twemcache_basic.py new file mode 100644 index 000000000..2d3b6c15e --- /dev/null +++ b/test/integration/test_twemcache_basic.py @@ -0,0 +1,7 @@ +from .base import TwemcacheTest + +class TwemcacheBasicTest(TwemcacheTest): + def test_miss(self): + self.client.write('get foo\r\n') + self.assertRead('END') + self.assertMetrics(('request_parse', 1), ('get', 1), ('get_key_miss', 1))