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:
parent
8d6505558b
commit
da4208dcc9
5 changed files with 232 additions and 2 deletions
9
.github/workflows/python_test.yml
vendored
9
.github/workflows/python_test.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
16
codecov.yml
16
codecov.yml
|
|
@ -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
|
||||
|
|
|
|||
36
scripts/generate_coverage_config.md
Normal file
36
scripts/generate_coverage_config.md
Normal 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
|
||||
168
scripts/generate_coverage_config.py
Executable file
168
scripts/generate_coverage_config.py
Executable 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()
|
||||
5
src/backend/.gitignore
vendored
5
src/backend/.gitignore
vendored
|
|
@ -131,4 +131,7 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
*.db
|
||||
*.db
|
||||
|
||||
# Generated pytest-cov config
|
||||
.coveragerc
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue