Skip to content

Commit

Permalink
merged in from alanjds including callgraph output
Browse files Browse the repository at this point in the history
  • Loading branch information
garethr committed Mar 28, 2010
2 parents ac92d39 + a4e20c1 commit e5b2e74
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 15 deletions.
18 changes: 13 additions & 5 deletions src/test_extensions/management/commands/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class Command(BaseCommand):
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('--callgraph', action='store_true', dest='callgraph',
default=False,
help='Generate execution call graph (slow!)'),
make_option('--coverage', action='store_true', dest='coverage',
default=False,
help='Show coverage details'),
Expand All @@ -48,6 +51,7 @@ def handle(self, *test_labels, **options):

verbosity = int(options.get('verbosity', 1))
interactive = options.get('interactive', True)
callgraph = options.get('callgraph', False)

# it's quite possible someone, lets say South, might have stolen
# the syncdb command from django. For testing purposes we should
Expand Down Expand Up @@ -82,8 +86,6 @@ def handle(self, *test_labels, **options):
test_module_name = '.'
test_module = __import__(test_module_name, {}, {}, test_path[-1])
test_runner = getattr(test_module, test_path[-1])
#print test_runner
# print test_runner.__file__

if hasattr(settings, 'SKIP_TESTS'):
if not test_labels:
Expand All @@ -97,11 +99,17 @@ def handle(self, *test_labels, **options):
except ValueError:
pass
try:
failures = test_runner(test_labels, verbosity=verbosity,
interactive=interactive) # , skip_apps=skippers)

if options.get('coverage'):
failures = test_runner(test_labels, verbosity=verbosity,
interactive=interactive, callgraph=callgraph)
else:
failures = test_runner(test_labels, verbosity=verbosity,
interactive=interactive)

except TypeError: #Django 1.2
failures = test_runner(verbosity=verbosity, #TODO extend django.test.simple.DjangoTestSuiteRunner
interactive=interactive).run_tests(test_labels)

if failures:
sys.exit(failures)
123 changes: 113 additions & 10 deletions src/test_extensions/testrunners/codecoverage.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
import coverage
import os
import os, sys
from inspect import getmembers, ismodule

from django.conf import settings
from django.test.simple import run_tests as django_test_runner
from django.db.models import get_app, get_apps
from django.utils.functional import curry

from nodatabase import run_tests as nodatabase_run_tests

def is_wanted_module(mod):
included = getattr(settings, "COVERAGE_INCLUDE_MODULES", [])
excluded = getattr(settings, "COVERAGE_EXCLUDE_MODULES", [])

marked_to_include = None

for exclude in excluded:
if exclude.endswith("*"):
if mod.__name__.startswith(exclude[:-1]):
marked_to_include = False
elif mod.__name__ == exclude:
marked_to_include = False

for include in included:
if include.endswith("*"):
if mod.__name__.startswith(include[:-1]):
marked_to_include = True
elif mod.__name__ == include:
marked_to_include = True

# marked_to_include=None handles not user-defined states
if marked_to_include is None:
if included and excluded:
# User defined exactly what they want, so exclude other
marked_to_include = False
elif excluded:
# User could define what the want not, so include other.
marked_to_include = True
elif included:
# User enforced what they want, so exclude other
marked_to_include = False
else:
# Usar said nothing, so include anything
marked_to_include = True

return marked_to_include

def get_coverage_modules(app_module):
"""
Returns a list of modules to report coverage info for, given an
Expand Down Expand Up @@ -49,23 +87,20 @@ def get_all_coverage_modules(app_module):
return mod_list

def run_tests(test_labels, verbosity=1, interactive=True,
extra_tests=[], nodatabase=False, xml_out=False):
extra_tests=[], nodatabase=False, xml_out=False, callgraph=False):
"""
Test runner which displays a code coverage report at the end of the
run.
"""
cov = coverage.coverage()
cov.erase()
cov.use_cache(0)
cov.start()
if nodatabase:
results = nodatabase_run_tests(test_labels, verbosity, interactive,
extra_tests)
else:
results = django_test_runner(test_labels, verbosity, interactive,
extra_tests)
cov.stop()

test_labels = test_labels or getattr(settings, "TEST_APPS", None)
cover_branch = getattr(settings, "COVERAGE_BRANCH_COVERAGE", False)
cov = coverage.coverage(branch=cover_branch, cover_pylib=False)
cov.use_cache(0)

coverage_modules = []
if test_labels:
for label in test_labels:
Expand All @@ -78,6 +113,72 @@ def run_tests(test_labels, verbosity=1, interactive=True,
for app in get_apps():
coverage_modules.extend(get_all_coverage_modules(app))

morfs = filter(is_wanted_module, coverage_modules)

if callgraph:
try:
import pycallgraph
#_include = [i.__name__ for i in coverage_modules]
_included = getattr(settings, "COVERAGE_INCLUDE_MODULES", [])
_excluded = getattr(settings, "COVERAGE_EXCLUDE_MODULES", [])

_included = [i.strip('*')+'*' for i in _included]
_excluded = [i.strip('*')+'*' for i in _included]

_filter_func = pycallgraph.GlobbingFilter(
include=_included or ['*'],
#include=['lotericas.*'],
#exclude=[],
#max_depth=options.max_depth,
)

pycallgraph_enabled = True
except ImportError:
pycallgraph_enabled = False
else:
pycallgraph_enabled = False

cov.start()

if pycallgraph_enabled:
pycallgraph.start_trace(filter_func=_filter_func)

if nodatabase:
results = nodatabase_run_tests(test_labels, verbosity, interactive,
extra_tests)
else:
from django.db import connection
connection.creation.destroy_test_db = lambda *a, **k: None
results = django_test_runner(test_labels, verbosity, interactive,
extra_tests)

if callgraph and pycallgraph_enabled:
pycallgraph.stop_trace()

cov.stop()

report_methd = cov.report
if getattr(settings, "COVERAGE_HTML_REPORT", False) or \
os.environ.get("COVERAGE_HTML_REPORT"):
output_dir = getattr(settings, "COVERAGE_HTML_DIRECTORY", "covhtml")
report_method = curry(cov.html_report, directory=output_dir)
if callgraph and pycallgraph_enabled:
callgraph_path = output_dir + '/' + 'callgraph.png'
pycallgraph.make_dot_graph(callgraph_path)

print >>sys.stdout
print >>sys.stdout, "Coverage HTML reports were output to '%s'" %output_dir
if callgraph:
if pycallgraph_enabled:
print >>sys.stdout, "Call graph was output to '%s'" %callgraph_path
else:
print >>sys.stdout, "Call graph was not generated: Install 'pycallgraph' module to do so"

else:
report_method = cov.report

morfs and report_method(morfs=morfs)

if coverage_modules:
if xml_out:
# using the same output directory as the --xml function uses for testing
Expand All @@ -95,3 +196,5 @@ def run_tests_xml (test_labels, verbosity=1, interactive=True,
extra_tests=[], nodatabase=False):
return run_tests(test_labels, verbosity, interactive,
extra_tests, nodatabase, xml_out=True)


0 comments on commit e5b2e74

Please sign in to comment.