From 4a6315fada15355fd752769e2adbe0e9a61e989a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Andr=C3=A9?= <88906996+joaoandre-avaiga@users.noreply.github.com> Date: Thu, 23 Jan 2025 06:56:26 -0300 Subject: [PATCH] chore: replace coverage job (#2404) * chore: replace coverage job * feat: check coverage is above threshold --- .coveragerc | 8 +++ .github/workflows/overall-tests.yml | 24 +++++-- pyproject.toml | 1 + tools/coverage_check.py | 101 ++++++++++++++++++++++++++++ 4 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 .coveragerc create mode 100644 tools/coverage_check.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000..5dc3069f51 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,8 @@ +[run] +omit = + tests/* + +[report] +exclude_lines = + # Ignore pragma: no cover comments + pragma: no cover diff --git a/.github/workflows/overall-tests.yml b/.github/workflows/overall-tests.yml index ccf20c23cb..a396c3ef94 100644 --- a/.github/workflows/overall-tests.yml +++ b/.github/workflows/overall-tests.yml @@ -20,9 +20,12 @@ jobs: coverage: timeout-minutes: 50 runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.pull_request.base.repo.full_name == github.event.pull_request.head.repo.full_name steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch full history to enable proper diff - uses: actions/setup-python@v5 with: @@ -38,13 +41,22 @@ jobs: - name: Pytest run: | - pipenv run pytest --cov=taipy --cov-report="xml:overall-coverage.xml" tests + python -m pip install xmltodict + pipenv run pytest --cov=taipy --cov-report=xml:${{ github.workspace }}/coverage.xml --cov-config=.coveragerc - - name: Coverage - uses: orgoro/coverage@v3.1 - with: - coverageFile: overall-coverage.xml - token: ${{ secrets.GITHUB_TOKEN }} + + - name: Fetch base branch + run: | + git fetch origin ${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }} + + + - name: Check total project coverage + run: | + python tools/coverage_check.py check-total --coverage-file ${{ github.workspace }}/coverage.xml --threshold 80 + + - name: Check pull request coverage + run: | + python tools/coverage_check.py check-changed --coverage-file ${{ github.workspace }}/coverage.xml --threshold 80 --base-branch ${{ github.event.pull_request.base.ref }} overall-tests: needs: [partial-tests] diff --git a/pyproject.toml b/pyproject.toml index 943381566e..837001344a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ exclude = [ "build", "dist", "releases", + "tools", ".venv", ".mypy_cache", ".pytest_cache", diff --git a/tools/coverage_check.py b/tools/coverage_check.py new file mode 100644 index 0000000000..4cd307d14b --- /dev/null +++ b/tools/coverage_check.py @@ -0,0 +1,101 @@ +# Copyright 2021-2025 Avaiga Private Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. + +import argparse +import xmltodict +import sys +import subprocess + + +def check_total_coverage(coverage_file, threshold=80): + """Check the total project coverage.""" + with open(coverage_file) as f: + data = xmltodict.parse(f.read()) + total_coverage = float(data['coverage']['@line-rate']) * 100 + print(f"Total Coverage: {total_coverage:.2f}%") + if total_coverage < threshold: + print(f"Total project coverage is below {threshold}%: {total_coverage:.2f}%") + sys.exit(1) + + +def check_changed_files_coverage(coverage_file, changed_files, threshold=80): + """Check the coverage of changed files.""" + with open(coverage_file) as f: + data = xmltodict.parse(f.read()) + + # Handle multiple packages in the coverage report + packages = data["coverage"]["packages"]["package"] + if not isinstance(packages, list): + packages = [packages] + + # Extract coverage data for all files + files = {} + for package in packages: + classes = package["classes"]["class"] + if not isinstance(classes, list): + classes = [classes] + for cls in classes: + files[cls["@filename"]] = float(cls["@line-rate"]) * 100 + qty = 0 + sum_coverage = 0 + for file in changed_files: + if file in files: + coverage = files[file] + print(f"Coverage for {file}: {coverage:.2f}%") + sum_coverage += coverage + qty += 1 + else: + print(f"No coverage data found for {file}") + + if sum_coverage/qty < threshold: + print(f"Coverage for changed files is below {threshold}%: {sum_coverage/qty:.2f}%") + sys.exit(1) + print(f"Coverage for changed files: {sum_coverage/qty:.2f}%") + + +def get_changed_files(base_branch): + """Get the list of changed Python files in the pull request.""" + try: + result = subprocess.run( + ['git', 'diff', '--name-only', f"origin/{base_branch}", '--', '*.py'], + capture_output=True, + text=True, + check=True, + ) + changed_files = [ + file.replace("taipy/", "") for file in result.stdout.strip().splitlines() if not file.startswith(('tests/', 'tools/')) + ] + return changed_files + except subprocess.CalledProcessError as e: + print(f"Error fetching changed files: {e}") + sys.exit(1) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Coverage check script.") + parser.add_argument('command', choices=['check-total', 'check-changed'], help="Command to execute") + parser.add_argument('--coverage-file', default='coverage.xml', help="Path to the coverage XML file") + parser.add_argument('--threshold', type=float, default=80, help="Coverage threshold percentage") + parser.add_argument('--base-branch', help="Base branch for comparing changed files") + + args = parser.parse_args() + + if args.command == 'check-total': + check_total_coverage(args.coverage_file, args.threshold) + elif args.command == 'check-changed': + if not args.base_branch: + print("Error: --base-branch is required for check-changed") + sys.exit(1) + changed_files = get_changed_files(args.base_branch) + if not changed_files: + print("No relevant Python files changed.") + sys.exit(0) + check_changed_files_coverage(args.coverage_file, changed_files, args.threshold)