Skip to content

Commit

Permalink
The beginnings of a test suite.
Browse files Browse the repository at this point in the history
Has failures at this point due to known issues,
which will be fixed by subsequent commits.

Added -c option to specify alternate config file,
and -l option to specify alternate lock file;
without these it's pretty much impossible to test
cleanly or in a non-root environment.
  • Loading branch information
Adam Spiers committed Mar 7, 2012
1 parent 8a646ee commit b00daf7
Show file tree
Hide file tree
Showing 14 changed files with 512 additions and 15 deletions.
4 changes: 4 additions & 0 deletions Makefile.am
Expand Up @@ -55,6 +55,8 @@ bootharbitratordir = ${INITDDIR}

bootharbitrator_SCRIPTS = script/lsb/booth-arbitrator

TESTS = test/runtests.py

SUBDIRS = src

coverity:
Expand All @@ -77,6 +79,8 @@ uninstall-local:
rmdir $(DESTDIR)/${boothsitedir} || :;
rmdir $(DESTDIR)/${SOCKETDIR} || :;

test: check

lint:
for dir in src; do make -C $$dir lint; done

Expand Down
3 changes: 2 additions & 1 deletion src/booth.h
Expand Up @@ -28,11 +28,12 @@
#define BOOTH_RUN_DIR "/var/run"
#define BOOTH_LOG_DIR "/var/log"
#define BOOTH_LOGFILE_NAME "booth.log"
#define BOOTH_LOCKFILE_NAME "booth.pid"
#define BOOTH_DEFAULT_LOCKFILE BOOTH_RUN_DIR "/booth.pid"
#define BOOTH_DEFAULT_CONF "/etc/sysconfig/booth"

#define DAEMON_NAME "booth"
#define BOOTH_NAME_LEN 63
#define BOOTH_PATH_LEN 127

#define BOOTHC_SOCK_PATH "boothc_lock"
#define BOOTH_PROTO_FAMILY AF_INET
Expand Down
34 changes: 20 additions & 14 deletions src/main.c
Expand Up @@ -73,6 +73,8 @@ struct command_line {
int type; /* ACT_ */
int op; /* OP_ */
int force;
char configfile[BOOTH_PATH_LEN];
char lockfile[BOOTH_PATH_LEN];
char site[BOOTH_NAME_LEN];
char ticket[BOOTH_NAME_LEN];
};
Expand Down Expand Up @@ -365,7 +367,7 @@ static int setup_config(int type)
{
int rv;

rv = read_config(BOOTH_DEFAULT_CONF);
rv = read_config(cl.configfile);
if (rv < 0)
goto out;

Expand Down Expand Up @@ -707,17 +709,14 @@ static int do_revoke(void)

static int lockfile(void)
{
char path[PATH_MAX];
char buf[16];
struct flock lock;
int fd, rv;

snprintf(path, PATH_MAX, "%s/%s", BOOTH_RUN_DIR, BOOTH_LOCKFILE_NAME);

fd = open(path, O_CREAT|O_WRONLY, 0666);
fd = open(cl.lockfile, O_CREAT|O_WRONLY, 0666);
if (fd < 0) {
log_error("lockfile open error %s: %s",
path, strerror(errno));
cl.lockfile, strerror(errno));
return -1;
}

Expand All @@ -729,14 +728,14 @@ static int lockfile(void)
rv = fcntl(fd, F_SETLK, &lock);
if (rv < 0) {
log_error("lockfile setlk error %s: %s",
path, strerror(errno));
cl.lockfile, strerror(errno));
goto fail;
}

rv = ftruncate(fd, 0);
if (rv < 0) {
log_error("lockfile truncate error %s: %s",
path, strerror(errno));
cl.lockfile, strerror(errno));
goto fail;
}

Expand All @@ -746,7 +745,7 @@ static int lockfile(void)
rv = write(fd, buf, strlen(buf));
if (rv <= 0) {
log_error("lockfile write error %s: %s",
path, strerror(errno));
cl.lockfile, strerror(errno));
goto fail;
}

Expand All @@ -758,10 +757,7 @@ static int lockfile(void)

static void unlink_lockfile(int fd)
{
char path[PATH_MAX];

snprintf(path, PATH_MAX, "%s/%s", BOOTH_RUN_DIR, BOOTH_LOCKFILE_NAME);
unlink(path);
unlink(cl.lockfile);
close(fd);
}

Expand All @@ -782,6 +778,8 @@ static void print_usage(void)
printf(" revoke: Revoke ticket T(-t T) from site S(-s S)\n");
printf("\n");
printf("Options:\n");
printf(" -c FILE Specify config file [default " BOOTH_DEFAULT_CONF "]\n");
printf(" -l LOCKFILE Specify lock file [default " BOOTH_DEFAULT_LOCKFILE "]\n");
printf(" -D Enable debugging to stderr and don't fork\n");
printf(" -t ticket name\n");
printf(" -s site name\n");
Expand All @@ -790,7 +788,7 @@ static void print_usage(void)
printf(" -h Print this help, then exit\n");
}

#define OPTION_STRING "Dt:s:fh"
#define OPTION_STRING "c:Dl:t:s:fh"

static char *logging_entity = NULL;

Expand Down Expand Up @@ -861,12 +859,18 @@ static int read_arguments(int argc, char **argv)
optchar = getopt(argc, argv, OPTION_STRING);

switch (optchar) {
case 'c':
strncpy(cl.configfile, optarg, BOOTH_PATH_LEN - 1);
break;
case 'D':
debug_level = 1;
log_logfile_priority = LOG_DEBUG;
log_syslog_priority = LOG_DEBUG;
break;

case 'l':
strncpy(cl.lockfile, optarg, BOOTH_PATH_LEN - 1);
break;
case 't':
if (cl.op == OP_GRANT || cl.op == OP_REVOKE)
strcpy(cl.ticket, optarg);
Expand Down Expand Up @@ -1013,6 +1017,8 @@ int main(int argc, char *argv[])
int rv;

memset(&cl, 0, sizeof(cl));
strncpy(cl.configfile, BOOTH_DEFAULT_CONF, BOOTH_PATH_LEN - 1);
strncpy(cl.lockfile, BOOTH_DEFAULT_LOCKFILE, BOOTH_PATH_LEN - 1);

rv = read_arguments(argc, argv);
if (rv < 0)
Expand Down
6 changes: 6 additions & 0 deletions test/arbtests.py
@@ -0,0 +1,6 @@
#!/usr/bin/python

from servertests import ServerTests

class ArbitratorConfigTests(ServerTests):
mode = 'arbitrator'
41 changes: 41 additions & 0 deletions test/assertions.py
@@ -0,0 +1,41 @@
class BoothAssertions:
def configFileMissingMyIP(self, config_file=None, lock_file=None):
(pid, ret, stdout, stderr, runner) = \
self.run_booth(config_file=config_file, lock_file=lock_file,
expected_exitcode=1)

expected_error = "ERROR: can't find myself in config file"
self.assertRegexpMatches(self.read_log(), expected_error)

def assertLockFileError(self, config_file=None, config_text=None,
lock_file=True, args=[]):
(pid, ret, stdout, stderr, runner) = \
self.run_booth(config_text=config_text, config_file=config_file,
lock_file=lock_file, args=args, expected_exitcode=1)
expected_error = 'lockfile open error %s: Permission denied' % runner.lock_file_used()
self.assertRegexpMatches(self.read_log(), expected_error)

######################################################################
# backported from 2.7 just in case we're running on an older Python
def assertRegexpMatches(self, text, expected_regexp, msg=None):
"""Fail the test unless the text matches the regular expression."""
if isinstance(expected_regexp, basestring):
expected_regexp = re.compile(expected_regexp)
if not expected_regexp.search(text):
msg = msg or "Regexp didn't match"
msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text)
raise self.failureException(msg)

def assertNotRegexpMatches(self, text, unexpected_regexp, msg=None):
"""Fail the test if the text matches the regular expression."""
if isinstance(unexpected_regexp, basestring):
unexpected_regexp = re.compile(unexpected_regexp)
match = unexpected_regexp.search(text)
if match:
msg = msg or "Regexp matched"
msg = '%s: %r matches %r in %r' % (msg,
text[match.start():match.end()],
unexpected_regexp.pattern,
text)
raise self.failureException(msg)
######################################################################
108 changes: 108 additions & 0 deletions test/boothrunner.py
@@ -0,0 +1,108 @@
#!/usr/bin/python

import os
import subprocess
import time
import unittest

class BoothRunner:
default_config_file = '/etc/sysconfig/booth'
default_lock_file = '/var/run/booth.pid'

def __init__(self, boothd_path, mode, args):
self.boothd_path = boothd_path
self.args = [ mode ]
self.final_args = args # will be appended to self.args
self.mode = mode
self.config_file = None
self.lock_file = None

def set_config_file_arg(self):
self.args += [ '-c', self.config_file ]

def set_config_file(self, config_file):
self.config_file = config_file
self.set_config_file_arg()

def set_lock_file(self, lock_file):
self.lock_file = lock_file
self.args += [ '-l', self.lock_file ]

def set_debug(self):
self.args += [ '-D' ]

def all_args(self):
return [ self.boothd_path ] + self.args + self.final_args

def show_output(self, stdout, stderr):
if stdout:
print "STDOUT:"
print "------"
print stdout,
if stderr:
print "STDERR:"
print "------"
print stderr,
print "-" * 70

def subproc_completed_within(self, p, timeout):
start = time.time()
wait = 0.1
while True:
if p.poll() is not None:
return True
elapsed = time.time() - start
if elapsed + wait > timeout:
wait = timeout - elapsed
print "Waiting on %d for %.1fs ..." % (p.pid, wait)
time.sleep(wait)
elapsed = time.time() - start
if elapsed >= timeout:
return False
wait *= 2

def lock_file_used(self):
return self.lock_file or self.default_lock_file

def config_file_used(self):
return self.config_file or self.default_config_file

def config_text_used(self):
config_file = self.config_file_used()
try:
c = open(config_file)
except:
return None
text = "".join(c.readlines())
c.close()

text = text.replace('\t', '<TAB>')
text = text.replace('\n', '|\n')

return text

def show_args(self):
print "\n"
print "-" * 70
print "Running", ' '.join(self.all_args())
msg = "with config from %s" % self.config_file_used()
config_text = self.config_text_used()
if config_text is not None:
msg += ": [%s]" % config_text
print msg

def run(self):
p = subprocess.Popen(self.all_args(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if not p:
raise RuntimeError, "failed to start subprocess"

print "Started subprocess pid %d" % p.pid

completed = self.subproc_completed_within(p, 2)

if completed:
(stdout, stderr) = p.communicate()
self.show_output(stdout, stderr)
return (p.pid, p.returncode, stdout, stderr)

return (p.pid, None, None, None)
57 changes: 57 additions & 0 deletions test/boothtestenv.py
@@ -0,0 +1,57 @@
#!/usr/bin/python

import os
import time
import tempfile
import unittest

from assertions import BoothAssertions
from boothrunner import BoothRunner

class BoothTestEnvironment(unittest.TestCase, BoothAssertions):
test_src_path = os.path.abspath(os.path.dirname(__file__))
dist_path = os.path.join(test_src_path, '..' )
src_path = os.path.join(dist_path, 'src' )
boothd_path = os.path.join(src_path, 'boothd')
conf_path = os.path.join(dist_path, 'conf' )
example_config_path = os.path.join(conf_path, 'booth.conf.example')

def setUp(self):
if not self._testMethodName.startswith('test_'):
raise RuntimeError, "unexpected test method name: " + self._testMethodName
self.test_name = self._testMethodName[5:]
self.test_path = os.path.join(self.test_run_path, self.test_name)
os.makedirs(self.test_path)

def get_tempfile(self, identity):
tf = tempfile.NamedTemporaryFile(
prefix='%s.%d.' % (identity, time.time()),
dir=self.test_path,
delete=False
)
return tf.name

def init_log(self):
self.log_file = self.get_tempfile('log')
os.putenv('HA_debugfile', self.log_file) # See cluster-glue/lib/clplumbing/cl_log.c

def read_log(self):
if not os.path.exists(self.log_file):
return ''

l = open(self.log_file)
msgs = ''.join(l.readlines())
l.close()
return msgs

def check_return_code(self, pid, return_code, expected_exitcode):
if return_code is None:
print "pid %d still running" % pid
if expected_exitcode is not None:
self.fail("expected exit code %d, not long-running process" % expected_exitcode)
else:
print "pid %d exited with code %d" % (pid, return_code)
msg = "should exit with code %s" % expected_exitcode
msg += "\nlog follows (see %s)" % self.log_file
msg += "\n-----------\n%s" % self.read_log()
self.assertEqual(return_code, expected_exitcode, msg)
24 changes: 24 additions & 0 deletions test/clientenv.py
@@ -0,0 +1,24 @@
#!/usr/bin/python

from boothtestenv import BoothTestEnvironment
from boothrunner import BoothRunner

class ClientTestEnvironment(BoothTestEnvironment):
mode = 'client'

def run_booth(self, config_text=None, config_file=None, lock_file=True, args=[],
expected_exitcode=0, debug=False):
'''
Runs boothd.
Returns a (pid, return_code, stdout, stderr, runner) tuple,
where return_code/stdout/stderr are None iff pid is still running.
'''
self.init_log()

runner = BoothRunner(self.boothd_path, self.mode, args)
runner.show_args()
(pid, return_code, stdout, stderr) = runner.run()
self.check_return_code(pid, return_code, expected_exitcode)

return (pid, return_code, stdout, stderr, runner)

0 comments on commit b00daf7

Please sign in to comment.