提交 6cc40bbd 编写于 作者: M Marbin Tan 提交者: C.J. Jameson

Remove figleaf

Signed-off-by: NC.J. Jameson <cjameson@pivotal.io>
上级 536554fd
"""
figleaf is another tool to trace Python code coverage.
figleaf uses the sys.settrace hook to record which statements are
executed by the CPython interpreter; this record can then be saved
into a file, or otherwise communicated back to a reporting script.
figleaf differs from the gold standard of Python coverage tools
('coverage.py') in several ways. First and foremost, figleaf uses the
same criterion for "interesting" lines of code as the sys.settrace
function, which obviates some of the complexity in coverage.py (but
does mean that your "loc" count goes down). Second, figleaf does not
record code executed in the Python standard library, which results in
a significant speedup. And third, the format in which the coverage
format is saved is very simple and easy to work with.
You might want to use figleaf if you're recording coverage from
multiple types of tests and need to aggregate the coverage in
interesting ways, and/or control when coverage is recorded.
coverage.py is a better choice for command-line execution, and its
reporting is a fair bit nicer.
Command line usage: ::
figleaf <python file to execute> <args to python file>
The figleaf output is saved into the file '.figleaf', which is an
*aggregate* of coverage reports from all figleaf runs from this
directory. '.figleaf' contains a pickled dictionary of sets; the keys
are source code filenames, and the sets contain all line numbers
executed by the Python interpreter. See the docs or command-line
programs in bin/ for more information.
High level API: ::
* ``start(ignore_lib=True)`` -- start recording code coverage.
* ``stop()`` -- stop recording code coverage.
* ``get_trace_obj()`` -- return the (singleton) trace object.
* ``get_info()`` -- get the coverage dictionary
Classes & functions worth knowing about (lower level API):
* ``get_lines(fp)`` -- return the set of interesting lines in the fp.
* ``combine_coverage(d1, d2)`` -- combine coverage info from two dicts.
* ``read_coverage(filename)`` -- load the coverage dictionary
* ``write_coverage(filename)`` -- write the coverage out.
* ``annotate_coverage(...)`` -- annotate a Python file with its coverage info.
Known problems:
-- module docstrings are *covered* but not found.
AUTHOR: C. Titus Brown, titus@idyll.org, with contributions from Iain Lowe.
'figleaf' is Copyright (C) 2006, 2007 C. Titus Brown. It is under the
BSD license.
"""
__version__ = "0.6.1"
# __all__ == @CTB
import sys
import os
from cPickle import dump, load
from optparse import OptionParser
import internals
# use builtin sets if in >= 2.4, otherwise use 'sets' module.
try:
set()
except NameError:
from sets import Set as set
def get_lines(fp):
"""
Return the set of interesting lines in the source code read from
this file handle.
"""
# rstrip is a workaround for http://bugs.python.org/issue4262
src = fp.read().rstrip() + "\n"
code = compile(src, "", "exec")
return internals.get_interesting_lines(code)
def combine_coverage(d1, d2):
"""
Given two coverage dictionaries, combine the recorded coverage
and return a new dictionary.
"""
keys = set(d1.keys())
keys.update(set(d2.keys()))
new_d = {}
for k in keys:
v = d1.get(k, set())
v2 = d2.get(k, set())
s = set(v)
s.update(v2)
new_d[k] = s
return new_d
def write_coverage(filename, append=True):
"""
Write the current coverage info out to the given filename. If
'append' is false, destroy any previously recorded coverage info.
"""
if _t is None:
return
data = internals.CoverageData(_t)
d = data.gather_files()
# sum existing coverage?
if append:
old = {}
fp = None
try:
fp = open(filename)
except IOError:
pass
if fp:
old = load(fp)
fp.close()
d = combine_coverage(d, old)
# ok, save.
outfp = open(filename, 'w')
try:
dump(d, outfp)
finally:
outfp.close()
def read_coverage(filename):
"""
Read a coverage dictionary in from the given file.
"""
fp = open(filename)
try:
d = load(fp)
finally:
fp.close()
return d
def dump_pickled_coverage(out_fp):
"""
Dump coverage information in pickled format into the given file handle.
"""
dump(_t, out_fp)
def load_pickled_coverage(in_fp):
"""
Replace (overwrite) coverage information from the given file handle.
"""
global _t
_t = load(in_fp)
def annotate_coverage(in_fp, out_fp, covered, all_lines,
mark_possible_lines=False):
"""
A simple example coverage annotator that outputs text.
"""
for i, line in enumerate(in_fp):
i = i + 1
if i in covered:
symbol = '>'
elif i in all_lines:
symbol = '!'
else:
symbol = ' '
symbol2 = ''
if mark_possible_lines:
symbol2 = ' '
if i in all_lines:
symbol2 = '-'
out_fp.write('%s%s %s' % (symbol, symbol2, line,))
def get_data():
if _t:
return internals.CoverageData(_t)
#######################
#
# singleton functions/top-level API
#
_t = None
def init(exclude_path=None, include_only=None):
from internals import CodeTracer
global _t
if _t is None:
_t = CodeTracer(exclude_path, include_only)
def start(ignore_python_lib=True):
"""
Start tracing code coverage. If 'ignore_python_lib' is True on
initial call, ignore all files that live below the same directory as
the 'os' module.
"""
global _t
if not _t:
exclude_path = None
if ignore_python_lib:
exclude_path = os.path.realpath(os.path.dirname(os.__file__))
init(exclude_path, None)
_t.start()
def start_section(name):
global _t
_t.start_section(name)
def stop_section():
global _t
_t.stop_section()
def stop():
"""
Stop tracing code coverage.
"""
global _t
if _t is not None:
_t.stop()
def get_trace_obj():
"""
Return the (singleton) trace object, if it exists.
"""
return _t
def get_info(section_name=None):
"""
Get the coverage dictionary from the trace object.
"""
if _t:
return get_data().gather_files(section_name)
#############
def display_ast():
l = internals.LineGrabber(open(sys.argv[1]))
l.pretty_print()
print l.lines
def main():
"""
Execute the given Python file with coverage, making it look like it is
__main__.
"""
ignore_pylibs = False
# gather args
n = 1
figleaf_args = []
for n in range(1, len(sys.argv)):
arg = sys.argv[n]
if arg.startswith('-'):
figleaf_args.append(arg)
else:
break
remaining_args = sys.argv[n:]
usage = "usage: %prog [options] [python_script arg1 arg2 ...]"
option_parser = OptionParser(usage=usage)
option_parser.add_option('-i', '--ignore-pylibs', action="store_true",
dest="ignore_pylibs", default=False,
help="ignore Python library modules")
(options, args) = option_parser.parse_args(args=figleaf_args)
assert len(args) == 0
if not remaining_args:
option_parser.error("you must specify a python script to run!")
ignore_pylibs = options.ignore_pylibs
## Reset system args so that the subsequently exec'd file can read
## from sys.argv
sys.argv = remaining_args
sys.path[0] = os.path.dirname(sys.argv[0])
cwd = os.getcwd()
start(ignore_pylibs) # START code coverage
import __main__
try:
execfile(sys.argv[0], __main__.__dict__)
finally:
stop() # STOP code coverage
write_coverage(os.path.join(cwd, '.figleaf'))
import os.path, sys
libdir = os.path.join(os.path.dirname(__file__), '../')
libdir = os.path.normpath(libdir)
if libdir not in sys.path:
sys.path.insert(0, libdir)
"""
Common functions for annotating files with figleaf coverage information.
"""
import sys, os
from optparse import OptionParser
import ConfigParser
import re
import logging
import figleaf
thisdir = os.path.dirname(__file__)
try: # 2.3 compatibility
logging.basicConfig(format='%(message)s', level=logging.WARNING)
except TypeError:
pass
logger = logging.getLogger('figleaf.annotate')
DEFAULT_CONFIGURE_FILE = ".figleafrc"
### utilities
def safe_conf_get(conf, section, name, default):
try:
val = conf.get(section, name)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
val = default
return val
def configure(parser):
"""
Configure the optparse.OptionParser object with defaults, optionally
loaded from a configuration file.
"""
CONFIG_FILE = os.environ.get('FIGLEAFRC', DEFAULT_CONFIGURE_FILE)
parser.add_option("-c", "--coverage-file", action="store",
type="string", dest="coverage_file",
help="File containing figleaf coverage information.")
parser.add_option("-s", "--sections-file", action="store",
type="string", dest="sections_file",
help="File containing figleaf sections coverage info.")
parser.add_option("-v", "--verbose", action="store_true",
dest="verbose")
conf_file = ConfigParser.ConfigParser()
conf_file.read(CONFIG_FILE) # ignores if not present
default_coverage_file = safe_conf_get(conf_file,
'figleaf', 'coverage_file',
'.figleaf')
default_sections_file = safe_conf_get(conf_file,
'figleaf', 'sections_file',
'.figleaf_sections')
default_verbose = int(safe_conf_get(conf_file, 'figleaf', 'verbose',
0))
parser.set_defaults(coverage_file=default_coverage_file,
sections_file=default_sections_file,
verbose=default_verbose)
def filter_coverage(coverage, re_match):
"""
...
"""
if not re_match:
return coverage
regexp = re.compile(re_match)
d = {}
for filename, lines in coverage.items():
if regexp.match(filename):
d[filename] = lines
return d
### commands
def list(options, match=""):
"""
List the filenames in the coverage file, optionally limiting it to
those files matching to the regexp 'match'.
"""
if options.verbose:
print>>sys.stderr, '** Reading coverage from coverage file %s' % \
(options.coverage_file,)
if match:
print>>sys.stderr, '** Filtering against regexp "%s"' % (match,)
coverage = figleaf.read_coverage(options.coverage_file)
coverage = filter_coverage(coverage, match)
for filename in coverage.keys():
print filename
def list_sections(options, match=""):
"""
List the filenames in the coverage file, optionally limiting it to
those files matching to the regexp 'match'.
"""
if options.verbose:
print>>sys.stderr, '** Reading sections info from sections file %s' % \
(options.sections_file,)
if match:
print>>sys.stderr, '** Filtering against regexp "%s"' % (match,)
fp = open(options.sections_file)
figleaf.load_pickled_coverage(fp) # @CTB
data = figleaf.internals.CoverageData(figleaf._t)
coverage = data.gather_files()
coverage = filter_coverage(coverage, match)
for filename in coverage.keys():
print filename
###
def read_exclude_patterns(filename):
"""
Read in exclusion patterns from a file; these are just regexps.
"""
if not filename:
return []
exclude_patterns = []
fp = open(filename)
for line in fp:
line = line.rstrip()
if line and not line.startswith('#'):
pattern = re.compile(line)
exclude_patterns.append(pattern)
return exclude_patterns
def read_files_list(filename):
"""
Read in a list of files from a file; these are relative or absolute paths.
"""
s = {}
for line in open(filename):
f = line.strip()
s[os.path.abspath(f)] = 1
return s
def filter_files(filenames, exclude_patterns = [], files_list = {}):
files_list = dict(files_list) # make copy
# list of files specified?
if files_list:
for filename in files_list.keys():
yield filename
filenames = [ os.path.abspath(x) for x in filenames ]
for filename in filenames:
try:
del files_list[filename]
except KeyError:
logger.info('SKIPPING %s -- not in files list' % (filename,))
return
### no files list given -- handle differently
for filename in filenames:
abspath = os.path.abspath(filename)
# check to see if we match anything in the exclude_patterns list
skip = False
for pattern in exclude_patterns:
if pattern.search(filename):
logger.info('SKIPPING %s -- matches exclusion pattern' % \
(filename,))
skip = True
break
if skip:
continue
# next, check to see if we're part of the figleaf package.
if thisdir in filename:
logger.debug('SKIPPING %s -- part of the figleaf package' % \
(filename,))
continue
# also, check for <string> (source file from things like 'exec'):
if filename == '<string>':
continue
# miscellaneous other things: doctests
if filename.startswith('<doctest '):
continue
yield filename
###
def main():
parser = OptionParser()
configure(parser)
options, args = parser.parse_args()
if not len(args):
print "ERROR: You must specify a command like 'list' or 'report'. Use"
print "\n %s -h\n" % (sys.argv[0],)
print "for help on commands and options."
sys.exit(-1)
cmd = args.pop(0)
if cmd == 'list':
list(options, *args)
elif cmd == 'list_sections':
list_sections(options, *args)
sys.exit(0)
import figleaf
import os
import re
from annotate import read_exclude_patterns, filter_files, logger
def report_as_cover(coverage, exclude_patterns=[], ):
### now, output.
keys = coverage.keys()
info_dict = {}
for k in filter_files(keys):
try:
pyfile = open(k, 'rU')
lines = figleaf.get_lines(pyfile)
except IOError:
logger.warning('CANNOT OPEN: %s' % k)
continue
except KeyboardInterrupt:
raise
except Exception, e:
logger.error('ERROR: file %s, exception %s' % (pyfile, str(e)))
continue
# ok, got all the info. now annotate file ==> html.
covered = coverage[k]
pyfile = open(k)
(n_covered, n_lines, output) = make_cover_lines(lines, covered, pyfile)
try:
pcnt = n_covered * 100. / n_lines
except ZeroDivisionError:
pcnt = 100
info_dict[k] = (n_lines, n_covered, pcnt)
outfile = make_cover_filename(k)
try:
outfp = open(outfile, 'w')
outfp.write("\n".join(output))
outfp.write("\n")
outfp.close()
except IOError:
logger.warning('cannot open filename %s' % (outfile,))
continue
logger.info('reported on %s' % (outfile,))
### print a summary, too.
info_dict_items = info_dict.items()
def sort_by_pcnt(a, b):
a = a[1][2]
b = b[1][2]
return -cmp(a,b)
info_dict_items.sort(sort_by_pcnt)
logger.info('reported on %d file(s) total\n' % len(info_dict))
return len(info_dict)
def make_cover_lines(line_info, coverage_info, fp):
n_covered = n_lines = 0
output = []
for i, line in enumerate(fp):
is_covered = False
is_line = False
i += 1
if i in coverage_info:
is_covered = True
prefix = '+'
n_covered += 1
n_lines += 1
elif i in line_info:
prefix = '-'
is_line = True
n_lines += 1
else:
prefix = '0'
line = line.rstrip()
output.append(prefix + ' ' + line)
return (n_covered, n_lines, output)
def make_cover_filename(orig):
return orig + '.cover'
def main():
import sys
import logging
from optparse import OptionParser
###
option_parser = OptionParser()
option_parser.add_option('-x', '--exclude-patterns', action="store",
dest="exclude_patterns_file",
help="file containing regexp patterns to exclude")
option_parser.add_option('-q', '--quiet', action='store_true',
dest='quiet',
help="file containig regexp patterns of files to exclude from report")
option_parser.add_option('-D', '--debug', action='store_true',
dest='debug',
help='Show all debugging messages')
(options, args) = option_parser.parse_args()
if options.quiet:
logging.disable(logging.DEBUG)
if options.debug:
logger.setLevel(logging.DEBUG)
### load
if not args:
args = ['.figleaf']
coverage = {}
for filename in args:
logger.debug("loading coverage info from '%s'\n" % (filename,))
d = figleaf.read_coverage(filename)
coverage = figleaf.combine_coverage(coverage, d)
if not coverage:
logger.warning('EXITING -- no coverage info!\n')
sys.exit(-1)
exclude = read_exclude_patterns(options.exclude_patterns_file)
report_as_cover(coverage, exclude)
import figleaf
import os
import re
# use builtin sets if in >= 2.4, otherwise use 'sets' module.
try:
set()
except NameError:
from sets import Set as set
from figleaf.annotate import read_exclude_patterns, filter_files, logger, \
read_files_list
###
def annotate_file(fp, lines, covered):
# initialize
n_covered = n_lines = 0
output = []
for i, line in enumerate(fp):
is_covered = False
is_line = False
i += 1
if i in covered:
is_covered = True
n_covered += 1
n_lines += 1
elif i in lines:
is_line = True
n_lines += 1
color = 'black'
if is_covered:
color = 'green'
elif is_line:
color = 'red'
line = escape_html(line.rstrip())
output.append('<font color="%s">%4d. %s</font>' % (color, i, line))
try:
percent = n_covered * 100. / n_lines
except ZeroDivisionError:
percent = 100
return output, n_covered, n_lines, percent
def write_html_summary(info_dict, directory):
info_dict_items = info_dict.items()
def sort_by_percent(a, b):
a = a[1][2]
b = b[1][2]
return -cmp(a,b)
info_dict_items.sort(sort_by_percent)
summary_lines = sum([ v[0] for (k, v) in info_dict_items])
summary_cover = sum([ v[1] for (k, v) in info_dict_items])
summary_percent = 100
if summary_lines:
summary_percent = float(summary_cover) * 100. / float(summary_lines)
percents = [ float(v[1]) * 100. / float(v[0])
for (k, v) in info_dict_items if v[0] ]
percent_90 = [ x for x in percents if x >= 90 ]
percent_75 = [ x for x in percents if x >= 75 ]
percent_50 = [ x for x in percents if x >= 50 ]
### write out summary.
index_fp = open('%s/index.html' % (directory,), 'w')
index_fp.write('''
<html>
<title>figleaf code coverage report</title>
<h2>Summary</h2>
%d files total: %d files &gt; 90%%, %d files &gt; 75%%, %d files &gt; 50%%
<p>
<table border=1>
<tr>
<th>Filename</th><th># lines</th><th># covered</th><th>%% covered</th>
</tr>
<tr>
<td><b>totals:</b></td>
<td><b>%d</b></td>
<td><b>%d</b></td>
<td><b>%.1f%%</b></td>
</tr>
<tr></tr>
''' % (len(percents), len(percent_90), len(percent_75), len(percent_50),
summary_lines, summary_cover, summary_percent,))
for filename, (n_lines, n_covered, percent_covered,) in info_dict_items:
html_outfile = make_html_filename(filename)
index_fp.write('''
<tr>
<td><a href="./%s">%s</a></td>
<td>%d</td>
<td>%d</td>
<td>%.1f</td>
</tr>
''' % (html_outfile, filename, n_lines, n_covered, percent_covered,))
index_fp.write('</table>\n')
index_fp.close()
def report_as_html(coverage, directory, exclude_patterns, files_list):
"""
Write an HTML report on all of the files, plus a summary.
"""
### now, output.
keys = coverage.keys()
info_dict = {}
for pyfile in filter_files(keys, exclude_patterns, files_list):
try:
fp = open(pyfile, 'rU')
lines = figleaf.get_lines(fp)
except KeyboardInterrupt:
raise
except IOError:
logger.error('CANNOT OPEN: %s' % (pyfile,))
continue
except Exception, e:
logger.error('ERROR: file %s, exception %s' % (pyfile, str(e)))
continue
#
# ok, we want to annotate this file. now annotate file ==> html.
#
# initialize
covered = coverage.get(pyfile, set())
# rewind
fp.seek(0)
# annotate
output, n_covered, n_lines, percent = annotate_file(fp, lines, covered)
# summarize
info_dict[pyfile] = (n_lines, n_covered, percent)
# write out the file
html_outfile = make_html_filename(pyfile)
html_outfile = os.path.join(directory, html_outfile)
html_outfp = open(html_outfile, 'w')
html_outfp.write('source file: <b>%s</b><br>\n' % (pyfile,))
html_outfp.write('''
file stats: <b>%d lines, %d executed: %.1f%% covered</b>
<pre>
%s
</pre>
''' % (n_lines, n_covered, percent, "\n".join(output)))
html_outfp.close()
logger.info('reported on %s' % (pyfile,))
### print a summary, too.
write_html_summary(info_dict, directory)
logger.info('reported on %d file(s) total\n' % len(info_dict))
def prepare_reportdir(dirname):
"Create output directory."
try:
os.mkdir(dirname)
except OSError: # already exists
pass
def make_html_filename(orig):
"'escape' original paths into a single filename"
orig = os.path.abspath(orig)
# orig = os.path.basename(orig)
orig = os.path.splitdrive(orig)[1]
orig = orig.replace('_', '__')
orig = orig.replace(os.path.sep, '_')
orig += '.html'
return orig
def escape_html(s):
s = s.replace("&", "&amp;")
s = s.replace("<", "&lt;")
s = s.replace(">", "&gt;")
s = s.replace('"', "&quot;")
return s
def main():
import sys
import logging
from optparse import OptionParser
###
usage = "usage: %prog [options] [coverage files ... ]"
option_parser = OptionParser(usage=usage)
option_parser.add_option('-x', '--exclude-patterns', action="store",
dest="exclude_patterns_file",
help="file containing regexp patterns of files to exclude from report")
option_parser.add_option('-f', '--files-list', action="store",
dest="files_list",
help="file containing filenames to report on")
option_parser.add_option('-d', '--output-directory', action='store',
dest="output_dir",
default = "html",
help="directory for HTML output")
option_parser.add_option('-q', '--quiet', action='store_true',
dest='quiet',
help='Suppress all but error messages')
option_parser.add_option('-D', '--debug', action='store_true',
dest='debug',
help='Show all debugging messages')
(options, args) = option_parser.parse_args()
if options.quiet:
logging.disable(logging.DEBUG)
if options.debug:
logger.setLevel(logging.DEBUG)
### load/combine
if not args:
args = ['.figleaf']
coverage = {}
for filename in args:
logger.debug("loading coverage info from '%s'\n" % (filename,))
try:
d = figleaf.read_coverage(filename)
coverage = figleaf.combine_coverage(coverage, d)
except IOError:
logger.error("cannot open filename '%s'\n" % (filename,))
if not coverage:
logger.warning('EXITING -- no coverage info!\n')
sys.exit(-1)
exclude = []
if options.exclude_patterns_file:
exclude = read_exclude_patterns(options.exclude_patterns_file)
files_list = {}
if options.files_list:
files_list = read_files_list(options.files_list)
### make directory
prepare_reportdir(options.output_dir)
report_as_html(coverage, options.output_dir, exclude, files_list)
print 'figleaf: HTML output written to %s' % (options.output_dir,)
#! /usr/bin/env python
import figleaf
from figleaf import internals
from sets import Set as set
import sys
from cPickle import load
import os
from optparse import OptionParser
def main():
#### OPTIONS
parser = OptionParser()
parser.add_option('-c', '--coverage', nargs=1, action="store",
dest="coverage_file",
help = 'load coverage info from this file',
default='.figleaf_sections')
####
(options, args) = parser.parse_args(sys.argv[1:])
coverage_file = options.coverage_file
figleaf.load_pickled_coverage(open(coverage_file))
data = internals.CoverageData(figleaf._t)
full_cov = data.gather_files()
for filename in args:
annotate_file_with_sections(filename, data, full_cov)
def annotate_file_with_sections(short, data, full_cov):
full = os.path.abspath(short)
tags = {}
sections = data.gather_sections(full)
sections.update(data.gather_sections(short))
print data.sections
print '*** PROCESSING:', short, '\n\t==>', short + '.sections'
for tag, cov in sections.items():
if cov:
tags[tag] = cov
if not tags:
print '*** No coverage info for file', short
tag_names = tags.keys()
tag_names.sort()
tag_names.reverse()
tags["-- all coverage --"] = full_cov.get(full, set())
tag_names.insert(0, "-- all coverage --")
n_tags = len(tag_names)
fp = open(short + '.sections', 'w')
for i, tag in enumerate(tag_names):
fp.write('%s%s\n' % ('| ' * i, tag))
fp.write('| ' * n_tags)
fp.write('\n\n')
source = open(full)
for n, line in enumerate(source):
marks = ""
for tag in tag_names:
cov = tags[tag]
symbol = ' '
if (n+1) in cov:
symbol = '+ '
marks += symbol
fp.write('%s | %s' % (marks, line))
fp.close()
#! /usr/bin/env python
"""
Output an HTML-ized coverage report.
"""
import _lib
import figleaf.annotate_html
figleaf.annotate_html.main()
"""
Coverage tracking internals.
"""
import sys
import threading
err = sys.stderr
import types, symbol
# use builtin sets if in >= 2.4, otherwise use 'sets' module.
try:
set()
except NameError:
from sets import Set as set
def get_interesting_lines(code):
"""
Count 'interesting' lines of Python in a code object, where
'interesting' is defined as 'lines that could possibly be
executed'.
This is done by dissassembling the code objecte and returning
line numbers.
"""
# clean up weird end-of-file issues
lines = set([ l for (o, l) in findlinestarts(code) ])
for const in code.co_consts:
if type(const) == types.CodeType:
lines.update(get_interesting_lines(const))
return lines
def findlinestarts(code):
"""Find the offsets in a byte code which are start of lines in the source.
Generate pairs (offset, lineno) as described in Python/compile.c.
CTB -- swiped from Python 2.5, module 'dis', so that earlier versions
of Python could use the function, too.
"""
byte_increments = [ord(c) for c in code.co_lnotab[0::2]]
line_increments = [ord(c) for c in code.co_lnotab[1::2]]
lastlineno = None
lineno = code.co_firstlineno
addr = 0
for byte_incr, line_incr in zip(byte_increments, line_increments):
if byte_incr:
if lineno != lastlineno:
yield (addr, lineno)
lastlineno = lineno
addr += byte_incr
lineno += line_incr
if lineno != lastlineno:
yield (addr, lineno)
class CodeTracer:
"""
Basic mechanisms for code coverage tracking, using sys.settrace.
"""
def __init__(self, exclude_prefix, include_only_prefix):
self.common = self.c = set()
self.section_name = None
self.sections = {}
self.started = False
assert not (exclude_prefix and include_only_prefix), \
"mutually exclusive"
self.excl = exclude_prefix
self.incl = include_only_prefix
def start(self):
"""
Start recording.
"""
if not self.started:
self.started = True
if self.excl and not self.incl:
global_trace_fn = self.g1
elif self.incl and not self.excl:
global_trace_fn = self.g2
else:
global_trace_fn = self.g0
sys.settrace(global_trace_fn)
if hasattr(threading, 'settrace'):
threading.settrace(global_trace_fn)
def stop(self):
if self.started:
sys.settrace(None)
if hasattr(threading, 'settrace'):
threading.settrace(None)
self.started = False
self.stop_section()
def g0(self, f, e, a):
"""
global trace function, no exclude/include info.
f == frame, e == event, a == arg .
"""
if e == 'call':
return self.t
def g1(self, f, e, a):
"""
global trace function like g0, but ignores files starting with
'self.excl'.
"""
if e == 'call':
excl = self.excl
path = f.f_globals.get('__file__')
if path is None:
path = f.f_code.co_filename
if excl and path.startswith(excl):
return
return self.t
def g2(self, f, e, a):
"""
global trace function like g0, but only records files starting with
'self.incl'.
"""
if e == 'call':
incl = self.incl
if incl and f.f_code.co_filename.startswith(incl):
return self.t
def t(self, f, e, a):
"""
local trace function.
"""
if e is 'line':
self.c.add((f.f_code.co_filename, f.f_lineno))
return self.t
def clear(self):
"""
wipe out coverage info
"""
self.c = {}
def start_section(self, name):
self.stop_section()
self.section_name = name
self.c = self.sections.get(name, set())
def stop_section(self):
if self.section_name:
self.sections[self.section_name] = self.c
self.section_name = None
self.c = self.common
class CoverageData:
"""
A class to manipulate and combine data from the CodeTracer object.
In general, do not pickle this object; it's simpler and more
straightforward to just pass the basic Python objects around
(e.g. CoverageData.common, a set, and CoverageData.sections, a
dictionary of sets).
"""
def __init__(self, trace_obj=None):
self.common = set()
self.sections = {}
if trace_obj:
self.update(trace_obj)
def update(self, trace_obj):
# transfer common-block code coverage -- if no sections are set,
# this will be all of the code coverage info.
self.common.update(trace_obj.common)
# update our internal section dictionary with the (filename, line_no)
# pairs from the section coverage as well.
for section_name, section_d in trace_obj.sections.items():
section_set = self.sections.get(section_name, set())
section_set.update(section_d)
self.sections[section_name] = section_set
def gather_files(self, name=None):
"""
Return the dictionary of lines of executed code; the dict
keys are filenames and values are sets containing individual
(integer) line numbers.
'name', if set, is the desired section name from which to gather
coverage info.
"""
cov = set()
cov.update(self.common)
if name is None:
for section_name, coverage_set in self.sections.items():
cov.update(coverage_set)
else:
coverage_set = self.sections.get(name, set())
cov.update(coverage_set)
# cov = list(cov)
# cov.sort()
files = {}
for (filename, line) in cov: # @CTB could optimize
d = files.get(filename, set())
d.add(line)
files[filename] = d
return files
def gather_sections(self, file):
"""
Return a dictionary of sets containing section coverage information for
a specific file. Dict keys are sections, and the dict values are
sets containing (integer) line numbers.
"""
sections = {}
for k, c in self.sections.items():
s = set()
for (filename, line) in c.keys():
if filename == file:
s.add(line)
sections[k] = s
return sections
"""
figleafsections plugin for nose.
Automatically records coverage info for Python tests and connects it with
with test was being run at the time. Can be used to produce a "barcode"
of code execution.
"""
DEFAULT_COVERAGE_FILE='.figleaf_sections'
import pkg_resources
try:
pkg_resources.require('figleaf>=0.6.1')
import figleaf
except ImportError:
figleaf = None
import sys
err = sys.stderr
import nose.case
from nose.plugins.base import Plugin
import logging
import os
log = logging.getLogger(__name__)
def calc_testname(test):
"""
Build a reasonably human-readable testname from each test.
"""
name = str(test)
if ' ' in name:
name = name.split(' ')[1]
return name
class FigleafSections(Plugin):
def __init__(self):
self.name = 'figleafsections'
Plugin.__init__(self)
self.testname = None
def add_options(self, parser, env=os.environ):
env_opt = 'NOSE_WITH_%s' % self.name.upper()
env_opt.replace('-', '_')
parser.add_option("--with-%s" % self.name,
action="store_true",
dest=self.enableOpt,
default=env.get(env_opt),
help="Enable plugin %s: %s [%s]" %
(self.__class__.__name__, self.help(), env_opt))
parser.add_option("--figleaf-file",
action="store",
dest="figleaf_file",
default=None,
help="Store figleaf section coverage in this file")
def configure(self, options, config):
"""
Configure: enable plugin? And if so, where should the coverage
info be placed?
"""
self.conf = config
# enable?
if hasattr(options, self.enableOpt):
self.enabled = getattr(options, self.enableOpt)
### save coverage file name, if given.
if options.figleaf_file:
self.figleaf_file = options.figleaf_file
else:
self.figleaf_file = DEFAULT_COVERAGE_FILE
if self.enabled and figleaf is None:
raise Exception("You must install figleaf 0.6.1 before you can use the figleafsections plugin! See http://darcs.idyll.org/~t/projects/figleaf/doc/")
def begin(self):
"""
Initialize: start recording coverage info.
"""
figleaf.start()
def finalize(self, result):
"""
Finalize: stop recording coverage info, save & exit.
"""
figleaf.stop()
fp = open(self.figleaf_file, 'w')
figleaf.dump_pickled_coverage(fp)
fp.close()
def startTest(self, test):
"""
Run at the beginning of each test, before per-test fixtures.
One weakness is that this is only run for specific kinds of
nose testcases.
"""
if isinstance(test, nose.case.Test):
self.testname = calc_testname(test)
assert self.testname
figleaf.start_section(self.testname)
def stopTest(self, test):
"""
Run at the end of each test, after per-test fixtures.
"""
if self.testname:
figleaf.stop_section()
self.testname = None
......@@ -9,7 +9,7 @@
from gppylib.mainUtils import *
import os, sys
import pickle, base64, figleaf
import pickle, base64
from optparse import Option, OptionGroup, OptionParser, OptionValueError
......
TINCHOME=$(shell cd .. && pwd)
FIGLEAF_DIR=$(TINCHOME)/ext/figleaf-latest
TESTER=$(TINCHOME)/ext/unittest2/unit2
all:
......@@ -11,16 +10,3 @@ unit:
regress:
$(TESTER) discover -s . -p "regress_*.py" -v
#-------------------------------------------------------------------------------
# COVERAGE
#-------------------------------------------------------------------------------
coverage:
@echo "Running code coverage of TINC unit test"
@$(RM) .figleaf
@$(MAKE) -C $(TINCHOME)/ext figleaf
@$(FIGLEAF_DIR)/bin/figleaf $(TESTER) discover -s . -p "test_*.py"
@$(FIGLEAF_DIR)/bin/figleaf $(TESTER) discover -s . -p "regress_*.py"
@find . -name "*.py" -not -size 0c | grep -v "integration" > pyfilelist
@$(FIGLEAF_DIR)/bin/figleaf2html -d coverage_report .figleaf -f pyfilelist
TINCHOME=$(shell cd .. && pwd)
FIGLEAF_DIR=$(TINCHOME)/ext/figleaf-latest
TESTER=$(TINCHOME)/ext/unittest2/unit2
all:
......@@ -11,16 +10,3 @@ unit:
regress:
$(TESTER) discover -s . -p "regress_*.py" -v
#-------------------------------------------------------------------------------
# COVERAGE
#-------------------------------------------------------------------------------
coverage:
@echo "Running code coverage of TINC unit test"
@$(RM) .figleaf
@$(MAKE) -C $(TINCHOME)/ext figleaf
@$(FIGLEAF_DIR)/bin/figleaf $(TESTER) discover -s . -p "test_*.py"
@$(FIGLEAF_DIR)/bin/figleaf $(TESTER) discover -s . -p "regress_*.py"
@find . -name "*.py" -not -size 0c | grep -v "integration" > pyfilelist
@$(FIGLEAF_DIR)/bin/figleaf2html -d coverage_report .figleaf -f pyfilelist
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册