diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2013-12-13 22:45:10 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2013-12-13 22:45:10 -0500 |
commit | 2df9b1c35cbb5c92204fc5923368a3d619a34f6d (patch) | |
tree | d1ede8ffef812ba4e345b08f698f001ebe69cb56 /coverage | |
parent | 84221611890880b749dbb650e8d07ac8918dba46 (diff) | |
parent | 7c66441eab3af17539c478a2cb4e19cd93ba0cf4 (diff) | |
download | python-coveragepy-git-2df9b1c35cbb5c92204fc5923368a3d619a34f6d.tar.gz |
Merged 4.0 to default
Diffstat (limited to 'coverage')
-rw-r--r-- | coverage/annotate.py | 1 | ||||
-rw-r--r-- | coverage/backward.py | 43 | ||||
-rw-r--r-- | coverage/cmdline.py | 31 | ||||
-rw-r--r-- | coverage/codeunit.py | 2 | ||||
-rw-r--r-- | coverage/config.py | 2 | ||||
-rw-r--r-- | coverage/control.py | 16 | ||||
-rw-r--r-- | coverage/data.py | 16 | ||||
-rw-r--r-- | coverage/debug.py | 2 | ||||
-rw-r--r-- | coverage/execfile.py | 64 | ||||
-rw-r--r-- | coverage/files.py | 2 | ||||
-rw-r--r-- | coverage/html.py | 29 | ||||
-rw-r--r-- | coverage/misc.py | 10 | ||||
-rw-r--r-- | coverage/parser.py | 50 | ||||
-rw-r--r-- | coverage/phystokens.py | 10 | ||||
-rw-r--r-- | coverage/results.py | 43 | ||||
-rw-r--r-- | coverage/templite.py | 2 | ||||
-rw-r--r-- | coverage/version.py | 2 | ||||
-rw-r--r-- | coverage/xmlreport.py | 7 |
18 files changed, 116 insertions, 216 deletions
diff --git a/coverage/annotate.py b/coverage/annotate.py index 5c396784..19777eaf 100644 --- a/coverage/annotate.py +++ b/coverage/annotate.py @@ -2,7 +2,6 @@ import os, re -from coverage.backward import sorted # pylint: disable=W0622 from coverage.report import Reporter class AnnotateReporter(Reporter): diff --git a/coverage/backward.py b/coverage/backward.py index 7d268545..8237d01b 100644 --- a/coverage/backward.py +++ b/coverage/backward.py @@ -8,47 +8,6 @@ import os, re, sys -# Python 2.3 doesn't have `set` -try: - set = set # new in 2.4 -except NameError: - from sets import Set as set - -# Python 2.3 doesn't have `sorted`. -try: - sorted = sorted -except NameError: - def sorted(iterable): - """A 2.3-compatible implementation of `sorted`.""" - lst = list(iterable) - lst.sort() - return lst - -# Python 2.3 doesn't have `reversed`. -try: - reversed = reversed -except NameError: - def reversed(iterable): - """A 2.3-compatible implementation of `reversed`.""" - lst = list(iterable) - return lst[::-1] - -# rpartition is new in 2.5 -try: - "".rpartition -except AttributeError: - def rpartition(s, sep): - """Implement s.rpartition(sep) for old Pythons.""" - i = s.rfind(sep) - if i == -1: - return ('', '', s) - else: - return (s[:i], sep, s[i+len(sep):]) -else: - def rpartition(s, sep): - """A common interface for new Pythons.""" - return s.rpartition(sep) - # Pythons 2 and 3 differ on where to get StringIO try: from cStringIO import StringIO @@ -164,7 +123,7 @@ else: def binary_bytes(byte_values): """Produce a byte string with the ints from `byte_values`.""" - return "".join([chr(b) for b in byte_values]) + return "".join(chr(b) for b in byte_values) def byte_to_int(byte_value): """Turn an element of a bytes object into an int.""" diff --git a/coverage/cmdline.py b/coverage/cmdline.py index ea112a8b..c311976d 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -2,7 +2,6 @@ import optparse, os, sys, time, traceback -from coverage.backward import sorted # pylint: disable=W0622 from coverage.execfile import run_python_file, run_python_module from coverage.misc import CoverageException, ExceptionDuringRun, NoSource from coverage.debug import info_formatter @@ -565,17 +564,16 @@ class CoverageScript(object): self.coverage.start() code_ran = True try: - try: - if options.module: - sys.path[0] = '' - self.run_python_module(args[0], args) - else: - filename = args[0] - sys.path[0] = os.path.abspath(os.path.dirname(filename)) - self.run_python_file(filename, args) - except NoSource: - code_ran = False - raise + if options.module: + sys.path[0] = '' + self.run_python_module(args[0], args) + else: + filename = args[0] + sys.path[0] = os.path.abspath(os.path.dirname(filename)) + self.run_python_file(filename, args) + except NoSource: + code_ran = False + raise finally: self.coverage.stop() if code_ran: @@ -722,21 +720,18 @@ def main(argv=None): end = time.clock() if 0: print("time: %.3fs" % (end - start)) - except ExceptionDuringRun: + except ExceptionDuringRun as err: # An exception was caught while running the product code. The # sys.exc_info() return tuple is packed into an ExceptionDuringRun # exception. - _, err, _ = sys.exc_info() traceback.print_exception(*err.args) status = ERR - except CoverageException: + except CoverageException as err: # A controlled error inside coverage.py: print the message to the user. - _, err, _ = sys.exc_info() print(err) status = ERR - except SystemExit: + except SystemExit as err: # The user called `sys.exit()`. Exit with their argument, if any. - _, err, _ = sys.exc_info() if err.args: status = err.args[0] else: diff --git a/coverage/codeunit.py b/coverage/codeunit.py index ca1ae5c5..c58e237b 100644 --- a/coverage/codeunit.py +++ b/coverage/codeunit.py @@ -52,7 +52,7 @@ class CodeUnit(object): else: f = morf # .pyc files should always refer to a .py instead. - if f.endswith('.pyc') or f.endswith('.pyo'): + if f.endswith(('.pyc', '.pyo')): f = f[:-1] elif f.endswith('$py.class'): # Jython f = f[:-9] + ".py" diff --git a/coverage/config.py b/coverage/config.py index 87318ff1..6223afda 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -25,7 +25,7 @@ class HandyConfigParser(configparser.RawConfigParser): def dollar_replace(m): """Called for each $replacement.""" # Only one of the groups will have matched, just get its text. - word = [w for w in m.groups() if w is not None][0] + word = next(w for w in m.groups() if w is not None) if word == "$": return "$" else: diff --git a/coverage/control.py b/coverage/control.py index f75a3dda..fa6fec74 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -3,7 +3,7 @@ import atexit, os, random, socket, sys from coverage.annotate import AnnotateReporter -from coverage.backward import string_class, iitems, sorted # pylint: disable=W0622 +from coverage.backward import string_class, iitems from coverage.codeunit import code_unit_factory, CodeUnit from coverage.collector import Collector from coverage.config import CoverageConfig @@ -98,8 +98,7 @@ class coverage(object): config_file = ".coveragerc" try: self.config.from_file(config_file) - except ValueError: - _, err, _ = sys.exc_info() + except ValueError as err: raise CoverageException( "Couldn't read config file %s: %s" % (config_file, err) ) @@ -689,12 +688,11 @@ class coverage(object): outfile = open(self.config.xml_output, "w") file_to_close = outfile try: - try: - reporter = XmlReporter(self, self.config) - return reporter.report(morfs, outfile=outfile) - except CoverageException: - delete_file = True - raise + reporter = XmlReporter(self, self.config) + return reporter.report(morfs, outfile=outfile) + except CoverageException: + delete_file = True + raise finally: if file_to_close: file_to_close.close() diff --git a/coverage/data.py b/coverage/data.py index fb88c5b1..042b6405 100644 --- a/coverage/data.py +++ b/coverage/data.py @@ -2,7 +2,7 @@ import os -from coverage.backward import iitems, pickle, sorted # pylint: disable=W0622 +from coverage.backward import iitems, pickle from coverage.files import PathAliases from coverage.misc import file_be_gone @@ -101,13 +101,13 @@ class CoverageData(object): def line_data(self): """Return the map from filenames to lists of line numbers executed.""" return dict( - [(f, sorted(lmap.keys())) for f, lmap in iitems(self.lines)] + (f, sorted(lmap.keys())) for f, lmap in iitems(self.lines) ) def arc_data(self): """Return the map from filenames to lists of line number pairs.""" return dict( - [(f, sorted(amap.keys())) for f, amap in iitems(self.arcs)] + (f, sorted(amap.keys())) for f, amap in iitems(self.arcs) ) def write_file(self, filename): @@ -128,11 +128,8 @@ class CoverageData(object): self.debug.write("Writing data to %r" % (filename,)) # Write the pickle to the file. - fdata = open(filename, 'wb') - try: + with open(filename, 'wb') as fdata: pickle.dump(data, fdata, 2) - finally: - fdata.close() def read_file(self, filename): """Read the coverage data from `filename`.""" @@ -142,11 +139,8 @@ class CoverageData(object): """Return the raw pickled data from `filename`.""" if self.debug and self.debug.should('dataio'): self.debug.write("Reading data from %r" % (filename,)) - fdata = open(filename, 'rb') - try: + with open(filename, 'rb') as fdata: data = pickle.load(fdata) - finally: - fdata.close() return data def _read_file(self, filename): diff --git a/coverage/debug.py b/coverage/debug.py index 104f3b1d..6908383d 100644 --- a/coverage/debug.py +++ b/coverage/debug.py @@ -41,7 +41,7 @@ def info_formatter(info): nicely formatted, ready to print. """ - label_len = max([len(l) for l, _d in info]) + label_len = max(len(l) for l, _d in info) for label, data in info: if data == []: data = "-none-" diff --git a/coverage/execfile.py b/coverage/execfile.py index f6ebdf79..f90096e9 100644 --- a/coverage/execfile.py +++ b/coverage/execfile.py @@ -31,35 +31,33 @@ def run_python_module(modulename, args): openfile = None glo, loc = globals(), locals() try: - try: - # Search for the module - inside its parent package, if any - using - # standard import mechanics. - if '.' in modulename: - packagename, name = rsplit1(modulename, '.') - package = __import__(packagename, glo, loc, ['__path__']) - searchpath = package.__path__ - else: - packagename, name = None, modulename - searchpath = None # "top-level search" in imp.find_module() + # Search for the module - inside its parent package, if any - using + # standard import mechanics. + if '.' in modulename: + packagename, name = rsplit1(modulename, '.') + package = __import__(packagename, glo, loc, ['__path__']) + searchpath = package.__path__ + else: + packagename, name = None, modulename + searchpath = None # "top-level search" in imp.find_module() + openfile, pathname, _ = imp.find_module(name, searchpath) + + # Complain if this is a magic non-file module. + if openfile is None and pathname is None: + raise NoSource( + "module does not live in a file: %r" % modulename + ) + + # If `modulename` is actually a package, not a mere module, then we + # pretend to be Python 2.7 and try running its __main__.py script. + if openfile is None: + packagename = modulename + name = '__main__' + package = __import__(packagename, glo, loc, ['__path__']) + searchpath = package.__path__ openfile, pathname, _ = imp.find_module(name, searchpath) - - # Complain if this is a magic non-file module. - if openfile is None and pathname is None: - raise NoSource( - "module does not live in a file: %r" % modulename - ) - - # If `modulename` is actually a package, not a mere module, then we - # pretend to be Python 2.7 and try running its __main__.py script. - if openfile is None: - packagename = modulename - name = '__main__' - package = __import__(packagename, glo, loc, ['__path__']) - searchpath = package.__path__ - openfile, pathname, _ = imp.find_module(name, searchpath) - except ImportError: - _, err, _ = sys.exc_info() - raise NoSource(str(err)) + except ImportError as err: + raise NoSource(str(err)) finally: if openfile: openfile.close() @@ -94,7 +92,7 @@ def run_python_file(filename, args, package=None): try: # Make a code object somehow. - if filename.endswith(".pyc") or filename.endswith(".pyo"): + if filename.endswith((".pyc", ".pyo")): code = make_code_from_pyc(filename) else: code = make_code_from_py(filename) @@ -129,10 +127,8 @@ def make_code_from_py(filename): except IOError: raise NoSource("No file to run: %r" % filename) - try: + with source_file: source = source_file.read() - finally: - source_file.close() # We have the source. `compile` still needs the last line to be clean, # so make sure it is, then compile a code object from it. @@ -150,7 +146,7 @@ def make_code_from_pyc(filename): except IOError: raise NoCode("No file to run: %r" % filename) - try: + with fpyc: # First four bytes are a version-specific magic number. It has to # match or we won't run the file. magic = fpyc.read(4) @@ -165,7 +161,5 @@ def make_code_from_pyc(filename): # The rest of the file is the code object we want. code = marshal.load(fpyc) - finally: - fpyc.close() return code diff --git a/coverage/files.py b/coverage/files.py index 464535a8..94388f96 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -137,7 +137,7 @@ def prep_patterns(patterns): """ prepped = [] for p in patterns or []: - if p.startswith("*") or p.startswith("?"): + if p.startswith(("*", "?")): prepped.append(p) else: prepped.append(abs_file(p)) diff --git a/coverage/html.py b/coverage/html.py index 5242236c..d168e351 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -43,11 +43,8 @@ def data_filename(fname, pkgdir=""): def data(fname): """Return the contents of a data file of ours.""" - data_file = open(data_filename(fname)) - try: + with open(data_filename(fname)) as data_file: return data_file.read() - finally: - data_file.close() class HtmlReporter(Reporter): @@ -140,11 +137,8 @@ class HtmlReporter(Reporter): def write_html(self, fname, html): """Write `html` to `fname`, properly encoded.""" - fout = open(fname, "wb") - try: + with open(fname, "wb") as fout: fout.write(html.encode('ascii', 'xmlcharrefreplace')) - finally: - fout.close() def file_hash(self, source, cu): """Compute a hash that changes if the file needs to be re-reported.""" @@ -156,10 +150,8 @@ class HtmlReporter(Reporter): def html_file(self, cu, analysis): """Generate an HTML file for one source file.""" source_file = cu.source_file() - try: + with source_file: source = source_file.read() - finally: - source_file.close() # Find out if the file on disk is already correct. flat_rootname = cu.flat_rootname() @@ -195,8 +187,7 @@ class HtmlReporter(Reporter): lines = [] - for lineno, line in enumerate(source_token_lines(source)): - lineno += 1 # 1-based line numbers. + for lineno, line in enumerate(source_token_lines(source), start=1): # Figure out how to mark this line. line_class = [] annotate_html = "" @@ -271,7 +262,7 @@ class HtmlReporter(Reporter): data("index.html"), self.template_globals ) - self.totals = sum([f['nums'] for f in self.files]) + self.totals = sum(f['nums'] for f in self.files) html = index_tmpl.render({ 'arcs': self.arcs, @@ -310,11 +301,8 @@ class HtmlStatus(object): usable = False try: status_file = os.path.join(directory, self.STATUS_FILE) - fstatus = open(status_file, "rb") - try: + with open(status_file, "rb") as fstatus: status = pickle.load(fstatus) - finally: - fstatus.close() except (IOError, ValueError): usable = False else: @@ -339,11 +327,8 @@ class HtmlStatus(object): 'settings': self.settings, 'files': self.files, } - fout = open(status_file, "wb") - try: + with open(status_file, "wb") as fout: pickle.dump(status, fout) - finally: - fout.close() def settings_hash(self): """Get the hash of the coverage.py settings.""" diff --git a/coverage/misc.py b/coverage/misc.py index 0378173f..c88d4ecd 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -3,9 +3,8 @@ import errno import inspect import os -import sys -from coverage.backward import md5, sorted # pylint: disable=W0622 +from coverage.backward import md5 from coverage.backward import string_class, to_bytes @@ -59,7 +58,7 @@ def format_lines(statements, lines): def short_stack(): """Return a string summarizing the call stack.""" stack = inspect.stack()[:0:-1] - return "\n".join(["%30s : %s @%d" % (t[3],t[1],t[2]) for t in stack]) + return "\n".join("%30s : %s @%d" % (t[3],t[1],t[2]) for t in stack) def expensive(fn): @@ -88,7 +87,7 @@ def bool_or_none(b): def join_regex(regexes): """Combine a list of regexes into one that matches any of them.""" if len(regexes) > 1: - return "|".join(["(%s)" % r for r in regexes]) + return "|".join("(%s)" % r for r in regexes) elif regexes: return regexes[0] else: @@ -99,8 +98,7 @@ def file_be_gone(path): """Remove a file, and don't get annoyed if it doesn't exist.""" try: os.remove(path) - except OSError: - _, e, _ = sys.exc_info() + except OSError as e: if e.errno != errno.ENOENT: raise diff --git a/coverage/parser.py b/coverage/parser.py index 7a145a2a..de6590aa 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -1,10 +1,9 @@ """Code parsing for Coverage.""" -import dis, re, sys, token, tokenize +import collections, dis, re, token, tokenize -from coverage.backward import set, sorted, StringIO # pylint: disable=W0622 +from coverage.backward import StringIO from coverage.backward import open_source, range # pylint: disable=W0622 -from coverage.backward import reversed # pylint: disable=W0622 from coverage.backward import bytes_to_ints from coverage.bytecode import ByteCodes, CodeObjects from coverage.misc import nice_pair, expensive, join_regex @@ -26,13 +25,9 @@ class CodeParser(object): self.text = text if not self.text: try: - sourcef = open_source(self.filename) - try: + with open_source(self.filename) as sourcef: self.text = sourcef.read() - finally: - sourcef.close() - except IOError: - _, err, _ = sys.exc_info() + except IOError as err: raise NoSource( "No source for code: '%s': %s" % (self.filename, err) ) @@ -66,13 +61,13 @@ class CodeParser(object): # Lazily-created ByteParser self._byte_parser = None - def _get_byte_parser(self): + @property + def byte_parser(self): """Create a ByteParser on demand.""" if not self._byte_parser: self._byte_parser = \ ByteParser(text=self.text, filename=self.filename) return self._byte_parser - byte_parser = property(_get_byte_parser) def lines_matching(self, *regexes): """Find the lines matching one of a list of regexes. @@ -84,9 +79,9 @@ class CodeParser(object): """ regex_c = re.compile(join_regex(regexes)) matches = set() - for i, ltext in enumerate(self.lines): + for i, ltext in enumerate(self.lines, start=1): if regex_c.search(ltext): - matches.add(i+1) + matches.add(i) return matches def _raw_parse(self): @@ -208,8 +203,7 @@ class CodeParser(object): """ try: self._raw_parse() - except (tokenize.TokenError, IndentationError): - _, tokerr, _ = sys.exc_info() + except (tokenize.TokenError, IndentationError) as tokerr: msg, lineno = tokerr.args raise NotPython( "Couldn't parse '%s' as Python source: '%s' at %s" % @@ -225,6 +219,7 @@ class CodeParser(object): return lines, excluded_lines + @expensive def arcs(self): """Get information about the arcs available in the code. @@ -239,8 +234,8 @@ class CodeParser(object): if fl1 != fl2: all_arcs.append((fl1, fl2)) return sorted(all_arcs) - arcs = expensive(arcs) + @expensive def exit_counts(self): """Get a mapping from line numbers to count of exits from that line. @@ -248,7 +243,7 @@ class CodeParser(object): """ excluded_lines = self.first_lines(self.excluded) - exit_counts = {} + exit_counts = collections.defaultdict(int) for l1, l2 in self.arcs(): if l1 < 0: # Don't ever report -1 as a line number @@ -259,8 +254,6 @@ class CodeParser(object): if l2 in excluded_lines: # Arcs to excluded lines shouldn't count. continue - if l1 not in exit_counts: - exit_counts[l1] = 0 exit_counts[l1] += 1 # Class definitions have one extra exit, so remove one for each: @@ -270,7 +263,6 @@ class CodeParser(object): exit_counts[l] -= 1 return exit_counts - exit_counts = expensive(exit_counts) ## Opcodes that guide the ByteParser. @@ -336,19 +328,15 @@ class ByteParser(object): else: if not text: assert filename, "If no code or text, need a filename" - sourcef = open_source(filename) - try: + with open_source(filename) as sourcef: text = sourcef.read() - finally: - sourcef.close() self.text = text try: # Python 2.3 and 2.4 don't like partial last lines, so be sure # the text ends nicely for them. self.code = compile(text + '\n', filename, "exec") - except SyntaxError: - _, synerr, _ = sys.exc_info() + except SyntaxError as synerr: raise NotPython( "Couldn't parse '%s' as Python source: '%s' at line %d" % (filename, synerr.msg, synerr.lineno) @@ -371,7 +359,7 @@ class ByteParser(object): """ children = CodeObjects(self.code) - return [ByteParser(code=c, text=self.text) for c in children] + return (ByteParser(code=c, text=self.text) for c in children) def _bytes_lines(self): """Map byte offsets to line numbers in `code`. @@ -415,7 +403,7 @@ class ByteParser(object): def _block_stack_repr(self, block_stack): """Get a string version of `block_stack`, for debugging.""" blocks = ", ".join( - ["(%s, %r)" % (dis.opname[b[0]], b[1]) for b in block_stack] + "(%s, %r)" % (dis.opname[b[0]], b[1]) for b in block_stack ) return "[" + blocks + "]" @@ -554,9 +542,9 @@ class ByteParser(object): def validate_chunks(self, chunks): """Validate the rule that chunks have a single entrance.""" # starts is the entrances to the chunks - starts = set([ch.byte for ch in chunks]) + starts = set(ch.byte for ch in chunks) for ch in chunks: - assert all([(ex in starts or ex < 0) for ex in ch.exits]) + assert all((ex in starts or ex < 0) for ex in ch.exits) def _arcs(self): """Find the executable arcs in the code. @@ -569,7 +557,7 @@ class ByteParser(object): chunks = self._split_into_chunks() # A map from byte offsets to chunks jumped into. - byte_chunks = dict([(c.byte, c) for c in chunks]) + byte_chunks = dict((c.byte, c) for c in chunks) # There's always an entrance at the first chunk. yield (-1, byte_chunks[0].line) diff --git a/coverage/phystokens.py b/coverage/phystokens.py index 99b1d5ba..e79ce01f 100644 --- a/coverage/phystokens.py +++ b/coverage/phystokens.py @@ -1,7 +1,6 @@ """Better tokenizing for coverage.py.""" import codecs, keyword, re, sys, token, tokenize -from coverage.backward import set # pylint: disable=W0622 from coverage.parser import generate_tokens @@ -143,13 +142,8 @@ def source_encoding(source): # invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found, # 'utf-8-sig' is returned. - # If no encoding is specified, then the default will be returned. The - # default varied with version. - - if sys.version_info <= (2, 4): - default = 'iso-8859-1' - else: - default = 'ascii' + # If no encoding is specified, then the default will be returned. + default = 'ascii' bom_found = False encoding = None diff --git a/coverage/results.py b/coverage/results.py index db6df0d3..0576ae1f 100644 --- a/coverage/results.py +++ b/coverage/results.py @@ -1,8 +1,9 @@ """Results of coverage measurement.""" +import collections import os -from coverage.backward import iitems, set, sorted # pylint: disable=W0622 +from coverage.backward import iitems from coverage.misc import format_lines, join_regex, NoSource from coverage.parser import CodeParser @@ -36,9 +37,9 @@ class Analysis(object): n_branches = self.total_branches() mba = self.missing_branch_arcs() n_partial_branches = sum( - [len(v) for k,v in iitems(mba) if k not in self.missing] + len(v) for k,v in iitems(mba) if k not in self.missing ) - n_missing_branches = sum([len(v) for k,v in iitems(mba)]) + n_missing_branches = sum(len(v) for k,v in iitems(mba)) else: n_branches = n_partial_branches = n_missing_branches = 0 self.no_branch = set() @@ -112,18 +113,18 @@ class Analysis(object): """Returns a sorted list of the arcs actually executed in the code.""" executed = self.coverage.data.executed_arcs(self.filename) m2fl = self.parser.first_line - executed = [(m2fl(l1), m2fl(l2)) for (l1,l2) in executed] + executed = ((m2fl(l1), m2fl(l2)) for (l1,l2) in executed) return sorted(executed) def arcs_missing(self): """Returns a sorted list of the arcs in the code not executed.""" possible = self.arc_possibilities() executed = self.arcs_executed() - missing = [ + missing = ( p for p in possible if p not in executed and p[0] not in self.no_branch - ] + ) return sorted(missing) def arcs_unpredicted(self): @@ -133,11 +134,11 @@ class Analysis(object): # Exclude arcs here which connect a line to itself. They can occur # in executed data in some cases. This is where they can cause # trouble, and here is where it's the least burden to remove them. - unpredicted = [ + unpredicted = ( e for e in executed if e not in possible and e[0] != e[1] - ] + ) return sorted(unpredicted) def branch_lines(self): @@ -148,7 +149,7 @@ class Analysis(object): def total_branches(self): """How many total branches are there?""" exit_counts = self.parser.exit_counts() - return sum([count for count in exit_counts.values() if count > 1]) + return sum(count for count in exit_counts.values() if count > 1) def missing_branch_arcs(self): """Return arcs that weren't executed from branch lines. @@ -158,11 +159,9 @@ class Analysis(object): """ missing = self.arcs_missing() branch_lines = set(self.branch_lines()) - mba = {} + mba = collections.defaultdict(list) for l1, l2 in missing: if l1 in branch_lines: - if l1 not in mba: - mba[l1] = [] mba[l1].append(l2) return mba @@ -210,25 +209,26 @@ class Numbers(object): self.n_partial_branches = n_partial_branches self.n_missing_branches = n_missing_branches + @classmethod def set_precision(cls, precision): """Set the number of decimal places used to report percentages.""" assert 0 <= precision < 10 cls._precision = precision cls._near0 = 1.0 / 10**precision cls._near100 = 100.0 - cls._near0 - set_precision = classmethod(set_precision) - def _get_n_executed(self): + @property + def n_executed(self): """Returns the number of executed statements.""" return self.n_statements - self.n_missing - n_executed = property(_get_n_executed) - def _get_n_executed_branches(self): + @property + def n_executed_branches(self): """Returns the number of executed branches.""" return self.n_branches - self.n_missing_branches - n_executed_branches = property(_get_n_executed_branches) - def _get_pc_covered(self): + @property + def pc_covered(self): """Returns a single percentage value for coverage.""" if self.n_statements > 0: pc_cov = (100.0 * (self.n_executed + self.n_executed_branches) / @@ -236,9 +236,9 @@ class Numbers(object): else: pc_cov = 100.0 return pc_cov - pc_covered = property(_get_pc_covered) - def _get_pc_covered_str(self): + @property + def pc_covered_str(self): """Returns the percent covered, as a string, without a percent sign. Note that "0" is only returned when the value is truly zero, and "100" @@ -254,15 +254,14 @@ class Numbers(object): else: pc = round(pc, self._precision) return "%.*f" % (self._precision, pc) - pc_covered_str = property(_get_pc_covered_str) + @classmethod def pc_str_width(cls): """How many characters wide can pc_covered_str be?""" width = 3 # "100" if cls._precision > 0: width += 1 + cls._precision return width - pc_str_width = classmethod(pc_str_width) def __add__(self, other): nums = Numbers() diff --git a/coverage/templite.py b/coverage/templite.py index e5c0bafe..429a5ccc 100644 --- a/coverage/templite.py +++ b/coverage/templite.py @@ -4,8 +4,6 @@ import re -from coverage.backward import set # pylint: disable=W0622 - class CodeBuilder(object): """Build source code conveniently.""" diff --git a/coverage/version.py b/coverage/version.py index a43bde80..27c2f6b1 100644 --- a/coverage/version.py +++ b/coverage/version.py @@ -1,7 +1,7 @@ """The version and URL for coverage.py""" # This file is exec'ed in setup.py, don't import anything! -__version__ = "3.7.1" # see detailed history in CHANGES.txt +__version__ = "4.0a0" # see detailed history in CHANGES.txt __url__ = "http://nedbatchelder.com/code/coverage" if max(__version__).isalpha(): diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py index 26ac02ad..f5a4c1ba 100644 --- a/coverage/xmlreport.py +++ b/coverage/xmlreport.py @@ -4,7 +4,6 @@ import os, sys, time import xml.dom.minidom from coverage import __url__, __version__ -from coverage.backward import sorted, rpartition # pylint: disable=W0622 from coverage.report import Reporter def rate(hit, num): @@ -97,7 +96,7 @@ class XmlReporter(Reporter): # Create the 'lines' and 'package' XML elements, which # are populated later. Note that a package == a directory. - package_name = rpartition(cu.name, ".")[0] + package_name = cu.name.rpartition(".")[0] className = cu.name package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0]) @@ -138,8 +137,8 @@ class XmlReporter(Reporter): class_hits = class_lines - len(analysis.missing) if self.arcs: - class_branches = sum([t for t,k in branch_stats.values()]) - missing_branches = sum([t-k for t,k in branch_stats.values()]) + class_branches = sum(t for t, k in branch_stats.values()) + missing_branches = sum(t - k for t, k in branch_stats.values()) class_br_hits = class_branches - missing_branches else: class_branches = 0.0 |