summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source3/stf/comfychair.py214
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__