Skip to content

Commit

Permalink
pytest: Collect all uncovered Python files in code coverage (OSGeo#4077)
Browse files Browse the repository at this point in the history
* Create .coveragerc config file to keep options when using subprocess

* Create coverage_mapper.py to fix non-standard installation paths of scripts

* pytest: Upload artifact of HTML code coverage report containing test context

* pytest: Fix non-standard installed scripts paths in coverage data

* Update .gitignore
  • Loading branch information
echoix authored Jul 21, 2024
1 parent c223cd0 commit fdf087c
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 1 deletion.
64 changes: 64 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
[run]
; branch = True
; dynamic_context = test_function
concurrency = multiprocessing,thread
parallel = True
data_file = ${INITIAL_PWD-.}/.coverage
omit =
${INITIAL_PWD-.}/testreport
${INITIAL_PWD-.}/.github/*
${INITIAL_PWD-.}/bin.*/*
${INITIAL_PWD-.}/dist.*/*
**/OBJ.*/*
source =
.
${INITIAL_PWD-.}/
${INITIAL_GISBASE-/usr/local/grass??}/

[paths]
root =
./
${INITIAL_GISBASE-/usr/local/grass??}/
/home/*/install/grass??/
python =
./python/
${INITIAL_GISBASE-/usr/local/grass??}/etc/python/
/home/*/install/grass??/etc/python/
special_d_mon =
./display/d.mon/
${INITIAL_GISBASE-/usr/local/grass??}/etc/d.mon/
/home/*/install/grass??/etc/d.mon/
special_r_in_wms =
./scripts/r.in.wms/
${INITIAL_GISBASE-/usr/local/grass??}/etc/r.in.wms/
/home/*/install/grass??/etc/r.in.wms/


[report]
; Since our file structure isn't an importable package, not all files are found
; This allows to find python files even if there is missing __init__.py files, but is slow
include_namespace_packages = True
skip_covered = False
; Regexes for lines to exclude from consideration
exclude_also =
; Don't complain about missing debug-only code:
def __repr__
if self\.debug

; Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError

; Don't complain if non-runnable code isn't run:
; if 0:
; if __name__ == .__main__.:

; Don't complain about abstract methods, they aren't run:
@(abc\.)?abstractmethod

ignore_errors = True
precision = 2

[html]
directory = coverage_html_report
show_contexts = true
20 changes: 19 additions & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,21 @@ jobs:
run: |
export PYTHONPATH=`grass --config python_path`:$PYTHONPATH
export LD_LIBRARY_PATH=$(grass --config path)/lib:$LD_LIBRARY_PATH
pytest --verbose --color=yes --durations=0 --durations-min=0.5 \
export INITIAL_GISBASE="$(grass --config path)"
INITIAL_PWD="${PWD}" pytest --verbose --color=yes --durations=0 --durations-min=0.5 \
--cov \
--cov-context=test \
-ra . \
-m 'needs_solo_run'
- name: Fix non-standard installed script paths in coverage data
run: |
export PYTHONPATH=`grass --config python_path`:$PYTHONPATH
export LD_LIBRARY_PATH=$(grass --config path)/lib:$LD_LIBRARY_PATH
export INITIAL_GISBASE="$(grass --config path)"
export INITIAL_PWD="${PWD}"
python utils/coverage_mapper.py
coverage combine
coverage html
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
Expand All @@ -101,6 +112,13 @@ jobs:
flags: pytest-python-${{ matrix.python-version }}
name: pytest-python-${{ matrix.python-version }}
token: ${{ secrets.CODECOV_TOKEN }}
- name: Make python-only code coverage test report available
if: ${{ !cancelled() }}
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with:
name: python-codecoverage-report-${{ matrix.os }}-${{ matrix.python-version }}
path: coverage_html_report
retention-days: 1

pytest-success:
name: pytest Result
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,5 @@ lib/*/latex/
*.gcno
*.gcda
.coverage
.coverage.*
coverage.xml
41 changes: 41 additions & 0 deletions utils/coverage_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import os
import subprocess
from pathlib import Path


def get_grass_config_path():
grass_config_path = None
try:
grass_config_path = subprocess.run(
["grass", "--config", "path"], capture_output=True, text=True, check=True
).stdout.rstrip()
except OSError:
grass_config_path = None
return grass_config_path


INITIAL_GISBASE = os.getenv("INITIAL_GISBASE", get_grass_config_path())
INITIAL_PWD = os.getenv("INITIAL_PWD", str(Path.cwd().absolute()))


def map_scripts_paths(old_path):
if INITIAL_GISBASE is None or INITIAL_PWD is None:
return old_path
p = Path(old_path)
temporal_base = Path(INITIAL_GISBASE) / "scripts" / "t.*"
base = Path(INITIAL_GISBASE) / "scripts" / "*"
if p.match(str(temporal_base)):
return str(Path(INITIAL_PWD) / "temporal" / (p.name) / (p.name)) + ".py"
if p.match(str(base)):
return str(Path(INITIAL_PWD) / "scripts" / (p.name) / (p.name)) + ".py"

return old_path


if __name__ == "__main__":
from coverage import CoverageData

a = CoverageData(".coverage")
b = CoverageData(".coverage.fixed_scripts")
b.update(a, map_path=map_scripts_paths)
b.write()

0 comments on commit fdf087c

Please sign in to comment.