summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/codeunit.py41
-rw-r--r--coverage/collector.py38
-rw-r--r--coverage/control.py18
-rw-r--r--coverage/django.py61
-rw-r--r--coverage/parser.py2
5 files changed, 135 insertions, 25 deletions
diff --git a/coverage/codeunit.py b/coverage/codeunit.py
index 9282687d..59382c23 100644
--- a/coverage/codeunit.py
+++ b/coverage/codeunit.py
@@ -7,6 +7,8 @@ from coverage.misc import CoverageException, NoSource
from coverage.parser import CodeParser, PythonParser
from coverage.phystokens import source_token_lines, source_encoding
+from coverage.django import DjangoTracer
+
def code_unit_factory(morfs, file_locator):
"""Construct a list of CodeUnits from polymorphic inputs.
@@ -22,6 +24,8 @@ def code_unit_factory(morfs, file_locator):
if not isinstance(morfs, (list, tuple)):
morfs = [morfs]
+ django_tracer = DjangoTracer()
+
code_units = []
for morf in morfs:
# Hacked-in Mako support. Define COVERAGE_MAKO_PATH as a fragment of
@@ -35,6 +39,8 @@ def code_unit_factory(morfs, file_locator):
cu.name += '_fako'
code_units.append(cu)
klass = MakoCodeUnit
+ elif isinstance(morf, string_class) and morf.endswith(".html"):
+ klass = DjangoCodeUnit
else:
klass = PythonCodeUnit
code_units.append(klass(morf, file_locator))
@@ -134,6 +140,12 @@ class CodeUnit(object):
"No source for code '%s'." % self.filename
)
+ def source_token_lines(self, source):
+ """Return the 'tokenized' text for the code."""
+ # TODO: Taking source here is wrong, change it?
+ for line in source.splitlines():
+ yield [('txt', line)]
+
def should_be_python(self):
"""Does it seem like this file should contain Python?
@@ -258,12 +270,29 @@ class MakoCodeUnit(CodeUnit):
def get_parser(self, exclude=None):
return MakoParser(self.metadata)
- def source_token_lines(self, source):
- """Return the 'tokenized' text for the code."""
- # TODO: Taking source here is wrong, change it?
- for line in source.splitlines():
- yield [('txt', line)]
-
def source_encoding(self, source):
# TODO: Taking source here is wrong, change it!
return self.metadata['source_encoding']
+
+
+class DjangoCodeUnit(CodeUnit):
+ def source(self):
+ with open(self.filename) as f:
+ return f.read()
+
+ def get_parser(self, exclude=None):
+ return DjangoParser(self.filename)
+
+ def source_encoding(self, source):
+ return "utf8"
+
+
+class DjangoParser(CodeParser):
+ def __init__(self, filename):
+ self.filename = filename
+
+ def parse_source(self):
+ with open(self.filename) as f:
+ source = f.read()
+ executable = set(range(1, len(source.splitlines())+1))
+ return executable, set()
diff --git a/coverage/collector.py b/coverage/collector.py
index 94af5df5..07a87e03 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -45,6 +45,7 @@ class PyTracer(object):
self.should_trace = None
self.should_trace_cache = None
self.warn = None
+ self.handler = None
self.cur_file_data = None
self.last_line = 0
self.data_stack = []
@@ -76,7 +77,7 @@ class PyTracer(object):
self.cur_file_data[pair] = None
if self.coroutine_id_func:
self.data_stack = self.data_stacks[self.coroutine_id_func()]
- self.cur_file_data, self.last_line = self.data_stack.pop()
+ self.handler, self.cur_file_data, self.last_line = self.data_stack.pop()
self.last_exc_back = None
if event == 'call':
@@ -85,19 +86,22 @@ class PyTracer(object):
if self.coroutine_id_func:
self.data_stack = self.data_stacks[self.coroutine_id_func()]
self.last_coroutine = self.coroutine_id_func()
- self.data_stack.append((self.cur_file_data, self.last_line))
+ self.data_stack.append((self.handler, self.cur_file_data, self.last_line))
filename = frame.f_code.co_filename
if filename not in self.should_trace_cache:
- tracename = self.should_trace(filename, frame)
- self.should_trace_cache[filename] = tracename
+ tracename, handler = self.should_trace(filename, frame)
+ self.should_trace_cache[filename] = tracename, handler
else:
- tracename = self.should_trace_cache[filename]
+ tracename, handler = self.should_trace_cache[filename]
#print("called, stack is %d deep, tracename is %r" % (
# len(self.data_stack), tracename))
+ if tracename and handler:
+ tracename = handler.file_name(frame)
if tracename:
if tracename not in self.data:
self.data[tracename] = {}
self.cur_file_data = self.data[tracename]
+ self.handler = handler
else:
self.cur_file_data = None
# Set the last_line to -1 because the next arc will be entering a
@@ -107,14 +111,20 @@ class PyTracer(object):
# Record an executed line.
#if self.coroutine_id_func:
# assert self.last_coroutine == self.coroutine_id_func()
- if self.cur_file_data is not None:
- if self.arcs:
- #print("lin", self.last_line, frame.f_lineno)
- self.cur_file_data[(self.last_line, frame.f_lineno)] = None
- else:
- #print("lin", frame.f_lineno)
- self.cur_file_data[frame.f_lineno] = None
- self.last_line = frame.f_lineno
+ if self.handler:
+ lineno_from, lineno_to = self.handler.line_number_range(frame)
+ else:
+ lineno_from, lineno_to = frame.f_lineno, frame.f_lineno
+ if lineno_from != -1:
+ if self.cur_file_data is not None:
+ if self.arcs:
+ #print("lin", self.last_line, frame.f_lineno)
+ self.cur_file_data[(self.last_line, lineno_from)] = None
+ else:
+ #print("lin", frame.f_lineno)
+ for lineno in range(lineno_from, lineno_to+1):
+ self.cur_file_data[lineno] = None
+ self.last_line = lineno_to
elif event == 'return':
if self.arcs and self.cur_file_data:
first = frame.f_code.co_firstlineno
@@ -123,7 +133,7 @@ class PyTracer(object):
if self.coroutine_id_func:
self.data_stack = self.data_stacks[self.coroutine_id_func()]
self.last_coroutine = self.coroutine_id_func()
- self.cur_file_data, self.last_line = self.data_stack.pop()
+ self.handler, self.cur_file_data, self.last_line = self.data_stack.pop()
#print("returned, stack is %d deep" % (len(self.data_stack)))
elif event == 'exception':
#print("exc", self.last_line, frame.f_lineno)
diff --git a/coverage/control.py b/coverage/control.py
index 14f9b80e..a65a7153 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -18,6 +18,9 @@ from coverage.results import Analysis, Numbers
from coverage.summary import SummaryReporter
from coverage.xmlreport import XmlReporter
+from coverage.django import DjangoTracer
+
+
# Pypy has some unusual stuff in the "stdlib". Consider those locations
# when deciding where the stdlib is.
try:
@@ -152,6 +155,8 @@ class coverage(object):
coroutine=self.config.coroutine,
)
+ self.django_tracer = DjangoTracer() # should this be a class? Singleton...
+
# Suffixes are a bit tricky. We want to use the data suffix only when
# collecting data, not when combining data. So we save it as
# `self.run_suffix` now, and promote it to `self.data_suffix` if we
@@ -277,6 +282,10 @@ class coverage(object):
canonical = self.file_locator.canonical_filename(filename)
+ # DJANGO HACK
+ if self.django_tracer.should_trace(canonical):
+ return canonical, self.django_tracer
+
# If the user specified source or include, then that's authoritative
# about the outer bound of what to measure and we don't have to apply
# any canned exclusions. If they didn't, then we have to exclude the
@@ -302,7 +311,7 @@ class coverage(object):
if self.omit_match and self.omit_match.match(canonical):
return None, "is inside an --omit pattern"
- return canonical, "because we love you"
+ return canonical, None
def _should_trace(self, filename, frame):
"""Decide whether to trace execution in `filename`.
@@ -310,14 +319,15 @@ class coverage(object):
Calls `_should_trace_with_reason`, and returns just the decision.
"""
- canonical, reason = self._should_trace_with_reason(filename, frame)
+ canonical, other = self._should_trace_with_reason(filename, frame)
if self.debug.should('trace'):
if not canonical:
- msg = "Not tracing %r: %s" % (filename, reason)
+ msg = "Not tracing %r: %s" % (filename, other)
+ other = None
else:
msg = "Tracing %r" % (filename,)
self.debug.write(msg)
- return canonical
+ return canonical, other
def _warn(self, msg):
"""Use `msg` as a warning."""
diff --git a/coverage/django.py b/coverage/django.py
new file mode 100644
index 00000000..00f2ed54
--- /dev/null
+++ b/coverage/django.py
@@ -0,0 +1,61 @@
+import sys
+
+
+ALL_TEMPLATE_MAP = {}
+
+def get_line_map(filename):
+ if filename not in ALL_TEMPLATE_MAP:
+ with open(filename) as template_file:
+ template_source = template_file.read()
+ line_lengths = [len(l) for l in template_source.splitlines(True)]
+ ALL_TEMPLATE_MAP[filename] = list(running_sum(line_lengths))
+ return ALL_TEMPLATE_MAP[filename]
+
+def get_line_number(line_map, offset):
+ for lineno, line_offset in enumerate(line_map, start=1):
+ if line_offset >= offset:
+ return lineno
+ return -1
+
+class DjangoTracer(object):
+ def should_trace(self, canonical):
+ return "/django/template/" in canonical
+
+ def source(self, frame):
+ if frame.f_code.co_name != 'render':
+ return None
+ that = frame.f_locals['self']
+ return getattr(that, "source", None)
+
+ def file_name(self, frame):
+ source = self.source(frame)
+ if not source:
+ return None
+ return source[0].name.encode(sys.getfilesystemencoding())
+
+ def line_number_range(self, frame):
+ source = self.source(frame)
+ if not source:
+ return -1, -1
+ filename = source[0].name
+ line_map = get_line_map(filename)
+ start = get_line_number(line_map, source[1][0])
+ end = get_line_number(line_map, source[1][1])
+ if start < 0 or end < 0:
+ return -1, -1
+ return start, end
+
+def running_sum(seq):
+ total = 0
+ for num in seq:
+ total += num
+ yield total
+
+def ppp(obj):
+ ret = []
+ import inspect
+ for name, value in inspect.getmembers(obj):
+ if not callable(value):
+ ret.append("%s=%r" % (name, value))
+ attrs = ", ".join(ret)
+ return "%s: %s" % (obj.__class__, attrs)
diff --git a/coverage/parser.py b/coverage/parser.py
index 5bb15466..c5e95baa 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -15,7 +15,7 @@ class CodeParser(object):
Base class for any code parser.
"""
def translate_lines(self, lines):
- return lines
+ return set(lines)
def translate_arcs(self, arcs):
return arcs