Skip to content

Commit

Permalink
Merge pull request #258 from PedroGGBM/code-coverage
Browse files Browse the repository at this point in the history
Improved Metrics for Code Coverage Deploy
  • Loading branch information
ozgurakgun authored Apr 2, 2024
2 parents 96c76fa + 0ecbb5e commit 0244f5e
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 13 deletions.
108 changes: 96 additions & 12 deletions .github/workflows/code-coverage-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,44 @@ jobs:
run: |
echo "num=$(cat deploy/prnumber)" > $GITHUB_OUTPUT
- name: Retrieve list of artifacts for the PR
id: find_artifacts
uses: actions/github-script@v6
with:
script: |
const prNum = process.env.num;
const artifacts = await github.rest.actions.listArtifactsForRepo({
owner: context.repo.owner,
repo: context.repo.repo
});
// Filter artifacts by PR number in their name
const artifactList = artifacts.data.artifacts.filter(artifact => artifact.name.includes(`coverage-${prNum}-`));
return artifactList.map((artifact) => ({
id: artifact.id,
name: artifact.name,
created_at: artifact.created_at
}));
# WARNING: Artifacts are deleted after 90 days (or per configured retention policy)
- name: Find previous artifact
if: steps.find_artifacts.outputs.result != '[]'
run: |
# parse the output from the previous step into a JSON array
previous_artifacts = ${{ fromJson(steps.find_artifacts.outputs.result) }}
# assuming filtered and sorted the previous_artifacts by creation date in descending order
if (previous_artifacts.length > 1) {
# get the second latest artifact (since the latest would be the current one)
echo "previous_artifact_id=${{ previous_artifacts[1].id }}" >> $GITHUB_ENV
}

- name: Install rust ${{ env.rust_release }}
run: rustup update ${{ env.rust_release }} && rustup default ${{ env.rust_release }}

- name: Generate lcov summary for main and pr
continue-on-error: true
id: lcov
run: |
sudo apt-get install -y lcov
wget https://${{github.repository_owner}}.github.io/conjure-oxide/coverage/main/lcov.info
lcov --summary lcov.info > cov.txt
Expand Down Expand Up @@ -195,41 +225,95 @@ jobs:
repo: context.repo.repo,
comment_id: ${{ steps.fc.outputs.comment-id }}
})
- name: Download the previous lcov.info (historical) file
uses: actions/download-artifact@v2
with: |
name: code-coverage-${{ env.num }}-${{ env.previous_artifact_id }}
path: ./tools/code-coverage-diff/deploy-prev
# this will be used for future visualizations in the coverage report (requiring external python libs)
- name: Install python dependencies
run: |
pip install -r tools/code-coverage-diff/requirements.txt
- name: Calculate current coverage difference PR <> main
id: coveragediff
run: |
# pipeline lcov relevant stats to python script
python ./tools/code-coverage-diff/calculate_coverage_difference.py ${{ steps.lcov.outputs.main }} ${{ steps.lcov.outputs.pr }} > ./tools/code-coverage-diff/lcov/coverage_diff.txt
# store into environment variable
echo "diff<<EOFABC" >> $GITHUB_OUTPUT
cat ./tools/code-coverage-diff/lcov/coverage_diff.txt >> $GITHUB_ENV
echo 'EOFABC' >> $GITHUB_OUTPUT
# create summary of previous (historical) lcov summary for main and pr
lcov --summary ./tools/code-coverage-diff/deploy-prev/code-coverage-${{ env.num }}-${{ env.previous_artifact_id }} > historical_coverage_lcov.txt
echo "hist_main_summary<<EOFABC" >> $GITHUB_OUTPUT
echo "$(cat historical_coverage_lcov.txt | tail -n +3)" >> $GITHUB_OUTPUT
echo 'EOFABC' >> $GITHUB_OUTPUT
- name: Calculate coverage difference with previous historical lcov artifact
id: historicalcoveragediff
run: |
# call python script to compare historical previous main coverage with current main coverage
python ./tools/code-coverage-diff/calculate_coverage_difference.py ${{ steps.lcov.outputs.main }} ${{ steps.coveragediff.outputs.hist_main_summary }} > ./tools/code-coverage-diff/lcov/hist_coverage_diff.txt
# store into output variable for comment display
echo "hist_diff<<EOFABC" >> $GITHUB_OUTPUT
cat ./tools/code-coverage-diff/lcov/hist_coverage_diff.txt >> $GITHUB_ENV
echo 'EOFABC' >> $GITHUB_OUTPUT
- name: Create coverage comment
uses: peter-evans/create-or-update-comment@v1
with:
issue-number: ${{steps.prnum.outputs.num}}
issue-number: ${{ steps.prnum.outputs.num }}
body: |
## Documentation Coverage
## Code and Documentation Coverage Report
### Documentation Coverage
<details>
<summary>This PR</summary>
<summary>Click to view documentation coverage for this PR</summary>
```
${{steps.doccov.outputs.pr}}
${{ steps.doccov.outputs.pr }}
```
</details>
<details>
<summary>Main</summary>
<summary>Click to view documentation coverage for main</summary>
```
${{steps.doccov.outputs.main}}
${{ steps.doccov.outputs.main }}
```
</details>
## Code Coverage
### Code Coverage Summary
[This PR](https://${{github.repository_owner}}.github.io/conjure-oxide/coverage/${{steps.sha.outputs.result}})
**This PR**: [Detailed Report](https://${{ github.repository_owner }}.github.io/conjure-oxide/coverage/${{ steps.sha.outputs.result }}/index.html)
```
${{steps.lcov.outputs.pr}}
${{ steps.lcov.outputs.pr }}
```
[Main](https://${{github.repository_owner}}.github.io/conjure-oxide/coverage/main)
**Main**: [Detailed Report](https://${{ github.repository_owner }}.github.io/conjure-oxide/coverage/main/index.html)
```
${{ steps.lcov.outputs.main }}
```
${{steps.lcov.outputs.main}}
### Coverage Main & PR Coverage Change
```diff
${{ steps.coveragediff.outputs.diff }}
```
### Previous Main Coverage Change
```diff
${{ steps.historicalcoveragediff.outputs.hist_diff }}
```
1 change: 0 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ jobs:
echo -e "sha=${{ github.sha }}" >> "$GITHUB_OUTPUT"
echo -e "sha=${{ github.sha }}"
- name: Install rust ${{ env.rust_release }}
run: rustup update ${{ env.rust_release }} && rustup default ${{ env.rust_release }}

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ on:
- crates/**
- Cargo.*
- .github/workflows/test.yml
- .github/workflows/code-coverage-deploy.yml
pull_request:
paths:
- conjure_oxide/**
- solvers/**
- crates/**
- Cargo.*
- .github/workflows/test.yml
- .github/workflows/code-coverage-deploy.yml
workflow_dispatch:

env:
Expand Down
103 changes: 103 additions & 0 deletions tools/code-coverage-diff/calculate_coverage_difference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# calculate_coverage_difference.py
# @author Pedro Gronda Garrigues

# dependencies
import sys
import re

def parse_coverage_data(coverage_data):
"""
Parse the coverage data from the raw input format to a dictionary.
:param coverage_data: string containing the coverage information.
:return: dictionary with coverage categories as keys and tuples of percentages and counts as values.
Returns None as value if no data was found for a category.
"""

coverage_dict = {}
for line in coverage_data.splitlines():
match = re.match(r'(\w+).*: ([\d.]+)% \((\d+) of (\d+)', line)
if match:
# store coverage details in a dictionary
category = match.group(1).lower()
percentage = float(match.group(2))
covered = int(match.group(3))
total = int(match.group(4))
coverage_dict[category] = (percentage, covered, total)
elif 'no data found' in line:
# handle 'no data found' case by setting to None
category = line.split(':')[0].strip().lower()
coverage_dict[category] = None

return coverage_dict

def calculate_differences(main_data, pr_data):
"""
Calculates the differences in coverage between the main branch and a pull request.
:param main_data: coverage data from the main branch.
:param pr_data: coverage data from the pull request.
:return: a dictionary with the coverage difference for each category.
"""

# parses both inputs into dictionaries
main_cov = parse_coverage_data(main_data)
pr_cov = parse_coverage_data(pr_data)
diffs = {}

# iterates through each category to calculate differences
for category in main_cov.keys():
if main_cov[category] is None or pr_cov[category] is None:
# no comparison data available for this category
diffs[category] = {'diff_percentage': "no data"}
else:
# calculate difference in coverage percentage and covered units
diff_percentage = pr_cov[category][0] - main_cov[category][0]
diff_covered = pr_cov[category][1] - main_cov[category][1]
diffs[category] = {
'diff_percentage': f"{diff_percentage:.2f}%",
'diff_covered': diff_covered
}

return diffs

def format_diff_output(diffs):
"""
Formats the differences in coverage data into a readable string output.
:param diffs: a dictionary containing the coverage differences.
:return: formatted string with the changes in coverage data.
"""

lines = []
# construct the output for each coverage category
for category, diff_data in diffs.items():
if diff_data['diff_percentage'] == "no data":
lines.append(f"{category.capitalize()} coverage: No comparison data available")
else:
lines.append(f"{category.capitalize()} coverage changed by {diff_data['diff_percentage']} and covered lines changed by {diff_data['diff_covered']}")

return "\n".join(lines)

def main():
try:
# get parameter data for main and pr (or historical main and main)
main_coverage_data = sys.argv[1]
pr_coverage_data = sys.argv[2]

# calculate the differences between the sets of data
diffs = calculate_differences(main_coverage_data, pr_coverage_data)

# format output using differences
formatted_output = format_diff_output(diffs)

# print for bash stdout capture
print(formatted_output)
except IndexError:
print("ERROR: Missing required command-line arguments.")
except Exception as e:
# generic error handling
print(f"An error occurred: {e}")

if __name__ == "__main__":
main()
Empty file.

0 comments on commit 0244f5e

Please sign in to comment.