diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index a195b57..8b536ff 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -410,7 +410,7 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.user_messages.LegacyFallbackS
 ###########
 
 # The name of the method to use to invoke the test suite
-TEST_RUNNER = 'django.test.simple.run_tests'
+TEST_RUNNER = 'django.test.simple.DefaultTestRunner'
 
 # The name of the database to use for testing purposes.
 # If None, a name of 'test_' + DATABASE_NAME will be assumed
@@ -424,6 +424,55 @@ TEST_DATABASE_CHARSET = None
 TEST_DATABASE_COLLATION = None
 
 ############
+# COVERAGE #
+############
+
+
+# Specify the coverage test runner
+COVERAGE_TEST_RUNNER = 'django.test.test_coverage.ConsoleReportCoverageRunner'
+
+# Specify regular expressions of code blocks the coverage analyzer should
+# ignore as statements (e.g. ``raise NotImplemented``).
+# These statements are not figured in as part of the coverage statistics.
+# This setting is optional.
+COVERAGE_CODE_EXCLUDES = [
+    'def __unicode__\(self\):', 'def get_absolute_url\(self\):',
+    'from .* import .*', 'import .*',
+    ]
+
+# Specify a list of regular expressions of paths to exclude from
+# coverage analysis.
+# Note these paths are ignored by the module introspection tool and take
+# precedence over any package/module settings such as:
+# TODO: THE SETTING FOR MODULES
+# Use this to exclude subdirectories like ``r'\.svn'``, for example.
+# This setting is optional.
+COVERAGE_PATH_EXCLUDES = [r'\.svn']
+
+# Specify a list of additional module paths to include
+# in the coverage analysis. By default, only modules within installed
+# apps are reported. If you have utility modules outside of the app
+# structure, you can include them here.
+# Note this list is *NOT* regular expression, so you have to be explicit,
+# such as 'myproject.utils', and not 'utils$'.
+# This setting is optional.
+COVERAGE_ADDITIONAL_MODULES = []
+
+# Specify a list of regular expressions of module paths to exclude
+# from the coverage analysis. Examples are ``'tests$'`` and ``'urls$'``.
+# This setting is optional.
+COVERAGE_MODULE_EXCLUDES = ['tests$', 'settings$','urls$', 'common.views.test',
+                            '__init__', 'django']
+
+# Specify the directory where you would like the coverage report to create
+# the HTML files.
+# You'll need to make sure this directory exists and is writable by the
+# user account running the test.
+# You should probably set this one explicitly in your own settings file.
+COVERAGE_REPORT_HTML_OUTPUT_DIR = 'test_html'
+
+
+############
 # FIXTURES #
 ############
 
diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py
index 8ebf3da..1f9edab 100644
--- a/django/core/management/commands/test.py
+++ b/django/core/management/commands/test.py
@@ -1,11 +1,16 @@
 from django.core.management.base import BaseCommand
 from optparse import make_option
 import sys
+import inspect
 
 class Command(BaseCommand):
     option_list = BaseCommand.option_list + (
         make_option('--noinput', action='store_false', dest='interactive', default=True,
             help='Tells Django to NOT prompt the user for input of any kind.'),
+        make_option('--coverage', action='store_true', dest='coverage', default=False,
+                    help='Tells Django to run the coverage runner'),
+        make_option('--reports', action='store_true', dest='reports', default=False,
+                    help='Tells Django to output coverage results as HTML reports'),
     )
     help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.'
     args = '[appname ...]'
@@ -18,8 +23,13 @@ class Command(BaseCommand):
 
         verbosity = int(options.get('verbosity', 1))
         interactive = options.get('interactive', True)
-        test_runner = get_runner(settings)
-
-        failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
+        cover = options.get('coverage', False)
+        report = options.get('reports', False)
+        test_runner = get_runner(settings, coverage=cover, reports=report)
+        if inspect.isclass(test_runner):
+            tr = test_runner()
+            failures = tr.run_tests(test_labels, verbosity=verbosity, interactive=interactive)
+        else:
+            failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
         if failures:
             sys.exit(failures)
diff --git a/django/test/simple.py b/django/test/simple.py
index f3c48ba..d79b6ef 100644
--- a/django/test/simple.py
+++ b/django/test/simple.py
@@ -1,4 +1,4 @@
-import unittest
+import sys, time, traceback, unittest
 from django.conf import settings
 from django.db.models import get_app, get_apps
 from django.test import _doctest as doctest
@@ -146,52 +146,65 @@ def reorder_suite(suite, classes):
         bins[0].addTests(bins[i+1])
     return bins[0]
 
-def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
+class DefaultTestRunner(object):
     """
-    Run the unit tests for all the test labels in the provided list.
-    Labels must be of the form:
-     - app.TestClass.test_method
-        Run a single specific test method
-     - app.TestClass
-        Run all the test methods in a given class
-     - app
-        Search for doctests and unittests in the named application.
-
-    When looking for tests, the test runner will look in the models and
-    tests modules for the application.
-
-    A list of 'extra' tests may also be provided; these tests
-    will be added to the test suite.
-
-    Returns the number of tests that failed.
+    The original test runner. No coverage reporting.
     """
-    setup_test_environment()
 
-    settings.DEBUG = False
-    suite = unittest.TestSuite()
-
-    if test_labels:
-        for label in test_labels:
-            if '.' in label:
-                suite.addTest(build_test(label))
-            else:
-                app = get_app(label)
+    def __init__(self):
+        """
+        Placeholder constructor. Want to make it obvious that it can
+        be overridden.
+        """
+        self.isloaded = True
+
+
+    def run_tests(self, test_labels, verbosity=1, interactive=True, extra_tests=[]):
+        """
+        Run the unit tests for all the test labels in the provided list.
+        Labels must be of the form:
+         - app.TestClass.test_method
+            Run a single specific test method
+         - app.TestClass
+            Run all the test methods in a given class
+         - app
+            Search for doctests and unittests in the named application.
+
+        When looking for tests, the test runner will look in the models and
+        tests modules for the application.
+
+        A list of 'extra' tests may also be provided; these tests
+        will be added to the test suite.
+
+        Returns the number of tests that failed.
+        """
+        setup_test_environment()
+
+        settings.DEBUG = False
+        suite = unittest.TestSuite()
+
+        if test_labels:
+            for label in test_labels:
+                if '.' in label:
+                    suite.addTest(build_test(label))
+                else:
+                    app = get_app(label)
+                    suite.addTest(build_suite(app))
+        else:
+            for app in get_apps():
                 suite.addTest(build_suite(app))
-    else:
-        for app in get_apps():
-            suite.addTest(build_suite(app))
 
-    for test in extra_tests:
-        suite.addTest(test)
+        for test in extra_tests:
+            suite.addTest(test)
 
-    suite = reorder_suite(suite, (TestCase,))
+        suite = reorder_suite(suite, (TestCase,))
 
-    old_name = settings.DATABASE_NAME
-    from django.db import connection
-    connection.creation.create_test_db(verbosity, autoclobber=not interactive)
-    result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
-    connection.creation.destroy_test_db(old_name, verbosity)
+        old_name = settings.DATABASE_NAME
+        from django.db import connection
+        connection.creation.create_test_db(verbosity, autoclobber=not interactive)
+        result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
+        connection.creation.destroy_test_db(old_name, verbosity)
 
-    teardown_test_environment()
+        teardown_test_environment()
 
-    return len(result.failures) + len(result.errors)
+        return len(result.failures) + len(result.errors)
diff --git a/django/test/test_coverage.py b/django/test/test_coverage.py
new file mode 100644
index 0000000..337350d
--- /dev/null
+++ b/django/test/test_coverage.py
@@ -0,0 +1,110 @@
+import coverage, time
+import os, sys
+
+from django.conf import settings
+from django.db.models.loading import get_app, get_apps
+from django.test.simple import DefaultTestRunner as base_run_tests
+from django.utils.module_tools import get_all_modules
+from django.utils.translation import ugettext as _
+
+def _get_app_package(app_model_module):
+    """
+    Returns the app module name from the app model module.
+    """
+    return '.'.join(app_model_module.__name__.split('.')[:-1])
+
+
+class BaseCoverageRunner(object):
+    """
+    Placeholder class for coverage runners. Intended to be easily extended.
+    """
+
+    def __init__(self):
+        """Placeholder (since it is overrideable)"""
+        self.cov = coverage.coverage(cover_pylib=True, auto_data=True)
+        self.cov.use_cache(True)
+        self.cov.load()
+        #self.cov.combine()
+
+    def run_tests(self, test_labels, verbosity=1, interactive=True,
+                  extra_tests=[]):
+        """
+        Runs the specified tests while generating code coverage statistics. Upon
+        the tests' completion, the results are printed to stdout.
+        """
+
+        self.cov.start()
+        brt = base_run_tests()
+        results = brt.run_tests(test_labels, verbosity, interactive, extra_tests)
+        self.cov.stop()
+        coverage_modules = []
+        if test_labels:
+            for label in test_labels:
+                label = label.split('.')[0]
+                app = get_app(label)
+                coverage_modules.append(_get_app_package(app))
+        else:
+            for app in get_apps():
+                coverage_modules.append(_get_app_package(app))
+        coverage_modules.extend(getattr(settings, 'COVERAGE_ADDITIONAL_MODULES', []))
+        #This code seems to be a lot a bit magical to include in Django.
+        #Need to look at other implementations and see how they get all the
+        #correct modules to report on.
+        packages, self.modules, self.excludes, self.errors = get_all_modules(
+            coverage_modules, getattr(settings, 'COVERAGE_MODULE_EXCLUDES', []),
+            getattr(settings, 'COVERAGE_PATH_EXCLUDES', []))
+
+        return results
+
+class ConsoleReportCoverageRunner(BaseCoverageRunner):
+
+    def run_tests(self, *args, **kwargs):
+        """docstring for run_tests"""
+        res = super(ConsoleReportCoverageRunner, self).run_tests(*args, **kwargs)
+        self.cov.report(self.modules.values(), show_missing=1)
+        if self.excludes:
+            print >> sys.stdout
+            print >> sys.stdout, _("The following packages or modules were excluded:"),
+            for e in self.excludes:
+                print >> sys.stdout, e,
+            print >>sys.stdout
+        if self.errors:
+            print >> sys.stdout
+            print >> sys.stderr, _("There were problems with the following packages or modules:"),
+            for e in self.errors:
+                print >> sys.stderr, e,
+            print >> sys.stdout
+        return res
+
+class ReportingCoverageRunner(BaseCoverageRunner):
+    """Runs coverage.py analysis, as well as generating detailed HTML reports."""
+    def __init__(self, outdir = None):
+        """
+        Constructor, overrides BaseCoverageRunner. Sets output directory
+        for reports. Parameter or setting.
+        """
+        super(ReportingCoverageRunner, self).__init__()
+        if outdir:
+            self.outdir = outdir
+        else:
+            # Realistically, we aren't going to ship the entire reporting framework..
+            # but for the time being I have left it in.
+            self.outdir = getattr(settings, 'COVERAGE_REPORT_HTML_OUTPUT_DIR', 'test_html')
+            self.outdir = os.path.abspath(self.outdir)
+            # Create directory
+            if not os.path.exists(self.outdir):
+                os.mkdir(self.outdir)
+
+    def run_tests(self, *args, **kwargs):
+        """
+        Overrides BaseCoverageRunner.run_tests, and adds html report generation
+        with the results
+        """
+        res = super(ReportingCoverageRunner, self).run_tests( *args, **kwargs)
+        print _("Outputting HTML reports")
+        self.cov.html_report(self.modules.values(),
+                                directory=self.outdir,
+                                ignore_errors=True,
+                                omit_prefixes=['modeltests'])
+        print _("HTML reports were output to '%s'") % self.outdir
+        return res
diff --git a/django/test/utils.py b/django/test/utils.py
index 9d39eee..1431059 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -68,8 +68,18 @@ def teardown_test_environment():
 
     del mail.outbox
 
-def get_runner(settings):
-    test_path = settings.TEST_RUNNER.split('.')
+def get_runner(settings, coverage=False, reports=False):
+    """
+    Based on the settings and parameters, returns the appropriate test
+    runner class.
+    """
+    if coverage:
+        if reports:
+            test_path = 'django.test.test_coverage.ReportingCoverageRunner'.split('.')
+        else:
+            test_path = settings.COVERAGE_TEST_RUNNER.split('.')
+    else:
+        test_path = settings.TEST_RUNNER.split('.')
     # Allow for Python 2.5 relative paths
     if len(test_path) > 1:
         test_module_name = '.'.join(test_path[:-1])
diff --git a/django/utils/module_tools/__init__.py b/django/utils/module_tools/__init__.py
new file mode 100644
index 0000000..976d4b5
--- /dev/null
+++ b/django/utils/module_tools/__init__.py
@@ -0,0 +1,3 @@
+from module_loader import *
+from module_walker import *
+
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
--- /dev/null
+++ b/django/utils/module_tools/data_storage.py
@@ -0,0 +1,42 @@
+"""
+Copyright 2009 55 Minutes (http://www.55minutes.com)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+__all__ = ('Packages', 'Modules', 'Excluded', 'Errors')
+
+class SingletonType(type):
+    def __call__(cls, *args, **kwargs):
+        if getattr(cls, '__instance__', None) is None:
+            instance = cls.__new__(cls)
+            instance.__init__(*args, **kwargs)
+            cls.__instance__ = instance
+        return cls.__instance__
+
+class Packages(object):
+    __metaclass__ = SingletonType
+    packages = {}
+
+class Modules(object):
+    __metaclass__ = SingletonType
+    modules = {}
+
+class Excluded(object):
+    __metaclass__ = SingletonType
+    excluded = []
+
+class Errors(object):
+    __metaclass__ = SingletonType
+    errors = []
+
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
--- /dev/null
+++ b/django/utils/module_tools/module_loader.py
@@ -0,0 +1,79 @@
+"""
+Copyright 2009 55 Minutes (http://www.55minutes.com)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import imp, sys, types
+
+__all__ = ('find_or_load_module',)
+
+def _brute_force_find_module(module_name, module_path, module_type):
+    for m in [m for n, m in sys.modules.iteritems() if type(m) == types.ModuleType]:
+        m_path = []
+        try:
+            if module_type in (imp.PY_COMPILED, imp.PY_SOURCE):
+                m_path = [m.__file__]
+            elif module_type==imp.PKG_DIRECTORY:
+                m_path = m.__path__
+        except AttributeError:
+            pass
+        for p in m_path:
+            if p.startswith(module_path):
+                return m
+    return None
+
+def _load_module(module_name, fo, fp, desc):
+    suffix, mode, mtype = desc
+    if module_name in sys.modules and \
+       sys.modules[module_name].__file__.startswith(fp):
+        module = sys.modules[module_name]
+    else:
+        module = _brute_force_find_module(module_name, fp, mtype)
+    if not module:
+        try:
+            module = imp.load_module(module_name, fo, fp, desc)
+        except:
+            raise ImportError
+    return module
+
+def _load_package(pkg_name, fp, desc):
+    suffix, mode, mtype = desc
+    if pkg_name in sys.modules:
+        if fp in sys.modules[pkg_name].__path__:
+            pkg = sys.modules[pkg_name]
+    else:
+        pkg = _brute_force_find_module(pkg_name, fp, mtype)
+    if not pkg:
+        pkg = imp.load_module(pkg_name, None, fp, desc)
+    return pkg
+
+def find_or_load_module(module_name, path=None):
+    """
+    Attempts to lookup ``module_name`` in ``sys.modules``, else uses the
+    facilities in the ``imp`` module to load the module.
+
+    If module_name specified is not of type ``imp.PY_SOURCE`` or
+    ``imp.PKG_DIRECTORY``, raise ``ImportError`` since we don't know
+    what to do with those.
+    """
+    fo, fp, desc = imp.find_module(module_name.split('.')[-1], path)
+    suffix, mode, mtype = desc
+    if mtype in (imp.PY_SOURCE, imp.PY_COMPILED):
+        module = _load_module(module_name, fo, fp, desc)
+    elif mtype==imp.PKG_DIRECTORY:
+        module = _load_package(module_name, fp, desc)
+    else:
+        raise ImportError("Don't know how to handle this module type.")
+    return module
+
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
--- /dev/null
+++ b/django/utils/module_tools/module_walker.py
@@ -0,0 +1,135 @@
+"""
+Copyright 2009 55 Minutes (http://www.55minutes.com)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import os, re, sys
+from glob import glob
+
+from data_storage import *
+from module_loader import find_or_load_module
+
+try:
+    set
+except:
+    from sets import Set as set
+
+__all__ = ('get_all_modules',)
+
+def _build_pkg_path(pkg_name, pkg, path):
+    for rp in [x for x in pkg.__path__ if path.startswith(x)]:
+        p = path.replace(rp, '').replace(os.path.sep, '.')
+        return pkg_name + p
+
+def _build_module_path(pkg_name, pkg, path):
+    return _build_pkg_path(pkg_name, pkg, os.path.splitext(path)[0])
+
+def _prune_whitelist(whitelist, blacklist):
+    excluded = Excluded().excluded
+
+    for wp in whitelist[:]:
+        for bp in blacklist:
+            if re.search(bp, wp):
+                whitelist.remove(wp)
+                excluded.append(wp)
+                break
+    return whitelist
+
+def _parse_module_list(m_list):
+    packages = Packages().packages
+    modules = Modules().modules
+    excluded = Excluded().excluded
+    errors = Errors().errors
+
+    for m in m_list:
+        components = m.split('.')
+        m_name = ''
+        search_path = []
+        processed=False
+        for i, c in enumerate(components):
+            m_name = '.'.join([x for x in m_name.split('.') if x] + [c])
+            try:
+                module = find_or_load_module(m_name, search_path or None)
+            except ImportError:
+                processed=True
+                errors.append(m)
+                break
+            try:
+                search_path.extend(module.__path__)
+            except AttributeError:
+                processed = True
+                if i+1==len(components):
+                    modules[m_name] = module
+                else:
+                    errors.append(m)
+                    break
+        if not processed:
+            packages[m_name] = module
+
+def prune_dirs(root, dirs, exclude_dirs):
+    _dirs = [os.path.join(root, d) for d in dirs]
+    for i, p in enumerate(_dirs):
+        for e in exclude_dirs:
+            if re.search(e, p):
+                del dirs[i]
+                break
+
+def _get_all_packages(pkg_name, pkg, blacklist, exclude_dirs):
+    packages = Packages().packages
+    errors = Errors().errors
+
+    for path in pkg.__path__:
+        for root, dirs, files in os.walk(path):
+            prune_dirs(root, dirs, exclude_dirs or [])
+            m_name = _build_pkg_path(pkg_name, pkg, root)
+            try:
+                if _prune_whitelist([m_name], blacklist):
+                    m = find_or_load_module(m_name, [os.path.split(root)[0]])
+                    packages[m_name] = m
+                else:
+                    for d in dirs[:]:
+                        dirs.remove(d)
+            except ImportError:
+                errors.append(m_name)
+                for d in dirs[:]:
+                    dirs.remove(d)
+
+def _get_all_modules(pkg_name, pkg, blacklist):
+    modules = Modules().modules
+    errors = Errors().errors
+
+    for p in pkg.__path__:
+        for f in glob('%s/*.py' %p):
+            m_name = _build_module_path(pkg_name, pkg, f)
+            try:
+                if _prune_whitelist([m_name], blacklist):
+                    m = find_or_load_module(m_name, [p])
+                    modules[m_name] = m
+            except ImportError:
+               errors.append(m_name)
+
+def get_all_modules(whitelist, blacklist=None, exclude_dirs=None):
+    packages = Packages().packages
+    modules = Modules().modules
+    excluded = Excluded().excluded
+    errors = Errors().errors
+
+    whitelist = _prune_whitelist(whitelist, blacklist or [])
+    _parse_module_list(whitelist)
+    for pkg_name, pkg in packages.copy().iteritems():
+        _get_all_packages(pkg_name, pkg, blacklist, exclude_dirs)
+    for pkg_name, pkg in packages.copy().iteritems():
+        _get_all_modules(pkg_name, pkg, blacklist)
+    return packages, modules, list(set(excluded)), list(set(errors))
+
diff --git a/tests/runtests.py b/tests/runtests.py
index 9f5b1a6..a5b94c7 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 
 import os, sys, traceback
+import inspect
 import unittest
 
 import django.contrib as contrib
@@ -10,7 +11,6 @@ try:
 except NameError:
     from sets import Set as set     # For Python 2.3
 
-
 CONTRIB_DIR_NAME = 'django.contrib'
 MODEL_TESTS_DIR_NAME = 'modeltests'
 REGRESSION_TESTS_DIR_NAME = 'regressiontests'
@@ -98,6 +98,16 @@ def django_tests(verbosity, interactive, test_labels):
     old_language_code = settings.LANGUAGE_CODE
     old_middleware_classes = settings.MIDDLEWARE_CLASSES
 
+    #establish coverage settings for the regression suite
+    settings.COVERAGE_MODULE_EXCLUDES = ['modeltests*', 'regressiontests*']
+    settings.COVERAGE_CODE_EXCLUDES = ['def __unicode__\(self\):',
+                                        'def get_absolute_url\(self\):',
+                                        'from .* import .*',
+                                        'import .*',
+                                        'from *']
+    # depending on how this is run, we might need to tell the coverage libraries to consider django.*
+    settings.COVERAGE_ADDITIONAL_MODULES = ['django']
+
     # Redirect some settings for the duration of these tests.
     settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
     settings.ROOT_URLCONF = 'urls'
@@ -158,9 +168,17 @@ def django_tests(verbosity, interactive, test_labels):
     from django.test.utils import get_runner
     if not hasattr(settings, 'TEST_RUNNER'):
         settings.TEST_RUNNER = 'django.test.simple.run_tests'
-    test_runner = get_runner(settings)
-
-    failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests)
+    if do_coverage:
+        test_runner = get_runner(settings, coverage=True, reports=True)
+    else:
+        test_runner = get_runner(settings, coverage=False, reports=False)
+
+    #Check if this is an old-style testrunner, and behave accordingly.
+    if inspect.isclass(test_runner):
+        tr = test_runner()
+        failures = tr.run_tests(test_labels, verbosity=verbosity, interactive=interactive)
+    else:
+        failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
     if failures:
         sys.exit(failures)
 
@@ -184,10 +202,13 @@ if __name__ == "__main__":
         help='Tells Django to NOT prompt the user for input of any kind.')
     parser.add_option('--settings',
         help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
+    parser.add_option('--coverage', action='store_true', dest='coverage', default=False,
+            help='Tells Django to run the tests with code coverage as well.')
     options, args = parser.parse_args()
     if options.settings:
         os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
     elif "DJANGO_SETTINGS_MODULE" not in os.environ:
         parser.error("DJANGO_SETTINGS_MODULE is not set in the environment. "
                       "Set it or use --settings.")
+    do_coverage = options.coverage
     django_tests(int(options.verbosity), options.interactive, args)
