Commits

Ask Solem  committed 318e511

coverage: Now uses coverage 3.x HTML reports instead of the homemade solution.
Also added --cover-branch/NOSE_COVER_BRANCH argument for enabling branch
coverage, and --cover-exclude/NOSE_COVER_EXCLUDE for a list of modules to
exclude from the coverage report (with wildcard support), e.g.
--cover-exclude=os.path,django.core.*,*.core.dispatch

  • Participants
  • Parent commits 5a37b92

Comments (0)

Files changed (1)

File nose/plugins/cover.py

 
 log =  logging.getLogger(__name__)
 
-COVERAGE_TEMPLATE = '''<html>
-<head>
-%(title)s
-</head>
-<body>
-%(header)s
-<style>
-.coverage pre {float: left; margin: 0px 1em; border: none;
-               padding: 0px; }
-.num pre { margin: 0px }
-.nocov, .nocov pre {background-color: #faa}
-.cov, .cov pre {background-color: #cfc}
-div.coverage div { clear: both; height: 1.1em}
-</style>
-<div class="stats">
-%(stats)s
-</div>
-<div class="coverage">
-%(body)s
-</div>
-</body>
-</html>
-'''
-
 COVERAGE_STATS_TEMPLATE = '''Covered: %(covered)s lines<br/>
 Missed: %(missed)s lines<br/>
 Skipped %(skipped)s lines<br/>
 Percent: %(percent)s %%<br/>
 '''
 
-
 class Coverage(Plugin):
     """
     Activate a coverage report using Ned Batchelder's coverage module.
     coverPackages = None
     score = 200
     status = {}
+    _cov = None
+
+    def _get_cov(self):
+        if self._cov is None:
+            import coverage
+            self._cov = coverage.coverage(branch=self.coverBranch, cover_pylib=False)
+        return self._cov
+    cov = property(_get_cov)
 
     def options(self, parser, env):
         """
         Add options to command line.
         """
+
+        def list_callback(option, opt, value, parser):
+            setattr(parser.values, option.dest, value.split(","))
+
         Plugin.options(self, parser, env)
         parser.add_option("--cover-package", action="append",
                           default=env.get('NOSE_COVER_PACKAGE'),
                           default=env.get('NOSE_COVER_TESTS'),
                           help="Include test modules in coverage report "
                           "[NOSE_COVER_TESTS]")
+        parser.add_option("--cover-branch", action="store_true",
+                          dest="cover_branch",
+                          default=env.get('NOSE_COVER_BRANCH'),
+                          help="Include branch coverage. "
+                          "[NOSE_COVER_BRANCH]")
+        parser.add_option("--cover-exclude", action="callback",
+                          dest="cover_exclude",
+                          default=env.get('NOSE_COVER_EXCLUDE'),
+                          type="string",
+                          callback=list_callback,
+                          help="List of modules to exclude from coverage. "
+                          "Supports wildcard matching at both start and "
+                          "end. Example: *.core.dispatch.* "
+                          "[NOSE_COVER_EXCLUDE]")
         parser.add_option("--cover-inclusive", action="store_true",
                           dest="cover_inclusive",
                           default=env.get('NOSE_COVER_INCLUSIVE'),
         except KeyError:
             pass
         Plugin.configure(self, options, config)
-        if config.worker:
-            return
         if self.enabled:
             try:
                 import coverage
         self.conf = config
         self.coverErase = options.cover_erase
         self.coverTests = options.cover_tests
+        self.coverBranch = options.cover_branch
+        self.coverExclude = options.cover_exclude or []
         self.coverPackages = []
         if options.cover_packages:
             for pkgs in [tolist(x) for x in options.cover_packages]:
         Begin recording coverage information.
         """
         log.debug("Coverage begin")
-        import coverage
         self.skipModules = sys.modules.keys()[:]
         if self.coverErase:
             log.debug("Clearing previously collected coverage statistics")
-            coverage.erase()
-        coverage.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
-        coverage.start()
+            self.cov.erase()
+        self.cov.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
+        self.cov.start()
 
     def report(self, stream):
         """
         Output code coverage report.
         """
         log.debug("Coverage report")
-        import coverage
-        coverage.stop()
+        self.cov.stop()
         modules = [ module
                     for name, module in sys.modules.items()
-                    if self.wantModuleCoverage(name, module) ]
+                    if self.wantModuleCoverage(name, module)
+                    and not self.isExcludedModule(module)]
         log.debug("Coverage report will cover modules: %s", modules)
-        coverage.report(modules, file=stream)
+        self.cov.report(modules, file=stream)
         if self.coverHtmlDir:
             if not os.path.exists(self.coverHtmlDir):
                 os.makedirs(self.coverHtmlDir)
             log.debug("Generating HTML coverage report")
-            files = {}
-            for m in modules:
-                if hasattr(m, '__name__') and hasattr(m, '__file__'):
-                    files[m.__name__] = m.__file__
-            coverage.annotate(files.values())
-            global_stats =  {'covered': 0, 'missed': 0, 'skipped': 0}
-            file_list = []
-            for m, f in files.iteritems():
-                if f.endswith('pyc'):
-                    f = f[:-1]
-                coverfile = f+',cover'
-                outfile, stats = self.htmlAnnotate(m, f, coverfile,
-                                                   self.coverHtmlDir)
-                for field in ('covered', 'missed', 'skipped'):
-                    global_stats[field] += stats[field]
-                file_list.append((stats['percent'], m, outfile, stats))
-                os.unlink(coverfile)
-            file_list.sort()
-            global_stats['percent'] = self.computePercent(
-                global_stats['covered'], global_stats['missed'])
-            # Now write out an index file for the coverage HTML
-            index = open(os.path.join(self.coverHtmlDir, 'index.html'), 'w')
-            index.write('<html><head><title>Coverage Index</title></head>'
-                        '<body><p>')
-            index.write(COVERAGE_STATS_TEMPLATE % global_stats)
-            index.write('<table><tr><td>File</td><td>Covered</td><td>Missed'
-                        '</td><td>Skipped</td><td>Percent</td></tr>')
-            for junk, name, outfile, stats in file_list:
-                stats['a'] = '<a href="%s">%s</a>' % (outfile, name)
-                index.write('<tr><td>%(a)s</td><td>%(covered)s</td><td>'
-                            '%(missed)s</td><td>%(skipped)s</td><td>'
-                            '%(percent)s %%</td></tr>' % stats)
-            index.write('</table></p></html')
-            index.close()
+            self.cov.html_report(directory=self.coverHtmlDir, morfs=modules)
 
-    def htmlAnnotate(self, name, file, coverfile, outputDir):
-        log.debug('Name: %s file: %s' % (name, file, ))
-        rows = []
-        data = open(coverfile, 'r').read().split('\n')
-        padding = len(str(len(data)))
-        stats = {'covered': 0, 'missed': 0, 'skipped': 0}
-        for lineno, line in enumerate(data):
-            lineno += 1
-            if line:
-                status = line[0]
-                line = line[2:]
-            else:
-                status = ''
-                line = ''
-            lineno = (' ' * (padding - len(str(lineno)))) + str(lineno)
-            for old, new in (('&', '&amp;'), ('<', '&lt;'), ('>', '&gt;'),
-                             ('"', '&quot;'), ):
-                line = line.replace(old, new)
-            if status == '!':
-                rows.append('<div class="nocov"><span class="num"><pre>'
-                            '%s</pre></span><pre>%s</pre></div>' % (lineno,
-                                                                    line))
-                stats['missed'] += 1
-            elif status == '>':
-                rows.append('<div class="cov"><span class="num"><pre>%s</pre>'
-                            '</span><pre>%s</pre></div>' % (lineno, line))
-                stats['covered'] += 1
-            else:
-                rows.append('<div class="skip"><span class="num"><pre>%s</pre>'
-                            '</span><pre>%s</pre></div>' % (lineno, line))
-                stats['skipped'] += 1
-        stats['percent'] = self.computePercent(stats['covered'],
-                                               stats['missed'])
-        html = COVERAGE_TEMPLATE % {'title': '<title>%s</title>' % name,
-                                    'header': name,
-                                    'body': '\n'.join(rows),
-                                    'stats': COVERAGE_STATS_TEMPLATE % stats,
-                                   }
-        outfilename = name + '.html'
-        outfile = open(os.path.join(outputDir, outfilename), 'w')
-        outfile.write(html)
-        outfile.close()
-        return outfilename, stats
+    def isExcludedModule(self, mod):
+        for exclude in self.coverExclude:
+            wildstart, wildend = exclude[0] == "*", exclude[-1] == "*"
+            if wildstart and wildend and exclude[1:-1] in mod.__name__:
+                return True
+            if wildend and mod.__name__.startswith(exclude[:-1]):
+                return True
+            if wildstart and mod.__name__.endswith(exclude[1:]):
+                return True
+            if mod.__name__ == exclude:
+                return True
+        return False
 
     def computePercent(self, covered, missed):
         if covered + missed == 0: