Skip to content

Commit 3f28a3b

Browse files
committed
Add coverage workflow to GitHub Actions
This action performs a coverage analysis for any source files that have been modified, and reports if there are any new or modified lines that are not covered by any tests. The coverage analysis is focused on new code because we know there are paths that are currently not covered; the intent of this new action is only to prevent a pull request from making the situation worse. Signed-off-by: John Pennycook <john.pennycook@intel.com>
1 parent 6c25257 commit 3f28a3b

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

.github/workflows/check-coverage.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import json
4+
import subprocess # nosec B404
5+
import sys
6+
7+
# Parse command-line arguments.
8+
parser = argparse.ArgumentParser()
9+
parser.add_argument("--commits", nargs="+", default=[])
10+
parser.add_argument("file", metavar="<coverage.json>", action="store")
11+
args = parser.parse_args(sys.argv[1:])
12+
13+
# Read the coverage information into an object.
14+
with open(args.file, "rb") as f:
15+
coverage = json.load(f)
16+
17+
# For each file:
18+
# - Determine which lines were not covered;
19+
# - Check when the lines were last modified;
20+
# - Print details of new, uncovered, lines.
21+
report = {}
22+
for filename, info in coverage["files"].items():
23+
if not isinstance(filename, str):
24+
raise TypeError("filename must be a string")
25+
26+
missing = info["missing_lines"]
27+
if not missing:
28+
continue
29+
30+
for lineno in missing:
31+
if not isinstance(lineno, int):
32+
raise TypeError("line numbers must be integers")
33+
cmd = [
34+
"git",
35+
"blame",
36+
filename,
37+
"-L",
38+
f"{lineno},{lineno}",
39+
"--no-abbrev",
40+
]
41+
completed = subprocess.run(cmd, capture_output=True)
42+
commit = completed.stdout.decode().split()[0].strip()
43+
44+
if commit in args.commits:
45+
if filename not in report:
46+
report[filename] = []
47+
report[filename].append(str(lineno))
48+
49+
for filename in report:
50+
n = len(report[filename])
51+
print(f'{n} uncovered lines in {filename}: {",".join(report[filename])}')
52+
53+
# Use the exit code to communicate failure to GitHub.
54+
if len(report) != 0:
55+
sys.exit(1)
56+
else:
57+
sys.exit(0)

.github/workflows/coverage.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: coverage
2+
3+
permissions: read-all
4+
5+
on:
6+
pull_request:
7+
branches: [ "main" ]
8+
paths:
9+
- 'codebasin/**'
10+
11+
jobs:
12+
check-coverage:
13+
name: Ensure modified lines are tested
14+
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
python-version: ["3.12"]
18+
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- name: Set up Python ${{ matrix.python-version }}
23+
uses: actions/setup-python@v4
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
27+
- name: Install `code-base-investigator`
28+
run: |
29+
python -m pip install -U pip
30+
pip install .
31+
32+
- name: Install `coverage`
33+
run: |
34+
pip install coverage
35+
36+
- name: Run `coverage`
37+
run: |
38+
python -m coverage run -m unittest
39+
40+
- name: Generate coverage.json
41+
run: |
42+
python -m coverage json --include=$(git diff --name-status main codebasin/*.py | grep "^M" | awk '{ print $2 }' | paste -sd,)
43+
44+
- name: Check coverage against latest commits
45+
run: |
46+
FROM=${{ github.event.pull_request.base.sha }}
47+
TO=${{ github.event.pull_request.head.sha }}
48+
COMMITS=$(git rev-list $FROM..$TO)
49+
python .github/workflows/check-coverage.py coverage.json --commits $COMMITS

0 commit comments

Comments
 (0)