diff options
-rw-r--r-- | source3/stf/comfychair.py | 214 |
1 files changed, 139 insertions, 75 deletions
diff --git a/source3/stf/comfychair.py b/source3/stf/comfychair.py index 00b2262b26..b552baccd2 100644 --- a/source3/stf/comfychair.py +++ b/source3/stf/comfychair.py @@ -22,31 +22,11 @@ Copyright (C) 2002, 2003 by Martin Pool <mbp@samba.org> This is a test framework designed for testing programs written in -Python, or (through a fork/exec interface) any other language. It is -similar in design to the very nice 'svntest' system used by -Subversion, but has no Subversion-specific features. +Python, or (through a fork/exec interface) any other language. -It is somewhat similar to PyUnit, except: +For more information, see the file README.comfychair. - - it allows capture of detailed log messages from a test, to be - optionally displayed if the test fails. - - - it allows execution of a specified subset of tests - - - it avoids Java idioms that are not so useful in Python - -WRITING TESTS: - - Each test case is a callable object, typically a function. Its - documentation string describes the test, and the first line of the - docstring should be a brief name. - - The test should return 0 for pass, or non-zero for failure. - Alternatively they may raise an exception. - - Tests may import this "comfychair" module to get some useful - utilities, but that is not strictly required. - +To run a test suite based on ComfyChair, just run it as a program. """ # TODO: Put everything into a temporary directory? @@ -66,15 +46,15 @@ class TestCase: self.test_log = "" self.background_pids = [] - def setUp(self): + def setup(self): """Set up test fixture.""" pass - def tearDown(self): + def teardown(self): """Tear down test fixture.""" pass - def runTest(self): + def runtest(self): """Run the test.""" pass @@ -82,10 +62,39 @@ class TestCase: """Say the test failed.""" raise AssertionError(reason) + + ############################################################# + # Requisition methods + + def require(self, predicate, message): + """Check a predicate for running this test. + +If the predicate value is not true, the test is skipped with a message explaining +why.""" + if not predicate: + raise NotRunError, message + + def require_root(self): + """Skip this test unless run by root.""" + import os + self.require(os.getuid() == 0, + "must be root to run this test") + + ############################################################# + # Assertion methods + def assert_(self, expr, reason = ""): if not expr: raise AssertionError(reason) + def assert_equal(self, a, b): + if not a == b: + raise AssertionError("assertEquals failed: %s" % `(a, b)`) + + def assert_notequal(self, a, b): + if a == b: + raise AssertionError("assertNotEqual failed: %s" % `(a, b)`) + def assert_re_match(self, pattern, s): """Assert that a string matches a particular pattern @@ -97,9 +106,11 @@ class TestCase: AssertionError if not matched """ if not re.match(pattern, s): - raise AssertionError("string %s does not match regexp %s" % (`s`, `pattern`)) + raise AssertionError("string does not match regexp\n" + " string: %s\n" + " re: %s" % (`s`, `pattern`)) - def assert_regexp(self, pattern, s): + def assert_re_search(self, pattern, s): """Assert that a string *contains* a particular pattern Inputs: @@ -110,7 +121,9 @@ class TestCase: AssertionError if not matched """ if not re.search(pattern, s): - raise AssertionError("string %s does not contain regexp %s" % (`s`, `pattern`)) + raise AssertionError("string does not contain regexp\n" + " string: %s\n" + " re: %s" % (`s`, `pattern`)) def assert_no_file(self, filename): @@ -118,7 +131,10 @@ class TestCase: assert not os.path.exists(filename), ("file exists but should not: %s" % filename) - def runCmdNoWait(self, cmd): + ############################################################# + # Methods for running programs + + def runcmd_background(self, cmd): import os name = cmd[0] self.test_log = self.test_log + "Run in background:\n" + `cmd` + "\n" @@ -127,17 +143,17 @@ class TestCase: return pid - def runCmd(self, cmd, expectedResult = 0): + def runcmd(self, cmd, expectedResult = 0): """Run a command, fail if the command returns an unexpected exit code. Return the output produced.""" - rc, output = self.runCmdUnchecked(cmd) + rc, output = self.runcmd_unchecked(cmd) if rc != expectedResult: raise AssertionError("command returned %d; expected %s: \"%s\"" % (rc, expectedResult, cmd)) return output - def runCmdUnchecked(self, cmd, skip_on_noexec = 0): + def runcmd_unchecked(self, cmd, skip_on_noexec = 0): """Invoke a command; return (exitcode, stdout)""" import os, popen2 pobj = popen2.Popen4(cmd) @@ -157,7 +173,7 @@ Output: raise NotRunError, "could not execute %s" % cmd return rc, output - def explainFailure(self, exc_info = None): + def explain_failure(self, exc_info = None): import traceback # Move along, nothing to see here if not exc_info and self.test_log == "": @@ -168,19 +184,6 @@ Output: print self.test_log print "-----------------------------------------------------------------" - def require(self, predicate, message): - """Check a predicate for running this test. - -If the predicate value is not true, the test is skipped with a message explaining -why.""" - if not predicate: - raise NotRunError, message - - def require_root(self): - """Skip this test unless run by root.""" - import os - self.require(os.getuid() == 0, - "must be root to run this test") def log(self, msg): """Log a message to the test log. This message is displayed if @@ -188,23 +191,12 @@ why.""" the verbose option.""" self.test_log = self.test_log + msg + "\n" + class NotRunError(Exception): + """Raised if a test must be skipped because of missing resources""" def __init__(self, value = None): self.value = value -def test_name(test): - """Return a human-readable name for a test. - - Inputs: - test some kind of callable test object - - Returns: - name string: a short printable name - """ - try: - return test.__name__ - except: - return `test` def runtests(test_list, verbose = 0): """Run a series of tests. @@ -220,47 +212,119 @@ def runtests(test_list, verbose = 0): """ import traceback ret = 0 - for test in test_list: - print "%-60s" % test_name(test), + for test_class in test_list: + print "%-60s" % _test_name(test_class), # flush now so that long running tests are easier to follow sys.stdout.flush() try: try: # run test and show result - obj = test() - if hasattr(obj, "setUp"): - obj.setUp() - obj.runTest() + obj = test_class() + if hasattr(obj, "setup"): + obj.setup() + obj.runtest() print "OK" except KeyboardInterrupt: print "INTERRUPT" - obj.explainFailure(sys.exc_info()) + obj.explain_failure(sys.exc_info()) ret = 2 break except NotRunError, msg: print "NOTRUN, %s" % msg.value except: print "FAIL" - obj.explainFailure(sys.exc_info()) + obj.explain_failure(sys.exc_info()) ret = 1 finally: try: - if hasattr(obj, "tearDown"): - obj.tearDown() + if hasattr(obj, "teardown"): + obj.teardown() except KeyboardInterrupt: - print "interrupted during tearDown" - obj.explainFailure(sys.exc_info()) + print "interrupted during teardown" + obj.explain_failure(sys.exc_info()) ret = 2 break except: - print "error during tearDown" - obj.explainFailure(sys.exc_info()) + print "error during teardown" + obj.explain_failure(sys.exc_info()) ret = 1 # Display log file if we're verbose if ret == 0 and verbose: - obj.explainFailure() + obj.explain_failure() return ret + +def _test_name(test_class): + """Return a human-readable name for a test class. + """ + try: + return test_class.__name__ + except: + return `test_class` + + +def print_help(): + """Help for people running tests""" + import sys + print """%s: software test suite based on ComfyChair + +usage: + To run all tests, just run this program. To run particular tests, + list them on the command line. + +options: + --help show usage message + --list list available tests + --verbose show more information while running tests +""" % sys.argv[0] + + +def print_list(test_list): + """Show list of available tests""" + for test_class in test_list: + print " %s" % _test_name(test_class) + + +def main(tests): + """Main entry point for test suites based on ComfyChair. + +Test suites should contain this boilerplate: + + if __name__ == '__main__': + comfychair.main(tests) + +This function handles standard options such as --help and --list, and +by default runs all tests in the suggested order. + +Calls sys.exit() on completion. +""" + from sys import argv + import getopt, sys + + verbose = 0 + + opts, args = getopt.getopt(argv[1:], '', ['help', 'list', 'verbose']) + if ('--help', '') in opts: + print_help() + return + elif ('--list', '') in opts: + print_list(tests) + return + + if ('--verbose', '') in opts: + verbose = 1 + + if args: + by_name = {} + for t in tests: + by_name[_test_name(t)] = t + which_tests = [by_name[name] for name in args] + else: + which_tests = tests + + sys.exit(runtests(which_tests, verbose)) + + if __name__ == '__main__': print __doc__ |