Skip to content

Commit

Permalink
Merge pull request #184 from py-cov-action/run-coverage-in-subdir
Browse files Browse the repository at this point in the history
  • Loading branch information
ewjoachim authored Aug 3, 2023
2 parents 867d142 + d48e9b7 commit 0325d3e
Show file tree
Hide file tree
Showing 20 changed files with 205 additions and 93 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.DS_Store
.coverage
dev-env-vars
.coverage*
43 changes: 26 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ See [an example](https://github.com/py-cov-action/python-coverage-comment-action
### Default branch mode

On repository's default branch, it will extract the coverage rate and create
files that will be stored on a dedicated independant branch in your repository.
files that will be stored on a dedicated independent branch in your repository.

These files include:

Expand All @@ -42,7 +42,7 @@ These files include:
repository is public to customize the look of your badge
- Another `json` file used internally by the action to report on coverage
evolution (does a PR make the coverage go up or down?)
- A short file-by-file coverage report embedded directy into the branch's README. An excerpt from this is also output directly as a [job summary](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary).
- A short file-by-file coverage report embedded directly into the branch's README. An excerpt from this is also output directly as a [job summary](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary).
- The full HTML coverage report and links to make this report browsable

See [an example](https://github.com/py-cov-action/python-coverage-comment-action-v3-example)
Expand All @@ -56,7 +56,7 @@ Please ensure that your `.coverage` file(s) is created with the option

Please ensure that the branch `python-coverage-comment-action-data` is not
protected (there's no reason that it would be the case, except if you have very
sprecific wildcard rules). If it is, either adjust your rules, or set the
specific wildcard rules). If it is, either adjust your rules, or set the
`COVERAGE_DATA_BRANCH` parameter as described below. GitHub Actions will create
this branch with initial data at the first run if it doesn't exist, and will
independently commit to that branch after each commit to your default branch.
Expand All @@ -70,6 +70,7 @@ badge to your Readme will be displayed in:
- The text output of the workflow run

### Basic usage

```yaml
# .github/workflows/ci.yml
name: CI
Expand All @@ -78,7 +79,7 @@ on:
pull_request:
push:
branches:
- 'main'
- "main"

jobs:
test:
Expand All @@ -96,7 +97,7 @@ jobs:
- uses: actions/checkout@v3

- name: Install everything, run the tests, produce the .coverage file
run: make test # This is the part where you put your own test command
run: make test # This is the part where you put your own test command

- name: Coverage comment
id: coverage_comment
Expand Down Expand Up @@ -166,9 +167,9 @@ on:
pull_request:
push:
branches:
- 'master'
- "master"
tags:
- '*'
- "*"

jobs:
build:
Expand All @@ -193,7 +194,7 @@ jobs:
python-version: ${{ matrix.python_version }}

- name: Install everything, run the tests, produce a .coverage.xxx file
run: make test # This is the part where you put your own test command
run: make test # This is the part where you put your own test command
env:
COVERAGE_FILE: ".coverage.${{ matrix.python_version }}"
# Alternatively you can run coverage with the --parallel flag or add
Expand All @@ -220,7 +221,7 @@ jobs:
- uses: actions/download-artifact@v3
id: download
with:
name: 'coverage'
name: "coverage"

- name: Coverage comment
id: coverage_comment
Expand All @@ -235,10 +236,10 @@ jobs:
with:
name: python-coverage-comment-action
path: python-coverage-comment-action.txt

```
### All options
```yaml
- name: Display coverage
id: coverage_comment
Expand All @@ -249,6 +250,10 @@ jobs:
# Only necessary in the "workflow_run" workflow.
GITHUB_PR_RUN_ID: ${{ inputs.GITHUB_PR_RUN_ID }}

# Use this in case the folder to run coverage commands from is not the
# top level of your repository
COVERAGE_PATH: my_project/

# If the coverage percentage is above or equal to this value, the badge will be green.
MINIMUM_GREEN: 100

Expand Down Expand Up @@ -291,18 +296,18 @@ By default, comments are generated from a
[Jinja](https://jinja.palletsprojects.com) template that you can read
[here](https://github.com/py-cov-action/python-coverage-comment-action/blob/v3/coverage_comment/template_files/comment.md.j2).
If you want to change this template, you can set ``COMMENT_TEMPLATE``. This is
If you want to change this template, you can set `COMMENT_TEMPLATE`. This is
an advanced usage, so you're likely to run into more road bumps.

You will need to follow some rules for your template to be valid:

- Your template needs to be syntactically correct with Jinja2 rules
- You may define a new template from scratch, but in this case you are required
to include ``{{ marker }}``, which includes an HTML comment (invisible on
to include `{{ marker }}`, which includes an HTML comment (invisible on
GitHub) that the action uses to identify its own comments.
- If you'd rather want to change parts of the default template, you can do so
by starting your comment with ``{% extends "base" %}``, and then override the
blocks (``{% block foo %}``) that you wish to change. If you're unsure how it
by starting your comment with `{% extends "base" %}`, and then override the
blocks (`{% block foo %}`) that you wish to change. If you're unsure how it
works, see [the Jinja
documentation](https://jinja.palletsprojects.com/en/3.0.x/templates/#template-inheritance)
- In either case, you will most likely want to get yourself familiar with the
Expand All @@ -311,6 +316,7 @@ You will need to follow some rules for your template to be valid:
Should those variables change, we'll do our best to bump the action's major version.

### Examples

In the first example, we change the emoji that illustrates coverage going down from
`:down_arrow:` to `:sob:`:

Expand All @@ -327,14 +333,16 @@ coverage (percentage) of the whole project from the PR build:
```

# Other topics

## Pinning

On the examples above, the version was set to `v3` (a branch). You can also pin
a specific version such as `v3.0.0` (a tag). There are still things left to
figure out in how to manage releases and version. If you're interested, please
open an issue to discuss this.

In terms of security/reproductibility, the best solution is probably to pin the
version to an exact tag, and use dependabot to update it regularily.
In terms of security/reproducibility, the best solution is probably to pin the
version to an exact tag, and use dependabot to update it regularly.

## Note on the state of this action

Expand All @@ -358,7 +366,6 @@ a brand new action (this action) was created.
You can find the (unmaintained) language-generic version
[here](https://github.com/marketplace/actions/coverage-comment).


## Why do we need `relative_files = true` ?

Yes, I agree, this is annoying! The reason is that by default, coverage writes
Expand All @@ -375,9 +382,11 @@ anymore.
## .coverage file generated on a Windows file system

If your project's coverage was built on Windows, you may get an error like:

```
CoverageWarning: Couldn't parse 'yourproject\__init__.py': No source for code: 'yourproject\__init__.py'. (couldnt-parse)
```

This is likely due to coverage being confused with the coverage being computed with `\` but read with `/`. You can most probably fix it with the following in your [coverage configuration](https://coverage.readthedocs.io/en/latest/config.html):

```
Expand Down
11 changes: 9 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Python Coverage Comment
branding:
icon: 'umbrella'
color: 'purple'
icon: "umbrella"
color: "purple"
description: >
Publish diff coverage report as PR comment, and create a coverage badge
to display on the readme.
Expand Down Expand Up @@ -29,6 +29,12 @@ inputs:
branch is not protected.
default: python-coverage-comment-action-data
required: false
COVERAGE_PATH:
description: >
Path to the directory under the git root where the coverage data is
stored. Default is '.'.
default: "."
required: false
COMMENT_ARTIFACT_NAME:
description: >
Name of the artifact in which the body of the comment to post on the PR is stored.
Expand Down Expand Up @@ -82,6 +88,7 @@ runs:
GITHUB_PR_RUN_ID: ${{ inputs.GITHUB_PR_RUN_ID }}
COMMENT_TEMPLATE: ${{ inputs.COMMENT_TEMPLATE }}
COVERAGE_DATA_BRANCH: ${{ inputs.COVERAGE_DATA_BRANCH }}
COVERAGE_PATH: ${{ inputs.COVERAGE_PATH }}
COMMENT_ARTIFACT_NAME: ${{ inputs.COMMENT_ARTIFACT_NAME }}
COMMENT_FILENAME: ${{ inputs.COMMENT_FILENAME }}
MINIMUM_GREEN: ${{ inputs.MINIMUM_GREEN }}
Expand Down
47 changes: 33 additions & 14 deletions coverage_comment/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@ def compute_coverage(num_covered: int, num_total: int) -> decimal.Decimal:
return decimal.Decimal(num_covered) / decimal.Decimal(num_total)


def get_coverage_info(merge: bool) -> Coverage:
def get_coverage_info(merge: bool, coverage_path: pathlib.Path) -> Coverage:
try:
if merge:
subprocess.run("coverage", "combine")
subprocess.run("coverage", "combine", path=coverage_path)

json_coverage = subprocess.run("coverage", "json", "-o", "-")
json_coverage = subprocess.run(
"coverage", "json", "-o", "-", path=coverage_path
)
except subprocess.SubProcessError as exc:
if "No source for code:" in str(exc):
log.error(
Expand All @@ -87,18 +89,33 @@ def get_coverage_info(merge: bool) -> Coverage:
)
raise

return extract_info(json.loads(json_coverage))
return extract_info(data=json.loads(json_coverage), coverage_path=coverage_path)


def generate_coverage_html_files(path: pathlib.Path) -> None:
subprocess.run("coverage", "html", "--skip-empty", "--directory", str(path))
def generate_coverage_html_files(
destination: pathlib.Path, coverage_path: pathlib.Path
) -> None:
subprocess.run(
"coverage",
"html",
"--skip-empty",
"--directory",
str(destination),
path=coverage_path,
)


def generate_coverage_markdown() -> str:
return subprocess.run("coverage", "report", "--format=markdown", "--show-missing")
def generate_coverage_markdown(coverage_path: pathlib.Path) -> str:
return subprocess.run(
"coverage",
"report",
"--format=markdown",
"--show-missing",
path=coverage_path,
)


def extract_info(data) -> Coverage:
def extract_info(data: dict, coverage_path: pathlib.Path) -> Coverage:
"""
{
"meta": {
Expand Down Expand Up @@ -146,8 +163,9 @@ def extract_info(data) -> Coverage:
show_contexts=data["meta"]["show_contexts"],
),
files={
pathlib.Path(path): FileCoverage(
path=pathlib.Path(path),
coverage_path
/ path: FileCoverage(
path=coverage_path / path,
excluded_lines=file_data["excluded_lines"],
executed_lines=file_data["executed_lines"],
missing_lines=file_data["missing_lines"],
Expand Down Expand Up @@ -191,9 +209,9 @@ def extract_info(data) -> Coverage:
)


def get_diff_coverage_info(base_ref: str) -> DiffCoverage:
subprocess.run("git", "fetch", "--depth=1000")
subprocess.run("coverage", "xml")
def get_diff_coverage_info(base_ref: str, coverage_path: pathlib.Path) -> DiffCoverage:
subprocess.run("git", "fetch", "--depth=1000", path=pathlib.Path.cwd())
subprocess.run("coverage", "xml", path=coverage_path)
with tempfile.NamedTemporaryFile("r") as f:
subprocess.run(
"diff-cover",
Expand All @@ -202,6 +220,7 @@ def get_diff_coverage_info(base_ref: str) -> DiffCoverage:
f"--json-report={f.name}",
"--diff-range-notation=..",
"--quiet",
path=coverage_path,
)
diff_json = json.loads(pathlib.Path(f.name).read_text())

Expand Down
14 changes: 9 additions & 5 deletions coverage_comment/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,14 @@ def get_urls(url_getter: Callable) -> ImageURLs:
}


def get_coverage_html_files(gen_dir: pathlib.Path = pathlib.Path("/tmp")) -> ReplaceDir:
source = pathlib.Path(tempfile.mkdtemp(dir=gen_dir))
coverage.generate_coverage_html_files(path=source)
def get_coverage_html_files(
*, coverage_path: pathlib.Path, gen_dir: pathlib.Path = pathlib.Path("/tmp")
) -> ReplaceDir:
html_dir = pathlib.Path(tempfile.mkdtemp(dir=gen_dir))
coverage.generate_coverage_html_files(
destination=html_dir, coverage_path=coverage_path
)
dest = pathlib.Path("htmlcov")
# Coverage may or may not create a .gitignore.
(source / ".gitignore").unlink(missing_ok=True)
return ReplaceDir(source=source, path=dest)
(html_dir / ".gitignore").unlink(missing_ok=True)
return ReplaceDir(source=html_dir, path=dest)
15 changes: 10 additions & 5 deletions coverage_comment/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ def action(
return 1

if event_name in {"pull_request", "push"}:
coverage = coverage_module.get_coverage_info(merge=config.MERGE_COVERAGE_FILES)

coverage = coverage_module.get_coverage_info(
merge=config.MERGE_COVERAGE_FILES, coverage_path=config.COVERAGE_PATH
)
if event_name == "pull_request":
diff_coverage = coverage_module.get_diff_coverage_info(
base_ref=config.GITHUB_BASE_REF
base_ref=config.GITHUB_BASE_REF, coverage_path=config.COVERAGE_PATH
)
if config.ANNOTATE_MISSING_LINES:
annotations.create_pr_annotations(
Expand Down Expand Up @@ -276,9 +277,13 @@ def save_coverage_data_files(
is_public = repo_info.is_public()
if is_public:
log.info("Generating HTML coverage report")
operations.append(files.get_coverage_html_files())
operations.append(
files.get_coverage_html_files(coverage_path=config.COVERAGE_PATH)
)

markdown_report = coverage_module.generate_coverage_markdown()
markdown_report = coverage_module.generate_coverage_markdown(
coverage_path=config.COVERAGE_PATH
)

github.add_job_summary(
content=f"## Coverage report\n\n{markdown_report}",
Expand Down
Loading

0 comments on commit 0325d3e

Please sign in to comment.