root/galaxy-central/eggs/nose-0.11.1-py2.6.egg/nose/plugins/cover.py @ 3

リビジョン 3, 11.7 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1"""If you have Ned Batchelder's coverage_ module installed, you may activate a
2coverage report with the ``--with-coverage`` switch or NOSE_WITH_COVERAGE
3environment variable. The coverage report will cover any python source module
4imported after the start of the test run, excluding modules that match
5testMatch. If you want to include those modules too, use the ``--cover-tests``
6switch, or set the NOSE_COVER_TESTS environment variable to a true value. To
7restrict the coverage report to modules from a particular package or packages,
8use the ``--cover-packages`` switch or the NOSE_COVER_PACKAGES environment
9variable.
10
11.. _coverage: http://www.nedbatchelder.com/code/modules/coverage.html
12"""
13import logging
14import os
15import sys
16from nose.plugins.base import Plugin
17from nose.util import src, tolist
18
19log =  logging.getLogger(__name__)
20
21COVERAGE_TEMPLATE = '''<html>
22<head>
23%(title)s
24</head>
25<body>
26%(header)s
27<style>
28.coverage pre {float: left; margin: 0px 1em; border: none;
29               padding: 0px; }
30.num pre { margin: 0px }
31.nocov, .nocov pre {background-color: #faa}
32.cov, .cov pre {background-color: #cfc}
33div.coverage div { clear: both; height: 1.1em}
34</style>
35<div class="stats">
36%(stats)s
37</div>
38<div class="coverage">
39%(body)s
40</div>
41</body>
42</html>
43'''
44
45COVERAGE_STATS_TEMPLATE = '''Covered: %(covered)s lines<br/>
46Missed: %(missed)s lines<br/>
47Skipped %(skipped)s lines<br/>
48Percent: %(percent)s %%<br/>
49'''
50
51
52class Coverage(Plugin):
53    """
54    Activate a coverage report using Ned Batchelder's coverage module.
55    """
56    coverTests = False
57    coverPackages = None
58    score = 200
59    status = {}
60
61    def options(self, parser, env):
62        """
63        Add options to command line.
64        """
65        Plugin.options(self, parser, env)
66        parser.add_option("--cover-package", action="append",
67                          default=env.get('NOSE_COVER_PACKAGE'),
68                          metavar="PACKAGE",
69                          dest="cover_packages",
70                          help="Restrict coverage output to selected packages "
71                          "[NOSE_COVER_PACKAGE]")
72        parser.add_option("--cover-erase", action="store_true",
73                          default=env.get('NOSE_COVER_ERASE'),
74                          dest="cover_erase",
75                          help="Erase previously collected coverage "
76                          "statistics before run")
77        parser.add_option("--cover-tests", action="store_true",
78                          dest="cover_tests",
79                          default=env.get('NOSE_COVER_TESTS'),
80                          help="Include test modules in coverage report "
81                          "[NOSE_COVER_TESTS]")
82        parser.add_option("--cover-inclusive", action="store_true",
83                          dest="cover_inclusive",
84                          default=env.get('NOSE_COVER_INCLUSIVE'),
85                          help="Include all python files under working "
86                          "directory in coverage report.  Useful for "
87                          "discovering holes in test coverage if not all "
88                          "files are imported by the test suite. "
89                          "[NOSE_COVER_INCLUSIVE]")
90        parser.add_option("--cover-html", action="store_true",
91                          default=env.get('NOSE_COVER_HTML'),
92                          dest='cover_html',
93                          help="Produce HTML coverage information")
94        parser.add_option('--cover-html-dir', action='store',
95                          default=env.get('NOSE_COVER_HTML_DIR', 'cover'),
96                          dest='cover_html_dir',
97                          metavar='DIR',
98                          help='Produce HTML coverage information in dir')
99
100    def configure(self, options, config):
101        """
102        Configure plugin.
103        """
104        try:
105            self.status.pop('active')
106        except KeyError:
107            pass
108        Plugin.configure(self, options, config)
109        if self.enabled:
110            try:
111                import coverage
112            except ImportError:
113                log.error("Coverage not available: "
114                          "unable to import coverage module")
115                self.enabled = False
116                return
117        self.conf = config
118        self.coverErase = options.cover_erase
119        self.coverTests = options.cover_tests
120        self.coverPackages = []
121        if options.cover_packages:
122            for pkgs in [tolist(x) for x in options.cover_packages]:
123                self.coverPackages.extend(pkgs)
124        self.coverInclusive = options.cover_inclusive
125        if self.coverPackages:
126            log.info("Coverage report will include only packages: %s",
127                     self.coverPackages)
128        self.coverHtmlDir = None
129        if options.cover_html:
130            self.coverHtmlDir = options.cover_html_dir
131            log.debug('Will put HTML coverage report in %s', self.coverHtmlDir)
132        if self.enabled:
133            self.status['active'] = True
134
135    def begin(self):
136        """
137        Begin recording coverage information.
138        """
139        log.debug("Coverage begin")
140        import coverage
141        self.skipModules = sys.modules.keys()[:]
142        if self.coverErase:
143            log.debug("Clearing previously collected coverage statistics")
144            coverage.erase()
145        coverage.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
146        coverage.start()
147
148    def report(self, stream):
149        """
150        Output code coverage report.
151        """
152        log.debug("Coverage report")
153        import coverage
154        coverage.stop()
155        modules = [ module
156                    for name, module in sys.modules.items()
157                    if self.wantModuleCoverage(name, module) ]
158        log.debug("Coverage report will cover modules: %s", modules)
159        coverage.report(modules, file=stream)
160        if self.coverHtmlDir:
161            if not os.path.exists(self.coverHtmlDir):
162                os.makedirs(self.coverHtmlDir)
163            log.debug("Generating HTML coverage report")
164            files = {}
165            for m in modules:
166                if hasattr(m, '__name__') and hasattr(m, '__file__'):
167                    files[m.__name__] = m.__file__
168            coverage.annotate(files.values())
169            global_stats =  {'covered': 0, 'missed': 0, 'skipped': 0}
170            file_list = []
171            for m, f in files.iteritems():
172                if f.endswith('pyc'):
173                    f = f[:-1]
174                coverfile = f+',cover'
175                outfile, stats = self.htmlAnnotate(m, f, coverfile,
176                                                   self.coverHtmlDir)
177                for field in ('covered', 'missed', 'skipped'):
178                    global_stats[field] += stats[field]
179                file_list.append((stats['percent'], m, outfile, stats))
180                os.unlink(coverfile)
181            file_list.sort()
182            global_stats['percent'] = self.computePercent(
183                global_stats['covered'], global_stats['missed'])
184            # Now write out an index file for the coverage HTML
185            index = open(os.path.join(self.coverHtmlDir, 'index.html'), 'w')
186            index.write('<html><head><title>Coverage Index</title></head>'
187                        '<body><p>')
188            index.write(COVERAGE_STATS_TEMPLATE % global_stats)
189            index.write('<table><tr><td>File</td><td>Covered</td><td>Missed'
190                        '</td><td>Skipped</td><td>Percent</td></tr>')
191            for junk, name, outfile, stats in file_list:
192                stats['a'] = '<a href="%s">%s</a>' % (outfile, name)
193                index.write('<tr><td>%(a)s</td><td>%(covered)s</td><td>'
194                            '%(missed)s</td><td>%(skipped)s</td><td>'
195                            '%(percent)s %%</td></tr>' % stats)
196            index.write('</table></p></html')
197            index.close()
198
199    def htmlAnnotate(self, name, file, coverfile, outputDir):
200        log.debug('Name: %s file: %s' % (name, file, ))
201        rows = []
202        data = open(coverfile, 'r').read().split('\n')
203        padding = len(str(len(data)))
204        stats = {'covered': 0, 'missed': 0, 'skipped': 0}
205        for lineno, line in enumerate(data):
206            lineno += 1
207            if line:
208                status = line[0]
209                line = line[2:]
210            else:
211                status = ''
212                line = ''
213            lineno = (' ' * (padding - len(str(lineno)))) + str(lineno)
214            for old, new in (('&', '&amp;'), ('<', '&lt;'), ('>', '&gt;'),
215                             ('"', '&quot;'), ):
216                line = line.replace(old, new)
217            if status == '!':
218                rows.append('<div class="nocov"><span class="num"><pre>'
219                            '%s</pre></span><pre>%s</pre></div>' % (lineno,
220                                                                    line))
221                stats['missed'] += 1
222            elif status == '>':
223                rows.append('<div class="cov"><span class="num"><pre>%s</pre>'
224                            '</span><pre>%s</pre></div>' % (lineno, line))
225                stats['covered'] += 1
226            else:
227                rows.append('<div class="skip"><span class="num"><pre>%s</pre>'
228                            '</span><pre>%s</pre></div>' % (lineno, line))
229                stats['skipped'] += 1
230        stats['percent'] = self.computePercent(stats['covered'],
231                                               stats['missed'])
232        html = COVERAGE_TEMPLATE % {'title': '<title>%s</title>' % name,
233                                    'header': name,
234                                    'body': '\n'.join(rows),
235                                    'stats': COVERAGE_STATS_TEMPLATE % stats,
236                                   }
237        outfilename = name + '.html'
238        outfile = open(os.path.join(outputDir, outfilename), 'w')
239        outfile.write(html)
240        outfile.close()
241        return outfilename, stats
242
243    def computePercent(self, covered, missed):
244        if covered + missed == 0:
245            percent = 1
246        else:
247            percent = covered/(covered+missed+0.0)
248        return int(percent * 100)
249
250    def wantModuleCoverage(self, name, module):
251        if not hasattr(module, '__file__'):
252            log.debug("no coverage of %s: no __file__", name)
253            return False
254        module_file = src(module.__file__)
255        if not module_file or not module_file.endswith('.py'):
256            log.debug("no coverage of %s: not a python file", name)
257            return False
258        if self.coverPackages:
259            for package in self.coverPackages:
260                if (name.startswith(package)
261                    and (self.coverTests
262                         or not self.conf.testMatch.search(name))):
263                    log.debug("coverage for %s", name)
264                    return True
265        if name in self.skipModules:
266            log.debug("no coverage for %s: loaded before coverage start",
267                      name)
268            return False
269        if self.conf.testMatch.search(name) and not self.coverTests:
270            log.debug("no coverage for %s: is a test", name)
271            return False
272        # accept any package that passed the previous tests, unless
273        # coverPackages is on -- in that case, if we wanted this
274        # module, we would have already returned True
275        return not self.coverPackages
276
277    def wantFile(self, file, package=None):
278        """If inclusive coverage enabled, return true for all source files
279        in wanted packages.
280        """
281        if self.coverInclusive:
282            if file.endswith(".py"):
283                if package and self.coverPackages:
284                    for want in self.coverPackages:
285                        if package.startswith(want):
286                            return True
287                else:
288                    return True
289        return None
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。