diff options
Diffstat (limited to 'coverage')
-rw-r--r-- | coverage/cmdline.py | 40 | ||||
-rw-r--r-- | coverage/html.py | 16 | ||||
-rw-r--r-- | coverage/htmlfiles/pyfile.html | 20 | ||||
-rw-r--r-- | coverage/parser.py | 29 | ||||
-rw-r--r-- | coverage/plugin.py | 14 | ||||
-rw-r--r-- | coverage/templite.py | 100 |
6 files changed, 118 insertions, 101 deletions
diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 221c18d6..38f4b43a 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -699,32 +699,32 @@ def unglob_args(args): HELP_TOPICS = { 'help': """\ - Coverage.py, version %(__version__)s - Measure, collect, and report on code coverage in Python programs. - - usage: %(program_name)s <command> [options] [args] - - Commands: - annotate Annotate source files with execution information. - combine Combine a number of data files. - erase Erase previously collected coverage data. - help Get help on using coverage.py. - html Create an HTML report. - report Report coverage stats on modules. - run Run a Python program and measure code execution. - xml Create an XML report of coverage results. - - Use "%(program_name)s help <command>" for detailed help on any command. - For full documentation, see %(__url__)s + Coverage.py, version %(__version__)s + Measure, collect, and report on code coverage in Python programs. + + usage: %(program_name)s <command> [options] [args] + + Commands: + annotate Annotate source files with execution information. + combine Combine a number of data files. + erase Erase previously collected coverage data. + help Get help on using coverage.py. + html Create an HTML report. + report Report coverage stats on modules. + run Run a Python program and measure code execution. + xml Create an XML report of coverage results. + + Use "%(program_name)s help <command>" for detailed help on any command. + For full documentation, see %(__url__)s """, 'minimum_help': """\ - Code coverage for Python. Use '%(program_name)s help' for help. + Code coverage for Python. Use '%(program_name)s help' for help. """, 'version': """\ - Coverage.py, version %(__version__)s. - Documentation at %(__url__)s + Coverage.py, version %(__version__)s. + Documentation at %(__url__)s """, } diff --git a/coverage/html.py b/coverage/html.py index 0d6e6f96..a7a92095 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -231,7 +231,8 @@ class HtmlReporter(Reporter): else: annotate_long = "%d missed branches: %s" % ( len(longs), - ", ".join("%d) %s" % (num, ann_long) for num, ann_long in enumerate(longs, start=1)), + ", ".join("%d) %s" % (num, ann_long) + for num, ann_long in enumerate(longs, start=1)), ) elif lineno in analysis.statements: line_class.append(c_run) @@ -262,7 +263,7 @@ class HtmlReporter(Reporter): 'fr': fr, 'nums': nums, 'lines': lines, 'time_stamp': self.time_stamp, } - html = spaceless(self.source_tmpl.render(template_values)) + html = self.source_tmpl.render(template_values) html_filename = rootname + ".html" html_path = os.path.join(self.directory, html_filename) @@ -425,17 +426,6 @@ def escape(t): return t.replace("&", "&").replace("<", "<") -def spaceless(html): - """Squeeze out some annoying extra space from an HTML string. - - Nicely-formatted templates mean lots of extra space in the result. - Get rid of some. - - """ - html = re.sub(r">\s+<p ", ">\n<p ", html) - return html - - def pair(ratio): """Format a pair of numbers so JavaScript can read them in an attribute.""" return "%s %s" % ratio diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html index fb8e131e..8542a467 100644 --- a/coverage/htmlfiles/pyfile.html +++ b/coverage/htmlfiles/pyfile.html @@ -71,14 +71,22 @@ <table> <tr> <td class="linenos"> - {% for line in lines %} - <p id="n{{line.number}}" class="{{line.class}}"><a href="#n{{line.number}}">{{line.number}}</a></p> - {% endfor %} +{% for line in lines -%} + <p id="n{{line.number}}" class="{{line.class}}"><a href="#n{{line.number}}">{{line.number}}</a></p> +{% endfor %} </td> <td class="text"> - {% for line in lines %} - <p id="t{{line.number}}" class="{{line.class}}">{% if line.annotate %}<span class="annotate short">{{line.annotate}}</span><span class="annotate long">{{line.annotate_long}}</span>{% endif %}{{line.html}}<span class="strut"> </span></p> - {% endfor %} +{# These are the source lines, which are very sensitive to whitespace. -#} +{# The `{ # - # }` below are comments which slurp up the following space. -#} +{% for line in lines -%} + <p id="t{{line.number}}" class="{{line.class}}">{#-#} + {% if line.annotate -%} + <span class="annotate short">{{line.annotate}}</span>{#-#} + <span class="annotate long">{{line.annotate_long}}</span>{#-#} + {% endif -%} + {{line.html}}<span class="strut"> </span>{#-#} + </p> +{% endfor %} </td> </tr> </table> diff --git a/coverage/parser.py b/coverage/parser.py index f84133d2..f34a26fb 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -305,6 +305,7 @@ class PythonParser(object): return exit_counts def missing_arc_description(self, start, end): + """Provide an English sentence describing a missing arc.""" if self._missing_arc_fragments is None: self._analyze_ast() @@ -316,6 +317,9 @@ class PythonParser(object): if emsg is None: if end < 0: + # Hmm, maybe we have a one-line callable, let's check. + if (-end, end) in self._missing_arc_fragments: + return self.missing_arc_description(-end, end) emsg = "didn't jump to the function exit" else: emsg = "didn't jump to line {lineno}" @@ -491,6 +495,7 @@ class AstArcAnalyzer(object): code_object_handler(node) def add_arc(self, start, end, smsg=None, emsg=None): + """Add an arc, including message fragments to use if it is missing.""" if self.debug: print("\nAdding arc: ({}, {}): {!r}, {!r}".format(start, end, smsg, emsg)) print(short_stack(limit=6)) @@ -672,7 +677,8 @@ class AstArcAnalyzer(object): @contract(returns='ArcStarts') def _handle__Break(self, node): here = self.line_for_node(node) - self.process_break_exits([ArcStart(here, cause="the break on line {lineno} wasn't executed")]) + break_start = ArcStart(here, cause="the break on line {lineno} wasn't executed") + self.process_break_exits([break_start]) return set() @contract(returns='ArcStarts') @@ -703,7 +709,8 @@ class AstArcAnalyzer(object): @contract(returns='ArcStarts') def _handle__Continue(self, node): here = self.line_for_node(node) - self.process_continue_exits([ArcStart(here, cause="the continue on line {lineno} wasn't executed")]) + continue_start = ArcStart(here, cause="the continue on line {lineno} wasn't executed") + self.process_continue_exits([continue_start]) return set() @contract(returns='ArcStarts') @@ -743,14 +750,16 @@ class AstArcAnalyzer(object): @contract(returns='ArcStarts') def _handle__Raise(self, node): here = self.line_for_node(node) - self.process_raise_exits([ArcStart(here, cause="the raise on line {lineno} wasn't executed")]) + raise_start = ArcStart(here, cause="the raise on line {lineno} wasn't executed") + self.process_raise_exits([raise_start]) # `raise` statement jumps away, no exits from here. return set() @contract(returns='ArcStarts') def _handle__Return(self, node): here = self.line_for_node(node) - self.process_return_exits([ArcStart(here, cause="the return on line {lineno} wasn't executed")]) + return_start = ArcStart(here, cause="the return on line {lineno} wasn't executed") + self.process_return_exits([return_start]) # `return` statement jumps away, no exits from here. return set() @@ -794,7 +803,8 @@ class AstArcAnalyzer(object): if last_handler_start is not None: self.add_arc(last_handler_start, handler_start) last_handler_start = handler_start - from_start = ArcStart(handler_start, cause="the exception caught by line {lineno} didn't happen") + from_cause = "the exception caught by line {lineno} didn't happen" + from_start = ArcStart(handler_start, cause=from_cause) handler_exits |= self.add_body_arcs(handler_node.body, from_start=from_start) if node.orelse: @@ -900,7 +910,7 @@ class AstArcAnalyzer(object): if node.body: exits = self.add_body_arcs(node.body, from_start=ArcStart(-1)) for xit in exits: - self.add_arc(xit.lineno, -start, xit.cause, 'exit the module') + self.add_arc(xit.lineno, -start, xit.cause, "didn't exit the module") else: # Empty module. self.add_arc(-1, start) @@ -922,18 +932,19 @@ class AstArcAnalyzer(object): for xit in exits: self.add_arc( xit.lineno, -start, xit.cause, - "exit the body of class '{0}'".format(node.name), + "didn't exit the body of class '{0}'".format(node.name), ) def _make_oneline_code_method(noun): # pylint: disable=no-self-argument - def _method(self, node): + """A function to make methods for online callable _code_object__ methods.""" + def _code_object__oneline_callable(self, node): start = self.line_for_node(node) self.add_arc(-1, start) self.add_arc( start, -start, None, "didn't run the {0} on line {1}".format(noun, start), ) - return _method + return _code_object__oneline_callable _code_object__Lambda = _make_oneline_code_method("lambda") _code_object__GeneratorExp = _make_oneline_code_method("generator expression") diff --git a/coverage/plugin.py b/coverage/plugin.py index 85521e34..97d9c16e 100644 --- a/coverage/plugin.py +++ b/coverage/plugin.py @@ -330,19 +330,13 @@ class FileReporter(object): return {} def missing_arc_description(self, start, end): - """Provide an English phrase describing a missing arc. + """Provide an English sentence describing a missing arc. - For an arc like (123, 456), it should read well to use the phrase like - this:: - - "Line {0} didn't {1}".format(123, missing_arc_description(123, 456)) - - TODO: say more. - - By default, this simply returns the string "jump to {end}". + By default, this simply returns the string "Line {start} didn't jump + to {end}". """ - return "jump to line {end}".format(end=end) + return "Line {start} didn't jump to line {end}".format(start=start, end=end) def source_token_lines(self): """Generate a series of tokenized lines, one for each line in `source`. diff --git a/coverage/templite.py b/coverage/templite.py index f131f748..4c09c113 100644 --- a/coverage/templite.py +++ b/coverage/templite.py @@ -90,6 +90,9 @@ class Templite(object): {# This will be ignored #} + Any of these constructs can have a hypen at the end (`-}}`, `-%}`, `-#}`), + which will collapse the whitespace following the tag. + Construct a Templite with the template text, then use `render` against a dictionary context to create a finished string:: @@ -151,53 +154,64 @@ class Templite(object): # Split the text to form a list of tokens. tokens = re.split(r"(?s)({{.*?}}|{%.*?%}|{#.*?#})", text) + squash = False + for token in tokens: - if token.startswith('{#'): - # Comment: ignore it and move on. - continue - elif token.startswith('{{'): - # An expression to evaluate. - expr = self._expr_code(token[2:-2].strip()) - buffered.append("to_str(%s)" % expr) - elif token.startswith('{%'): - # Action tag: split into words and parse further. - flush_output() - words = token[2:-2].strip().split() - if words[0] == 'if': - # An if statement: evaluate the expression to determine if. - if len(words) != 2: - self._syntax_error("Don't understand if", token) - ops_stack.append('if') - code.add_line("if %s:" % self._expr_code(words[1])) - code.indent() - elif words[0] == 'for': - # A loop: iterate over expression result. - if len(words) != 4 or words[2] != 'in': - self._syntax_error("Don't understand for", token) - ops_stack.append('for') - self._variable(words[1], self.loop_vars) - code.add_line( - "for c_%s in %s:" % ( - words[1], - self._expr_code(words[3]) + if token.startswith('{'): + start, end = 2, -2 + squash = (token[-3] == '-') + if squash: + end = -3 + + if token.startswith('{#'): + # Comment: ignore it and move on. + continue + elif token.startswith('{{'): + # An expression to evaluate. + expr = self._expr_code(token[start:end].strip()) + buffered.append("to_str(%s)" % expr) + elif token.startswith('{%'): + # Action tag: split into words and parse further. + flush_output() + + words = token[start:end].strip().split() + if words[0] == 'if': + # An if statement: evaluate the expression to determine if. + if len(words) != 2: + self._syntax_error("Don't understand if", token) + ops_stack.append('if') + code.add_line("if %s:" % self._expr_code(words[1])) + code.indent() + elif words[0] == 'for': + # A loop: iterate over expression result. + if len(words) != 4 or words[2] != 'in': + self._syntax_error("Don't understand for", token) + ops_stack.append('for') + self._variable(words[1], self.loop_vars) + code.add_line( + "for c_%s in %s:" % ( + words[1], + self._expr_code(words[3]) + ) ) - ) - code.indent() - elif words[0].startswith('end'): - # Endsomething. Pop the ops stack. - if len(words) != 1: - self._syntax_error("Don't understand end", token) - end_what = words[0][3:] - if not ops_stack: - self._syntax_error("Too many ends", token) - start_what = ops_stack.pop() - if start_what != end_what: - self._syntax_error("Mismatched end tag", end_what) - code.dedent() - else: - self._syntax_error("Don't understand tag", words[0]) + code.indent() + elif words[0].startswith('end'): + # Endsomething. Pop the ops stack. + if len(words) != 1: + self._syntax_error("Don't understand end", token) + end_what = words[0][3:] + if not ops_stack: + self._syntax_error("Too many ends", token) + start_what = ops_stack.pop() + if start_what != end_what: + self._syntax_error("Mismatched end tag", end_what) + code.dedent() + else: + self._syntax_error("Don't understand tag", words[0]) else: # Literal content. If it isn't empty, output it. + if squash: + token = token.lstrip() if token: buffered.append(repr(token)) |