Skip to content

Commit

Permalink
Attempt to drop our usage of lcov and do things with coverage.py
Browse files Browse the repository at this point in the history
  • Loading branch information
alex committed Jan 16, 2025
1 parent da62c2f commit 10de741
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 35 deletions.
97 changes: 97 additions & 0 deletions .github/bin/merge_rust_coverage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.

import collections
import sys

import coverage


class RustCoveragePlugin(coverage.CoveragePlugin):
def file_reporter(self, filename: str):
return RustCoverageFileReporter(filename)


class RustCoverageFileReporter(coverage.FileReporter):
def lines(self) -> set[int]:
# XXX: Need a better way to handle this state!
return set(raw_data[self.filename])


def coverage_init(
reg: coverage.plugin_support.Plugins,
options: coverage.types.TConfigSectionOut,
) -> None:
reg.add_file_tracer(RustCoveragePlugin())


def main(*lcov_paths: str):
cov = coverage.Coverage()
# XXX: Nasty mucking in semi-public APIs
cov.config.plugins.append("coverage_rust_plugin")
sys.modules["coverage_rust_plugin"] = sys.modules[__name__]

coverage_data = coverage.CoverageData(suffix="rust")

# XXX: global state! Bad!
global raw_data
# {filename: {line_number: count}}
raw_data = collections.defaultdict(lambda: collections.defaultdict(int))
current_file = None
for p in lcov_paths:
with open(p) as f:
for line in f:
line = line.strip()
if line == "end_of_record":
assert current_file is not None
current_file = None
continue

prefix, suffix = line.split(":", 1)
match prefix:
case "SF":
current_file = raw_data[suffix]
case "DA":
assert current_file is not None
line_number, count = suffix.split(",")
current_file[int(line_number)] += int(count)
case (
"BRF"
| "BRH"
| "FN"
| "FNDA"
| "FNF"
| "FNH"
| "LF"
| "LH"
):
# These are various forms of metadata and summary stats
# that we don't need.
pass
case _:
raise NotImplementedError(prefix)

covered_lines = {
file_name: [line for line, c in lines.items() if c > 0]
for file_name, lines in raw_data.items()
}
coverage_data.add_lines(covered_lines)
coverage_data.add_file_tracers(
{
file_name: "coverage_rust_plugin.RustCoveragePlugin"
for file_name in covered_lines
}
)
coverage_data.write()

cov.combine()
coverage_percent = cov.report(show_missing=True)
if coverage_percent < 100:
print("+++ Python coverage under 100% +++")
cov.html_report()
sys.exit(1)


if __name__ == "__main__":
main(*sys.argv[1:])
41 changes: 6 additions & 35 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -449,50 +449,21 @@ jobs:
id: combinecoverage
run: |
set +e
python -m coverage combine
echo "## Python Coverage" >> $GITHUB_STEP_SUMMARY
python -m coverage report -m --fail-under=100 > COV_REPORT
echo "## Coverage" >> $GITHUB_STEP_SUMMARY
python .github/bin/merge_rust_coverage.py *.lcov > COV_REPORT
COV_EXIT_CODE=$?
cat COV_REPORT
if [ $COV_EXIT_CODE -ne 0 ]; then
echo "🚨 Python Coverage failed. Under 100" | tee -a $GITHUB_STEP_SUMMARY
echo "🚨 Coverage failed. Under 100" | tee -a $GITHUB_STEP_SUMMARY
fi
echo '```' >> $GITHUB_STEP_SUMMARY
echo "```" >> $GITHUB_STEP_SUMMARY
cat COV_REPORT >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "```" >> $GITHUB_STEP_SUMMARY
exit $COV_EXIT_CODE
- name: Combine rust coverage and fail if it's <100%.
if: ${{ always() }}
id: combinerustcoverage
run: |
set +e
sudo apt-get install -y lcov
RUST_COVERAGE_OUTPUT=$(lcov $(for f in *.lcov; do echo --add-tracefile "$f"; done) -o combined.lcov | grep lines)
echo "## Rust Coverage" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo $RUST_COVERAGE_OUTPUT >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
if ! echo "$RUST_COVERAGE_OUTPUT" | grep "100.0%"; then
echo "🚨 Rust Coverage failed. Under 100" | tee -a $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Create rust coverage HTML
run: genhtml combined.lcov -o rust-coverage
if: ${{ failure() && steps.combinerustcoverage.outcome == 'failure' }}
- name: Create coverage HTML
run: python -m coverage html
if: ${{ failure() && steps.combinecoverage.outcome == 'failure' }}
- name: Upload HTML report.
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: _html-report
name: _html-coverage-report
path: htmlcov
if-no-files-found: ignore
if: ${{ failure() && steps.combinecoverage.outcome == 'failure' }}
- name: Upload rust HTML report.
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: _html-rust-report
path: rust-coverage
if-no-files-found: ignore
if: ${{ failure() && steps.combinerustcoverage.outcome == 'failure' }}

0 comments on commit 10de741

Please sign in to comment.