changeset 442:a8787de4fbc3

Improve the still experimental support for using `coverage.py`.
author cmlenz
date Tue, 21 Aug 2007 16:42:53 +0000
parents 641a73f078f5
children 1c922d944bd2
files bitten/build/pythontools.py bitten/slave.py bitten/util/testrunner.py setup.py
diffstat 4 files changed, 48 insertions(+), 101 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/build/pythontools.py
+++ b/bitten/build/pythontools.py
@@ -175,11 +175,11 @@
     :param exclude: patterns of files or directories to exclude from the report
     """
     assert summary, 'Missing required attribute "summary"'
-    assert coverdir, 'Missing required attribute "coverdir"'
 
-    summary_line_re = re.compile(r'^\s*(?P<lines>\d+)\s+(?P<cov>\d+)%\s+'
-                                 r'(?P<module>.*?)\s+\((?P<filename>.*?)\)')
-    coverage_line_re = re.compile(r'\s*(?:(?P<hits>\d+): )?(?P<line>.*)')
+    summary_line_re = re.compile(r'^(?P<module>.*?)\s+(?P<stmts>\d+)\s+'
+                                 r'(?P<exec>\d+)\s+(?P<cov>\d+)%\s+'
+                                 r'(?:(?P<missing>(?:\d+(?:-\d+)?(?:, )?)*)\s+)?'
+                                 r'(?P<file>.+)$')
 
     fileset = FileSet(ctxt.basedir, include, exclude)
     missing_files = []
@@ -189,46 +189,6 @@
         missing_files.append(filename)
     covered_modules = set()
 
-    def handle_file(elem, sourcefile, coverfile=None):
-        code_lines = set()
-        for lineno, linetype, line in loc.count(sourcefile):
-            if linetype == loc.CODE:
-                code_lines.add(lineno)
-        num_covered = 0
-        lines = []
-
-        if coverfile:
-            prev_hits = '0'
-            for idx, coverline in enumerate(coverfile):
-                match = coverage_line_re.search(coverline)
-                if match:
-                    hits = match.group(1)
-                    if hits: # Line covered
-                        if hits != '0':
-                            num_covered += 1
-                        lines.append(hits)
-                        prev_hits = hits
-                    elif coverline.startswith('>'): # Line not covered
-                        lines.append('0')
-                        prev_hits = '0'
-                    elif idx not in code_lines: # Not a code line
-                        lines.append('-')
-                        prev_hits = '0'
-                    else: # A code line not flagged by trace.py
-                        if prev_hits != '0':
-                            num_covered += 1
-                        lines.append(prev_hits)
-
-            elem.append(xmlio.Element('line_hits')[' '.join(lines)])
-
-        num_lines = len(code_lines)
-        if num_lines:
-            percentage = int(round(num_covered * 100 / num_lines))
-        else:
-            percentage = 0
-        elem.attr['percentage'] = percentage
-        elem.attr['lines'] = num_lines
-
     try:
         summary_file = open(ctxt.resolve(summary), 'r')
         try:
@@ -236,8 +196,8 @@
             for summary_line in summary_file:
                 match = summary_line_re.search(summary_line)
                 if match:
-                    modname = match.group(3)
-                    filename = match.group(4)
+                    modname = match.group(1)
+                    filename = match.group(6)
                     if not os.path.isabs(filename):
                         filename = os.path.normpath(os.path.join(ctxt.basedir,
                                                                  filename))
@@ -249,26 +209,13 @@
                     if not filename in fileset:
                         continue
 
+                    percentage = int(match.group(4).rstrip('%'))
+
                     missing_files.remove(filename)
                     covered_modules.add(modname)
                     module = xmlio.Element('coverage', name=modname,
-                                           file=filename.replace(os.sep, '/'))
-                    sourcefile = file(ctxt.resolve(filename))
-                    try:
-                        coverpath = ctxt.resolve(coverdir, modname + '.cover')
-                        if os.path.isfile(coverpath):
-                            coverfile = file(coverpath, 'r')
-                        else:
-                            log.warning('No coverage file for module %s at %s',
-                                        modname, coverpath)
-                            coverfile = None
-                        try:
-                            handle_file(module, sourcefile, coverfile)
-                        finally:
-                            if coverfile:
-                                coverfile.close()
-                    finally:
-                        sourcefile.close()
+                                           file=filename.replace(os.sep, '/'),
+                                           percentage=percentage)
                     coverage.append(module)
 
             for filename in missing_files:
@@ -279,12 +226,6 @@
                 module = xmlio.Element('coverage', name=modname,
                                        file=filename.replace(os.sep, '/'),
                                        percentage=0)
-                filepath = ctxt.resolve(filename)
-                fileobj = file(filepath, 'r')
-                try:
-                    handle_file(module, fileobj)
-                finally:
-                    fileobj.close()
                 coverage.append(module)
 
             ctxt.report('coverage', coverage)
--- a/bitten/slave.py
+++ b/bitten/slave.py
@@ -58,7 +58,7 @@
 
     def __init__(self, url, name=None, config=None, dry_run=False,
                  work_dir=None, keep_files=False, single_build=False,
-                 username=None, password=None):
+                 username=None, password=None, dump_reports=False):
         """Create the build slave instance.
         
         :param url: the URL of the build master, or the path to a build recipe
@@ -75,6 +75,9 @@
         :param username: the username to use when authentication against the
                          build master is requested
         :param password: the password to use when authentication is needed
+        :param dump_reports: whether report data should be written to the
+                             standard output, in addition to being transmitted
+                             to the build master
         """
         self.url = url
         self.local = not url.startswith('http') and not url.startswith('https')
@@ -90,6 +93,7 @@
         self.work_dir = work_dir
         self.keep_files = keep_files
         self.single_build = single_build
+        self.dump_reports = dump_reports
 
         if not self.local:
             self.opener = urllib2.build_opener(SaneHTTPErrorProcessor)
@@ -211,6 +215,8 @@
                     step.execute(recipe.ctxt):
                 if type == Recipe.ERROR:
                     failed = True
+                if type == Recipe.REPORT and self.dump_reports:
+                    print output
                 xml.append(xmlio.Element(type, category=category,
                                          generator=generator)[
                     output
@@ -272,28 +278,36 @@
                       help='name of this slave (defaults to host name)')
     parser.add_option('-f', '--config', action='store', dest='config',
                       metavar='FILE', help='path to configuration file')
-    parser.add_option('-d', '--work-dir', action='store', dest='work_dir',
-                      metavar='DIR', help='working directory for builds')
-    parser.add_option('-k', '--keep-files', action='store_true',
-                      dest='keep_files', 
-                      help='don\'t delete files after builds')
-    parser.add_option('-l', '--log', dest='logfile', metavar='FILENAME',
-                      help='write log messages to FILENAME')
-    parser.add_option('-n', '--dry-run', action='store_true', dest='dry_run',
-                      help='don\'t report results back to master')
-    parser.add_option('-v', '--verbose', action='store_const', dest='loglevel',
-                      const=logging.DEBUG, help='print as much as possible')
-    parser.add_option('-q', '--quiet', action='store_const', dest='loglevel',
-                      const=logging.WARN, help='print as little as possible')
-    parser.add_option('-s', '--single', action='store_true',
-                      dest='single_build',
-                      help='exit after completing a single build')
     parser.add_option('-u', '--user', dest='username',
                       help='the username to use for authentication')
     parser.add_option('-p', '--password', dest='password',
                       help='the password to use when authenticating')
+
+    group = parser.add_option_group('building')
+    group.add_option('-d', '--work-dir', action='store', dest='work_dir',
+                     metavar='DIR', help='working directory for builds')
+    group.add_option('-k', '--keep-files', action='store_true',
+                     dest='keep_files', 
+                     help='don\'t delete files after builds')
+    group.add_option('-s', '--single', action='store_true',
+                     dest='single_build',
+                     help='exit after completing a single build')
+    group.add_option('-n', '--dry-run', action='store_true', dest='dry_run',
+                     help='don\'t report results back to master')
+
+    group = parser.add_option_group('logging')
+    group.add_option('-l', '--log', dest='logfile', metavar='FILENAME',
+                     help='write log messages to FILENAME')
+    group.add_option('-v', '--verbose', action='store_const', dest='loglevel',
+                     const=logging.DEBUG, help='print as much as possible')
+    group.add_option('-q', '--quiet', action='store_const', dest='loglevel',
+                     const=logging.WARN, help='print as little as possible')
+    group.add_option('--dump-reports', action='store_true', dest='dump_reports',
+                     help='whether report data should be printed')
+
     parser.set_defaults(dry_run=False, keep_files=False,
-                        loglevel=logging.INFO, single_build=False)
+                        loglevel=logging.INFO, single_build=False,
+                        dump_reports=False)
     options, args = parser.parse_args()
 
     if len(args) < 1:
@@ -319,7 +333,8 @@
                        dry_run=options.dry_run, work_dir=options.work_dir,
                        keep_files=options.keep_files,
                        single_build=options.single_build,
-                       username=options.username, password=options.password)
+                       username=options.username, password=options.password,
+                       dump_reports=options.dump_reports)
     try:
         try:
             slave.run()
--- a/bitten/util/testrunner.py
+++ b/bitten/util/testrunner.py
@@ -31,8 +31,6 @@
     def __init__(self, stream, descriptions, verbosity):
         _TextTestResult.__init__(self, stream, descriptions, verbosity)
         self.tests = []
-        self.orig_stdout = self.orig_stderr = None
-        self.buf_stdout = self.buf_stderr = None
 
     def startTest(self, test):
         _TextTestResult.startTest(self, test)
@@ -41,17 +39,8 @@
             filename = filename[:-1]
         self.tests.append([test, filename, time.time(), None, None])
 
-        # Record output by the test to stdout and stderr
-        self.old_stdout, self.buf_stdout = sys.stdout, StringIO()
-        self.old_stderr, self.buf_stderr = sys.stderr, StringIO()
-        sys.stdout, sys.stderr = self.buf_stdout, self.buf_stderr
-
     def stopTest(self, test):
         self.tests[-1][2] = time.time() - self.tests[-1][2]
-        self.tests[-1][3] = self.buf_stdout.getvalue()
-        self.tests[-1][4] = self.buf_stderr.getvalue()
-        sys.stdout, sys.stderr = self.orig_stdout, self.orig_stderr
-
         _TextTestResult.stopTest(self, test)
 
 
@@ -159,7 +148,6 @@
         import coverage
         coverage.use_cache(False)
         coverage.start()
-        log.info('running tests under coverage.py')
         try:
             self._run_tests()
         finally:
@@ -185,7 +173,9 @@
                         fileobj.write(line)
                         continue
                     filename = os.path.normpath(sys.modules[name].__file__)
-                    fileobj.write(line + ' ' + filename + '\n')
+                    if filename.endswith('.pyc') or filename.endswith('.pyo'):
+                        filename = filename[:-1]
+                    fileobj.write(line.rstrip() + ' ' + filename + '\n')
             finally:
                 fileobj.close()
 
--- a/setup.py
+++ b/setup.py
@@ -69,6 +69,7 @@
             NS + 'php#phing = bitten.build.phptools:phing',
             NS + 'php#phpunit = bitten.build.phptools:phpunit',
             NS + 'php#coverage = bitten.build.phptools:coverage',
+            NS + 'python#coverage = bitten.build.pythontools:coverage',
             NS + 'python#distutils = bitten.build.pythontools:distutils',
             NS + 'python#exec = bitten.build.pythontools:exec_',
             NS + 'python#pylint = bitten.build.pythontools:pylint',
Copyright (C) 2012-2017 Edgewall Software