Skip to content

Commit

Permalink
Test-runner: next increment.
Browse files Browse the repository at this point in the history
Add test/lib/ for python modules.
Create the first module that implements
admin connection.
Automatically read host and port
and pidfile from tarantool configuration
file, and thus remove them from suite.ini.
Change .gitignore and remove a too
broad ignore mask (it ignored __init__.py,
which is mandatory file name for a module
in python).
Fix a bug in config reader for tarantool
that would leave "box.pid" in quotes.
Move TestSuite and Test to a lib/ module.
Patch cmdline.py and ./admin.py to
read input in line-buffered fashion (used
to be block-buffered), regardless of whether
input is a terminal or a pipe. This allows to
work with these tools interactively.
  • Loading branch information
kostja committed Dec 14, 2010
1 parent d8df4ae commit 53cb970
Show file tree
Hide file tree
Showing 10 changed files with 422 additions and 344 deletions.
6 changes: 5 additions & 1 deletion .gitignore
@@ -1,7 +1,10 @@
.gitignore .gitignore
.gdb_history .gdb_history
TAGS TAGS
_* _debug_box
_release_box
_debug_feeder
_release_feeder
config.mk config.mk
lcov lcov
*.o *.o
Expand All @@ -10,3 +13,4 @@ lcov
*.xlog *.xlog
tarantool_version.h tarantool_version.h
test/var test/var
test/lib/*.pyc
53 changes: 10 additions & 43 deletions test/admin.py
Expand Up @@ -33,13 +33,14 @@
import socket import socket
import sys import sys
import string import string
import lib.admin


class Options: class Options:
def __init__(self): def __init__(self):
"""Add all program options, with their defaults.""" """Add all program options, with their defaults."""


parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description = "Tarantool regression test suite client.") description = "Tarantool administrative console client.")


parser.add_argument( parser.add_argument(
"--host", "--host",
Expand Down Expand Up @@ -76,55 +77,20 @@ def __init__(self):
self.args = parser.parse_args() self.args = parser.parse_args()




class Connection:
def __init__(self, host, port):
self.host = host
self.port = port
self.is_connected = False

def connect(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((self.host, self.port))
self.is_connected = True

def disconnect(self):
if self.is_connected:
self.socket.close()
self.is_connected = False

def execute(self, command):
self.socket.sendall(command)

bufsiz = 4096
res = ""

while True:
buf = self.socket.recv(bufsiz)
if not buf:
break
res+= buf;
if res.rfind("---\n"):
break

return res

def __enter__(self):
self.connect()
return self

def __exit__(self, type, value, tb):
self.disconnect()


def main(): def main():
options = Options() options = Options()
try: try:
with Connection(options.args.host, options.args.port) as con: with lib.admin.Connection(options.args.host, options.args.port) as con:
result_prefix = options.args.result_prefix result_prefix = options.args.result_prefix
prompt = options.args.prompt prompt = options.args.prompt
if prompt != "": if prompt != "":
sys.stdout.write(prompt) sys.stdout.write(prompt)
for line in iter(sys.stdin.readline, ""): # We need line-buffering, and thus don't use 'for' loop
while True:
line = sys.stdin.readline()
sys.stdout.flush()
if not line:
break;
if result_prefix != None and line.find(result_prefix) == 0: if result_prefix != None and line.find(result_prefix) == 0:
continue continue
output = con.execute(line) output = con.execute(line)
Expand All @@ -134,6 +100,7 @@ def main():
else: else:
sys.stdout.write(output) sys.stdout.write(output)
sys.stdout.write(prompt) sys.stdout.write(prompt)
sys.stdout.flush()


return 0 return 0
except (RuntimeError, socket.error, KeyboardInterrupt) as e: except (RuntimeError, socket.error, KeyboardInterrupt) as e:
Expand Down
1 change: 0 additions & 1 deletion test/box/suite.ini
Expand Up @@ -2,4 +2,3 @@
description = tarantool/silverbox, minimal configuration description = tarantool/silverbox, minimal configuration
client = admin.py --prompt "" --result-prefix "r> " client = admin.py --prompt "" --result-prefix "r> "
config = tarantool.cfg config = tarantool.cfg
pidfile = box.pid
1 change: 0 additions & 1 deletion test/cmd/suite.ini
Expand Up @@ -2,4 +2,3 @@
description = tarantool/silverbox, command line options description = tarantool/silverbox, command line options
client = cmdline.py $server --result-prefix "r> " client = cmdline.py $server --result-prefix "r> "
config = tarantool.cfg config = tarantool.cfg
pidfile = box.pid
7 changes: 6 additions & 1 deletion test/cmdline.py
Expand Up @@ -61,7 +61,11 @@ def main():
try: try:
result_prefix = args.result_prefix result_prefix = args.result_prefix


for line in iter(sys.stdin.readline, ""): # We need line-buffering, and thus don't use 'for' loop
while True:
line = sys.stdin.readline()
if not line:
break;
if result_prefix != None and line.find(result_prefix) == 0: if result_prefix != None and line.find(result_prefix) == 0:
continue continue
path = string.join([args.bin, line]) path = string.join([args.bin, line])
Expand All @@ -73,6 +77,7 @@ def main():
"\n" + result_prefix) "\n" + result_prefix)
else: else:
sys.stdout.write(output) sys.stdout.write(output)
sys.stdout.flush()


return 0 return 0
except RuntimeError as e: except RuntimeError as e:
Expand Down
Empty file added test/lib/__init__.py
Empty file.
66 changes: 66 additions & 0 deletions test/lib/admin.py
@@ -0,0 +1,66 @@
__author__ = "Konstantin Osipov <kostja.osipov@gmail.com>"

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

import socket
import sys
import string

class Connection:
def __init__(self, host, port):
self.host = host
self.port = port
self.is_connected = False

def connect(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((self.host, self.port))
self.is_connected = True

def disconnect(self):
if self.is_connected:
self.socket.close()
self.is_connected = False

def execute(self, command):
self.socket.sendall(command)

bufsiz = 4096
res = ""

while True:
buf = self.socket.recv(bufsiz)
if not buf:
break
res+= buf;
if res.rfind("---\n"):
break

return res

def __enter__(self):
self.connect()
return self

def __exit__(self, type, value, tb):
self.disconnect()

124 changes: 124 additions & 0 deletions test/lib/tarantool_silverbox_server.py
@@ -0,0 +1,124 @@
import os
import stat
import shutil
import subprocess
import pexpect
import sys
import signal
import time

class TarantoolSilverboxServer:
"""Server represents a single server instance. Normally, the
program operates with only one server, but in future we may add
replication slaves. The server is started once at the beginning
of each suite, and stopped at the end."""

def __init__(self, args, config, pidfile):
"""Set server options: path to configuration file, pid file, exe, etc."""
self.args = args
self.path_to_config = config
self.path_to_pidfile = os.path.join(args.vardir, pidfile)
self.path_to_exe = None
self.abspath_to_exe = None
self.is_started = False

def start(self):
"""Start server instance: check if the old one exists, kill it
if necessary, create necessary directories and files, start
the server. The server working directory is taken from 'vardir',
specified in the prgoram options.
Currently this is implemented for tarantool_silverbox only."""

if not self.is_started:
print "Starting the server..."

if self.path_to_exe == None:
self.path_to_exe = self.find_exe()
self.abspath_to_exe = os.path.abspath(self.path_to_exe)

print " Found executable at " + self.path_to_exe + "."

print " Creating and populating working directory in " +\
self.args.vardir + "..."

if os.access(self.args.vardir, os.F_OK):
print " Found old vardir, deleting..."
self.kill_old_server()
shutil.rmtree(self.args.vardir, ignore_errors = True)

os.mkdir(self.args.vardir)
shutil.copy(self.path_to_config, self.args.vardir)

subprocess.check_call([self.abspath_to_exe, "--init_storage"],
cwd = self.args.vardir,
# catch stdout/stderr to not clutter output
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)

if self.args.start_and_exit:
subprocess.check_call([self.abspath_to_exe, "--daemonize"],
cwd = self.args.vardir,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
else:
self.server = pexpect.spawn(self.abspath_to_exe,
cwd = self.args.vardir)
self.logfile_read = sys.stdout
self.server.expect_exact("entering event loop")

version = subprocess.Popen([self.abspath_to_exe, "--version"],
cwd = self.args.vardir,
stdout = subprocess.PIPE).stdout.read().rstrip()

print "Started {0} {1}.".format(os.path.basename(self.abspath_to_exe),
version)

# Set is_started flag, to nicely support cleanup during an exception.
self.is_started = True
else:
print "The server is already started."

def stop(self):
"""Stop server instance. Do nothing if the server is not started,
to properly shut down the server in case of an exception during
start up."""
if self.is_started:
print "Stopping the server..."
self.server.terminate()
self.server.expect(pexpect.EOF)
self.is_started = False
else:
print "The server is not started."

def find_exe(self):
"""Locate server executable in the bindir. We just take
the first thing looking like an exe in there."""

if (os.access(self.args.bindir, os.F_OK) == False or
stat.S_ISDIR(os.stat(self.args.bindir).st_mode) == False):
raise TestRunException("Directory " + self.args.bindir +
" doesn't exist")

for f in os.listdir(self.args.bindir):
f = os.path.join(self.args.bindir, f)
st_mode = os.stat(f).st_mode
if stat.S_ISREG(st_mode) and st_mode & stat.S_IXUSR:
return f
raise TestRunException("Can't find server executable in " +
self.args.bindir)

def kill_old_server(self):
"""Kill old server instance if it exists."""
if os.access(self.path_to_pidfile, os.F_OK) == False:
return # Nothing to do
pid = 0
with open(self.path_to_pidfile) as f:
pid = int(f.read())
print " Found old server, pid {0}, killing...".format(pid)
try:
os.kill(pid, signal.SIGTERM)
while os.kill(pid, 0) != -1:
time.sleep(0.01)
except OSError:
pass

0 comments on commit 53cb970

Please sign in to comment.