-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
275 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
name: Coverage | ||
|
||
on: | ||
workflow_call: | ||
inputs: | ||
base-branch: | ||
required: true | ||
type: string | ||
badge-template: | ||
required: false | ||
type: string | ||
coverage-branch: | ||
required: false | ||
type: string | ||
default: coverage | ||
coverage-report: | ||
required: false | ||
type: string | ||
default: coverage.txt | ||
coverage-badge: | ||
required: false | ||
type: string | ||
default: coverage.svg | ||
coverage-data-pattern: | ||
required: true | ||
type: string | ||
|
||
jobs: | ||
pull-request-coverage: | ||
name: Check Pull Request Coverage | ||
if: github.event_name == 'pull_request' | ||
runs-on: ubuntu-latest | ||
env: | ||
GITHUB_PR_NUMBER: ${{github.event.pull_request.number}} | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-python@v5 | ||
with: | ||
# Use latest Python, so it understands all syntax. | ||
python-version: "3.7" | ||
|
||
- uses: actions/download-artifact@v4 | ||
with: | ||
pattern: coverage-data-* | ||
merge-multiple: true | ||
|
||
- name: Combine coverage | ||
run: | | ||
python -Im pip install --upgrade "git+https://github.com/percevalw/coveragepy.git#egg=coverage[toml]" | ||
python -Im coverage combine | ||
- name: Compare coverage | ||
if: github.event_name == 'pull_request' | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
run: | | ||
# Get the main branch, so that we can run `git diff main` | ||
git fetch origin ${{ inputs.base-branch }}:${{ inputs.base-branch }} --depth=1 | ||
# We share store main coverage data in a separate branch, so we fetch it too | ||
git fetch origin ${{ inputs.coverage-branch }}:${{ inputs.coverage-branch }} --depth=1 || true | ||
# Report and write to summary. | ||
echo '## Coverage Report' > /tmp/report.md | ||
if git show ${{ inputs.coverage-branch }}:${{ inputs.coverage-report }} > /tmp/main-coverage.txt; then | ||
coverage report --skip-covered --skip-empty --show-missing --format=diff --base-coverage-report=/tmp/main-coverage.txt --base-revision=${{ inputs.base-branch }} --fail-under=base >> /tmp/report.md || status=$? | ||
else | ||
coverage report --skip-covered --skip-empty --show-missing --format=diff --base-revision=main >> /tmp/report.md || status=$? | ||
fi | ||
cat /tmp/report.md >> $GITHUB_STEP_SUMMARY | ||
COMMENT_BODY_JSON=$(jq -Rs . <<< $(cat /tmp/report.md)) | ||
HEADER="Authorization: token $GITHUB_TOKEN" | ||
PR_COMMENTS_URL="https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" | ||
# Fetch existing comments to find if one from this workflow already exists | ||
COMMENTS=$(curl -s -H "$HEADER" "$PR_COMMENTS_URL") | ||
COMMENT_ID=$(echo "$COMMENTS" | jq -r '.[] | select(.user.login == "github-actions[bot]" and (.body | startswith("## Coverage Report"))) | .id') | ||
# Check if we have a comment ID, if so, update it, otherwise create a new one | ||
if [[ "$COMMENT_ID" ]]; then | ||
# Update existing comment | ||
curl -s -X PATCH -H "$HEADER" -H "Content-Type: application/json" -d "{\"body\": $COMMENT_BODY_JSON}" "https://api.github.com/repos/${{ github.repository }}/issues/comments/$COMMENT_ID" | ||
else | ||
# Post new comment | ||
curl -s -X POST -H "$HEADER" -H "Content-Type: application/json" -d "{\"body\": $COMMENT_BODY_JSON}" "$PR_COMMENTS_URL" | ||
fi | ||
if [ $status -ne 0 ]; then | ||
exit $status | ||
fi | ||
main-coverage: | ||
name: Update Base Coverage | ||
if: github.ref_name == inputs.base-branch | ||
runs-on: ubuntu-latest | ||
env: | ||
GITHUB_PR_NUMBER: ${{github.event.pull_request.number}} | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-python@v5 | ||
with: | ||
# Use latest Python, so it understands all syntax. | ||
python-version: "3.7" | ||
|
||
- uses: actions/download-artifact@v4 | ||
with: | ||
pattern: ${{ inputs.coverage-data-pattern }} | ||
merge-multiple: true | ||
|
||
- name: Set up Git | ||
run: | | ||
git config user.name ${{ github.actor }} | ||
git config user.email ${{ github.actor }}@users.noreply.github.com | ||
- uses: actions/checkout@v4 | ||
with: | ||
repository: aphp/foldedtensor | ||
sparse-checkout: | | ||
.github/workflows/generate_badge.py | ||
- name: Combine coverage and generate the report and the badge | ||
run: | | ||
python -Im pip install --upgrade "git+https://github.com/percevalw/coveragepy.git[toml]" | ||
python -Im coverage combine | ||
coverage report --show-missing > /tmp/main-coverage.txt | ||
# Generate the coverage badge | ||
python .github/workflows/generate_badge.py -r /tmp/main-coverage.txt -t ${{ inputs.badge-template.svg }} > /tmp/coverage.svg | ||
git fetch origin ${{ inputs.coverage-branch }}:${{ inputs.coverage-branch }} --depth=1 || true | ||
git checkout ${{ inputs.coverage-branch }} || git checkout --orphan ${{ inputs.coverage-branch }} | ||
git reset --hard | ||
cp /tmp/main-coverage.txt ${{ inputs.coverage-report }} | ||
cp /tmp/coverage.svg ${{ inputs.coverage-badge }} | ||
git add ${{ inputs.coverage-report }} ${{ inputs.coverage-badge }} | ||
git commit -m "Update main coverage" | ||
git push origin ${{ inputs.coverage-branch }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# ruff: noqa: E471 | ||
import argparse | ||
import colorsys | ||
import re | ||
from pathlib import Path | ||
from typing import Optional, Tuple | ||
|
||
DEFAULT_TEMPLATE = """\ | ||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="108" height="20" role="img" aria-label="coverage: 97.1%"> | ||
<title>coverage: {COVERAGE}</title> | ||
<g shape-rendering="crispEdges"> | ||
<rect width="61" height="20" fill="#555"/> | ||
<rect x="61" width="47" height="20" fill="{COLOR}"/> | ||
</g> | ||
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"> | ||
<text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text> | ||
<text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="{WIDTH}">{COVERAGE}</text> | ||
</g> | ||
</svg>""" # noqa: E501 | ||
|
||
|
||
def parse_any_color_as_hsl(color: str) -> Tuple[float, float, float]: | ||
if color.startswith("hsl"): | ||
h, s, l = tuple(map(float, re.findall(r"\d+(?:\.\d+)?", color)))[:3] | ||
return (h / 360, s / 100, l / 100) | ||
elif color.startswith("#"): | ||
rgb = tuple(int(color[i : i + 2], 16) / 255 for i in (1, 3, 5)) | ||
h, l, s = colorsys.rgb_to_hls(*rgb) | ||
elif color.startswith("rgb"): | ||
h, l, s = colorsys.rgb_to_hls(*map(int, re.findall(r"\d+", color))) | ||
else: | ||
raise ValueError(f"Unknown color format: {color}") | ||
return h, s, l | ||
|
||
|
||
def make_badge( | ||
bad_color_hsl: Tuple[float, float, float], | ||
good_color_hsl: Tuple[float, float, float], | ||
min_coverage: int = 70, | ||
report_path: str = "coverage.txt", | ||
badge_template_path: Optional[str] = None, | ||
): | ||
with open(report_path) as f: | ||
coverage_str = float(f.read().splitlines(False)[-1].split()[-1].strip(" %")) | ||
coverage = float(coverage_str) | ||
ratio = (max(coverage, min_coverage) - min_coverage) / (100 - min_coverage) | ||
hue = bad_color_hsl[0] + ratio * (good_color_hsl[0] - bad_color_hsl[0]) | ||
saturation = bad_color_hsl[1] + ratio * (good_color_hsl[1] - bad_color_hsl[1]) | ||
lightness = bad_color_hsl[2] + ratio * (good_color_hsl[2] - bad_color_hsl[2]) | ||
rgb = colorsys.hls_to_rgb(hue, lightness, saturation) | ||
|
||
badge_template = ( | ||
Path(badge_template_path).read_text() | ||
if badge_template_path | ||
else DEFAULT_TEMPLATE | ||
) | ||
badge = badge_template.format( | ||
COVERAGE=f"{int(coverage)}%", | ||
COLOR=f"#{''.join(f'{int(c * 255):02x}' for c in rgb)}", | ||
WIDTH="250" if coverage < 100 else "330", | ||
) | ||
return badge | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument( | ||
"-b", | ||
"--bad-color", | ||
type=str, | ||
default="hsl(9.62deg 71.56% 57.25%)", | ||
) | ||
parser.add_argument( | ||
"-g", | ||
"--good-color", | ||
type=str, | ||
default="hsl(103.64deg 84.62% 43.33%)", | ||
) | ||
parser.add_argument( | ||
"-m", | ||
"--min-coverage", | ||
type=int, | ||
default=70, | ||
) | ||
parser.add_argument( | ||
"-r", | ||
"--report-path", | ||
type=str, | ||
default="coverage.txt", | ||
) | ||
parser.add_argument( | ||
"-t", | ||
"--badge-template-path", | ||
type=str, | ||
default=None, | ||
nargs="?", | ||
) | ||
args = parser.parse_args() | ||
badge = make_badge( | ||
bad_color_hsl=parse_any_color_as_hsl(args.bad_color), | ||
good_color_hsl=parse_any_color_as_hsl(args.good_color), | ||
min_coverage=args.min_coverage, | ||
report_path=args.report_path, | ||
badge_template_path=args.badge_template_path, | ||
) | ||
print(badge) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters