From 2a6461560d2dfe7447ec6e24d86cc4084ff1d2df Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 14 Oct 2025 18:55:36 -0300 Subject: [PATCH] ci(lfx): add coverage generation and Codecov reporting (#10266) * chore: update dependency markers and add pytest-cov for coverage reporting - Refined dependency markers for several packages to improve compatibility with Python 3.12 and specific platforms. - Added pytest-cov to development dependencies for enhanced test coverage reporting. - Updated dependencies for pyobjc frameworks to include platform-specific markers for better compatibility. * chore: enhance lfx_tests command with coverage reporting - Updated the lfx_tests target in the Makefile to include coverage reporting options for pytest. - Added coverage metrics output in XML, HTML, and terminal formats to improve test visibility and analysis. * chore: update codecov configuration for LFX coverage tracking - Added LFX coverage target and threshold to the codecov.yml file. - Defined separate coverage flags for frontend, backend, and LFX components. - Updated ignore patterns to exclude LFX test and component directories from coverage reports. * chore: add coverage upload steps to Python CI workflow - Implemented steps to upload coverage reports to Codecov for Python 3.10. - Added artifact upload for coverage reports, retaining them for 30 days. * chore: update LFX coverage target in codecov configuration - Increased the LFX coverage target from 40% to 60% to encourage aspirational improvement. - Clarified the allowable drop in coverage threshold from 44% to 39% without failing the status check. * chore: update coverage configuration in pyproject.toml - Enabled branch coverage in the coverage run configuration. - Fixed a typo in the main module check to ensure proper execution. --- .github/workflows/python_test.yml | 21 +++++++++++++++++++ Makefile | 2 +- codecov.yml | 22 ++++++++++++++++++-- src/lfx/pyproject.toml | 20 ++++++++++++++++++ uv.lock | 34 ++++++++++++++++--------------- 5 files changed, 80 insertions(+), 19 deletions(-) diff --git a/.github/workflows/python_test.yml b/.github/workflows/python_test.yml index 447d7ba9c3..9a48f98e61 100644 --- a/.github/workflows/python_test.yml +++ b/.github/workflows/python_test.yml @@ -168,6 +168,27 @@ jobs: env: DO_NOT_TRACK: true # disable telemetry reporting + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + if: matrix.python-version == '3.10' + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./src/lfx/coverage.xml + flags: lfx + name: lfx-coverage + fail_ci_if_error: false + directory: ./src/lfx/ + + - name: Upload coverage artifacts + uses: actions/upload-artifact@v4 + if: matrix.python-version == '3.10' + with: + name: lfx-coverage-report + path: | + src/lfx/coverage.xml + src/lfx/htmlcov/ + retention-days: 30 + test-cli: name: Test CLI - Python ${{ matrix.python-version }} runs-on: ${{ (inputs['runs-on'] && startsWith(format('{0}', inputs['runs-on']), '[') && fromJSON(inputs['runs-on'])) || inputs['runs-on'] || github.event.inputs['runs-on'] || 'ubuntu-latest' }} diff --git a/Makefile b/Makefile index 0e9771c4e8..4894d1ebc9 100644 --- a/Makefile +++ b/Makefile @@ -171,7 +171,7 @@ lfx_tests: ## run lfx package unit tests @echo 'Running LFX Package Tests...' @cd src/lfx && \ uv sync && \ - uv run pytest tests/unit -v $(args) + uv run pytest tests/unit -v --cov=src/lfx --cov-report=xml --cov-report=html --cov-report=term-missing $(args) integration_tests: uv run pytest src/backend/tests/integration \ diff --git a/codecov.yml b/codecov.yml index 9c8faf5930..524edae058 100644 --- a/codecov.yml +++ b/codecov.yml @@ -36,6 +36,17 @@ coverage: flags: - frontend + # LFX coverage: Target for core package (excludes components) + # Components (third-party integrations) tracked separately + # Current core coverage: ~44%, Target: 60% (aspirational improvement) + lfx: + target: 60% + # Threshold: Allowable drop in coverage before failing the check + # 5% = coverage can drop from 44% to 39% without failing status + threshold: 5% + flags: + - lfx + # New code coverage requirements - realistic target for current state # Encourages testing new features without blocking development patch: @@ -51,7 +62,7 @@ comment: require_base: false # Don't require base branch comparison require_head: true # Require current branch coverage -# Define separate coverage tracking for frontend and backend +# Define separate coverage tracking for frontend, backend, and lfx flags: backend: paths: @@ -61,6 +72,10 @@ flags: paths: - src/frontend/ carryforward: true # Preserve coverage data across builds if missing + lfx: + paths: + - src/lfx/ + carryforward: true # Preserve coverage data across builds if missing # Define coverage components for granular reporting component_management: @@ -86,6 +101,7 @@ ignore: - "src/backend/tests/**" - "src/frontend/tests/**" - "src/frontend/test-results/**" + - "src/lfx/tests/**" # Build artifacts and dependencies - "**/__pycache__/**" - "**/*.pyc" @@ -94,4 +110,6 @@ ignore: # Python package init files - typically just imports - "**/__init__.py" # Database migrations - - "**/migrations/**" \ No newline at end of file + - "**/migrations/**" + # LFX components - third-party integrations tracked separately + - "src/lfx/components/**" \ No newline at end of file diff --git a/src/lfx/pyproject.toml b/src/lfx/pyproject.toml index b40d62cc83..1693a9f304 100644 --- a/src/lfx/pyproject.toml +++ b/src/lfx/pyproject.toml @@ -125,5 +125,25 @@ dev = [ "hypothesis>=6.136.3", "pytest>=8.4.1", "pytest-asyncio>=0.26.0", + "pytest-cov>=7.0.0", "ruff>=0.9.10", ] + +[tool.coverage.run] +branch = true +omit = [ + "*/tests/*", + "*/__init__.py", + "*/components/*", # Third-party integrations tracked separately +] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "raise AssertionError", + "raise NotImplementedError", + "if __name__ == '__main__':", + "if TYPE_CHECKING:", + "@abstractmethod", +] diff --git a/uv.lock b/uv.lock index 105914a90a..e92b470612 100644 --- a/uv.lock +++ b/uv.lock @@ -2824,10 +2824,10 @@ name = "gassist" version = "0.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "python_full_version < '3.12' or platform_machine != 'arm64' or sys_platform != 'darwin'" }, - { name = "flask", marker = "python_full_version < '3.12' or platform_machine != 'arm64' or sys_platform != 'darwin'" }, - { name = "flask-cors", marker = "python_full_version < '3.12' or platform_machine != 'arm64' or sys_platform != 'darwin'" }, - { name = "tqdm", marker = "python_full_version < '3.12' or platform_machine != 'arm64' or sys_platform != 'darwin'" }, + { name = "colorama", marker = "(python_full_version >= '3.12' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "flask", marker = "(python_full_version >= '3.12' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "flask-cors", marker = "(python_full_version >= '3.12' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "tqdm", marker = "(python_full_version >= '3.12' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux') or (platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/b0/2e/f79632d7300874f7f0e60b61a6ab22455a245e1556116a1729542a77b0da/gassist-0.0.1-py3-none-any.whl", hash = "sha256:bb0fac74b453153a6c74b2db40a14fdde7879cbc10ec692ed170e576c8e2b6aa", size = 23819, upload-time = "2025-05-09T18:22:23.609Z" }, @@ -5864,6 +5864,7 @@ dev = [ { name = "hypothesis" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-cov" }, { name = "ruff" }, ] @@ -5910,6 +5911,7 @@ dev = [ { name = "hypothesis", specifier = ">=6.136.3" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-asyncio", specifier = ">=0.26.0" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, { name = "ruff", specifier = ">=0.9.10" }, ] @@ -7388,9 +7390,9 @@ name = "ocrmac" version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, - { name = "pillow" }, - { name = "pyobjc-framework-vision" }, + { name = "click", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, + { name = "pillow", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, + { name = "pyobjc-framework-vision", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dd/dc/de3e9635774b97d9766f6815bbb3f5ec9bce347115f10d9abbf2733a9316/ocrmac-1.0.0.tar.gz", hash = "sha256:5b299e9030c973d1f60f82db000d6c2e5ff271601878c7db0885e850597d1d2e", size = 1463997, upload-time = "2024-11-07T12:00:00.197Z" } wheels = [ @@ -9826,7 +9828,7 @@ name = "pyobjc-framework-cocoa" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, + { name = "pyobjc-core", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/7a866d24bc026f79239b74d05e2cf3088b03263da66d53d1b4cf5207f5ae/pyobjc_framework_cocoa-11.1.tar.gz", hash = "sha256:87df76b9b73e7ca699a828ff112564b59251bb9bbe72e610e670a4dc9940d038", size = 5565335, upload-time = "2025-06-14T20:56:59.683Z" } wheels = [ @@ -9842,8 +9844,8 @@ name = "pyobjc-framework-coreml" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/5d/4309f220981d769b1a2f0dcb2c5c104490d31389a8ebea67e5595ce1cb74/pyobjc_framework_coreml-11.1.tar.gz", hash = "sha256:775923eefb9eac2e389c0821b10564372de8057cea89f1ea1cdaf04996c970a7", size = 82005, upload-time = "2025-06-14T20:57:12.004Z" } wheels = [ @@ -9859,8 +9861,8 @@ name = "pyobjc-framework-quartz" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/6308fec6c9ffeda9942fef72724f4094c6df4933560f512e63eac37ebd30/pyobjc_framework_quartz-11.1.tar.gz", hash = "sha256:a57f35ccfc22ad48c87c5932818e583777ff7276605fef6afad0ac0741169f75", size = 3953275, upload-time = "2025-06-14T20:58:17.924Z" } wheels = [ @@ -9876,10 +9878,10 @@ name = "pyobjc-framework-vision" version = "11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreml" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreml", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "python_full_version >= '3.12' or sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/a8/7128da4d0a0103cabe58910a7233e2f98d18c590b1d36d4b3efaaedba6b9/pyobjc_framework_vision-11.1.tar.gz", hash = "sha256:26590512ee7758da3056499062a344b8a351b178be66d4b719327884dde4216b", size = 133721, upload-time = "2025-06-14T20:58:46.095Z" } wheels = [