Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/littlefs-project/littlefs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Haster <chaster@utexas.edu>2022-05-23 09:35:00 +0300
committerChristopher Haster <chaster@utexas.edu>2022-06-06 09:35:16 +0300
commit46cc6d44505579d137d9a51705b3807381a5d4b0 (patch)
treedaccc018288cdd0c665a4a58ff4f5c30d4bbfc60 /scripts
parent5b0a6d4747a4a7eba1320f8e936a7240064595f2 (diff)
Added support for annotated source in coverage.py
On one hand this isn't very different than the source annotation in gcov, on the other hand I find it a bit more readable after a bit of experimentation.
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/coverage.py104
1 files changed, 104 insertions, 0 deletions
diff --git a/scripts/coverage.py b/scripts/coverage.py
index 879c481..8c81e6f 100755
--- a/scripts/coverage.py
+++ b/scripts/coverage.py
@@ -164,6 +164,14 @@ def openio(path, mode='r'):
else:
return open(path, mode)
+def color(**args):
+ if args.get('color') == 'auto':
+ return sys.stdout.isatty()
+ elif args.get('color') == 'always':
+ return True
+ else:
+ return False
+
def collect(paths, **args):
results = {}
for path in paths:
@@ -221,6 +229,81 @@ def collect(paths, **args):
return func_results, results
+def annotate(paths, results, **args):
+ for path in paths:
+ # map to source file
+ src_path = re.sub('\.t\.a\.gcda$', '.c', path)
+ # TODO test this
+ if args.get('build_dir'):
+ src_path = re.sub('%s/*' % re.escape(args['build_dir']), '',
+ src_path)
+
+ # flatten to line info
+ line_results = {line: (hits, result)
+ for (_, _, line), (hits, result) in results.items()}
+
+ # calculate spans to show
+ if not args.get('annotate'):
+ spans = []
+ last = None
+ for line, (hits, result) in sorted(line_results.items()):
+ if ((args.get('lines') and hits == 0)
+ or (args.get('branches')
+ and result.coverage_branch_hits
+ < result.coverage_branch_count)):
+ if last is not None and line - last.stop <= args['context']:
+ last = range(
+ last.start,
+ line+1+args['context'])
+ else:
+ if last is not None:
+ spans.append(last)
+ last = range(
+ line-args['context'],
+ line+1+args['context'])
+ if last is not None:
+ spans.append(last)
+
+ with open(src_path) as f:
+ skipped = False
+ for i, line in enumerate(f):
+ # skip lines not in spans?
+ if (not args.get('annotate')
+ and not any(i+1 in s for s in spans)):
+ skipped = True
+ continue
+
+ if skipped:
+ skipped = False
+ print('%s@@ %s:%d @@%s' % (
+ '\x1b[36m' if color(**args) else '',
+ src_path,
+ i+1,
+ '\x1b[m' if color(**args) else ''))
+
+ # build line
+ if line.endswith('\n'):
+ line = line[:-1]
+
+ if i+1 in line_results:
+ hits, result = line_results[i+1]
+ line = '%-*s // %d hits, %d/%d branches' % (
+ args['width'],
+ line,
+ hits,
+ result.coverage_branch_hits,
+ result.coverage_branch_count)
+
+ if color(**args):
+ if args.get('lines') and hits == 0:
+ line = '\x1b[1;31m%s\x1b[m' % line
+ elif (args.get('branches') and
+ result.coverage_branch_hits
+ < result.coverage_branch_count):
+ line = '\x1b[35m%s\x1b[m' % line
+
+ print(line)
+
def main(**args):
# find sizes
if not args.get('use', None):
@@ -246,7 +329,10 @@ def main(**args):
*(result[f] for f in CoverageResult._fields))
for result in r
if all(result.get(f) not in {None, ''}
+
for f in CoverageResult._fields)}
+ paths = []
+ line_results = {}
# find previous results?
if args.get('diff'):
@@ -344,6 +430,10 @@ def main(**args):
if args.get('quiet'):
pass
+ elif (args.get('annotate')
+ or args.get('lines')
+ or args.get('branches')):
+ annotate(paths, line_results, **args)
elif args.get('summary'):
print_header('')
print_entries('total')
@@ -403,6 +493,20 @@ if __name__ == "__main__":
help="Show file-level coverage.")
parser.add_argument('-Y', '--summary', action='store_true',
help="Only show the total coverage.")
+ parser.add_argument('-p', '--annotate', action='store_true',
+ help="Show source files annotated with coverage info.")
+ parser.add_argument('-l', '--lines', action='store_true',
+ help="Show uncovered lines.")
+ parser.add_argument('-b', '--branches', action='store_true',
+ help="Show uncovered branches.")
+ parser.add_argument('-c', '--context', type=lambda x: int(x, 0), default=3,
+ help="Show a additional lines of context. Defaults to 3.")
+ parser.add_argument('-w', '--width', type=lambda x: int(x, 0), default=80,
+ help="Assume source is styled with this many columns. Defaults to 80.")
+ # TODO add this to test.py?
+ parser.add_argument('--color',
+ choices=['never', 'always', 'auto'], default='auto',
+ help="When to use terminal colors.")
parser.add_argument('-e', '--error-on-lines', action='store_true',
help="Error if any lines are not covered.")
parser.add_argument('-E', '--error-on-branches', action='store_true',