Coverage Integration
Coverage integration is one of Debtmap’s most powerful capabilities, enabling risk-based prioritization by correlating complexity metrics with test coverage. This helps you identify truly risky code—functions that are both complex and untested—rather than just highlighting complex but well-tested functions.
Why Coverage Matters
Without coverage data, complexity analysis shows you what’s complex, but not what’s risky. A complex function with 100% test coverage poses far less risk than a simple function with 0% coverage on a critical path.
Coverage integration transforms Debtmap from a complexity analyzer into a risk assessment tool:
- Prioritize testing efforts: Focus on high-complexity functions with low coverage
- Validate refactoring safety: See which complex code is already protected by tests
- Risk-based sprint planning: Surface truly risky code ahead of well-tested complexity
- Quantify risk reduction: Measure how coverage improvements reduce project risk
LCOV Format: The Universal Standard
Debtmap uses the LCOV format for coverage data. LCOV is a language-agnostic standard supported by virtually all coverage tools across all major languages.
Why LCOV?
- Universal compatibility: Works with Rust, Python, JavaScript, TypeScript, Go, and more
- Tool independence: Not tied to any specific test framework
- Simple text format: Easy to inspect and debug
- Widely supported: Generated by most modern coverage tools
LCOV File Structure
An LCOV file contains line-by-line coverage information:
SF:src/analyzer.rs
FN:42,calculate_complexity
FNDA:15,calculate_complexity
DA:42,15
DA:43,15
DA:44,12
DA:45,0
LH:3
LF:4
end_of_record
SF:- Source file pathFN:- Function name and starting lineFNDA:- Function execution countDA:- Line execution data (line number, hit count)LH:- Lines hitLF:- Lines found (total)
Generating Coverage Data
Rust: cargo-tarpaulin
Installation:
cargo install cargo-tarpaulin
Generate LCOV:
cargo tarpaulin --out lcov --output-dir target/coverage
Analyze with Debtmap:
debtmap analyze . --lcov target/coverage/lcov.info
Common Issues:
- Ensure tests compile before running tarpaulin
- Use
--ignore-testsif tests themselves show up in coverage - Check paths match your project structure (relative to project root)
JavaScript/TypeScript: Jest
Configuration (package.json or jest.config.js):
{
"jest": {
"coverageReporters": ["lcov", "text"]
}
}
Generate Coverage:
npm test -- --coverage
Analyze with Debtmap:
debtmap analyze . --lcov coverage/lcov.info
Python: pytest-cov
Installation:
pip install pytest-cov
Generate LCOV:
pytest --cov=src --cov-report=lcov
Analyze with Debtmap:
debtmap analyze . --lcov coverage.lcov
Go: go test with gocover-cobertura
Generate Coverage:
go test -coverprofile=coverage.out ./...
gocover-cobertura < coverage.out > coverage.xml
# Convert to LCOV using lcov tools
Note: Go’s native coverage format requires conversion. Most CI systems support LCOV conversion plugins.
How Coverage Affects Scoring
Coverage data fundamentally changes how Debtmap calculates debt scores. The scoring system operates in two different modes depending on whether coverage data is available.
Scoring Modes
Mode 1: With Coverage Data (Dampening Multiplier)
When you provide an LCOV file with --lcov, coverage acts as a dampening multiplier that reduces scores for well-tested code:
Base Score = (Complexity Factor × 0.50) + (Dependency Factor × 0.25)
Coverage Multiplier = 1.0 - coverage_percentage
Final Score = Base Score × Coverage Multiplier
This is the current implementation as of spec 122. Coverage dampens the base score rather than contributing as an additive component.
Mode 2: Without Coverage Data (Weighted Sum)
When no coverage data is available, Debtmap falls back to a weighted sum model:
Final Score = (Coverage × 0.50) + (Complexity × 0.35) + (Dependency × 0.15)
In this mode, coverage is assumed to be 0% (worst case), giving it a weight of 50% in the total score. See src/priority/scoring/calculation.rs:119-129 for the implementation.
Coverage Dampening Multiplier
When coverage data is provided, it acts as a multiplier that dampens the base score:
Coverage Multiplier = 1.0 - coverage_percentage
Final Score = Base Score × Coverage Multiplier
Examples:
| Base Score | Coverage | Multiplier | Final Score | Priority |
|---|---|---|---|---|
| 8.5 | 100% | 0.0 | 0.0 | Minimal (well-tested) |
| 8.5 | 50% | 0.5 | 4.25 | Medium |
| 8.5 | 0% | 1.0 | 8.5 | High (untested) |
Key Insight: Complex but well-tested code automatically drops in priority, while untested complex code rises to the top.
Special Cases:
- Test functions: Coverage multiplier = 0.0 (tests get near-zero scores regardless of complexity)
- Entry points: Handled through semantic classification (FunctionRole) system with role multipliers, not coverage-specific weighting
Invariant: Total debt score with coverage ≤ total debt score without coverage.
Implementation: See src/priority/scoring/calculation.rs:68-82 for the coverage dampening calculation.
Transitive Coverage Propagation
Debtmap doesn’t just look at direct coverage—it propagates coverage through the call graph using transitive analysis.
How It Works
A function’s effective coverage considers:
- Direct coverage: Lines executed by tests
- Caller coverage: Coverage of functions that call this function
Transitive Coverage = Direct Coverage + Σ(Caller Coverage × Weight)
Why It Matters
A function with 0% direct coverage might have high transitive coverage if it’s only called by well-tested functions:
#![allow(unused)] fn main() { // direct_coverage = 0% // But called only by `process_request` (100% coverage) // → transitive_coverage = 85% fn validate_input(data: &str) -> bool { data.len() > 0 } // direct_coverage = 100% fn process_request(input: String) -> Result<()> { if !validate_input(&input) { return Err("Invalid"); } // ... } }
Effect: validate_input has reduced urgency because it’s only reachable through well-tested code paths.
Performance Characteristics
Coverage integration is highly optimized for large codebases:
- Index Build: O(n), ~20-30ms for 5,000 functions
- Exact Lookup: O(1), ~0.5μs per lookup
- Fallback Lookup: O(log n), ~5-8μs when exact match fails
- Memory Usage: ~200 bytes per record (~2MB for 5,000 functions)
- Thread Safety: Lock-free parallel access via
Arc<CoverageIndex> - Analysis Overhead: ~2.5x baseline (target: ≤3x)
Result: Coverage integration adds minimal overhead even on projects with thousands of functions.
Implementation: See src/risk/coverage_index.rs:13-38 for the CoverageIndex struct and performance documentation.
CLI Options Reference
Primary Coverage Options
# Provide LCOV coverage file
debtmap analyze . --coverage-file path/to/lcov.info
# Shorthand alias
debtmap analyze . --lcov path/to/lcov.info
Context Providers
Coverage can be combined with other context providers for nuanced risk assessment:
# Enable all context providers (includes coverage propagation)
debtmap analyze . --lcov coverage.info --enable-context
# Specify specific providers
debtmap analyze . --lcov coverage.info \
--context-providers critical_path,dependency,git_history
# Disable specific providers
debtmap analyze . --lcov coverage.info \
--disable-context git_history
Available Context Providers:
critical_path: Identifies functions on critical execution pathsdependency: Analyzes dependency relationships and impactgit_history: Uses change frequency from version control
See Scoring Strategies for details on how these combine.
Validate Command Support
The validate command also supports coverage integration for risk-based quality gates:
# Fail CI builds if untested complex code exceeds thresholds
debtmap validate . --lcov coverage.info --max-debt-density 50
See CLI Reference for complete validation options.
Troubleshooting Coverage Integration
Coverage Not Correlating with Functions
Symptoms:
- Debtmap shows 0% coverage for all functions
- Warning: “No coverage data correlated with analyzed functions”
Solutions:
- Verify LCOV Format:
head coverage.info
# Should show: SF:, FN:, DA: lines
- Check Path Matching: Coverage file paths must be relative to project root:
# Good: SF:src/analyzer.rs
# Bad: SF:/home/user/project/src/analyzer.rs
- Enable Verbose Logging:
debtmap analyze . --lcov coverage.info -vv
This shows coverage lookup details for each function.
- Verify Coverage Tool Output:
# Ensure your coverage tool generated line data (DA: records)
grep "^DA:" coverage.info | head
Functions Still Show Up Despite 100% Coverage
This is expected behavior when:
- Function has high complexity (cyclomatic > 10)
- Function has other debt issues (duplication, nesting, etc.)
- You’re viewing function-level output (coverage dampens but doesn’t eliminate)
Coverage reduces priority but doesn’t hide issues. Use filters to focus:
# Show only critical and high priority items
debtmap analyze . --lcov coverage.info --min-priority high
# Show top 10 most urgent items
debtmap analyze . --lcov coverage.info --top 10
Coverage File Path Issues
Problem: Can’t find coverage file
Solutions:
# Use absolute path
debtmap analyze . --lcov /absolute/path/to/coverage.info
# Or ensure relative path is from project root
debtmap analyze . --lcov ./target/coverage/lcov.info
LCOV Format Errors
Problem: “Invalid LCOV format” error
Causes:
- Non-LCOV format (Cobertura XML, JaCoCo, etc.)
- Corrupted file
- Wrong file encoding
Solutions:
- Verify your coverage tool is configured for LCOV output
- Check for binary/encoding issues:
file coverage.info - Regenerate coverage with explicit LCOV format flag
See Troubleshooting for more debugging tips.
Best Practices
Analysis Workflow
-
Generate Coverage Before Analysis:
# Rust example cargo tarpaulin --out lcov --output-dir target/coverage debtmap analyze . --lcov target/coverage/lcov.info -
Use Coverage for Sprint Planning:
# Focus on untested complex code debtmap analyze . --lcov coverage.info --top 20 -
Combine with Tiered Prioritization: Coverage automatically feeds into Tiered Prioritization:
- Tier 1: Architectural issues (less affected by coverage)
- Tier 2: Complex untested code (coverage < 50%, complexity > 15)
- Tier 3: Testing gaps (coverage < 80%, complexity 10-15)
-
Validate Refactoring Impact:
# Before refactoring debtmap analyze . --lcov coverage-before.info -o before.json # After refactoring debtmap analyze . --lcov coverage-after.info -o after.json # Compare debtmap compare --before before.json --after after.json
Testing Strategy
Prioritize testing based on risk:
-
High Complexity + Low Coverage = Highest Priority:
debtmap analyze . --lcov coverage.info \ --filter Risk --min-priority high -
Focus on Business Logic: Entry points and infrastructure code have natural coverage patterns. Focus unit tests on business logic functions.
-
Use Dependency Analysis:
debtmap analyze . --lcov coverage.info \ --context-providers dependency -vvTests high-dependency functions first—they have the most impact.
-
Don’t Over-Test Entry Points: Entry points (main, handlers) are better tested with integration tests, not unit tests. Debtmap applies role multipliers through its semantic classification system (FunctionRole) to adjust scoring for different function types. See
src/priority/unified_scorer.rs:149andsrc/priority/scoring/classification.rsfor the classification system.
Configuration
In .debtmap.toml:
[scoring]
# Default weights for scoring WITHOUT coverage data
# When coverage data IS provided, it acts as a dampening multiplier instead
coverage = 0.50 # Default: 50% (only used when no LCOV provided)
complexity = 0.35 # Default: 35%
dependency = 0.15 # Default: 15%
[thresholds]
# Set minimum risk score to filter low-priority items
minimum_risk_score = 15.0
# Skip simple functions even if uncovered
minimum_cyclomatic_complexity = 5
Important: These weights only apply when coverage data is not available. When you provide --lcov, coverage acts as a dampening multiplier instead of a weighted component. See src/config.rs:122-132 for default weight definitions.
See Configuration for complete options.
CI Integration
Example GitHub Actions Workflow:
- name: Generate Coverage
run: cargo tarpaulin --out lcov --output-dir target/coverage
- name: Analyze with Debtmap
run: |
debtmap analyze . \
--lcov target/coverage/lcov.info \
--format json \
--output debtmap-report.json
- name: Validate Quality Gates
run: |
debtmap validate . \
--lcov target/coverage/lcov.info \
--max-debt-density 50
Quality Gate Strategy:
- Fail builds on new critical debt (Tier 1 architectural issues)
- Warn on new high-priority untested code (Tier 2)
- Track coverage trends over time with
comparecommand
Complete Language Examples
Rust End-to-End
# 1. Generate coverage
cargo tarpaulin --out lcov --output-dir target/coverage
# 2. Verify LCOV output
head target/coverage/lcov.info
# 3. Run Debtmap with coverage
debtmap analyze . --lcov target/coverage/lcov.info
# 4. Interpret results (look for [UNTESTED] markers on high-complexity functions)
JavaScript/TypeScript End-to-End
# 1. Configure Jest for LCOV (in package.json or jest.config.js)
# "coverageReporters": ["lcov", "text"]
# 2. Generate coverage
npm test -- --coverage
# 3. Verify LCOV output
head coverage/lcov.info
# 4. Run Debtmap
debtmap analyze . --lcov coverage/lcov.info --languages javascript,typescript
Python End-to-End
# 1. Install pytest-cov
pip install pytest-cov
# 2. Generate LCOV coverage
pytest --cov=src --cov-report=lcov
# 3. Verify output
head coverage.lcov
# 4. Run Debtmap
debtmap analyze . --lcov coverage.lcov --languages python
Go End-to-End
# 1. Generate native coverage
go test -coverprofile=coverage.out ./...
# 2. Convert to LCOV (requires gocover-cobertura or similar)
# Note: This step is tool-dependent
# 3. Run Debtmap
debtmap analyze . --lcov coverage.lcov --languages go
FAQ
Why does my 100% covered function still show up?
Coverage dampens debt scores but doesn’t eliminate debt. A function with cyclomatic complexity 25 and 100% coverage still represents technical debt—it’s just lower priority than untested complex code.
Use filters to focus on high-priority items:
debtmap analyze . --lcov coverage.info --top 10
What’s the difference between direct and transitive coverage?
- Direct coverage: Lines executed directly by tests
- Transitive coverage: Coverage considering call graph (functions called by well-tested code)
Transitive coverage reduces urgency for functions only reachable through well-tested paths.
Should I test everything to 100% coverage?
No. Use Debtmap’s risk scores to prioritize:
- Test high-complexity, low-coverage functions first
- Entry points are better tested with integration tests
- Simple utility functions (complexity < 5) may not need dedicated unit tests
Debtmap helps you achieve optimal coverage, not maximal coverage.
How do I debug coverage correlation issues?
Use verbose logging:
debtmap analyze . --lcov coverage.info -vv
This shows:
- Coverage file parsing details
- Function-to-coverage correlation attempts
- Path matching diagnostics
Can I use coverage with validate command?
Yes! The validate command supports --lcov for risk-based quality gates:
debtmap validate . --lcov coverage.info --max-debt-density 50
See CLI Reference for details.
Further Reading
- Scoring Strategies - Deep dive into how coverage affects unified scoring
- Tiered Prioritization - How coverage fits into tiered priority levels
- CLI Reference - Complete coverage option documentation
- Configuration - Customizing coverage scoring weights
- Troubleshooting - More debugging tips for coverage issues