Ticket #4501: 4501-coverage-soc.diff

File 4501-coverage-soc.diff, 26.4 KB (added by ericholscher, 5 years ago)

Initial cleanup of the summer of code work on this ticket.

  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    index a195b57..8b536ff 100644
    a b MESSAGE_STORAGE = 'django.contrib.messages.storage.user_messages.LegacyFallbackS 
    410410###########
    411411
    412412# The name of the method to use to invoke the test suite
    413 TEST_RUNNER = 'django.test.simple.run_tests'
     413TEST_RUNNER = 'django.test.simple.DefaultTestRunner'
    414414
    415415# The name of the database to use for testing purposes.
    416416# If None, a name of 'test_' + DATABASE_NAME will be assumed
    TEST_DATABASE_CHARSET = None 
    424424TEST_DATABASE_COLLATION = None
    425425
    426426############
     427# COVERAGE #
     428############
     429
     430
     431# Specify the coverage test runner
     432COVERAGE_TEST_RUNNER = 'django.test.test_coverage.ConsoleReportCoverageRunner'
     433
     434# Specify regular expressions of code blocks the coverage analyzer should
     435# ignore as statements (e.g. ``raise NotImplemented``).
     436# These statements are not figured in as part of the coverage statistics.
     437# This setting is optional.
     438COVERAGE_CODE_EXCLUDES = [
     439    'def __unicode__\(self\):', 'def get_absolute_url\(self\):',
     440    'from .* import .*', 'import .*',
     441    ]
     442
     443# Specify a list of regular expressions of paths to exclude from
     444# coverage analysis.
     445# Note these paths are ignored by the module introspection tool and take
     446# precedence over any package/module settings such as:
     447# TODO: THE SETTING FOR MODULES
     448# Use this to exclude subdirectories like ``r'\.svn'``, for example.
     449# This setting is optional.
     450COVERAGE_PATH_EXCLUDES = [r'\.svn']
     451
     452# Specify a list of additional module paths to include
     453# in the coverage analysis. By default, only modules within installed
     454# apps are reported. If you have utility modules outside of the app
     455# structure, you can include them here.
     456# Note this list is *NOT* regular expression, so you have to be explicit,
     457# such as 'myproject.utils', and not 'utils$'.
     458# This setting is optional.
     459COVERAGE_ADDITIONAL_MODULES = []
     460
     461# Specify a list of regular expressions of module paths to exclude
     462# from the coverage analysis. Examples are ``'tests$'`` and ``'urls$'``.
     463# This setting is optional.
     464COVERAGE_MODULE_EXCLUDES = ['tests$', 'settings$','urls$', 'common.views.test',
     465                            '__init__', 'django']
     466
     467# Specify the directory where you would like the coverage report to create
     468# the HTML files.
     469# You'll need to make sure this directory exists and is writable by the
     470# user account running the test.
     471# You should probably set this one explicitly in your own settings file.
     472COVERAGE_REPORT_HTML_OUTPUT_DIR = 'test_html'
     473
     474
     475############
    427476# FIXTURES #
    428477############
    429478
  • django/core/management/commands/test.py

    diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py
    index 8ebf3da..1f9edab 100644
    a b  
    11from django.core.management.base import BaseCommand
    22from optparse import make_option
    33import sys
     4import inspect
    45
    56class Command(BaseCommand):
    67    option_list = BaseCommand.option_list + (
    78        make_option('--noinput', action='store_false', dest='interactive', default=True,
    89            help='Tells Django to NOT prompt the user for input of any kind.'),
     10        make_option('--coverage', action='store_true', dest='coverage', default=False,
     11                    help='Tells Django to run the coverage runner'),
     12        make_option('--reports', action='store_true', dest='reports', default=False,
     13                    help='Tells Django to output coverage results as HTML reports'),
    914    )
    1015    help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.'
    1116    args = '[appname ...]'
    class Command(BaseCommand): 
    1823
    1924        verbosity = int(options.get('verbosity', 1))
    2025        interactive = options.get('interactive', True)
    21         test_runner = get_runner(settings)
    22 
    23         failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
     26        cover = options.get('coverage', False)
     27        report = options.get('reports', False)
     28        test_runner = get_runner(settings, coverage=cover, reports=report)
     29        if inspect.isclass(test_runner):
     30            tr = test_runner()
     31            failures = tr.run_tests(test_labels, verbosity=verbosity, interactive=interactive)
     32        else:
     33            failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
    2434        if failures:
    2535            sys.exit(failures)
  • django/test/simple.py

    diff --git a/django/test/simple.py b/django/test/simple.py
    index f3c48ba..d79b6ef 100644
    a b  
    1 import unittest
     1import sys, time, traceback, unittest
    22from django.conf import settings
    33from django.db.models import get_app, get_apps
    44from django.test import _doctest as doctest
    def reorder_suite(suite, classes): 
    146146        bins[0].addTests(bins[i+1])
    147147    return bins[0]
    148148
    149 def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
     149class DefaultTestRunner(object):
    150150    """
    151     Run the unit tests for all the test labels in the provided list.
    152     Labels must be of the form:
    153      - app.TestClass.test_method
    154         Run a single specific test method
    155      - app.TestClass
    156         Run all the test methods in a given class
    157      - app
    158         Search for doctests and unittests in the named application.
    159 
    160     When looking for tests, the test runner will look in the models and
    161     tests modules for the application.
    162 
    163     A list of 'extra' tests may also be provided; these tests
    164     will be added to the test suite.
    165 
    166     Returns the number of tests that failed.
     151    The original test runner. No coverage reporting.
    167152    """
    168     setup_test_environment()
    169153
    170     settings.DEBUG = False
    171     suite = unittest.TestSuite()
    172 
    173     if test_labels:
    174         for label in test_labels:
    175             if '.' in label:
    176                 suite.addTest(build_test(label))
    177             else:
    178                 app = get_app(label)
     154    def __init__(self):
     155        """
     156        Placeholder constructor. Want to make it obvious that it can
     157        be overridden.
     158        """
     159        self.isloaded = True
     160
     161
     162    def run_tests(self, test_labels, verbosity=1, interactive=True, extra_tests=[]):
     163        """
     164        Run the unit tests for all the test labels in the provided list.
     165        Labels must be of the form:
     166         - app.TestClass.test_method
     167            Run a single specific test method
     168         - app.TestClass
     169            Run all the test methods in a given class
     170         - app
     171            Search for doctests and unittests in the named application.
     172
     173        When looking for tests, the test runner will look in the models and
     174        tests modules for the application.
     175
     176        A list of 'extra' tests may also be provided; these tests
     177        will be added to the test suite.
     178
     179        Returns the number of tests that failed.
     180        """
     181        setup_test_environment()
     182
     183        settings.DEBUG = False
     184        suite = unittest.TestSuite()
     185
     186        if test_labels:
     187            for label in test_labels:
     188                if '.' in label:
     189                    suite.addTest(build_test(label))
     190                else:
     191                    app = get_app(label)
     192                    suite.addTest(build_suite(app))
     193        else:
     194            for app in get_apps():
    179195                suite.addTest(build_suite(app))
    180     else:
    181         for app in get_apps():
    182             suite.addTest(build_suite(app))
    183196
    184     for test in extra_tests:
    185         suite.addTest(test)
     197        for test in extra_tests:
     198            suite.addTest(test)
    186199
    187     suite = reorder_suite(suite, (TestCase,))
     200        suite = reorder_suite(suite, (TestCase,))
    188201
    189     old_name = settings.DATABASE_NAME
    190     from django.db import connection
    191     connection.creation.create_test_db(verbosity, autoclobber=not interactive)
    192     result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
    193     connection.creation.destroy_test_db(old_name, verbosity)
     202        old_name = settings.DATABASE_NAME
     203        from django.db import connection
     204        connection.creation.create_test_db(verbosity, autoclobber=not interactive)
     205        result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
     206        connection.creation.destroy_test_db(old_name, verbosity)
    194207
    195     teardown_test_environment()
     208        teardown_test_environment()
    196209
    197     return len(result.failures) + len(result.errors)
     210        return len(result.failures) + len(result.errors)
  • new file django/test/test_coverage.py

    diff --git a/django/test/test_coverage.py b/django/test/test_coverage.py
    new file mode 100644
    index 0000000..337350d
    - +  
     1import coverage, time
     2import os, sys
     3
     4from django.conf import settings
     5from django.db.models.loading import get_app, get_apps
     6from django.test.simple import DefaultTestRunner as base_run_tests
     7from django.utils.module_tools import get_all_modules
     8from django.utils.translation import ugettext as _
     9
     10def _get_app_package(app_model_module):
     11    """
     12    Returns the app module name from the app model module.
     13    """
     14    return '.'.join(app_model_module.__name__.split('.')[:-1])
     15
     16
     17class BaseCoverageRunner(object):
     18    """
     19    Placeholder class for coverage runners. Intended to be easily extended.
     20    """
     21
     22    def __init__(self):
     23        """Placeholder (since it is overrideable)"""
     24        self.cov = coverage.coverage(cover_pylib=True, auto_data=True)
     25        self.cov.use_cache(True)
     26        self.cov.load()
     27        #self.cov.combine()
     28
     29    def run_tests(self, test_labels, verbosity=1, interactive=True,
     30                  extra_tests=[]):
     31        """
     32        Runs the specified tests while generating code coverage statistics. Upon
     33        the tests' completion, the results are printed to stdout.
     34        """
     35
     36        self.cov.start()
     37        brt = base_run_tests()
     38        results = brt.run_tests(test_labels, verbosity, interactive, extra_tests)
     39        self.cov.stop()
     40        coverage_modules = []
     41        if test_labels:
     42            for label in test_labels:
     43                label = label.split('.')[0]
     44                app = get_app(label)
     45                coverage_modules.append(_get_app_package(app))
     46        else:
     47            for app in get_apps():
     48                coverage_modules.append(_get_app_package(app))
     49        coverage_modules.extend(getattr(settings, 'COVERAGE_ADDITIONAL_MODULES', []))
     50        #This code seems to be a lot a bit magical to include in Django.
     51        #Need to look at other implementations and see how they get all the
     52        #correct modules to report on.
     53        packages, self.modules, self.excludes, self.errors = get_all_modules(
     54            coverage_modules, getattr(settings, 'COVERAGE_MODULE_EXCLUDES', []),
     55            getattr(settings, 'COVERAGE_PATH_EXCLUDES', []))
     56
     57        return results
     58
     59class ConsoleReportCoverageRunner(BaseCoverageRunner):
     60
     61    def run_tests(self, *args, **kwargs):
     62        """docstring for run_tests"""
     63        res = super(ConsoleReportCoverageRunner, self).run_tests(*args, **kwargs)
     64        self.cov.report(self.modules.values(), show_missing=1)
     65        if self.excludes:
     66            print >> sys.stdout
     67            print >> sys.stdout, _("The following packages or modules were excluded:"),
     68            for e in self.excludes:
     69                print >> sys.stdout, e,
     70            print >>sys.stdout
     71        if self.errors:
     72            print >> sys.stdout
     73            print >> sys.stderr, _("There were problems with the following packages or modules:"),
     74            for e in self.errors:
     75                print >> sys.stderr, e,
     76            print >> sys.stdout
     77        return res
     78
     79class ReportingCoverageRunner(BaseCoverageRunner):
     80    """Runs coverage.py analysis, as well as generating detailed HTML reports."""
     81    def __init__(self, outdir = None):
     82        """
     83        Constructor, overrides BaseCoverageRunner. Sets output directory
     84        for reports. Parameter or setting.
     85        """
     86        super(ReportingCoverageRunner, self).__init__()
     87        if outdir:
     88            self.outdir = outdir
     89        else:
     90            # Realistically, we aren't going to ship the entire reporting framework..
     91            # but for the time being I have left it in.
     92            self.outdir = getattr(settings, 'COVERAGE_REPORT_HTML_OUTPUT_DIR', 'test_html')
     93            self.outdir = os.path.abspath(self.outdir)
     94            # Create directory
     95            if not os.path.exists(self.outdir):
     96                os.mkdir(self.outdir)
     97
     98    def run_tests(self, *args, **kwargs):
     99        """
     100        Overrides BaseCoverageRunner.run_tests, and adds html report generation
     101        with the results
     102        """
     103        res = super(ReportingCoverageRunner, self).run_tests( *args, **kwargs)
     104        print _("Outputting HTML reports")
     105        self.cov.html_report(self.modules.values(),
     106                                directory=self.outdir,
     107                                ignore_errors=True,
     108                                omit_prefixes=['modeltests'])
     109        print _("HTML reports were output to '%s'") % self.outdir
     110        return res
  • django/test/utils.py

    diff --git a/django/test/utils.py b/django/test/utils.py
    index 9d39eee..1431059 100644
    a b def teardown_test_environment(): 
    6868
    6969    del mail.outbox
    7070
    71 def get_runner(settings):
    72     test_path = settings.TEST_RUNNER.split('.')
     71def get_runner(settings, coverage=False, reports=False):
     72    """
     73    Based on the settings and parameters, returns the appropriate test
     74    runner class.
     75    """
     76    if coverage:
     77        if reports:
     78            test_path = 'django.test.test_coverage.ReportingCoverageRunner'.split('.')
     79        else:
     80            test_path = settings.COVERAGE_TEST_RUNNER.split('.')
     81    else:
     82        test_path = settings.TEST_RUNNER.split('.')
    7383    # Allow for Python 2.5 relative paths
    7484    if len(test_path) > 1:
    7585        test_module_name = '.'.join(test_path[:-1])
  • new file django/utils/module_tools/__init__.py

    diff --git a/django/utils/module_tools/__init__.py b/django/utils/module_tools/__init__.py
    new file mode 100644
    index 0000000..976d4b5
    - +  
     1from module_loader import *
     2from module_walker import *
     3
  • new file django/utils/module_tools/data_storage.py

    diff --git a/django/utils/module_tools/data_storage.py b/django/utils/module_tools/data_storage.py
    new file mode 100644
    index 0000000..aed5980
    - +  
     1"""
     2Copyright 2009 55 Minutes (http://www.55minutes.com)
     3
     4Licensed under the Apache License, Version 2.0 (the "License");
     5you may not use this file except in compliance with the License.
     6You may obtain a copy of the License at
     7
     8   http://www.apache.org/licenses/LICENSE-2.0
     9
     10Unless required by applicable law or agreed to in writing, software
     11distributed under the License is distributed on an "AS IS" BASIS,
     12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13See the License for the specific language governing permissions and
     14limitations under the License.
     15"""
     16
     17__all__ = ('Packages', 'Modules', 'Excluded', 'Errors')
     18
     19class SingletonType(type):
     20    def __call__(cls, *args, **kwargs):
     21        if getattr(cls, '__instance__', None) is None:
     22            instance = cls.__new__(cls)
     23            instance.__init__(*args, **kwargs)
     24            cls.__instance__ = instance
     25        return cls.__instance__
     26
     27class Packages(object):
     28    __metaclass__ = SingletonType
     29    packages = {}
     30
     31class Modules(object):
     32    __metaclass__ = SingletonType
     33    modules = {}
     34
     35class Excluded(object):
     36    __metaclass__ = SingletonType
     37    excluded = []
     38
     39class Errors(object):
     40    __metaclass__ = SingletonType
     41    errors = []
     42
  • new file django/utils/module_tools/module_loader.py

    diff --git a/django/utils/module_tools/module_loader.py b/django/utils/module_tools/module_loader.py
    new file mode 100644
    index 0000000..e6dd6ce
    - +  
     1"""
     2Copyright 2009 55 Minutes (http://www.55minutes.com)
     3
     4Licensed under the Apache License, Version 2.0 (the "License");
     5you may not use this file except in compliance with the License.
     6You may obtain a copy of the License at
     7
     8   http://www.apache.org/licenses/LICENSE-2.0
     9
     10Unless required by applicable law or agreed to in writing, software
     11distributed under the License is distributed on an "AS IS" BASIS,
     12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13See the License for the specific language governing permissions and
     14limitations under the License.
     15"""
     16
     17import imp, sys, types
     18
     19__all__ = ('find_or_load_module',)
     20
     21def _brute_force_find_module(module_name, module_path, module_type):
     22    for m in [m for n, m in sys.modules.iteritems() if type(m) == types.ModuleType]:
     23        m_path = []
     24        try:
     25            if module_type in (imp.PY_COMPILED, imp.PY_SOURCE):
     26                m_path = [m.__file__]
     27            elif module_type==imp.PKG_DIRECTORY:
     28                m_path = m.__path__
     29        except AttributeError:
     30            pass
     31        for p in m_path:
     32            if p.startswith(module_path):
     33                return m
     34    return None
     35
     36def _load_module(module_name, fo, fp, desc):
     37    suffix, mode, mtype = desc
     38    if module_name in sys.modules and \
     39       sys.modules[module_name].__file__.startswith(fp):
     40        module = sys.modules[module_name]
     41    else:
     42        module = _brute_force_find_module(module_name, fp, mtype)
     43    if not module:
     44        try:
     45            module = imp.load_module(module_name, fo, fp, desc)
     46        except:
     47            raise ImportError
     48    return module
     49
     50def _load_package(pkg_name, fp, desc):
     51    suffix, mode, mtype = desc
     52    if pkg_name in sys.modules:
     53        if fp in sys.modules[pkg_name].__path__:
     54            pkg = sys.modules[pkg_name]
     55    else:
     56        pkg = _brute_force_find_module(pkg_name, fp, mtype)
     57    if not pkg:
     58        pkg = imp.load_module(pkg_name, None, fp, desc)
     59    return pkg
     60
     61def find_or_load_module(module_name, path=None):
     62    """
     63    Attempts to lookup ``module_name`` in ``sys.modules``, else uses the
     64    facilities in the ``imp`` module to load the module.
     65
     66    If module_name specified is not of type ``imp.PY_SOURCE`` or
     67    ``imp.PKG_DIRECTORY``, raise ``ImportError`` since we don't know
     68    what to do with those.
     69    """
     70    fo, fp, desc = imp.find_module(module_name.split('.')[-1], path)
     71    suffix, mode, mtype = desc
     72    if mtype in (imp.PY_SOURCE, imp.PY_COMPILED):
     73        module = _load_module(module_name, fo, fp, desc)
     74    elif mtype==imp.PKG_DIRECTORY:
     75        module = _load_package(module_name, fp, desc)
     76    else:
     77        raise ImportError("Don't know how to handle this module type.")
     78    return module
     79
  • new file django/utils/module_tools/module_walker.py

    diff --git a/django/utils/module_tools/module_walker.py b/django/utils/module_tools/module_walker.py
    new file mode 100644
    index 0000000..442150e
    - +  
     1"""
     2Copyright 2009 55 Minutes (http://www.55minutes.com)
     3
     4Licensed under the Apache License, Version 2.0 (the "License");
     5you may not use this file except in compliance with the License.
     6You may obtain a copy of the License at
     7
     8   http://www.apache.org/licenses/LICENSE-2.0
     9
     10Unless required by applicable law or agreed to in writing, software
     11distributed under the License is distributed on an "AS IS" BASIS,
     12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13See the License for the specific language governing permissions and
     14limitations under the License.
     15"""
     16
     17import os, re, sys
     18from glob import glob
     19
     20from data_storage import *
     21from module_loader import find_or_load_module
     22
     23try:
     24    set
     25except:
     26    from sets import Set as set
     27
     28__all__ = ('get_all_modules',)
     29
     30def _build_pkg_path(pkg_name, pkg, path):
     31    for rp in [x for x in pkg.__path__ if path.startswith(x)]:
     32        p = path.replace(rp, '').replace(os.path.sep, '.')
     33        return pkg_name + p
     34
     35def _build_module_path(pkg_name, pkg, path):
     36    return _build_pkg_path(pkg_name, pkg, os.path.splitext(path)[0])
     37
     38def _prune_whitelist(whitelist, blacklist):
     39    excluded = Excluded().excluded
     40
     41    for wp in whitelist[:]:
     42        for bp in blacklist:
     43            if re.search(bp, wp):
     44                whitelist.remove(wp)
     45                excluded.append(wp)
     46                break
     47    return whitelist
     48
     49def _parse_module_list(m_list):
     50    packages = Packages().packages
     51    modules = Modules().modules
     52    excluded = Excluded().excluded
     53    errors = Errors().errors
     54
     55    for m in m_list:
     56        components = m.split('.')
     57        m_name = ''
     58        search_path = []
     59        processed=False
     60        for i, c in enumerate(components):
     61            m_name = '.'.join([x for x in m_name.split('.') if x] + [c])
     62            try:
     63                module = find_or_load_module(m_name, search_path or None)
     64            except ImportError:
     65                processed=True
     66                errors.append(m)
     67                break
     68            try:
     69                search_path.extend(module.__path__)
     70            except AttributeError:
     71                processed = True
     72                if i+1==len(components):
     73                    modules[m_name] = module
     74                else:
     75                    errors.append(m)
     76                    break
     77        if not processed:
     78            packages[m_name] = module
     79
     80def prune_dirs(root, dirs, exclude_dirs):
     81    _dirs = [os.path.join(root, d) for d in dirs]
     82    for i, p in enumerate(_dirs):
     83        for e in exclude_dirs:
     84            if re.search(e, p):
     85                del dirs[i]
     86                break
     87
     88def _get_all_packages(pkg_name, pkg, blacklist, exclude_dirs):
     89    packages = Packages().packages
     90    errors = Errors().errors
     91
     92    for path in pkg.__path__:
     93        for root, dirs, files in os.walk(path):
     94            prune_dirs(root, dirs, exclude_dirs or [])
     95            m_name = _build_pkg_path(pkg_name, pkg, root)
     96            try:
     97                if _prune_whitelist([m_name], blacklist):
     98                    m = find_or_load_module(m_name, [os.path.split(root)[0]])
     99                    packages[m_name] = m
     100                else:
     101                    for d in dirs[:]:
     102                        dirs.remove(d)
     103            except ImportError:
     104                errors.append(m_name)
     105                for d in dirs[:]:
     106                    dirs.remove(d)
     107
     108def _get_all_modules(pkg_name, pkg, blacklist):
     109    modules = Modules().modules
     110    errors = Errors().errors
     111
     112    for p in pkg.__path__:
     113        for f in glob('%s/*.py' %p):
     114            m_name = _build_module_path(pkg_name, pkg, f)
     115            try:
     116                if _prune_whitelist([m_name], blacklist):
     117                    m = find_or_load_module(m_name, [p])
     118                    modules[m_name] = m
     119            except ImportError:
     120               errors.append(m_name)
     121
     122def get_all_modules(whitelist, blacklist=None, exclude_dirs=None):
     123    packages = Packages().packages
     124    modules = Modules().modules
     125    excluded = Excluded().excluded
     126    errors = Errors().errors
     127
     128    whitelist = _prune_whitelist(whitelist, blacklist or [])
     129    _parse_module_list(whitelist)
     130    for pkg_name, pkg in packages.copy().iteritems():
     131        _get_all_packages(pkg_name, pkg, blacklist, exclude_dirs)
     132    for pkg_name, pkg in packages.copy().iteritems():
     133        _get_all_modules(pkg_name, pkg, blacklist)
     134    return packages, modules, list(set(excluded)), list(set(errors))
     135
  • tests/runtests.py

    diff --git a/tests/runtests.py b/tests/runtests.py
    index 9f5b1a6..a5b94c7 100755
    a b  
    11#!/usr/bin/env python
    22
    33import os, sys, traceback
     4import inspect
    45import unittest
    56
    67import django.contrib as contrib
    try: 
    1011except NameError:
    1112    from sets import Set as set     # For Python 2.3
    1213
    13 
    1414CONTRIB_DIR_NAME = 'django.contrib'
    1515MODEL_TESTS_DIR_NAME = 'modeltests'
    1616REGRESSION_TESTS_DIR_NAME = 'regressiontests'
    def django_tests(verbosity, interactive, test_labels): 
    9898    old_language_code = settings.LANGUAGE_CODE
    9999    old_middleware_classes = settings.MIDDLEWARE_CLASSES
    100100
     101    #establish coverage settings for the regression suite
     102    settings.COVERAGE_MODULE_EXCLUDES = ['modeltests*', 'regressiontests*']
     103    settings.COVERAGE_CODE_EXCLUDES = ['def __unicode__\(self\):',
     104                                        'def get_absolute_url\(self\):',
     105                                        'from .* import .*',
     106                                        'import .*',
     107                                        'from *']
     108    # depending on how this is run, we might need to tell the coverage libraries to consider django.*
     109    settings.COVERAGE_ADDITIONAL_MODULES = ['django']
     110
    101111    # Redirect some settings for the duration of these tests.
    102112    settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
    103113    settings.ROOT_URLCONF = 'urls'
    def django_tests(verbosity, interactive, test_labels): 
    158168    from django.test.utils import get_runner
    159169    if not hasattr(settings, 'TEST_RUNNER'):
    160170        settings.TEST_RUNNER = 'django.test.simple.run_tests'
    161     test_runner = get_runner(settings)
    162 
    163     failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests)
     171    if do_coverage:
     172        test_runner = get_runner(settings, coverage=True, reports=True)
     173    else:
     174        test_runner = get_runner(settings, coverage=False, reports=False)
     175
     176    #Check if this is an old-style testrunner, and behave accordingly.
     177    if inspect.isclass(test_runner):
     178        tr = test_runner()
     179        failures = tr.run_tests(test_labels, verbosity=verbosity, interactive=interactive)
     180    else:
     181        failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
    164182    if failures:
    165183        sys.exit(failures)
    166184
    if __name__ == "__main__": 
    184202        help='Tells Django to NOT prompt the user for input of any kind.')
    185203    parser.add_option('--settings',
    186204        help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
     205    parser.add_option('--coverage', action='store_true', dest='coverage', default=False,
     206            help='Tells Django to run the tests with code coverage as well.')
    187207    options, args = parser.parse_args()
    188208    if options.settings:
    189209        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
    190210    elif "DJANGO_SETTINGS_MODULE" not in os.environ:
    191211        parser.error("DJANGO_SETTINGS_MODULE is not set in the environment. "
    192212                      "Set it or use --settings.")
     213    do_coverage = options.coverage
    193214    django_tests(int(options.verbosity), options.interactive, args)
Back to Top