3
# Copyright (C) 2002, 2003 by Martin Pool <mbp@samba.org>
4
# Copyright (C) 2003 by Tim Potter <tpot@samba.org>
6
# This program is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU General Public License as
8
# published by the Free Software Foundation; either version 3 of the
9
# License, or (at your option) any later version.
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
# General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, see <http://www.gnu.org/licenses/>.
19
"""comfychair: a Python-based instrument of software torture.
21
Copyright (C) 2002, 2003 by Martin Pool <mbp@samba.org>
22
Copyright (C) 2003 by Tim Potter <tpot@samba.org>
24
This is a test framework designed for testing programs written in
25
Python, or (through a fork/exec interface) any other language.
27
For more information, see the file README.comfychair.
29
To run a test suite based on ComfyChair, just run it as a program.
36
"""A base class for tests. This class defines required functions which
37
can optionally be overridden by subclasses. It also provides some
38
utility functions for"""
42
self.background_pids = []
45
self._save_environment()
46
self.add_cleanup(self.teardown)
49
# --------------------------------------------------
50
# Save and restore directory
51
def _enter_rundir(self):
53
self.basedir = os.getcwd()
54
self.add_cleanup(self._restore_directory)
55
self.rundir = os.path.join(self.basedir,
57
self.__class__.__name__)
58
self.tmpdir = os.path.join(self.rundir, 'tmp')
59
os.system("rm -fr %s" % self.rundir)
60
os.makedirs(self.tmpdir)
61
os.system("mkdir -p %s" % self.rundir)
64
def _restore_directory(self):
66
os.chdir(self.basedir)
68
# --------------------------------------------------
69
# Save and restore environment
70
def _save_environment(self):
72
self._saved_environ = os.environ.copy()
73
self.add_cleanup(self._restore_environment)
75
def _restore_environment(self):
78
os.environ.update(self._saved_environ)
82
"""Set up test fixture."""
86
"""Tear down test fixture."""
94
def add_cleanup(self, c):
95
"""Queue a cleanup to be run when the test is complete."""
96
self._cleanups.append(c)
99
def fail(self, reason = ""):
100
"""Say the test failed."""
101
raise AssertionError(reason)
104
#############################################################
105
# Requisition methods
107
def require(self, predicate, message):
108
"""Check a predicate for running this test.
110
If the predicate value is not true, the test is skipped with a message explaining
113
raise NotRunError, message
115
def require_root(self):
116
"""Skip this test unless run by root."""
118
self.require(os.getuid() == 0,
119
"must be root to run this test")
121
#############################################################
124
def assert_(self, expr, reason = ""):
126
raise AssertionError(reason)
128
def assert_equal(self, a, b):
130
raise AssertionError("assertEquals failed: %s" % `(a, b)`)
132
def assert_notequal(self, a, b):
134
raise AssertionError("assertNotEqual failed: %s" % `(a, b)`)
136
def assert_re_match(self, pattern, s):
137
"""Assert that a string matches a particular pattern
140
pattern string: regular expression
141
s string: to be matched
144
AssertionError if not matched
146
if not re.match(pattern, s):
147
raise AssertionError("string does not match regexp\n"
149
" re: %s" % (`s`, `pattern`))
151
def assert_re_search(self, pattern, s):
152
"""Assert that a string *contains* a particular pattern
155
pattern string: regular expression
156
s string: to be searched
159
AssertionError if not matched
161
if not re.search(pattern, s):
162
raise AssertionError("string does not contain regexp\n"
164
" re: %s" % (`s`, `pattern`))
167
def assert_no_file(self, filename):
169
assert not os.path.exists(filename), ("file exists but should not: %s" % filename)
172
#############################################################
173
# Methods for running programs
175
def runcmd_background(self, cmd):
177
self.test_log = self.test_log + "Run in background:\n" + `cmd` + "\n"
182
os.execvp("/bin/sh", ["/bin/sh", "-c", cmd])
185
self.test_log = self.test_log + "pid: %d\n" % pid
189
def runcmd(self, cmd, expectedResult = 0):
190
"""Run a command, fail if the command returns an unexpected exit
191
code. Return the output produced."""
192
rc, output, stderr = self.runcmd_unchecked(cmd)
193
if rc != expectedResult:
194
raise AssertionError("""command returned %d; expected %s: \"%s\"
198
%s""" % (rc, expectedResult, cmd, output, stderr))
200
return output, stderr
203
def run_captured(self, cmd):
204
"""Run a command, capturing stdout and stderr.
206
Based in part on popen2.py
208
Returns (waitstatus, stdout, stderr)."""
215
openmode = os.O_WRONLY|os.O_CREAT|os.O_TRUNC
217
outfd = os.open('%d.out' % pid, openmode, 0666)
221
errfd = os.open('%d.err' % pid, openmode, 0666)
225
if isinstance(cmd, types.StringType):
226
cmd = ['/bin/sh', '-c', cmd]
228
os.execvp(cmd[0], cmd)
233
exited_pid, waitstatus = os.waitpid(pid, 0)
234
stdout = open('%d.out' % pid).read()
235
stderr = open('%d.err' % pid).read()
236
return waitstatus, stdout, stderr
239
def runcmd_unchecked(self, cmd, skip_on_noexec = 0):
240
"""Invoke a command; return (exitcode, stdout, stderr)"""
242
waitstatus, stdout, stderr = self.run_captured(cmd)
243
assert not os.WIFSIGNALED(waitstatus), \
244
("%s terminated with signal %d" % (`cmd`, os.WTERMSIG(waitstatus)))
245
rc = os.WEXITSTATUS(waitstatus)
246
self.test_log = self.test_log + ("""Run command: %s
247
Wait status: %#x (exit code %d, signal %d)
251
%s""" % (cmd, waitstatus, os.WEXITSTATUS(waitstatus), os.WTERMSIG(waitstatus),
253
if skip_on_noexec and rc == 127:
254
# Either we could not execute the command or the command
255
# returned exit code 127. According to system(3) we can't
256
# tell the difference.
257
raise NotRunError, "could not execute %s" % `cmd`
258
return rc, stdout, stderr
261
def explain_failure(self, exc_info = None):
267
"""Log a message to the test log. This message is displayed if
268
the test fails, or when the runtests function is invoked with
269
the verbose option."""
270
self.test_log = self.test_log + msg + "\n"
273
class NotRunError(Exception):
274
"""Raised if a test must be skipped because of missing resources"""
275
def __init__(self, value = None):
279
def _report_error(case, debugger):
280
"""Ask the test case to explain failure, and optionally run a debugger
283
case TestCase instance
284
debugger if true, a debugger function to be applied to the traceback
288
print "-----------------------------------------------------------------"
291
traceback.print_exc(file=sys.stdout)
292
case.explain_failure()
293
print "-----------------------------------------------------------------"
300
def runtests(test_list, verbose = 0, debugger = None):
301
"""Run a series of tests.
304
test_list sequence of TestCase classes
305
verbose print more information as testing proceeds
306
debugger debugger object to be applied to errors
309
unix return code: 0 for success, 1 for failures, 2 for test failure
313
for test_class in test_list:
314
print "%-30s" % _test_name(test_class),
315
# flush now so that long running tests are easier to follow
320
try: # run test and show result
325
except KeyboardInterrupt:
327
_report_error(obj, debugger)
330
except NotRunError, msg:
331
print "NOTRUN, %s" % msg.value
334
_report_error(obj, debugger)
337
while obj and obj._cleanups:
339
apply(obj._cleanups.pop())
340
except KeyboardInterrupt:
341
print "interrupted during teardown"
342
_report_error(obj, debugger)
346
print "error during teardown"
347
_report_error(obj, debugger)
349
# Display log file if we're verbose
350
if ret == 0 and verbose:
351
obj.explain_failure()
356
def _test_name(test_class):
357
"""Return a human-readable name for a test class.
360
return test_class.__name__
366
"""Help for people running tests"""
368
print """%s: software test suite based on ComfyChair
371
To run all tests, just run this program. To run particular tests,
372
list them on the command line.
375
--help show usage message
376
--list list available tests
377
--verbose, -v show more information while running tests
378
--post-mortem, -p enter Python debugger on error
382
def print_list(test_list):
383
"""Show list of available tests"""
384
for test_class in test_list:
385
print " %s" % _test_name(test_class)
388
def main(tests, extra_tests=[]):
389
"""Main entry point for test suites based on ComfyChair.
392
tests Sequence of TestCase subclasses to be run by default.
393
extra_tests Sequence of TestCase subclasses that are available but
396
Test suites should contain this boilerplate:
398
if __name__ == '__main__':
399
comfychair.main(tests)
401
This function handles standard options such as --help and --list, and
402
by default runs all tests in the suggested order.
404
Calls sys.exit() on completion.
412
opts, args = getopt.getopt(argv[1:], 'pv',
413
['help', 'list', 'verbose', 'post-mortem'])
414
for opt, opt_arg in opts:
418
elif opt == '--list':
419
print_list(tests + extra_tests)
421
elif opt == '--verbose' or opt == '-v':
423
elif opt == '--post-mortem' or opt == '-p':
425
debugger = pdb.post_mortem
428
all_tests = tests + extra_tests
431
by_name[_test_name(t)] = t
434
which_tests.append(by_name[name])
438
sys.exit(runtests(which_tests, verbose=opt_verbose,
442
if __name__ == '__main__':