[Zope3-checkins] CVS: Zope3 - test.py:1.88

Jeremy Hylton jeremy at zope.com
Tue Mar 16 00:11:03 EST 2004


Update of /cvs-repository/Zope3
In directory cvs.zope.org:/tmp/cvs-serv26659

Modified Files:
	test.py 
Log Message:
Report test error when potential test suite module can't be imported.
Add docstring that explains rules for finding tests.
Reflow some paragraphs.


=== Zope3/test.py 1.87 => 1.88 ===
--- Zope3/test.py:1.87	Mon Mar 15 15:41:38 2004
+++ Zope3/test.py	Tue Mar 16 00:11:02 2004
@@ -15,20 +15,33 @@
 """
 test.py [-aBbcdDfgGhLmPprtTuv] [modfilter [testfilter]]
 
-Test harness.
+Find and run tests written using the unittest module.
+
+The test runner searches for Python modules that contain test suites.
+It collects those suites, and runs the tests.  There are many options
+for controlling how the tests are run.  There are options for using
+the debugger, reporting code coverage, and checking for refcount problems.
+
+The test runner uses the following rules for finding tests to run.  It
+searches for packages and modules that contain "tests" as a component
+of the name, e.g. "frob.tests.nitz" matches this rule because tests is
+a sub-package of frob.  Within each "tests" package, it looks for
+modules that begin with the name "test."  For each test module, it
+imports the module and calls the test_suite() method, which must
+return a unittest TestSuite object.
 
 -a level
 --all
-    Run the tests at the given level.  Any test at a level at or below this is
-    run, any test at a level above this is not run.  Level 0 runs all tests.
-    The default is to run tests at level 1.  --all is a shortcut for -a 0.
+    Run the tests at the given level.  Any test at a level at or below
+    this is run, any test at a level above this is not run.  Level 0
+    runs all tests.  The default is to run tests at level 1.  --all is
+    a shortcut for -a 0.
 
 -b
 --build
     Run "python setup.py build" before running tests, where "python"
     is the version of python used to run test.py.  Highly recommended.
-    Tests will be run from the build directory.  (Note: In Python < 2.3
-    the -q flag is added to the setup.py command line.)
+    Tests will be run from the build directory.
 
 -B
     Run "python setup.py build_ext -i" before running tests.  Tests will be
@@ -62,10 +75,10 @@
     Run both unit and functional tests.
 
 -g threshold
-    Set the garbage collector generation0 threshold.  This can be used to
-    stress memory and gc correctness.  Some crashes are only reproducible when
-    the threshold is set to 1 (agressive garbage collection).  Do "-g 0" to
-    disable garbage collection altogether.
+    Set the garbage collector generation0 threshold.  This can be used
+    to stress memory and gc correctness.  Some crashes are only
+    reproducible when the threshold is set to 1 (agressive garbage
+    collection).  Do "-g 0" to disable garbage collection altogether.
 
 -G gc_option
     Set the garbage collection debugging flags.  The argument must be one
@@ -100,32 +113,33 @@
     This requires that Python was built --with-pydebug.
 
 -T
-    Use the trace module from Python for code coverage.  XXX This only works
-    if trace.py is explicitly added to PYTHONPATH.  The current utility writes
-    coverage files to a directory named `coverage' that is parallel to
-    `build'.  It also prints a summary to stdout.
+    Use the trace module from Python for code coverage.  The current
+    utility writes coverage files to a directory named `coverage' that
+    is parallel to `build'.  It also prints a summary to stdout.
 
 -v
-    Verbose output.  With one -v, unittest prints a dot (".") for each test
-    run.  With -vv, unittest prints the name of each test (for some definition
-    of "name" ...).  With no -v, unittest is silent until the end of the run,
-    except when errors occur.
-
-    When -p is also specified, the meaning of -v is slightly different.  With
-    -p and no -v only the percent indicator is displayed.  With -p and -v
-    the test name of the current test is shown to the right of the percent
-    indicator.  With -p and -vv the test name is not truncated to fit into
-    80 columns and it is not cleared after the test finishes.
+    Verbose output.  With one -v, unittest prints a dot (".") for each
+    test run.  With -vv, unittest prints the name of each test (for
+    some definition of "name" ...).  With no -v, unittest is silent
+    until the end of the run, except when errors occur.
+
+    When -p is also specified, the meaning of -v is slightly
+    different.  With -p and no -v only the percent indicator is
+    displayed.  With -p and -v the test name of the current test is
+    shown to the right of the percent indicator.  With -p and -vv the
+    test name is not truncated to fit into 80 columns and it is not
+    cleared after the test finishes.
 
 -u
 -m
-    Use the PyUnit GUI instead of output to the command line.  The GUI imports
-    tests on its own, taking care to reload all dependencies on each run.  The
-    debug (-d), verbose (-v), progress (-p), and Loop (-L) options will be
-    ignored.  The testfilter filter is also not applied.
+    Use the PyUnit GUI instead of output to the command line.  The GUI
+    imports tests on its own, taking care to reload all dependencies
+    on each run.  The debug (-d), verbose (-v), progress (-p), and
+    Loop (-L) options will be ignored.  The testfilter filter is also
+    not applied.
 
-    -m starts the gui minimized.  Double-clicking the progress bar will start
-    the import and run all tests.
+    -m starts the gui minimized.  Double-clicking the progress bar
+    will start the import and run all tests.
 
 
 modfilter
@@ -199,7 +213,7 @@
         self._debug = debug
         self._progress = progress
         self._progressWithNames = False
-        self._count = count
+        self.count = count
         self._testtimes = {}
         if progress and verbosity == 1:
             self.dots = False
@@ -261,9 +275,9 @@
     def startTest(self, test):
         if self._progress:
             self.stream.write("\r%4d" % (self.testsRun + 1))
-            if self._count:
-                self.stream.write("/%d (%5.1f%%)" % (self._count,
-                                  (self.testsRun + 1) * 100.0 / self._count))
+            if self.count:
+                self.stream.write("/%d (%5.1f%%)" % (self.count,
+                                  (self.testsRun + 1) * 100.0 / self.count))
             if self.showAll:
                 self.stream.write(": ")
             elif self._progressWithNames:
@@ -341,16 +355,22 @@
         self._debug = debug
         self._progress = progress
         self._profile = profile
+        # Create the test result here, so that we can add errors if
+        # the test suite search process has problems.  The count
+        # attribute must be set in run(), because we won't know the
+        # count until all test suites have been found.
+        self.result = ImmediateTestResult(
+            self.stream, self.descriptions, self.verbosity, debug=self._debug,
+            progress=self._progress)
 
     def _makeResult(self):
-        return ImmediateTestResult(self.stream, self.descriptions,
-                                   self.verbosity, debug=self._debug,
-                                   count=self._count, progress=self._progress)
+        # Needed base class run method.
+        return self.result
 
     def run(self, test):
+        self.result.count = test.countTestCases()
         if self._debug:
             club_debug(test)
-        self._count = test.countTestCases()
         if self._profile:
             prof = hotshot.Profile("tests_profile.prof")
             args = (self, test)
@@ -533,23 +553,41 @@
         mod = getattr(mod, part)
     return mod
 
-def get_suite(file):
+class PseudoTestCase:
+    """Minimal test case objects to create error reports.
+
+    If test.py finds something that looks like it should be a test but
+    can't load it or find its test suite, it will report an error
+    using a PseudoTestCase.
+    """
+
+    def __init__(self, name, descr=None):
+        self.name = name
+        self.descr = descr
+
+    def shortDescription(self):
+        return self.descr
+
+    def __str__(self):
+        return "Invalid Test (%s)" % self.name
+
+def get_suite(file, result):
     modname = finder.module_from_path(file)
     try:
         mod = package_import(modname)
     except ImportError, err:
-        # print traceback
-        print "Error importing %s\n%s" % (modname, err)
-        traceback.print_exc()
-        if debug:
-            raise
+        result.addError(PseudoTestCase(modname), sys.exc_info())
         return None
     try:
         suite_func = mod.test_suite
     except AttributeError:
-        print "No test_suite() in %s" % file
+        result.addError(PseudoTestCase(modname), sys.exc_info())
+        return None
+    try:
+        return suite_func()
+    except:
+        result.addError(PseudoTestCase(modname), sys.exc_info())
         return None
-    return suite_func()
 
 def filter_testcases(s, rx):
     new = unittest.TestSuite()
@@ -632,7 +670,7 @@
                                  descriptions=False)
     suite = unittest.TestSuite()
     for file in files:
-        s = get_suite(file)
+        s = get_suite(file, runner.result)
         # See if the levels match
         dolevel = (level == 0) or level >= getattr(s, "level", 0)
         if s is not None and dolevel:




More information about the Zope3-Checkins mailing list