Edgewall Software

source: trunk/bitten/util/testrunner.py @ 1001

Last change on this file since 1001 was 910, checked in by osimons, 13 years ago

Updated copyright to 2010.

  • Property svn:eol-style set to native
File size: 9.9 KB
CovLine 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
4# Copyright (C) 2008 Matt Good <matt@matt-good.net>
5# Copyright (C) 2008-2010 Edgewall Software
6# All rights reserved.
7#
8# This software is licensed as described in the file COPYING, which
9# you should have received as part of this distribution. The terms
10# are also available at http://bitten.edgewall.org/wiki/License.
11
012from distutils import log
013from distutils.errors import DistutilsOptionError
014import os
015import re
016from StringIO import StringIO
017import sys
018import time
019from pkg_resources import Distribution, EntryPoint, PathMetadata, \
020                          normalize_path, require, working_set
021from setuptools.command.test import test
022from unittest import _TextTestResult, TextTestRunner
23
024from bitten import __version__ as VERSION
025from bitten.util import xmlio
26
027__docformat__ = 'restructuredtext en'
28
29
030class XMLTestResult(_TextTestResult):
31
032    def __init__(self, stream, descriptions, verbosity):
133        _TextTestResult.__init__(self, stream, descriptions, verbosity)
134        self.tests = []
35
036    def startTest(self, test):
27537        _TextTestResult.startTest(self, test)
27538        filename = sys.modules[test.__module__].__file__
27539        if filename.endswith('.pyc') or filename.endswith('.pyo'):
340            filename = filename[:-1]
27541        self.tests.append([test, filename, time.time(), None, None])
42
043    def stopTest(self, test):
27544        self.tests[-1][2] = time.time() - self.tests[-1][2]
27545        _TextTestResult.stopTest(self, test)
46
47
048class XMLTestRunner(TextTestRunner):
49
050    def __init__(self, stream=sys.stdout, xml_stream=None):
151        TextTestRunner.__init__(self, stream, descriptions=0, verbosity=2)
152        self.xml_stream = xml_stream
53
054    def _makeResult(self):
155        return XMLTestResult(self.stream, self.descriptions, self.verbosity)
56
057    def run(self, test):
158        result = TextTestRunner.run(self, test)
159        if not self.xml_stream:
060            return result
61
162        root = xmlio.Element('unittest-results')
27663        for testcase, filename, timetaken, stdout, stderr in result.tests:
27564            status = 'success'
27565            tb = None
66
27567            if testcase in [e[0] for e in result.errors]:
068                status = 'error'
069                tb = [e[1] for e in result.errors if e[0] is testcase][0]
27570            elif testcase in [f[0] for f in result.failures]:
071                status = 'failure'
072                tb = [f[1] for f in result.failures if f[0] is testcase][0]
73
27574            name = str(testcase)
27575            fixture = None
27576            description = testcase.shortDescription() or ''
27577            if description.startswith('doctest of '):
078                name = 'doctest'
079                fixture = description[11:]
080                description = None
081            else:
27582                match = re.match('(\w+)\s+\(([\w.]+)\)', name)
27583                if match:
27584                    name = match.group(1)
27585                    fixture = match.group(2)
86
27587            test_elem = xmlio.Element('test', file=filename, name=name,
27588                                      fixture=fixture, status=status,
27589                                      duration=timetaken)
27590            if description:
891                test_elem.append(xmlio.Element('description')[description])
27592            if stdout:
093                test_elem.append(xmlio.Element('stdout')[stdout])
27594            if stderr:
095                test_elem.append(xmlio.Element('stdout')[stderr])
27596            if tb:         
097                test_elem.append(xmlio.Element('traceback')[tb])
27598            root.append(test_elem)
99
1100        root.write(self.xml_stream, newlines=True)
1101        return result
102
103
0104class unittest(test):
0105    description = test.description + ', and optionally record code coverage'
106
0107    user_options = test.user_options + [
0108        ('xml-output=', None,
0109            "Path to the XML file where test results are written to"),
0110        ('coverage-dir=', None,
0111            "Directory where coverage files are to be stored"),
0112        ('coverage-summary=', None,
0113            "Path to the file where the coverage summary should be stored"),
0114        ('coverage-method=', None,
0115            "Whether to use trace.py or coverage.py to collect code coverage. "
0116            "Valid options are 'trace' (the default) or 'coverage'.")
0117    ]
118
0119    def initialize_options(self):
0120        test.initialize_options(self)
0121        self.xml_output = None
0122        self.xml_output_file = None
0123        self.coverage_summary = None
0124        self.coverage_dir = None
0125        self.coverage_method = 'trace'
126
0127    def finalize_options(self):
0128        test.finalize_options(self)
129
0130        if self.xml_output is not None:
0131            output_dir = os.path.dirname(self.xml_output) or '.'
0132            if not os.path.exists(output_dir):
0133                os.makedirs(output_dir)
0134            self.xml_output_file = open(self.xml_output, 'w')
135
0136        if self.coverage_method not in ('trace', 'coverage', 'figleaf'):
0137            raise DistutilsOptionError('Unknown coverage method %r' %
0138                                       self.coverage_method)
139
0140    def run_tests(self):
0141        if self.coverage_summary:
0142            if self.coverage_method == 'coverage':
0143                self._run_with_coverage()
0144            elif self.coverage_method == 'figleaf':
0145                self._run_with_figleaf()
0146            else:
0147                self._run_with_trace()
0148        else:
0149            self._run_tests()
150
0151    def _run_with_figleaf(self):
0152        import figleaf
0153        figleaf.start()
0154        try:
0155            self._run_tests()
0156        finally:
0157            figleaf.stop()
0158            figleaf.write_coverage(self.coverage_summary)
159
0160    def _run_with_coverage(self):
0161        import coverage
0162        coverage.use_cache(False)
0163        coverage.start()
0164        try:
0165            self._run_tests()
0166        finally:
0167            coverage.stop()
168
0169            modules = [m for _, m in sys.modules.items()
0170                       if m is not None and hasattr(m, '__file__')
0171                       and os.path.splitext(m.__file__)[-1] in ('.py', '.pyc')]
172
173            # Generate summary file
0174            buf = StringIO()
0175            coverage.report(modules, file=buf)
0176            buf.seek(0)
0177            fileobj = open(self.coverage_summary, 'w')
0178            try:
0179                filter_coverage(buf, fileobj)
0180            finally:
0181                fileobj.close()
182
0183            if self.coverage_dir:
0184                if not os.path.exists(self.coverage_dir):
0185                    os.makedirs(self.coverage_dir)
0186                coverage.annotate(modules, directory=self.coverage_dir,
0187                                  ignore_errors=True)
188
0189    def _run_with_trace(self):
0190        from trace import Trace
0191        trace = Trace(ignoredirs=[sys.prefix, sys.exec_prefix], trace=False,
0192                      count=True)
0193        try:
0194            trace.runfunc(self._run_tests)
0195        finally:
0196            results = trace.results()
0197            real_stdout = sys.stdout
0198            sys.stdout = open(self.coverage_summary, 'w')
0199            try:
0200                results.write_results(show_missing=True, summary=True,
0201                                      coverdir=self.coverage_dir)
0202            finally:
0203                sys.stdout.close()
0204                sys.stdout = real_stdout
205
0206    def _run_tests(self):
1207        old_path = sys.path[:]
1208        ei_cmd = self.get_finalized_command("egg_info")
1209        path_item = normalize_path(ei_cmd.egg_base)
1210        metadata = PathMetadata(
1211            path_item, normalize_path(ei_cmd.egg_info)
1212        )
1213        dist = Distribution(path_item, metadata, project_name=ei_cmd.egg_name)
1214        working_set.add(dist)
1215        require(str(dist.as_requirement()))
1216        loader_ep = EntryPoint.parse("x=" + self.test_loader)
1217        loader_class = loader_ep.load(require=False)
218
1219        try:
1220            import unittest
1221            unittest.main(
1222                None, None, [unittest.__file__] + self.test_args,
1223                testRunner=XMLTestRunner(stream=sys.stdout,
1224                                         xml_stream=self.xml_output_file),
1225                testLoader=loader_class()
1226            )
1227        except SystemExit, e:
1228            return e.code
229
230
0231def filter_coverage(infile, outfile):
0232    for idx, line in enumerate(infile):
0233        if idx < 2 or line.startswith('--'):
0234            outfile.write(line)
0235            continue
0236        parts = line.split()
0237        name = parts[0]
0238        if name == 'TOTAL':
0239            continue
0240        if name not in sys.modules:
0241            outfile.write(line)
0242            continue
0243        filename = os.path.normpath(sys.modules[name].__file__)
0244        if filename.endswith('.pyc') or filename.endswith('.pyo'):
0245            filename = filename[:-1]
0246        outfile.write(line.rstrip() + ' ' + filename + '\n')
247
248
0249def main():
0250    from distutils.dist import Distribution
0251    from optparse import OptionParser
252
0253    parser = OptionParser(usage='usage: %prog [options] test_suite ...',
0254                          version='%%prog %s' % VERSION)
0255    parser.add_option('-o', '--xml-output', action='store', dest='xml_output',
0256                      metavar='FILE', help='write XML test results to FILE')
0257    parser.add_option('-d', '--coverage-dir', action='store',
0258                      dest='coverage_dir', metavar='DIR',
0259                      help='store coverage results in DIR')
0260    parser.add_option('-s', '--coverage-summary', action='store',
0261                      dest='coverage_summary', metavar='FILE',
0262                      help='write coverage summary to FILE')
0263    options, args = parser.parse_args()
0264    if len(args) < 1:
0265        parser.error('incorrect number of arguments')
266
0267    cmd = unittest(Distribution())
0268    cmd.initialize_options()
0269    cmd.test_suite = args[0]
0270    if hasattr(options, 'xml_output'):
0271        cmd.xml_output = options.xml_output
0272    if hasattr(options, 'coverage_summary'):
0273        cmd.coverage_summary = options.coverage_summary
0274    if hasattr(options, 'coverage_dir'):
0275        cmd.coverage_dir = options.coverage_dir
0276    cmd.finalize_options()
0277    cmd.run()
278
0279if __name__ == '__main__':
0280    main()
Note: See TracBrowser for help on using the repository browser.