feat: generate custom coverage config to omit legacy and external compoents (#9221)

* feat: generate custom coverage config to omit legacy and external components

* lint

* fix components config

* also remove deactivated componenets

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Eric Pinzur 2025-07-30 06:46:19 +02:00 committed by GitHub
commit da4208dcc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 232 additions and 2 deletions

View file

@ -70,12 +70,19 @@ jobs:
- name: Install the project
run: uv sync
- name: Generate dynamic coverage configuration
run: |
echo "Generating dynamic coverage configuration..."
python3 scripts/generate_coverage_config.py
echo "Generated .coveragerc with the following exclusions:"
echo "Bundled components and legacy files excluded from coverage"
- name: Run unit tests
uses: nick-fields/retry@v3
with:
timeout_minutes: 12
max_attempts: 2
command: make unit_tests args="-x -vv --splits ${{ matrix.splitCount }} --group ${{ matrix.group }} --reruns 5 --cov --cov-report=xml --cov-report=html"
command: make unit_tests args="-x -vv --splits ${{ matrix.splitCount }} --group ${{ matrix.group }} --reruns 5 --cov --cov-config=src/backend/.coveragerc --cov-report=xml --cov-report=html"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5

View file

@ -66,6 +66,22 @@ flags:
- src/frontend/
carryforward: true # Preserve coverage data across builds if missing
# Define coverage components for granular reporting
component_management:
default_rules: # default rules that will be inherited by all components
statuses:
- type: project # in this case every component that doens't have a status defined will have a project type one
target: auto
branches:
- "!main"
individual_components:
- component_id: backend_components
name: "Backend Components"
paths:
- src/backend/base/langflow/components/**
# Note: Many components excluded by .coveragerc (bundled + legacy)
# This tracks coverage of remaining "core" components only
# Files/directories to exclude from coverage calculations
ignore:
# Database migrations - infrastructure code, not business logic

View file

@ -0,0 +1,36 @@
# Dynamic Coverage Configuration
This script generates a custom `.coveragerc` file for backend testing that excludes:
1. **Bundled components** - Components listed in `SIDEBAR_BUNDLES` from `src/frontend/src/utils/styleUtils.ts`
2. **Legacy components** - Python files containing `legacy = True`
## How it works
1. **Reads frontend config**: Parses `styleUtils.ts` to extract bundled component names
2. **Scans backend files**: Finds all `.py` files in `src/backend/base/langflow/components/` with `legacy = True`
3. **Generates .coveragerc**: Creates exclusion patterns for pytest-cov
## Usage
### Local development
```bash
# Generate config and run tests
python3 scripts/generate_coverage_config.py
cd src/backend && python -m pytest --cov=src/backend/base/langflow --cov-config=.coveragerc
```
### CI Integration
The script runs automatically in CI before backend tests via `.github/workflows/python_test.yml`.
## Files affected
- **Input**: `src/frontend/src/utils/styleUtils.ts` (SIDEBAR_BUNDLES)
- **Input**: `src/backend/base/langflow/components/**/*.py` (legacy components)
- **Output**: `src/backend/.coveragerc` (auto-generated, in .gitignore)
## Benefits
- **Accurate coverage**: Focuses on actively maintained core code
- **Dynamic updates**: Automatically adapts when bundles/legacy components change
- **Codecov compatible**: Generated config works with both local testing and Codecov reporting

View file

@ -0,0 +1,168 @@
#!/usr/bin/env python3
"""Script to generate a custom .coveragerc file for backend testing.
This script:
1. Reads SIDEBAR_BUNDLES from frontend styleUtils.ts to get bundled component names
2. Scans backend components for files containing 'legacy = True'
3. Generates a .coveragerc file that omits these paths from coverage reporting
Usage:
python scripts/generate_coverage_config.py
"""
import re
from pathlib import Path
def extract_sidebar_bundles(frontend_path: Path) -> set[str]:
"""Extract component names from SIDEBAR_BUNDLES in styleUtils.ts."""
style_utils_path = frontend_path / "src/utils/styleUtils.ts"
if not style_utils_path.exists():
print(f"Warning: styleUtils.ts not found at {style_utils_path}")
return set()
bundle_names = set()
with style_utils_path.open(encoding="utf-8") as f:
content = f.read()
# Find SIDEBAR_BUNDLES array
sidebar_match = re.search(r"export const SIDEBAR_BUNDLES = \[(.*?)\];", content, re.DOTALL)
if not sidebar_match:
print("Warning: SIDEBAR_BUNDLES not found in styleUtils.ts")
return set()
bundles_content = sidebar_match.group(1)
# Extract name fields using regex
name_matches = re.findall(r'name:\s*["\']([^"\']+)["\']', bundles_content)
for name in name_matches:
bundle_names.add(name)
print(f"Found {len(bundle_names)} bundled components from SIDEBAR_BUNDLES")
return bundle_names
def find_legacy_components(backend_components_path: Path) -> set[str]:
"""Find Python files containing 'legacy = True'."""
legacy_files = set()
if not backend_components_path.exists():
print(f"Warning: Backend components path not found: {backend_components_path}")
return set()
# Walk through all Python files in components directory
for py_file in backend_components_path.rglob("*.py"):
try:
with py_file.open(encoding="utf-8") as f:
content = f.read()
# Check if file contains 'legacy = True'
if re.search(r"\blegacy\s*=\s*True\b", content):
# Get relative path from components directory
rel_path = py_file.relative_to(backend_components_path)
legacy_files.add(str(rel_path))
except (UnicodeDecodeError, PermissionError) as e:
print(f"Warning: Could not read {py_file}: {e}")
continue
print(f"Found {len(legacy_files)} legacy component files")
return legacy_files
def generate_coveragerc(bundle_names: set[str], legacy_files: set[str], output_path: Path):
"""Generate .coveragerc file with omit patterns."""
# Base coveragerc content
config_content = """# Auto-generated .coveragerc file
# Generated by scripts/generate_coverage_config.py
# Do not edit manually - changes will be overwritten
[run]
source = src/backend/base/langflow
omit =
# Test files
*/tests/*
*/test_*
*/*test*
# Migration files
*/alembic/*
*/migrations/*
# Cache and build files
*/__pycache__/*
*/.*
# Init files (typically just imports)
*/__init__.py
# Deactivate Components
*/components/deactivated/*
"""
# Add bundled components to omit list
if bundle_names:
config_content += " # Bundled components from SIDEBAR_BUNDLES\n"
for bundle_name in sorted(bundle_names):
config_content += f" */components/{bundle_name}/*\n"
config_content += "\n"
# Add legacy components to omit list
if legacy_files:
config_content += " # Legacy components (contain 'legacy = True')\n"
for legacy_file in sorted(legacy_files):
# Convert relative path to omit pattern
omit_pattern = f" */components/{legacy_file}\n"
config_content += omit_pattern
config_content += """
# Note: [report] and [html] sections omitted for Codecov compatibility
# Codecov handles its own reporting and ignores these sections
"""
# Write the config file
output_path.parent.mkdir(parents=True, exist_ok=True)
with output_path.open("w", encoding="utf-8") as f:
f.write(config_content)
print(f"Generated .coveragerc at {output_path}")
print(f" - Omitting {len(bundle_names)} bundled component directories")
print(f" - Omitting {len(legacy_files)} legacy component files")
def main():
"""Main function."""
# Determine project root (script is in scripts/ directory)
script_dir = Path(__file__).parent
project_root = script_dir.parent
# Paths
frontend_path = project_root / "src" / "frontend"
backend_components_path = project_root / "src" / "backend" / "base" / "langflow" / "components"
output_path = project_root / "src" / "backend" / ".coveragerc"
print(f"Project root: {project_root}")
print(f"Frontend path: {frontend_path}")
print(f"Backend components path: {backend_components_path}")
print(f"Output path: {output_path}")
print()
# Extract bundled component names
bundle_names = extract_sidebar_bundles(frontend_path)
# Find legacy components
legacy_files = find_legacy_components(backend_components_path)
# Generate .coveragerc file
generate_coveragerc(bundle_names, legacy_files, output_path)
print("\nDone! You can now run backend tests with coverage using:")
print("cd src/backend && python -m pytest --cov=src/backend/base/langflow --cov-config=.coveragerc")
if __name__ == "__main__":
main()

View file

@ -131,4 +131,7 @@ dmypy.json
# Pyre type checker
.pyre/
*.db
*.db
# Generated pytest-cov config
.coveragerc