changeset 2:3ba3fb6f0054

Generate coverage data in the distutils {{{unittest}}} command.
author cmlenz
date Sun, 22 May 2005 20:11:22 +0000
parents 32e9f0e94c69
children 9ac0ee86ec7c
files bitten/distutils/testrunner.py bitten/recipe.py bitten/tests/__init__.py bitten/tests/recipe.py scripts/build.py setup.cfg setup.py
diffstat 7 files changed, 91 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/distutils/testrunner.py
+++ b/bitten/distutils/testrunner.py
@@ -1,3 +1,4 @@
+import re
 import sys
 import time
 from distutils.core import Command
@@ -6,7 +7,7 @@
 from elementtree.ElementTree import Element, ElementTree, SubElement
 
 
-class FullTestResult(_TextTestResult):
+class XMLTestResult(_TextTestResult):
 
     def __init__(self, stream, descriptions, verbosity):
         _TextTestResult.__init__(self, stream, descriptions, verbosity)
@@ -14,9 +15,10 @@
 
     def startTest(self, test):
         _TextTestResult.startTest(self, test)
-        self.tests.append([test,
-                           sys.modules[test.__module__].__file__,
-                           time.time()])
+        filename = sys.modules[test.__module__].__file__
+        if filename.endswith('.pyc') or filename.endswith('.pyo'):
+            filename = filename[:-1]
+        self.tests.append([test, filename, time.time()])
 
     def stopTest(self, test):
         _TextTestResult.stopTest(self, test)
@@ -30,7 +32,7 @@
         self.xml_stream = xml_stream
 
     def _makeResult(self):
-        return FullTestResult(self.stream, self.descriptions, self.verbosity)
+        return XMLTestResult(self.stream, self.descriptions, self.verbosity)
 
     def run(self, test):
         result = TextTestRunner.run(self, test)
@@ -67,27 +69,51 @@
         return result
 
 
-class test(Command):
-    description = "Runs the unit tests"
+class unittest(Command):
+    description = "Runs the unit tests, and optionally records code coverage"
     user_options = [('test-suite=', 's',
                      'Name of the unittest suite to run'),
                     ('xml-output=', None,
-                     'Path of the XML file where test results are written to')]
+                     'Path of the XML file where test results are written to'),
+                    ('coverage-dir=', None,
+                     'Directory where coverage files are to be stored'),
+                     ('coverage-results=', None,
+                     'Name of the file where the coverage summary should be stored')]
 
     def initialize_options(self):
         self.test_suite = None
-        self.xml_output = None
-        self.test_descriptions = None
+        self.xml_results = None
+        self.coverage_results = None
+        self.coverage_dir = None
 
     def finalize_options(self):
         assert self.test_suite, 'Missing required attribute "test-suite"'
-        if self.xml_output is not None:
-            self.xml_output = open(self.xml_output, 'w')
+        if self.xml_results is not None:
+            self.xml_results = open(self.xml_results, 'w')
 
     def run(self):
+        if self.coverage_dir:
+            from trace import Trace
+            trace = Trace(ignoredirs=[sys.prefix, sys.exec_prefix],
+                          trace=False, count=True)
+            trace.runfunc(self._run_tests)
+            # make a report, telling it where you want output
+            results = trace.results()
+            real_stdout = sys.stdout
+            sys.stdout = open(self.coverage_results, 'w')
+            try:
+                results.write_results(show_missing=True, summary=True,
+                                      coverdir=self.coverage_dir)
+            finally:
+                sys.stdout.close()
+                sys.stdout = real_stdout
+        else:
+            self._run_tests()
+
+    def _run_tests(self):
         import unittest
         suite = __import__(self.test_suite)
         for comp in self.test_suite.split('.')[1:]:
             suite = getattr(suite, comp)
-        runner = XMLTestRunner(stream=sys.stderr, xml_stream=self.xml_output)
+        runner = XMLTestRunner(stream=sys.stderr, xml_stream=self.xml_results)
         runner.run(suite.suite())
--- a/bitten/recipe.py
+++ b/bitten/recipe.py
@@ -29,15 +29,24 @@
         """
 
 
+class Recipe(object):
+
+    def __init__(self, filename='recipe.xml', basedir=os.getcwd()):
+        self.filename = filename
+        self.basedir = basedir
+        self.path = os.path.join(basedir, filename)
+        self.tree = ElementTree.parse(self.path).getroot()
+
+    description = property(fget=lambda self: self.tree.attrib['description'])
+
+
 class RecipeExecutor(Component):
 
     commands = ExtensionPoint(ICommandExecutor)
     reporters = ExtensionPoint(IReportPreparator)
 
-    def execute(self, filename='recipe.xml', basedir=os.getcwd()):
-        path = os.path.join(basedir, filename)
-        recipe = ElementTree.parse(path).getroot()
-        for step in recipe:
+    def execute(self, recipe):
+        for step in recipe.tree:
             print '---> %s' % step.attrib['title']
             for element in step:
                 if element.tag == 'reports':
--- a/bitten/tests/__init__.py
+++ b/bitten/tests/__init__.py
@@ -1,5 +1,8 @@
 import unittest
 
+from bitten.tests import recipe
+
 def suite():
     suite = unittest.TestSuite()
+    suite.addTest(recipe.suite())
     return suite
new file mode 100644
--- /dev/null
+++ b/bitten/tests/recipe.py
@@ -0,0 +1,27 @@
+import os
+import os.path
+import tempfile
+import unittest
+
+from bitten.recipe import Recipe
+
+
+class RecipeTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.temp_dir = tempfile.gettempdir()
+        self.recipe_xml = open(os.path.join(self.temp_dir, 'recipe.xml'), 'w')
+
+    def tearDown(self):
+        os.unlink(os.path.join(self.temp_dir, 'recipe.xml'))
+
+    def testDescription(self):
+        self.recipe_xml.write('<?xml version="1.0"?>'
+                              '<recipe description="test">'
+                              '</recipe>')
+        self.recipe_xml.close()
+        recipe = Recipe(basedir=self.temp_dir)
+        self.assertEqual('test', recipe.description)
+
+def suite():
+    return unittest.makeSuite(RecipeTestCase, 'test')
--- a/scripts/build.py
+++ b/scripts/build.py
@@ -2,11 +2,11 @@
 
 from trac.core import ComponentManager
 
-from bitten.recipe import RecipeExecutor
+from bitten.recipe import Recipe, RecipeExecutor
 from bitten.python import cmd_distutils, rep_pylint
 from bitten.general import cmd_make
 
 if __name__ == '__main__':
     mgr = ComponentManager()
-    executor = RecipeExecutor(mgr)
-    executor.execute()
+    recipe = Recipe()
+    RecipeExecutor(mgr).execute(recipe)
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,5 @@
-[test]
+[unittest]
 test-suite = bitten.tests
+xml-results = build/test-results.xml
+coverage-results = build/test-coverage.txt
+coverage-dir = build/coverage
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
 from distutils.core import setup, Command
-from bitten.distutils.testrunner import test
+from bitten.distutils.testrunner import unittest
 
 setup(name='bitten', version='1.0',
       packages=['bitten', 'bitten.general', 'bitten.python'],
-      cmdclass={'test': test})
+      cmdclass={'unittest': unittest})
Copyright (C) 2012-2017 Edgewall Software