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__  | 
