Skip to content

Commit

Permalink
- fixed bugs in segmentation code and in its tests;
Browse files Browse the repository at this point in the history
  • Loading branch information
jaltmayerpizzorno committed Aug 5, 2024
1 parent 5de606f commit cc5acca
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 271 deletions.
31 changes: 10 additions & 21 deletions src/coverup/segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,6 @@ def find_enclosing(root, line):
if begin <= line <= node.end_lineno:
return (node, begin, node.end_lineno+1) # +1 for range() style

def line_is_docstring(line, node):
return node.body and line == node.body[0].lineno and \
isinstance(node.body[0], ast.Expr) and \
isinstance(node.body[0].value, ast.Constant) and \
isinstance(node.body[0].value.value, str)

for fname, fcov in coverage['files'].items():
with open(fname, "r") as src:
tree = ast.parse(src.read(), fname)
Expand All @@ -102,39 +96,34 @@ def line_is_docstring(line, node):
lines_of_interest = missing_lines.union(set(sum(missing_branches,[])))
lines_of_interest.discard(0) # may result from N->0 branches

# TODO remove from missing lines with load-time statements?
# - directly under ModuleDef, ClassDef
# note that docstring don't generate code, so can't be missing within functions
# But then: how to capture missing coverage if a module was never loaded?

lines_in_segments = set()

for line in sorted(lines_of_interest): # sorted() simplifies tests
if line in lines_in_segments:
# already in a segment
continue

# FIXME add segments for top-level elements (under ModuleDef)
if element := find_enclosing(tree, line):
node, begin, end = element
context = []

# if a class and above line limit, look for enclosing element
# If a class is above the line limit, look for enclosing element
# that might allow us to obey the limit
while isinstance(node, ast.ClassDef) and end - begin > line_limit and \
(element := find_enclosing(node, line)):
context.append((begin, node.lineno+1)) # +1 for range() style
node, begin, end = element

if line == node.lineno or line_is_docstring(line, node):
# 'class' and 'def' are processed at import time, not really interesting
# TODO load modules to remove such (and any others) at coverage collection time?
lines_of_interest.remove(line)
continue

# if 'line' is about a statement within a class and all that follows it
# are function/class definitions, we can trim the segment, reducing its 'end'
# Don't create a segment for a class that's too large... if we did, we
# might create a segment for a class after creating segments for its contents.
if isinstance(node, ast.ClassDef) and end - begin > line_limit:
if all(child.lineno <= line or \
isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) \
for child in ast.iter_child_nodes(node)):
end = min(find_first_line(child) for child in ast.iter_child_nodes(node) \
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) and \
child.lineno > line)
continue

assert line < end
assert (begin, end) not in line_ranges
Expand Down
Loading

0 comments on commit cc5acca

Please sign in to comment.