Add missing end tags
[Samba/bjacke.git] / source / stf / comfychair.py
blob247915d7e9ee3ca8b47f732400205e30d5340496
1 #! /usr/bin/env python
3 # Copyright (C) 2002, 2003 by Martin Pool <mbp@samba.org>
4 # Copyright (C) 2003 by Tim Potter <tpot@samba.org>
5 #
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.
30 """
32 import sys, re
35 class TestCase:
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"""
40 def __init__(self):
41 self.test_log = ""
42 self.background_pids = []
43 self._cleanups = []
44 self._enter_rundir()
45 self._save_environment()
46 self.add_cleanup(self.teardown)
49 # --------------------------------------------------
50 # Save and restore directory
51 def _enter_rundir(self):
52 import os
53 self.basedir = os.getcwd()
54 self.add_cleanup(self._restore_directory)
55 self.rundir = os.path.join(self.basedir,
56 'testtmp',
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)
62 os.chdir(self.rundir)
64 def _restore_directory(self):
65 import os
66 os.chdir(self.basedir)
68 # --------------------------------------------------
69 # Save and restore environment
70 def _save_environment(self):
71 import os
72 self._saved_environ = os.environ.copy()
73 self.add_cleanup(self._restore_environment)
75 def _restore_environment(self):
76 import os
77 os.environ.clear()
78 os.environ.update(self._saved_environ)
81 def setup(self):
82 """Set up test fixture."""
83 pass
85 def teardown(self):
86 """Tear down test fixture."""
87 pass
89 def runtest(self):
90 """Run the test."""
91 pass
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
111 why."""
112 if not predicate:
113 raise NotRunError, message
115 def require_root(self):
116 """Skip this test unless run by root."""
117 import os
118 self.require(os.getuid() == 0,
119 "must be root to run this test")
121 #############################################################
122 # Assertion methods
124 def assert_(self, expr, reason = ""):
125 if not expr:
126 raise AssertionError(reason)
128 def assert_equal(self, a, b):
129 if not a == b:
130 raise AssertionError("assertEquals failed: %s" % `(a, b)`)
132 def assert_notequal(self, a, b):
133 if 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
139 Inputs:
140 pattern string: regular expression
141 s string: to be matched
143 Raises:
144 AssertionError if not matched
146 if not re.match(pattern, s):
147 raise AssertionError("string does not match regexp\n"
148 " string: %s\n"
149 " re: %s" % (`s`, `pattern`))
151 def assert_re_search(self, pattern, s):
152 """Assert that a string *contains* a particular pattern
154 Inputs:
155 pattern string: regular expression
156 s string: to be searched
158 Raises:
159 AssertionError if not matched
161 if not re.search(pattern, s):
162 raise AssertionError("string does not contain regexp\n"
163 " string: %s\n"
164 " re: %s" % (`s`, `pattern`))
167 def assert_no_file(self, filename):
168 import os.path
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):
176 import os
177 self.test_log = self.test_log + "Run in background:\n" + `cmd` + "\n"
178 pid = os.fork()
179 if pid == 0:
180 # child
181 try:
182 os.execvp("/bin/sh", ["/bin/sh", "-c", cmd])
183 finally:
184 os._exit(127)
185 self.test_log = self.test_log + "pid: %d\n" % pid
186 return 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\"
195 stdout:
197 stderr:
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)."""
209 import os, types
210 pid = os.fork()
211 if pid == 0:
212 # child
213 try:
214 pid = os.getpid()
215 openmode = os.O_WRONLY|os.O_CREAT|os.O_TRUNC
217 outfd = os.open('%d.out' % pid, openmode, 0666)
218 os.dup2(outfd, 1)
219 os.close(outfd)
221 errfd = os.open('%d.err' % pid, openmode, 0666)
222 os.dup2(errfd, 2)
223 os.close(errfd)
225 if isinstance(cmd, types.StringType):
226 cmd = ['/bin/sh', '-c', cmd]
228 os.execvp(cmd[0], cmd)
229 finally:
230 os._exit(127)
231 else:
232 # parent
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)"""
241 import os
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)
248 stdout:
250 stderr:
251 %s""" % (cmd, waitstatus, os.WEXITSTATUS(waitstatus), os.WTERMSIG(waitstatus),
252 stdout, stderr))
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):
262 print "test_log:"
263 print self.test_log
266 def log(self, msg):
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):
276 self.value = value
279 def _report_error(case, debugger):
280 """Ask the test case to explain failure, and optionally run a debugger
282 Input:
283 case TestCase instance
284 debugger if true, a debugger function to be applied to the traceback
286 import sys
287 ex = sys.exc_info()
288 print "-----------------------------------------------------------------"
289 if ex:
290 import traceback
291 traceback.print_exc(file=sys.stdout)
292 case.explain_failure()
293 print "-----------------------------------------------------------------"
295 if debugger:
296 tb = ex[2]
297 debugger(tb)
300 def runtests(test_list, verbose = 0, debugger = None):
301 """Run a series of tests.
303 Inputs:
304 test_list sequence of TestCase classes
305 verbose print more information as testing proceeds
306 debugger debugger object to be applied to errors
308 Returns:
309 unix return code: 0 for success, 1 for failures, 2 for test failure
311 import traceback
312 ret = 0
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
316 sys.stdout.flush()
318 obj = None
319 try:
320 try: # run test and show result
321 obj = test_class()
322 obj.setup()
323 obj.runtest()
324 print "OK"
325 except KeyboardInterrupt:
326 print "INTERRUPT"
327 _report_error(obj, debugger)
328 ret = 2
329 break
330 except NotRunError, msg:
331 print "NOTRUN, %s" % msg.value
332 except:
333 print "FAIL"
334 _report_error(obj, debugger)
335 ret = 1
336 finally:
337 while obj and obj._cleanups:
338 try:
339 apply(obj._cleanups.pop())
340 except KeyboardInterrupt:
341 print "interrupted during teardown"
342 _report_error(obj, debugger)
343 ret = 2
344 break
345 except:
346 print "error during teardown"
347 _report_error(obj, debugger)
348 ret = 1
349 # Display log file if we're verbose
350 if ret == 0 and verbose:
351 obj.explain_failure()
353 return ret
356 def _test_name(test_class):
357 """Return a human-readable name for a test class.
359 try:
360 return test_class.__name__
361 except:
362 return `test_class`
365 def print_help():
366 """Help for people running tests"""
367 import sys
368 print """%s: software test suite based on ComfyChair
370 usage:
371 To run all tests, just run this program. To run particular tests,
372 list them on the command line.
374 options:
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
379 """ % sys.argv[0]
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.
391 inputs:
392 tests Sequence of TestCase subclasses to be run by default.
393 extra_tests Sequence of TestCase subclasses that are available but
394 not run by default.
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.
406 from sys import argv
407 import getopt, sys
409 opt_verbose = 0
410 debugger = None
412 opts, args = getopt.getopt(argv[1:], 'pv',
413 ['help', 'list', 'verbose', 'post-mortem'])
414 for opt, opt_arg in opts:
415 if opt == '--help':
416 print_help()
417 return
418 elif opt == '--list':
419 print_list(tests + extra_tests)
420 return
421 elif opt == '--verbose' or opt == '-v':
422 opt_verbose = 1
423 elif opt == '--post-mortem' or opt == '-p':
424 import pdb
425 debugger = pdb.post_mortem
427 if args:
428 all_tests = tests + extra_tests
429 by_name = {}
430 for t in all_tests:
431 by_name[_test_name(t)] = t
432 which_tests = []
433 for name in args:
434 which_tests.append(by_name[name])
435 else:
436 which_tests = tests
438 sys.exit(runtests(which_tests, verbose=opt_verbose,
439 debugger=debugger))
442 if __name__ == '__main__':
443 print __doc__