Code

Ticket #4501: 4501-coverage-2.diff

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

Very hacky, but it seems to work. Just need to whittle this down, maybe.

Line 
1diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py
2index 8ebf3da..d7f4caf 100644
3--- a/django/core/management/commands/test.py
4+++ b/django/core/management/commands/test.py
5@@ -1,11 +1,14 @@
6 from django.core.management.base import BaseCommand
7 from optparse import make_option
8 import sys
9+from django.test.utils import start_coverage, stop_coverage
10 
11 class Command(BaseCommand):
12     option_list = BaseCommand.option_list + (
13         make_option('--noinput', action='store_false', dest='interactive', default=True,
14             help='Tells Django to NOT prompt the user for input of any kind.'),
15+        make_option('--coverage', action='store_true', dest='coverage', default=False,
16+            help='Tells Django to run coverage analysis on your test modules.'),
17     )
18     help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.'
19     args = '[appname ...]'
20@@ -18,8 +21,17 @@ class Command(BaseCommand):
21 
22         verbosity = int(options.get('verbosity', 1))
23         interactive = options.get('interactive', True)
24+        use_coverage = options.get('coverage', False)
25         test_runner = get_runner(settings)
26 
27-        failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive)
28+        if use_coverage:
29+            use_coverage = start_coverage(verbosity)
30+
31+        failures = test_runner(test_labels, verbosity=verbosity,
32+                               interactive=interactive)
33+
34         if failures:
35             sys.exit(failures)
36+
37+        if use_coverage:
38+            stop_coverage(test_labels)
39diff --git a/django/test/utils.py b/django/test/utils.py
40index 29babec..3b70467 100644
41--- a/django/test/utils.py
42+++ b/django/test/utils.py
43@@ -65,7 +65,6 @@ def teardown_test_environment():
44 
45     del mail.outbox
46 
47-
48 def get_runner(settings):
49     test_path = settings.TEST_RUNNER.split('.')
50     # Allow for Python 2.5 relative paths
51@@ -76,3 +75,52 @@ def get_runner(settings):
52     test_module = __import__(test_module_name, {}, {}, test_path[-1])
53     test_runner = getattr(test_module, test_path[-1])
54     return test_runner
55+
56+def start_coverage(verbosity=1):
57+    """
58+    Try to start running coverage analysis.
59+    This tries to import coverage.py, returning False if it can't import.
60+    """
61+    try:
62+        import coverage
63+        if verbosity > 1:
64+            print "Running test runner with coverage."
65+        coverage.start()
66+    except:
67+        if verbosity > 0:
68+            print "coverage.py module is not available."
69+        return False
70+
71+def stop_coverage(test_labels, exclude_admin=True):
72+    #This was undefined if it wasn't imported.
73+    #If we get to this point, coverage exists (since it was imported in start)
74+    import coverage
75+    coverage.stop()
76+    coverage_models = []
77+    if test_labels:
78+        for label in test_labels:
79+            if '.' in label:
80+                #Test the app, don't know if we really want coverage here.
81+                label = label.split('.')[0]
82+            try:
83+                #This is for the common case of non-runtests
84+                coverage_models.append(__import__(label, globals(), locals(), ['']))
85+                continue
86+            except:
87+                #This is for the runtests case, this makes me feel that they
88+                #should be seperated totally in code :/
89+                for app in ('modeltests','regressiontests'):
90+                    try:
91+                        coverage_models.append(__import__("%s.%s" % (app,label), globals(), locals(), ['']))
92+                    except:
93+                        pass
94+    else:
95+        from django.db.models.loading import get_apps
96+        for app in get_apps():
97+            if not exclude_admin or app.__name__.find('django.contrib') == -1:
98+                coverage_models.append(app)
99+    print '---------------------------------------------------------------'
100+    print ' Unit Test Code Coverage Results'
101+    print '---------------------------------------------------------------'
102+    coverage.report(coverage_models, show_missing=1)
103+    print '---------------------------------------------------------------'
104diff --git a/tests/runtests.py b/tests/runtests.py
105index bd7f59b..a7b3ba1 100755
106--- a/tests/runtests.py
107+++ b/tests/runtests.py
108@@ -85,8 +85,9 @@ class InvalidModelTestCase(unittest.TestCase):
109         self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected))
110         self.assert_(not missing, "Missing Errors: " + '\n'.join(missing))
111 
112-def django_tests(verbosity, interactive, test_labels):
113+def django_tests(verbosity, interactive, use_coverage, test_labels):
114     from django.conf import settings
115+    from django.test.utils import start_coverage, stop_coverage
116 
117     old_installed_apps = settings.INSTALLED_APPS
118     old_test_database_name = settings.TEST_DATABASE_NAME
119@@ -154,10 +155,17 @@ def django_tests(verbosity, interactive, test_labels):
120         settings.TEST_RUNNER = 'django.test.simple.run_tests'
121     test_runner = get_runner(settings)
122 
123+    if use_coverage:
124+        use_coverage = start_coverage(verbosity)
125+
126     failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests)
127+
128     if failures:
129         sys.exit(failures)
130 
131+    if use_coverage:
132+        stop_coverage(test_labels, exclude_admin=False)
133+
134     # Restore the old settings.
135     settings.INSTALLED_APPS = old_installed_apps
136     settings.ROOT_URLCONF = old_root_urlconf
137@@ -176,6 +184,8 @@ if __name__ == "__main__":
138         help='Verbosity level; 0=minimal output, 1=normal output, 2=all output')
139     parser.add_option('--noinput', action='store_false', dest='interactive', default=True,
140         help='Tells Django to NOT prompt the user for input of any kind.')
141+    parser.add_option('--coverage', action='store_true', dest='coverage', default=False,
142+        help='Tells Django to run coverage analysis on your test modules.'),
143     parser.add_option('--settings',
144         help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
145     options, args = parser.parse_args()
146@@ -184,4 +194,4 @@ if __name__ == "__main__":
147     elif "DJANGO_SETTINGS_MODULE" not in os.environ:
148         parser.error("DJANGO_SETTINGS_MODULE is not set in the environment. "
149                       "Set it or use --settings.")
150-    django_tests(int(options.verbosity), options.interactive, args)
151+    django_tests(int(options.verbosity), options.interactive, options.coverage, args)