Permalink
Browse files

Test-runner: next increment.

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...
1 parent d8df4ae commit 53cb9701ec11a5bdd74959f2b79742f50e91fd74 @kostja kostja committed Dec 14, 2010
View
@@ -1,7 +1,10 @@
.gitignore
.gdb_history
TAGS
-_*
+_debug_box
+_release_box
+_debug_feeder
+_release_feeder
config.mk
lcov
*.o
@@ -10,3 +13,4 @@ lcov
*.xlog
tarantool_version.h
test/var
+test/lib/*.pyc
View
@@ -33,13 +33,14 @@
import socket
import sys
import string
+import lib.admin
class Options:
def __init__(self):
"""Add all program options, with their defaults."""
parser = argparse.ArgumentParser(
- description = "Tarantool regression test suite client.")
+ description = "Tarantool administrative console client.")
parser.add_argument(
"--host",
@@ -76,55 +77,20 @@ def __init__(self):
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():
options = Options()
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
prompt = options.args.prompt
if 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:
continue
output = con.execute(line)
@@ -134,6 +100,7 @@ def main():
else:
sys.stdout.write(output)
sys.stdout.write(prompt)
+ sys.stdout.flush()
return 0
except (RuntimeError, socket.error, KeyboardInterrupt) as e:
View
@@ -2,4 +2,3 @@
description = tarantool/silverbox, minimal configuration
client = admin.py --prompt "" --result-prefix "r> "
config = tarantool.cfg
-pidfile = box.pid
View
@@ -2,4 +2,3 @@
description = tarantool/silverbox, command line options
client = cmdline.py $server --result-prefix "r> "
config = tarantool.cfg
-pidfile = box.pid
View
@@ -61,7 +61,11 @@ def main():
try:
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:
continue
path = string.join([args.bin, line])
@@ -73,6 +77,7 @@ def main():
"\n" + result_prefix)
else:
sys.stdout.write(output)
+ sys.stdout.flush()
return 0
except RuntimeError as e:
View
No changes.
View
@@ -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()
+
@@ -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
+
Oops, something went wrong.

0 comments on commit 53cb970

Please sign in to comment.