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 2 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, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 """comfychair: a Python-based instrument of software torture.
23 Copyright (C) 2002, 2003 by Martin Pool <mbp@samba.org>
24 Copyright (C) 2003 by Tim Potter <tpot@samba.org>
26 This is a test framework designed for testing programs written in
27 Python, or (through a fork/exec interface) any other language.
29 For more information, see the file README.comfychair.
31 To run a test suite based on ComfyChair, just run it as a program.
34 # TODO: Put everything into a temporary directory?
36 # TODO: Have a means for tests to customize the display of their
37 # failure messages. In particular, if a shell command failed, then
43 """A base class for tests. This class defines required functions which
44 can optionally be overridden by subclasses. It also provides some
45 utility functions for"""
49 self.background_pids = []
52 """Set up test fixture."""
56 """Tear down test fixture."""
63 def fail(self, reason = ""):
64 """Say the test failed."""
65 raise AssertionError(reason)
68 #############################################################
71 def require(self, predicate, message):
72 """Check a predicate for running this test.
74 If the predicate value is not true, the test is skipped with a message explaining
77 raise NotRunError, message
79 def require_root(self):
80 """Skip this test unless run by root."""
82 self.require(os.getuid() == 0,
83 "must be root to run this test")
85 #############################################################
88 def assert_(self, expr, reason = ""):
90 raise AssertionError(reason)
92 def assert_equal(self, a, b):
94 raise AssertionError("assertEquals failed: %s" % `(a, b)`)
96 def assert_notequal(self, a, b):
98 raise AssertionError("assertNotEqual failed: %s" % `(a, b)`)
100 def assert_re_match(self, pattern, s):
101 """Assert that a string matches a particular pattern
104 pattern string: regular expression
105 s string: to be matched
108 AssertionError if not matched
110 if not re.match(pattern, s):
111 raise AssertionError("string does not match regexp\n"
113 " re: %s" % (`s`, `pattern`))
115 def assert_re_search(self, pattern, s):
116 """Assert that a string *contains* a particular pattern
119 pattern string: regular expression
120 s string: to be searched
123 AssertionError if not matched
125 if not re.search(pattern, s):
126 raise AssertionError("string does not contain regexp\n"
128 " re: %s" % (`s`, `pattern`))
131 def assert_no_file(self, filename):
133 assert not os.path.exists(filename), ("file exists but should not: %s" % filename)
136 #############################################################
137 # Methods for running programs
139 def runcmd_background(self, cmd):
142 self.test_log = self.test_log + "Run in background:\n" + `cmd` + "\n"
143 pid = os.spawnvp(os.P_NOWAIT, name, cmd)
144 self.test_log = self.test_log + "pid: %d\n" % pid
148 def runcmd(self, cmd, expectedResult = 0):
149 """Run a command, fail if the command returns an unexpected exit
150 code. Return the output produced."""
151 rc, output = self.runcmd_unchecked(cmd)
152 if rc != expectedResult:
153 raise AssertionError("command returned %d; expected %s: \"%s\"" %
154 (rc, expectedResult, cmd))
158 def runcmd_unchecked(self, cmd, skip_on_noexec = 0):
159 """Invoke a command; return (exitcode, stdout)"""
161 pobj = popen2.Popen4(cmd)
162 output = pobj.fromchild.read()
163 waitstatus = pobj.wait()
164 assert not os.WIFSIGNALED(waitstatus), \
165 ("%s terminated with signal %d", cmd, os.WTERMSIG(waitstatus))
166 rc = os.WEXITSTATUS(waitstatus)
167 self.test_log = self.test_log + ("""Run command: %s
168 Wait status: %#x (exit code %d, signal %d)
170 %s""" % (cmd, waitstatus, os.WEXITSTATUS(waitstatus), os.WTERMSIG(waitstatus),
172 if skip_on_noexec and rc == 127:
173 # Either we could not execute the command or the command
174 # returned exit code 127. According to system(3) we can't
175 # tell the difference.
176 raise NotRunError, "could not execute %s" % `cmd`
179 def explain_failure(self, exc_info = None):
181 # Move along, nothing to see here
182 if not exc_info and self.test_log == "":
184 print "-----------------------------------------------------------------"
186 traceback.print_exc(file=sys.stdout)
188 print "-----------------------------------------------------------------"
192 """Log a message to the test log. This message is displayed if
193 the test fails, or when the runtests function is invoked with
194 the verbose option."""
195 self.test_log = self.test_log + msg + "\n"
198 class NotRunError(Exception):
199 """Raised if a test must be skipped because of missing resources"""
200 def __init__(self, value = None):
204 def runtests(test_list, verbose = 0):
205 """Run a series of tests.
207 Eventually, this routine will also examine sys.argv[] to handle
211 test_list sequence of callable test objects
214 unix return code: 0 for success, 1 for failures, 2 for test failure
218 for test_class in test_list:
219 print "%-30s" % _test_name(test_class),
220 # flush now so that long running tests are easier to follow
224 try: # run test and show result
226 if hasattr(obj, "setup"):
230 except KeyboardInterrupt:
232 obj.explain_failure(sys.exc_info())
235 except NotRunError, msg:
236 print "NOTRUN, %s" % msg.value
239 obj.explain_failure(sys.exc_info())
243 if hasattr(obj, "teardown"):
245 except KeyboardInterrupt:
246 print "interrupted during teardown"
247 obj.explain_failure(sys.exc_info())
251 print "error during teardown"
252 obj.explain_failure(sys.exc_info())
254 # Display log file if we're verbose
255 if ret == 0 and verbose:
256 obj.explain_failure()
261 def _test_name(test_class):
262 """Return a human-readable name for a test class.
265 return test_class.__name__
271 """Help for people running tests"""
273 print """%s: software test suite based on ComfyChair
276 To run all tests, just run this program. To run particular tests,
277 list them on the command line.
280 --help show usage message
281 --list list available tests
282 --verbose show more information while running tests
286 def print_list(test_list):
287 """Show list of available tests"""
288 for test_class in test_list:
289 print " %s" % _test_name(test_class)
293 """Main entry point for test suites based on ComfyChair.
295 Test suites should contain this boilerplate:
297 if __name__ == '__main__':
298 comfychair.main(tests)
300 This function handles standard options such as --help and --list, and
301 by default runs all tests in the suggested order.
303 Calls sys.exit() on completion.
310 opts, args = getopt.getopt(argv[1:], '', ['help', 'list', 'verbose'])
311 if ('--help', '') in opts:
314 elif ('--list', '') in opts:
318 if ('--verbose', '') in opts:
324 by_name[_test_name(t)] = t
325 which_tests = [by_name[name] for name in args]
329 sys.exit(runtests(which_tests, verbose))
332 if __name__ == '__main__':