def analysis2(self, morf): """Analyze a module. `morf` is a module or a filename. It will be analyzed to determine its coverage statistics. The return value is a 5-tuple: * The filename for the module. * A list of line numbers of executable statements. * A list of line numbers of excluded statements. * A list of line numbers of statements not run (missing from execution). * A readable formatted string of the missing line numbers. The analysis uses the source file itself and the current measured coverage data. """ analysis = self._analyze(morf) return ( analysis.filename, sorted(analysis.statements), sorted(analysis.excluded), sorted(analysis.missing), analysis.missing_formatted(), )
def report(self, morfs, outfile=None): """Generate a Cobertura-compatible XML report for `morfs`. `morfs` is a list of modules or filenames. `outfile` is a file object to write the XML to. """ # Initial setup. outfile = outfile or sys.stdout # Create the DOM that will store the data. impl = xml.dom.minidom.getDOMImplementation() docType = impl.createDocumentType( "coverage", None, "http://cobertura.sourceforge.net/xml/coverage-03.dtd") self.xml_out = impl.createDocument(None, "coverage", docType) # Write header stuff. xcoverage = self.xml_out.documentElement xcoverage.setAttribute("version", __version__) xcoverage.setAttribute("timestamp", str(int(time.time() * 1000))) xcoverage.appendChild( self.xml_out.createComment(" Generated by coverage.py: %s " % __url__)) xpackages = self.xml_out.createElement("packages") xcoverage.appendChild(xpackages) # Call xml_file for each file in the data. self.packages = {} self.report_files(self.xml_file, morfs) lnum_tot, lhits_tot = 0, 0 bnum_tot, bhits_tot = 0, 0 # Populate the XML DOM with the package info. for pkg_name in sorted(self.packages.keys()): pkg_data = self.packages[pkg_name] class_elts, lhits, lnum, bhits, bnum = pkg_data xpackage = self.xml_out.createElement("package") xpackages.appendChild(xpackage) xclasses = self.xml_out.createElement("classes") xpackage.appendChild(xclasses) for class_name in sorted(class_elts.keys()): xclasses.appendChild(class_elts[class_name]) xpackage.setAttribute("name", pkg_name.replace(os.sep, '.')) xpackage.setAttribute("line-rate", rate(lhits, lnum)) xpackage.setAttribute("branch-rate", rate(bhits, bnum)) xpackage.setAttribute("complexity", "0") lnum_tot += lnum lhits_tot += lhits bnum_tot += bnum bhits_tot += bhits xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot)) xcoverage.setAttribute("branch-rate", rate(bhits_tot, bnum_tot)) # Use the DOM to write the output file. outfile.write(self.xml_out.toprettyxml())
def format_lines(statements, lines): """Nicely format a list of line numbers. Format a list of line numbers for printing by coalescing groups of lines as long as the lines represent consecutive statements. This will coalesce even if there are gaps between statements. For example, if `statements` is [1,2,3,4,5,10,11,12,13,14] and `lines` is [1,2,5,10,11,13,14] then the result will be "1-2, 5-11, 13-14". """ pairs = [] i = 0 j = 0 start = None statements = sorted(statements) lines = sorted(lines) while i < len(statements) and j < len(lines): if statements[i] == lines[j]: if start == None: start = lines[j] end = lines[j] j += 1 elif start: pairs.append((start, end)) start = None i += 1 if start: pairs.append((start, end)) ret = ', '.join(map(nice_pair, pairs)) return ret
def annotate_file(self, cu, analysis): """Annotate a single file. `cu` is the CodeUnit for the file to annotate. """ if not cu.relative: return filename = cu.filename source = cu.source_file() if self.directory: dest_file = os.path.join(self.directory, cu.flat_rootname()) dest_file += ".py,cover" else: dest_file = filename + ",cover" dest = open(dest_file, 'w') statements = sorted(analysis.statements) missing = sorted(analysis.missing) excluded = sorted(analysis.excluded) lineno = 0 i = 0 j = 0 covered = True while True: line = source.readline() if line == '': break lineno += 1 while i < len(statements) and statements[i] < lineno: i += 1 while j < len(missing) and missing[j] < lineno: j += 1 if i < len(statements) and statements[i] == lineno: covered = j >= len(missing) or missing[j] > lineno if self.blank_re.match(line): dest.write(' ') elif self.else_re.match(line): # Special logic for lines containing only 'else:'. if i >= len(statements) and j >= len(missing): dest.write('! ') elif i >= len(statements) or j >= len(missing): dest.write('> ') elif statements[i] == missing[j]: dest.write('! ') else: dest.write('> ') elif lineno in excluded: dest.write('- ') elif covered: dest.write('> ') else: dest.write('! ') dest.write(line) source.close() dest.close()
def report(self, morfs, outfile=None, config=None): """Generate a Cobertura-compatible XML report for `morfs`. `morfs` is a list of modules or filenames. `outfile` is a file object to write the XML to. `config` is a CoverageConfig instance. """ # Initial setup. outfile = outfile or sys.stdout # Create the DOM that will store the data. impl = xml.dom.minidom.getDOMImplementation() docType = impl.createDocumentType("coverage", None, "http://cobertura.sourceforge.net/xml/coverage-03.dtd") self.xml_out = impl.createDocument(None, "coverage", docType) # Write header stuff. xcoverage = self.xml_out.documentElement xcoverage.setAttribute("version", __version__) xcoverage.setAttribute("timestamp", str(int(time.time() * 1000))) xcoverage.appendChild(self.xml_out.createComment(" Generated by coverage.py: %s " % __url__)) xpackages = self.xml_out.createElement("packages") xcoverage.appendChild(xpackages) # Call xml_file for each file in the data. self.packages = {} self.report_files(self.xml_file, morfs, config) lnum_tot, lhits_tot = 0, 0 bnum_tot, bhits_tot = 0, 0 # Populate the XML DOM with the package info. for pkg_name in sorted(self.packages.keys()): pkg_data = self.packages[pkg_name] class_elts, lhits, lnum, bhits, bnum = pkg_data xpackage = self.xml_out.createElement("package") xpackages.appendChild(xpackage) xclasses = self.xml_out.createElement("classes") xpackage.appendChild(xclasses) for class_name in sorted(class_elts.keys()): xclasses.appendChild(class_elts[class_name]) xpackage.setAttribute("name", pkg_name.replace(os.sep, ".")) xpackage.setAttribute("line-rate", rate(lhits, lnum)) xpackage.setAttribute("branch-rate", rate(bhits, bnum)) xpackage.setAttribute("complexity", "0") lnum_tot += lnum lhits_tot += lhits bnum_tot += bnum bhits_tot += bhits xcoverage.setAttribute("line-rate", rate(lhits_tot, lnum_tot)) xcoverage.setAttribute("branch-rate", rate(bhits_tot, bnum_tot)) # Use the DOM to write the output file. outfile.write(self.xml_out.toprettyxml())
def report(self, morfs, outfile=None): """Generate a Cobertura-compatible XML report for `morfs`. `morfs` is a list of modules or filenames. `outfile` is a file object to write the XML to. """ outfile = outfile or sys.stdout impl = xml.dom.minidom.getDOMImplementation() docType = impl.createDocumentType( 'coverage', None, 'http://cobertura.sourceforge.net/xml/coverage-03.dtd') self.xml_out = impl.createDocument(None, 'coverage', docType) xcoverage = self.xml_out.documentElement xcoverage.setAttribute('version', __version__) xcoverage.setAttribute('timestamp', str(int(time.time() * 1000))) xcoverage.appendChild( self.xml_out.createComment(' Generated by coverage.py: %s ' % __url__)) xpackages = self.xml_out.createElement('packages') xcoverage.appendChild(xpackages) self.packages = {} self.report_files(self.xml_file, morfs) lnum_tot, lhits_tot = (0, 0) bnum_tot, bhits_tot = (0, 0) for pkg_name in sorted(self.packages.keys()): pkg_data = self.packages[pkg_name] class_elts, lhits, lnum, bhits, bnum = pkg_data xpackage = self.xml_out.createElement('package') xpackages.appendChild(xpackage) xclasses = self.xml_out.createElement('classes') xpackage.appendChild(xclasses) for class_name in sorted(class_elts.keys()): xclasses.appendChild(class_elts[class_name]) xpackage.setAttribute('name', pkg_name.replace(os.sep, '.')) xpackage.setAttribute('line-rate', rate(lhits, lnum)) xpackage.setAttribute('branch-rate', rate(bhits, bnum)) xpackage.setAttribute('complexity', '0') lnum_tot += lnum lhits_tot += lhits bnum_tot += bnum bhits_tot += bhits xcoverage.setAttribute('line-rate', rate(lhits_tot, lnum_tot)) xcoverage.setAttribute('branch-rate', rate(bhits_tot, bnum_tot)) outfile.write(self.xml_out.toprettyxml()) denom = lnum_tot + bnum_tot if denom == 0: pct = 0.0 else: pct = 100.0 * (lhits_tot + bhits_tot) / denom return pct
def update(self, v): """Add `v` to the hash, recursively if needed.""" self.md5.update(to_bytes(str(type(v)))) if isinstance(v, string_class): self.md5.update(to_bytes(v)) elif v is None: pass elif isinstance(v, (int, float)): self.md5.update(to_bytes(str(v))) elif isinstance(v, (tuple, list)): for e in v: self.update(e) elif isinstance(v, dict): keys = v.keys() for k in sorted(keys): self.update(k) self.update(v[k]) else: for k in dir(v): if k.startswith('__'): continue a = getattr(v, k) if inspect.isroutine(a): continue self.update(k) self.update(a)
def start(self): """Start measuring code coverage. Coverage measurement actually occurs in functions called after `start` is invoked. Statements in the same scope as `start` won't be measured. Once you invoke `start`, you must also call `stop` eventually, or your process might not shut down cleanly. """ if self.run_suffix: self.data_suffix = self.run_suffix if self.auto_data: self.load() if self.source or self.source_pkgs: self.source_match = TreeMatcher(self.source) else: if self.cover_dir: self.cover_match = TreeMatcher([self.cover_dir]) if self.pylib_dirs: self.pylib_match = TreeMatcher(self.pylib_dirs) if self.include: self.include_match = FnmatchMatcher(self.include) if self.omit: self.omit_match = FnmatchMatcher(self.omit) if self.debug.should('config'): self.debug.write('Configuration values:') config_info = sorted(self.config.__dict__.items()) self.debug.write_formatted_info(config_info) if self.debug.should('sys'): self.debug.write('Debugging info:') self.debug.write_formatted_info(self.sysinfo()) self.collector.start() self._started = True self._measured = True
def start(self): if self.run_suffix: self.data_suffix = self.run_suffix if self.auto_data: self.load() if self.source or self.source_pkgs: self.source_match = TreeMatcher(self.source) else: if self.cover_dir: self.cover_match = TreeMatcher([self.cover_dir]) if self.pylib_dirs: self.pylib_match = TreeMatcher(self.pylib_dirs) if self.include: self.include_match = FnmatchMatcher(self.include) if self.omit: self.omit_match = FnmatchMatcher(self.omit) if self.debug.should('config'): self.debug.write('Configuration values:') config_info = sorted(self.config.__dict__.items()) self.debug.write_formatted_info(config_info) if self.debug.should('sys'): self.debug.write('Debugging info:') self.debug.write_formatted_info(self.sysinfo()) self.collector.start() self._started = True self._measured = True
def arcz_to_arcs(self, arcz): """Convert a compact textual representation of arcs to a list of pairs. The text has space-separated pairs of letters. Period is -1, 1-9 are 1-9, A-Z are 10 through 36. The resulting list is sorted regardless of the order of the input pairs. ".1 12 2." --> [(-1,1), (1,2), (2,-1)] Minus signs can be included in the pairs: "-11, 12, 2-5" --> [(-1,1), (1,2), (2,-5)] """ arcs = [] for pair in arcz.split(): asgn = bsgn = 1 if len(pair) == 2: a,b = pair else: assert len(pair) == 3 if pair[0] == '-': _,a,b = pair asgn = -1 else: assert pair[1] == '-' a,_,b = pair bsgn = -1 arcs.append((asgn*self._arcz_map[a], bsgn*self._arcz_map[b])) return sorted(arcs)
def do_debug(self, args): """Implementation of 'coverage debug'.""" if not args: self.help_fn("What information would you like: data, sys?") return ERR for info in args: if info == 'sys': print("-- sys ----------------------------------------") for line in info_formatter(self.coverage.sysinfo()): print(" %s" % line) elif info == 'data': print("-- data ---------------------------------------") self.coverage.load() print("path: %s" % self.coverage.data.filename) print("has_arcs: %r" % self.coverage.data.has_arcs()) summary = self.coverage.data.summary(fullpath=True) if summary: filenames = sorted(summary.keys()) print("\n%d files:" % len(filenames)) for f in filenames: print("%s: %d lines" % (f, summary[f])) else: print("No data collected") else: self.help_fn("Don't know what you mean by %r" % info) return ERR return OK
def update(self, v): self.md5.update(to_bytes(str(type(v)))) if isinstance(v, string_class): self.md5.update(to_bytes(v)) elif isinstance(v, (int, float)): self.update(str(v)) elif isinstance(v, (tuple, list)): for e in v: self.update(e) elif isinstance(v, dict): keys = v.keys() for k in sorted(keys): self.update(k) self.update(v[k]) else: for k in dir(v): if k.startswith('__'): continue a = getattr(v, k) if inspect.isroutine(a): continue self.update(k) self.update(a)
def sysinfo(self): import coverage as covmod import platform, re try: implementation = platform.python_implementation() except AttributeError: implementation = 'unknown' info = [('version', covmod.__version__), ('coverage', covmod.__file__), ('cover_dir', self.cover_dir), ('pylib_dirs', self.pylib_dirs), ('tracer', self.collector.tracer_name()), ('config_files', self.config.attempted_config_files), ('configs_read', self.config.config_files), ('data_path', self.data.filename), ('python', sys.version.replace('\n', '')), ('platform', platform.platform()), ('implementation', implementation), ('executable', sys.executable), ('cwd', os.getcwd()), ('path', sys.path), ('environment', sorted([ '%s = %s' % (k, v) for k, v in iitems(os.environ) if re.search('^COV|^PY', k) ])), ('command_line', ' '.join(getattr(sys, 'argv', ['???'])))] if self.source_match: info.append(('source_match', self.source_match.info())) if self.include_match: info.append(('include_match', self.include_match.info())) if self.omit_match: info.append(('omit_match', self.omit_match.info())) if self.cover_match: info.append(('cover_match', self.cover_match.info())) if self.pylib_match: info.append(('pylib_match', self.pylib_match.info())) return info
def arcs_unpredicted(self): possible = self.arc_possibilities() executed = self.arcs_executed() unpredicted = [ e for e in executed if e not in possible and e[0] != e[1] ] return sorted(unpredicted)
def __init__(self, cov, code_unit): self.coverage = cov self.code_unit = code_unit self.filename = self.code_unit.filename actual_filename, source = self.find_source(self.filename) self.parser = CodeParser( text=source, filename=actual_filename, exclude=self.coverage._exclude_regex('exclude')) self.statements, self.excluded = self.parser.parse_source() executed = self.coverage.data.executed_lines(self.filename) exec1 = self.parser.first_lines(executed) self.missing = sorted(set(self.statements) - set(exec1)) if self.coverage.data.has_arcs(): self.no_branch = self.parser.lines_matching( join_regex(self.coverage.config.partial_list), join_regex(self.coverage.config.partial_always_list)) 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]) 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() self.numbers = Numbers(n_files=1, n_statements=len(self.statements), n_excluded=len(self.excluded), n_missing=len(self.missing), n_branches=n_branches, n_partial_branches=n_partial_branches, n_missing_branches=n_missing_branches)
def do_debug(self, args): if not args: self.help_fn('What information would you like: data, sys?') return ERR for info in args: if info == 'sys': print '-- sys ----------------------------------------' for line in info_formatter(self.coverage.sysinfo()): print ' %s' % line elif info == 'data': print '-- data ---------------------------------------' self.coverage.load() print 'path: %s' % self.coverage.data.filename print 'has_arcs: %r' % self.coverage.data.has_arcs() summary = self.coverage.data.summary(fullpath=True) if summary: filenames = sorted(summary.keys()) print '\n%d files:' % len(filenames) for f in filenames: print '%s: %d lines' % (f, summary[f]) else: print 'No data collected' else: self.help_fn("Don't know what you mean by %r" % info) return ERR return OK
def arcz_to_arcs(self, arcz): """Convert a compact textual representation of arcs to a list of pairs. The text has space-separated pairs of letters. Period is -1, 1-9 are 1-9, A-Z are 10 through 36. The resulting list is sorted regardless of the order of the input pairs. ".1 12 2." --> [(-1,1), (1,2), (2,-1)] Minus signs can be included in the pairs: "-11, 12, 2-5" --> [(-1,1), (1,2), (2,-5)] """ arcs = [] for pair in arcz.split(): asgn = bsgn = 1 if len(pair) == 2: a, b = pair else: assert len(pair) == 3 if pair[0] == '-': _, a, b = pair asgn = -1 else: assert pair[1] == '-' a, _, b = pair bsgn = -1 arcs.append((asgn * self._arcz_map[a], bsgn * self._arcz_map[b])) return sorted(arcs)
def arcs_unpredicted(self): """Returns a sorted list of the executed arcs missing from the code.""" possible = self.arc_possibilities() executed = self.arcs_executed() unpredicted = [ e for e in executed if e not in possible and e[0] != e[1] ] return sorted(unpredicted)
def arcs_missing(self): possible = self.arc_possibilities() executed = self.arcs_executed() missing = [ p for p in possible if p not in executed and p[0] not in self.no_branch ] return sorted(missing)
def arcs(self): all_arcs = [] for l1, l2 in self.byte_parser._all_arcs(): fl1 = self.first_line(l1) fl2 = self.first_line(l2) if fl1 != fl2: all_arcs.append((fl1, fl2)) return sorted(all_arcs)
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 = [ p for p in possible if p not in executed and p[0] not in self.no_branch ] return sorted(missing)
def report(self, morfs, outfile = None): outfile = outfile or sys.stdout impl = xml.dom.minidom.getDOMImplementation() docType = impl.createDocumentType('coverage', None, 'http://cobertura.sourceforge.net/xml/coverage-03.dtd') self.xml_out = impl.createDocument(None, 'coverage', docType) xcoverage = self.xml_out.documentElement xcoverage.setAttribute('version', __version__) xcoverage.setAttribute('timestamp', str(int(time.time() * 1000))) xcoverage.appendChild(self.xml_out.createComment(' Generated by coverage.py: %s ' % __url__)) xpackages = self.xml_out.createElement('packages') xcoverage.appendChild(xpackages) self.packages = {} self.report_files(self.xml_file, morfs) lnum_tot, lhits_tot = (0, 0) bnum_tot, bhits_tot = (0, 0) for pkg_name in sorted(self.packages.keys()): pkg_data = self.packages[pkg_name] class_elts, lhits, lnum, bhits, bnum = pkg_data xpackage = self.xml_out.createElement('package') xpackages.appendChild(xpackage) xclasses = self.xml_out.createElement('classes') xpackage.appendChild(xclasses) for class_name in sorted(class_elts.keys()): xclasses.appendChild(class_elts[class_name]) xpackage.setAttribute('name', pkg_name.replace(os.sep, '.')) xpackage.setAttribute('line-rate', rate(lhits, lnum)) xpackage.setAttribute('branch-rate', rate(bhits, bnum)) xpackage.setAttribute('complexity', '0') lnum_tot += lnum lhits_tot += lhits bnum_tot += bnum bhits_tot += bhits xcoverage.setAttribute('line-rate', rate(lhits_tot, lnum_tot)) xcoverage.setAttribute('branch-rate', rate(bhits_tot, bnum_tot)) outfile.write(self.xml_out.toprettyxml()) denom = lnum_tot + bnum_tot if denom == 0: pct = 0.0 else: pct = 100.0 * (lhits_tot + bhits_tot) / denom return pct
def adhoc_one_file(self, options, filename): """Process just one file.""" if options.dis or options.chunks: try: bp = ByteParser(filename=filename) except CoverageException: _, err, _ = sys.exc_info() print("%s" % (err,)) return if options.dis: print("Main code:") bp._disassemble() if options.chunks: chunks = bp._all_chunks() if options.recursive: print("%6d: %s" % (len(chunks), filename)) else: print("Chunks: %r" % chunks) arcs = bp._all_arcs() print("Arcs: %r" % sorted(arcs)) if options.source or options.tokens: cp = CodeParser(filename=filename, exclude=r"no\s*cover") cp.show_tokens = options.tokens cp._raw_parse() if options.source: if options.chunks: arc_width, arc_chars = self.arc_ascii_art(arcs) else: arc_width, arc_chars = 0, {} exit_counts = cp.exit_counts() for i, ltext in enumerate(cp.lines): lineno = i+1 m0 = m1 = m2 = m3 = a = ' ' if lineno in cp.statement_starts: m0 = '-' exits = exit_counts.get(lineno, 0) if exits > 1: m1 = str(exits) if lineno in cp.docstrings: m2 = '"' if lineno in cp.classdefs: m2 = 'C' if lineno in cp.excluded: m3 = 'x' a = arc_chars.get(lineno, '').ljust(arc_width) print("%4d %s%s%s%s%s %s" % (lineno, m0, m1, m2, m3, a, ltext) )
def arcs_unpredicted(self): """Returns a sorted list of the executed arcs missing from the code.""" possible = self.arc_possibilities() executed = self.arcs_executed() # 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 = [ e for e in executed if e not in possible and e[0] != e[1] ] return sorted(unpredicted)
def first_lines(self, lines, ignore=None): ignore = ignore or [] lset = set() for l in lines: if l in ignore: continue new_l = self.first_line(l) if new_l not in ignore: lset.add(new_l) return sorted(lset)
def adhoc_one_file(self, options, filename): """Process just one file.""" if options.dis or options.chunks: try: bp = ByteParser(filename=filename) except CoverageException: _, err, _ = sys.exc_info() print("%s" % (err, )) return if options.dis: print("Main code:") bp._disassemble() if options.chunks: chunks = bp._all_chunks() if options.recursive: print("%6d: %s" % (len(chunks), filename)) else: print("Chunks: %r" % chunks) arcs = bp._all_arcs() print("Arcs: %r" % sorted(arcs)) if options.source or options.tokens: cp = CodeParser(filename=filename, exclude=r"no\s*cover") cp.show_tokens = options.tokens cp._raw_parse() if options.source: if options.chunks: arc_width, arc_chars = self.arc_ascii_art(arcs) else: arc_width, arc_chars = 0, {} exit_counts = cp.exit_counts() for i, ltext in enumerate(cp.lines): lineno = i + 1 m0 = m1 = m2 = m3 = a = ' ' if lineno in cp.statement_starts: m0 = '-' exits = exit_counts.get(lineno, 0) if exits > 1: m1 = str(exits) if lineno in cp.docstrings: m2 = '"' if lineno in cp.classdefs: m2 = 'C' if lineno in cp.excluded: m3 = 'x' a = arc_chars.get(lineno, '').ljust(arc_width) print("%4d %s%s%s%s%s %s" % (lineno, m0, m1, m2, m3, a, ltext))
def first_lines(self, lines, ignore = None): ignore = ignore or [] lset = set() for l in lines: if l in ignore: continue new_l = self.first_line(l) if new_l not in ignore: lset.add(new_l) return sorted(lset)
def arcs(self): """Get information about the arcs available in the code. Returns a sorted list of line number pairs. Line numbers have been normalized to the first line of multiline statements. """ all_arcs = [] for l1, l2 in self.byte_parser._all_arcs(): fl1 = self.first_line(l1) fl2 = self.first_line(l2) if fl1 != fl2: all_arcs.append((fl1, fl2)) return sorted(all_arcs)
def __init__(self, cov, code_unit): self.coverage = cov self.code_unit = code_unit self.filename = self.code_unit.filename ext = os.path.splitext(self.filename)[1] source = None if ext == '.py': if not os.path.exists(self.filename): source = self.coverage.file_locator.get_zip_data(self.filename) if not source: raise NoSource("No source for code: '%s'" % self.filename) self.parser = CodeParser( text=source, filename=self.filename, exclude=self.coverage._exclude_regex('exclude') ) self.statements, self.excluded = self.parser.parse_source() # Identify missing statements. executed = self.coverage.data.executed_lines(self.filename) exec1 = self.parser.first_lines(executed) self.missing = sorted(set(self.statements) - set(exec1)) if self.coverage.data.has_arcs(): self.no_branch = self.parser.lines_matching( join_regex(self.coverage.config.partial_list), join_regex(self.coverage.config.partial_always_list) ) 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] ) 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() self.numbers = Numbers( n_files=1, n_statements=len(self.statements), n_excluded=len(self.excluded), n_missing=len(self.missing), n_branches=n_branches, n_partial_branches=n_partial_branches, n_missing_branches=n_missing_branches, )
def __init__(self, cov, code_unit): self.coverage = cov self.code_unit = code_unit self.filename = self.code_unit.filename ext = os.path.splitext(self.filename)[1] source = None if ext == '.py': if not os.path.exists(self.filename): source = self.coverage.file_locator.get_zip_data(self.filename) if not source: raise NoSource("No source for code: '%s'" % self.filename) self.parser = CodeParser( text=source, filename=self.filename, exclude=self.coverage._exclude_regex('exclude')) self.statements, self.excluded = self.parser.parse_source() # Identify missing statements. executed = self.coverage.data.executed_lines(self.filename) exec1 = self.parser.first_lines(executed) self.missing = sorted(set(self.statements) - set(exec1)) if self.coverage.data.has_arcs(): self.no_branch = self.parser.lines_matching( join_regex(self.coverage.config.partial_list), join_regex(self.coverage.config.partial_always_list)) 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]) 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() self.numbers = Numbers( n_files=1, n_statements=len(self.statements), n_excluded=len(self.excluded), n_missing=len(self.missing), n_branches=n_branches, n_partial_branches=n_partial_branches, n_missing_branches=n_missing_branches, )
def first_lines(self, lines, ignore=None): """Map the line numbers in `lines` to the correct first line of the statement. Skip any line mentioned in `ignore`. Returns a sorted list of the first lines. """ ignore = ignore or [] lset = set() for l in lines: if l in ignore: continue new_l = self.first_line(l) if new_l not in ignore: lset.add(new_l) return sorted(lset)
def sysinfo(self): """Return a list of (key, value) pairs showing internal information.""" import coverage as covmod import platform, re try: implementation = platform.python_implementation() except AttributeError: implementation = "unknown" info = [ ('version', covmod.__version__), ('coverage', covmod.__file__), ('cover_dir', self.cover_dir), ('pylib_dirs', self.pylib_dirs), ('tracer', self.collector.tracer_name()), ('config_files', self.config.attempted_config_files), ('configs_read', self.config.config_files), ('data_path', self.data.filename), ('python', sys.version.replace('\n', '')), ('platform', platform.platform()), ('implementation', implementation), ('executable', sys.executable), ('cwd', os.getcwd()), ('path', sys.path), ('environment', sorted([("%s = %s" % (k, v)) for k, v in iitems(os.environ) if re.search(r"^COV|^PY", k)])), ('command_line', " ".join(getattr(sys, 'argv', ['???']))), ] if self.source_match: info.append(('source_match', self.source_match.info())) if self.include_match: info.append(('include_match', self.include_match.info())) if self.omit_match: info.append(('omit_match', self.omit_match.info())) if self.cover_match: info.append(('cover_match', self.cover_match.info())) if self.pylib_match: info.append(('pylib_match', self.pylib_match.info())) return info
def start(self): """Start measuring code coverage. Coverage measurement actually occurs in functions called after `start` is invoked. Statements in the same scope as `start` won't be measured. Once you invoke `start`, you must also call `stop` eventually, or your process might not shut down cleanly. """ if self.run_suffix: # Calling start() means we're running code, so use the run_suffix # as the data_suffix when we eventually save the data. self.data_suffix = self.run_suffix if self.auto_data: self.load() # Create the matchers we need for _should_trace if self.source or self.source_pkgs: self.source_match = TreeMatcher(self.source) else: if self.cover_dir: self.cover_match = TreeMatcher([self.cover_dir]) if self.pylib_dirs: self.pylib_match = TreeMatcher(self.pylib_dirs) if self.include: self.include_match = FnmatchMatcher(self.include) if self.omit: self.omit_match = FnmatchMatcher(self.omit) # The user may want to debug things, show info if desired. if self.debug.should('config'): self.debug.write("Configuration values:") config_info = sorted(self.config.__dict__.items()) self.debug.write_formatted_info(config_info) if self.debug.should('sys'): self.debug.write("Debugging info:") self.debug.write_formatted_info(self.sysinfo()) self.collector.start() self._started = True self._measured = True
def __init__(self, cov, code_unit): self.coverage = cov self.code_unit = code_unit self.filename = self.code_unit.filename actual_filename, source = self.find_source(self.filename) self.parser = CodeParser( text=source, filename=actual_filename, exclude=self.coverage._exclude_regex('exclude') ) self.statements, self.excluded = self.parser.parse_source() # Identify missing statements. executed = self.coverage.data.executed_lines(self.filename) exec1 = self.parser.first_lines(executed) self.missing = sorted(set(self.statements) - set(exec1)) if self.coverage.data.has_arcs(): self.no_branch = self.parser.lines_matching( join_regex(self.coverage.config.partial_list), join_regex(self.coverage.config.partial_always_list) ) 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] ) 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() self.numbers = Numbers( n_files=1, n_statements=len(self.statements), n_excluded=len(self.excluded), n_missing=len(self.missing), n_branches=n_branches, n_partial_branches=n_partial_branches, n_missing_branches=n_missing_branches, )
def __init__(self, cov, code_unit): self.coverage = cov self.code_unit = code_unit self.filename = self.code_unit.filename ext = os.path.splitext(self.filename)[1] source = None if ext == '.py': if not os.path.exists(self.filename): source = self.coverage.file_locator.get_zip_data(self.filename) if not source: raise NoSource("No source for code: %r" % self.filename) self.parser = CodeParser( text=source, filename=self.filename, exclude=self.coverage.exclude_re ) self.statements, self.excluded = self.parser.parse_source() # Identify missing statements. executed = self.coverage.data.executed_lines(self.filename) exec1 = self.parser.first_lines(executed) self.missing = sorted(set(self.statements) - set(exec1)) if self.coverage.data.has_arcs(): n_branches = self.total_branches() mba = self.missing_branch_arcs() n_missing_branches = sum([len(v) for v in mba.values()]) else: n_branches = n_missing_branches = 0 self.numbers = Numbers( n_files=1, n_statements=len(self.statements), n_excluded=len(self.excluded), n_missing=len(self.missing), n_branches=n_branches, n_missing_branches=n_missing_branches, )
def arc_ascii_art(self, arcs): """Draw arcs as ascii art. Returns a width of characters needed to draw all the arcs, and a dictionary mapping line numbers to ascii strings to draw for that line. """ arc_chars = {} for lfrom, lto in sorted(arcs): if lfrom < 0: arc_chars[lto] = arc_chars.get(lto, "") + "v" elif lto < 0: arc_chars[lfrom] = arc_chars.get(lfrom, "") + "^" else: if lfrom == lto - 1: # Don't show obvious arcs. continue if lfrom < lto: l1, l2 = lfrom, lto else: l1, l2 = lto, lfrom w = max([len(arc_chars.get(l, "")) for l in range(l1, l2 + 1)]) for l in range(l1, l2 + 1): if l == lfrom: ch = "<" elif l == lto: ch = ">" else: ch = "|" arc_chars[l] = arc_chars.get(l, "").ljust(w) + ch arc_width = 0 if arc_chars: arc_width = max([len(a) for a in arc_chars.values()]) else: arc_width = 0 return arc_width, arc_chars
def arc_ascii_art(self, arcs): """Draw arcs as ascii art. Returns a width of characters needed to draw all the arcs, and a dictionary mapping line numbers to ascii strings to draw for that line. """ arc_chars = {} for lfrom, lto in sorted(arcs): if lfrom < 0: arc_chars[lto] = arc_chars.get(lto, '') + 'v' elif lto < 0: arc_chars[lfrom] = arc_chars.get(lfrom, '') + '^' else: if lfrom == lto - 1: # Don't show obvious arcs. continue if lfrom < lto: l1, l2 = lfrom, lto else: l1, l2 = lto, lfrom w = max([len(arc_chars.get(l, '')) for l in range(l1, l2 + 1)]) for l in range(l1, l2 + 1): if l == lfrom: ch = '<' elif l == lto: ch = '>' else: ch = '|' arc_chars[l] = arc_chars.get(l, '').ljust(w) + ch arc_width = 0 if arc_chars: arc_width = max([len(a) for a in arc_chars.values()]) else: arc_width = 0 return arc_width, arc_chars
def __init__(self, cov, code_unit): self.coverage = cov self.code_unit = code_unit self.filename = self.code_unit.filename ext = os.path.splitext(self.filename)[1] source = None if ext == '.py': if not os.path.exists(self.filename): source = self.coverage.file_locator.get_zip_data(self.filename) if not source: raise NoSource("No source for code: %r" % self.filename) self.parser = CodeParser(text=source, filename=self.filename, exclude=self.coverage.exclude_re) self.statements, self.excluded = self.parser.parse_source() # Identify missing statements. executed = self.coverage.data.executed_lines(self.filename) exec1 = self.parser.first_lines(executed) self.missing = sorted(set(self.statements) - set(exec1)) if self.coverage.data.has_arcs(): n_branches = self.total_branches() mba = self.missing_branch_arcs() n_missing_branches = sum([len(v) for v in mba.values()]) else: n_branches = n_missing_branches = 0 self.numbers = Numbers( n_files=1, n_statements=len(self.statements), n_excluded=len(self.excluded), n_missing=len(self.missing), n_branches=n_branches, n_missing_branches=n_missing_branches, )
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)])
def command_line(self, argv): """The bulk of the command line interface to Coverage. `argv` is the argument list to process. Returns 0 if all is well, 1 if something went wrong. """ # Collect the command-line options. if not argv: self.help_fn(topic='minimum_help') return OK # The command syntax we parse depends on the first argument. Classic # syntax always starts with an option. classic = argv[0].startswith('-') if classic: parser = ClassicOptionParser() else: parser = CMDS.get(argv[0]) if not parser: self.help_fn("Unknown command: '%s'" % argv[0]) return ERR argv = argv[1:] parser.help_fn = self.help_fn ok, options, args = parser.parse_args(argv) if not ok: return ERR # Handle help. if options.help: if classic: self.help_fn(topic='help') else: self.help_fn(parser=parser) return OK if "help" in options.actions: if args: for a in args: parser = CMDS.get(a) if parser: self.help_fn(parser=parser) else: self.help_fn(topic=a) else: self.help_fn(topic='help') return OK # Handle version. if options.version: self.help_fn(topic='version') return OK # Check for conflicts and problems in the options. for i in ['erase', 'execute']: for j in ['annotate', 'html', 'report', 'combine']: if (i in options.actions) and (j in options.actions): self.help_fn("You can't specify the '%s' and '%s' " "options at the same time." % (i, j)) return ERR if not options.actions: self.help_fn( "You must specify at least one of -e, -x, -c, -r, -a, or -b." ) return ERR args_allowed = ( 'execute' in options.actions or 'annotate' in options.actions or 'html' in options.actions or 'debug' in options.actions or 'report' in options.actions or 'xml' in options.actions ) if not args_allowed and args: self.help_fn("Unexpected arguments: %s" % " ".join(args)) return ERR if 'execute' in options.actions and not args: self.help_fn("Nothing to do.") return ERR # Listify the list options. source = unshell_list(options.source) omit = unshell_list(options.omit) include = unshell_list(options.include) # Do something. self.coverage = self.covpkg.coverage( data_suffix = options.parallel_mode, cover_pylib = options.pylib, timid = options.timid, branch = options.branch, config_file = options.rcfile, source = source, omit = omit, include = include, ) if 'debug' in options.actions: if not args: self.help_fn("What information would you like: data, sys?") return ERR for info in args: if info == 'sys': print("-- sys ----------------------------------------") for label, info in self.coverage.sysinfo(): if info == []: info = "-none-" if isinstance(info, list): print("%15s:" % label) for e in info: print("%15s %s" % ("", e)) else: print("%15s: %s" % (label, info)) elif info == 'data': print("-- data ---------------------------------------") self.coverage.load() print("path: %s" % self.coverage.data.filename) print("has_arcs: %r" % self.coverage.data.has_arcs()) summary = self.coverage.data.summary(fullpath=True) if summary: filenames = sorted(summary.keys()) print("\n%d files:" % len(filenames)) for f in filenames: print("%s: %d lines" % (f, summary[f])) else: print("No data collected") else: self.help_fn("Don't know what you mean by %r" % info) return ERR return OK if 'erase' in options.actions or options.erase_first: self.coverage.erase() else: self.coverage.load() if 'execute' in options.actions: # Run the script. self.coverage.start() code_ran = True try: try: if options.module: self.run_python_module(args[0], args) else: self.run_python_file(args[0], args) except NoSource: code_ran = False raise finally: if code_ran: self.coverage.stop() self.coverage.save() if 'combine' in options.actions: self.coverage.combine() self.coverage.save() # Remaining actions are reporting, with some common options. report_args = dict( morfs = args, ignore_errors = options.ignore_errors, omit = omit, include = include, ) if 'report' in options.actions: self.coverage.report( show_missing=options.show_missing, **report_args) if 'annotate' in options.actions: self.coverage.annotate( directory=options.directory, **report_args) if 'html' in options.actions: self.coverage.html_report( directory=options.directory, **report_args) if 'xml' in options.actions: outfile = options.outfile self.coverage.xml_report(outfile=outfile, **report_args) return OK
def xml_file(self, cu, analysis): """Add to the XML report for a single file.""" # Create the 'lines' and 'package' XML elements, which # are populated later. Note that a package == a directory. package_name = rpartition(cu.name, ".")[0] className = cu.name package = self.packages.setdefault(package_name, [{}, 0, 0, 0, 0]) xclass = self.xml_out.createElement("class") xclass.appendChild(self.xml_out.createElement("methods")) xlines = self.xml_out.createElement("lines") xclass.appendChild(xlines) xclass.setAttribute("name", className) filename = cu.file_locator.relative_filename(cu.filename) xclass.setAttribute("filename", filename.replace("\\", "/")) xclass.setAttribute("complexity", "0") branch_stats = analysis.branch_stats() # For each statement, create an XML 'line' element. for line in sorted(analysis.statements): xline = self.xml_out.createElement("line") xline.setAttribute("number", str(line)) # Q: can we get info about the number of times a statement is # executed? If so, that should be recorded here. xline.setAttribute("hits", str(int(line not in analysis.missing))) if self.arcs: if line in branch_stats: total, taken = branch_stats[line] xline.setAttribute("branch", "true") xline.setAttribute("condition-coverage", "%d%% (%d/%d)" % (100*taken/total, taken, total) ) xlines.appendChild(xline) class_lines = len(analysis.statements) 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_br_hits = class_branches - missing_branches else: class_branches = 0.0 class_br_hits = 0.0 # Finalize the statistics that are collected in the XML DOM. xclass.setAttribute("line-rate", rate(class_hits, class_lines)) xclass.setAttribute("branch-rate", rate(class_br_hits, class_branches)) package[0][className] = xclass package[1] += class_hits package[2] += class_lines package[3] += class_br_hits package[4] += class_branches
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)])
def line_data(self): return dict([ (f, sorted(lmap.keys())) for f, lmap in iitems(self.lines) ])
def arcs_executed(self): """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] return sorted(executed)
def arc_data(self): return dict([ (f, sorted(amap.keys())) for f, amap in iitems(self.arcs) ])
def arcs_executed(self): """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] return sorted(executed)
def check_coverage(self, text, lines=None, missing="", report="", excludes=None, partials="", arcz=None, arcz_missing="", arcz_unpredicted=""): """Check the coverage measurement of `text`. The source `text` is run and measured. `lines` are the line numbers that are executable, or a list of possible line numbers, any of which could match. `missing` are the lines not executed, `excludes` are regexes to match against for excluding lines, and `report` is the text of the measurement report. For arc measurement, `arcz` is a string that can be decoded into arcs in the code (see `arcz_to_arcs` for the encoding scheme), `arcz_missing` are the arcs that are not executed, and `arcs_unpredicted` are the arcs executed in the code, but not deducible from the code. """ # We write the code into a file so that we can import it. # Coverage wants to deal with things as modules with file names. modname = self.get_module_name() self.make_file(modname+".py", text) arcs = arcs_missing = arcs_unpredicted = None if arcz is not None: arcs = self.arcz_to_arcs(arcz) arcs_missing = self.arcz_to_arcs(arcz_missing or "") arcs_unpredicted = self.arcz_to_arcs(arcz_unpredicted or "") # Start up Coverage. cov = coverage.coverage(branch=(arcs_missing is not None)) cov.erase() for exc in excludes or []: cov.exclude(exc) for par in partials or []: cov.exclude(par, which='partial') mod = self.start_import_stop(cov, modname) # Clean up our side effects del sys.modules[modname] # Get the analysis results, and check that they are right. analysis = cov._analyze(mod) statements = sorted(analysis.statements) if lines is not None: if type(lines[0]) == type(1): # lines is just a list of numbers, it must match the statements # found in the code. self.assertEqual(statements, lines) else: # lines is a list of possible line number lists, one of them # must match. for line_list in lines: if statements == line_list: break else: self.fail("None of the lines choices matched %r" % statements ) if type(missing) == type(""): self.assertEqual(analysis.missing_formatted(), missing) else: for missing_list in missing: if analysis.missing_formatted() == missing_list: break else: self.fail("None of the missing choices matched %r" % analysis.missing_formatted() ) if arcs is not None: self.assertEqualArcs( analysis.arc_possibilities(), arcs, "Possible arcs differ" ) if arcs_missing is not None: self.assertEqualArcs( analysis.arcs_missing(), arcs_missing, "Missing arcs differ" ) if arcs_unpredicted is not None: self.assertEqualArcs( analysis.arcs_unpredicted(), arcs_unpredicted, "Unpredicted arcs differ" ) if report: frep = StringIO() cov.report(mod, file=frep) rep = " ".join(frep.getvalue().split("\n")[2].split()[1:]) self.assertEqual(report, rep)