Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions COVERAGE_FIX_EXPLANATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Python Coverage Fix: Including plugins/python/python/phlex/__init__.py

## Problem Statement

The file `plugins/python/python/phlex/__init__.py` was not appearing in Python test coverage reports, despite being imported and used by the test suite (specifically by `test/python/unit_test_variant.py`).

## Root Cause Analysis

The Python coverage configuration had multiple issues:

1. __Coverage Scope__ - Only monitoring the test directory:
- The pytest command used `--cov=${CMAKE_CURRENT_SOURCE_DIR}` which only covers the `test/python` directory
- Missing: Coverage for the `phlex` module located in `plugins/python/python/phlex/`

2. __Duplicate Targets__ - Two `coverage-python` targets were defined:
- One in `test/python/CMakeLists.txt` (correctly configured with test environment)
- One in `Modules/private/CreateCoverageTargets.cmake` (duplicate, missing proper environment)
- The duplicate was overriding the correct one

3. __Environment Setup__ - Tests require proper PYTHONPATH:
- Need test directory, plugins directory, Python site-packages
- The `coverage-python` target wasn't explicitly setting environment variables
- Tests failed because they couldn't import required modules (cppyy, numpy, etc.)

4. __In `test/python/.coveragerc`__:
- The `source` setting only included `.` (the current test directory)
- Did not include the path to the plugins directory

## The Fix

### Changes Made

1. __test/python/CMakeLists.txt__:

Added `--cov` flag for the plugins directory:

```cmake
--cov=${CMAKE_CURRENT_SOURCE_DIR}
--cov=${PROJECT_SOURCE_DIR}/plugins/python/python # Added this line
```

Updated `coverage-python` target to use proper test environment:

```cmake
add_custom_target(
coverage-python
COMMAND
${CMAKE_COMMAND} -E env PYTHONPATH=${TEST_PYTHONPATH}
PHLEX_INSTALL=${PYTHON_TEST_PHLEX_INSTALL} ${PYTEST_COMMAND}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running Python coverage report"
)
```

2. __Modules/private/CreateCoverageTargets.cmake__:

Removed the duplicate `coverage-python` target that was conflicting with the properly configured one in `test/python/CMakeLists.txt`.

3. __test/python/.coveragerc__:

```ini
[run]
source =
.
../../plugins/python/python # Added this line
```

## How pytest-cov Works

pytest-cov uses the `--cov` flag to specify which directories/packages to monitor for coverage:

- Multiple `--cov` flags can be specified to monitor multiple locations
- Each `--cov` flag adds a path to the coverage measurement scope
- The `.coveragerc` file's `source` setting works in conjunction with the `--cov` flags

## Environment Setup

The `TEST_PYTHONPATH` variable in `test/python/CMakeLists.txt` includes:

- `${CMAKE_CURRENT_SOURCE_DIR}` - the test directory
- `${PROJECT_SOURCE_DIR}/plugins/python/python` - the phlex package source
- `${Python_SITELIB}` and `${Python_SITEARCH}` - Python site-packages (for cppyy, numpy, etc.)
- `$ENV{PYTHONPATH}` - any existing PYTHONPATH from the environment

This comprehensive PYTHONPATH is required for tests to:

1. Import the `phlex` module from `plugins/python/python/phlex/__init__.py`
2. Import test dependencies like `cppyy`, `numpy`, `pytest`
3. Find any user-installed packages

The `coverage-python` target explicitly sets these environment variables so that the coverage run has the same environment as the regular test run.

## Testing the Fix

To verify this fix works:

1. __Build with coverage enabled__:

```bash
cd /home/runner/work/phlex/phlex
. scripts/setup-env.sh
cmake --preset coverage-clang # or coverage-gcc
ninja -C build
```

2. __Run Python tests with coverage__:

```bash
cd build
cmake --build . --target coverage-python
```

3. __Check the coverage report__:

```bash
# View the XML report
grep "plugins/python/python/phlex/__init__.py" coverage-python.xml

# Or view the HTML report
xdg-open coverage-python-html/index.html
```

4. __Expected Result__:
- The file `plugins/python/python/phlex/__init__.py` should now appear in coverage reports
- Coverage metrics should show which lines of the `Variant` class are executed during tests

## Why This Matters

The `phlex` Python package provides the `Variant` class, which is essential for:

- Wrapping Python callables with custom type annotations
- Allowing the same function to be registered multiple times with different types
- Supporting C++ template-like behavior in Python code

Without proper coverage tracking, we couldn't verify that:

- All code paths in the `Variant` class are tested
- Error handling works correctly
- Edge cases are covered

## Verification in CI

The GitHub Actions workflow (`.github/workflows/coverage.yaml`) will now:

1. Run tests with coverage collection including the plugins directory
2. Generate `coverage-python.xml` with the plugins included
3. Upload to Codecov with both C++ and Python coverage data

The `codecov.yml` configuration doesn't explicitly ignore the `plugins` directory, so the data will be properly processed and displayed in coverage reports.

## Related Files

- __Test file using the module__: `test/python/unit_test_variant.py` (imports `from phlex import Variant`)
- __Source module__: `plugins/python/python/phlex/__init__.py` (contains the `Variant` class)
- __Coverage workflow__: `.github/workflows/coverage.yaml` (CI coverage execution)
- __Codecov config__: `codecov.yml` (coverage reporting configuration)
35 changes: 2 additions & 33 deletions Modules/private/CreateCoverageTargets.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -552,39 +552,8 @@ function(_create_coverage_targets_impl)
COMMENT "Cleaning coverage data files (C++ and Python)"
)

# Add Python coverage target if pytest-cov is available
if(Python_FOUND)
execute_process(
COMMAND ${Python_EXECUTABLE} -c "import pytest_cov"
RESULT_VARIABLE PYTEST_COV_CHECK
OUTPUT_QUIET
ERROR_QUIET
)
if(PYTEST_COV_CHECK EQUAL 0)
add_custom_target(
coverage-python
COMMAND
${CMAKE_COMMAND} -E echo
"[Coverage] Generating Python coverage report using pytest-cov..."
COMMAND
${CMAKE_COMMAND} -E env PYTHONPATH=${PROJECT_SOURCE_DIR}/test/python
PHLEX_INSTALL=${PROJECT_SOURCE_DIR} ${Python_EXECUTABLE} -m pytest
${PROJECT_SOURCE_DIR}/test/python/test_phlex.py --cov=${PROJECT_SOURCE_DIR}/test/python
--cov-report=term-missing --cov-report=xml:${CMAKE_BINARY_DIR}/coverage-python.xml
--cov-report=html:${CMAKE_BINARY_DIR}/coverage-python-html
--cov-config=${PROJECT_SOURCE_DIR}/test/python/.coveragerc
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Generating Python coverage report"
VERBATIM
)
message(STATUS "Added 'coverage-python' target for Python test coverage (pytest-cov)")
else()
message(
STATUS
"pytest-cov not found; Python coverage target not available. Install with: pip install pytest-cov"
)
endif()
endif()
# Note: The coverage-python target is defined in test/python/CMakeLists.txt
# where it can use the proper test environment setup (PYTHONPATH, etc.)

message(
STATUS
Expand Down
13 changes: 12 additions & 1 deletion test/python/.coveragerc
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
[run]
# Include non-test Python modules for coverage measurement
source = .
source =
../../plugins/python/python
omit =
test_*.py
**/test_*.py
unit_test_*.py
**/unit_test_*.py
# Omit all files in test directories
*/test/*
*/tests/*

[report]
# Exclude test files from coverage reports
omit =
test_*.py
**/test_*.py
unit_test_*.py
**/unit_test_*.py
# Omit all files in test directories
*/test/*
*/tests/*
41 changes: 32 additions & 9 deletions test/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ if(HAS_CPPYY)
-m
pytest
--cov=${CMAKE_CURRENT_SOURCE_DIR}
--cov=${PROJECT_SOURCE_DIR}/plugins/python/python
--cov-report=term-missing
--cov-report=xml:${CMAKE_BINARY_DIR}/coverage-python.xml
--cov-report=html:${CMAKE_BINARY_DIR}/coverage-python-html
Expand All @@ -96,15 +97,6 @@ if(HAS_CPPYY)
add_test(NAME py:phlex COMMAND ${PYTEST_COMMAND} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

set_property(TEST py:phlex PROPERTY ENVIRONMENT "PHLEX_INSTALL=${PYTHON_TEST_PHLEX_INSTALL}")

if(HAS_PYTEST_COV)
add_custom_target(
coverage-python
COMMAND ${PYTEST_COMMAND}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running Python coverage report"
)
endif()
endif()

set(ACTIVE_PY_CPHLEX_TESTS "")
Expand Down Expand Up @@ -261,3 +253,34 @@ add_test(
COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/unit_test_variant.py
)
set_tests_properties(py:unit_variant PROPERTIES ENVIRONMENT "${PYTHON_TEST_ENVIRONMENT}")

# Python coverage target
if(HAS_PYTEST_COV AND ENABLE_COVERAGE)
if(HAS_CPPYY)
# When cppyy is available, use the full pytest command
add_custom_target(
coverage-python
COMMAND
${CMAKE_COMMAND} -E env PYTHONPATH=${TEST_PYTHONPATH}
PHLEX_INSTALL=${PYTHON_TEST_PHLEX_INSTALL} ${PYTEST_COMMAND}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running Python coverage report"
)
else()
# When cppyy is not available, run a simpler pytest command
# for standalone Python tests (like unit_test_variant.py)
set(PYTHON_TEST_PHLEX_INSTALL_FALLBACK ${CMAKE_SOURCE_DIR})
add_custom_target(
coverage-python
COMMAND
${CMAKE_COMMAND} -E env PYTHONPATH=${TEST_PYTHONPATH}
PHLEX_INSTALL=${PYTHON_TEST_PHLEX_INSTALL_FALLBACK} ${Python_EXECUTABLE} -m pytest
--cov=${CMAKE_CURRENT_SOURCE_DIR} --cov=${PROJECT_SOURCE_DIR}/plugins/python/python
--cov-report=term-missing --cov-report=xml:${CMAKE_BINARY_DIR}/coverage-python.xml
--cov-report=html:${CMAKE_BINARY_DIR}/coverage-python-html
--cov-config=${CMAKE_CURRENT_SOURCE_DIR}/.coveragerc unit_test_variant.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running Python coverage report (standalone tests only)"
)
endif()
endif()
Loading