New import of ComfyChair, many changes
[samba.git] / source3 / stf / comfychair.py
index 00b2262b264929a8b4d4d0d1dff496332f3f53a8..b552baccd20241e3ccd2dc5fc54851d711bcd335 100644 (file)
 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__