Skip to content

Conversation

brynary
Copy link
Member

@brynary brynary commented Aug 21, 2025

Summary

  • Implements automatic path fixing for code coverage reports in CI/CD environments
  • Adds DefaultPathFixer transformer that removes common CI/CD path prefixes
  • Automatically applies path fixing when manual options are not specified

Details

This PR introduces the DefaultPathFixer transformer to automatically handle path normalization for code coverage reports generated in various CI/CD environments. When users don't specify manual path fixing options (--path-replace or --path-prefix), the system will automatically remove common CI/CD path prefixes to ensure coverage paths match the repository structure.

Supported CI/CD Systems

The DefaultPathFixer recognizes and removes path prefixes from:

  • CircleCI (/home/circleci/)
  • GitHub Actions (/home/runner/work/, /Users/runner/work/)
  • Travis CI (/home/travis/build/)
  • Jenkins (/var/lib/jenkins/workspace/)
  • GitLab CI (/builds/)
  • Bitbucket Pipelines (/opt/atlassian/pipelines/agent/build/)
  • AWS CodeBuild (/codebuild/output/)
  • Azure Pipelines (/home/vsts/work/)

Changes

  1. Added DefaultPathFixer transformer (qlty-coverage/src/transformer.rs)

    • Implements automatic detection and removal of CI/CD path prefixes
    • Includes comprehensive unit tests for all supported CI systems
  2. Updated publish planner (qlty-coverage/src/publish/planner.rs)

    • Applies DefaultPathFixer when no manual path options are provided
    • Maintains backward compatibility with existing manual configuration
  3. Updated transform planner (qlty-coverage/src/transform/planner.rs)

    • Applies DefaultPathFixer when no manual path options are provided
    • Ensures consistent behavior across both publish and transform commands

Test Plan

  • Unit tests for DefaultPathFixer with all supported CI path prefixes
  • Integration tests for publish command with DefaultPathFixer
  • Integration tests for transform command with DefaultPathFixer
  • Verified backward compatibility with manual path options
  • Manual testing in actual CI/CD environments
  • Documentation update for new automatic path fixing feature

🤖 Generated with Claude Code

Implement DefaultPathFixer transformer that automatically removes common CI/CD path prefixes from code coverage reports when manual path fixing options are not specified. This improves the user experience by eliminating the need for manual configuration in most standard CI environments.

Supported CI/CD systems:
- CircleCI (/home/circleci/)
- GitHub Actions (/home/runner/work/, /Users/runner/work/)
- Travis CI (/home/travis/build/)
- Jenkins (/var/lib/jenkins/workspace/)
- GitLab CI (/builds/)
- Bitbucket Pipelines (/opt/atlassian/pipelines/agent/build/)
- AWS CodeBuild (/codebuild/output/)
- Azure Pipelines (/home/vsts/work/)

The DefaultPathFixer is automatically applied in both publish and transform commands when users don't specify --path-replace or --path-prefix options, making code coverage integration seamless across different CI/CD platforms.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
Copy link
Contributor

No issue mentions found. Please mention an issue in the pull request description.

Use GitHub automation to close the issue when a PR is merged

Copy link
Contributor

qltysh bot commented Aug 21, 2025

Diff Coverage for ubuntu-latest: The code coverage on the diff in this pull request is 98.0%.

Total Coverage for ubuntu-latest: This PR will increase coverage by 0.15%.

File Coverage Changes
Path File Coverage Δ Indirect
qlty-cli/src/commands/coverage/publish.rs 0.3
qlty-coverage/src/ci/github.rs -0.3
qlty-coverage/src/publish/planner.rs 1.3
qlty-coverage/src/publish/processor.rs 0.0
qlty-coverage/src/transform/planner.rs 83.5
qlty-coverage/src/transformer.rs 4.9
🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

Copy link
Contributor

qltysh bot commented Aug 21, 2025

Diff Coverage for macos-15: The code coverage on the diff in this pull request is 98.0%.

Total Coverage for macos-15: This PR will increase coverage by 0.16%.

File Coverage Changes
Path File Coverage Δ Indirect
qlty-cli/src/commands/coverage/publish.rs 0.3
qlty-coverage/src/ci/github.rs -0.3
qlty-coverage/src/publish/planner.rs 1.3
qlty-coverage/src/publish/processor.rs 0.0
qlty-coverage/src/transform/planner.rs 83.5
qlty-coverage/src/transformer.rs 5.8
🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

brynary and others added 2 commits August 21, 2025 09:26
The pattern r'^/?home/[^/]+/src/([^/]+/){3}' was too generic and could
match legitimate project paths. Removed to avoid false positives.
@noahd1
Copy link
Member

noahd1 commented Sep 10, 2025

@brynary I did a risk assessment of this pull request and also some thoughts on possible enhancements. Take away is that this is probably good to go as is.

Risks

Risk: over-eager pattern matching which transforms paths that shouldn't get transformed

These are the patterns:

            r"^/?home/circleci/project/",
            r"^/?(home|Users)/runner/work/[^/]+/[^/]+/",
            r"^/?(home|Users)/travis/build/[^/]+/[^/]+/",
            r"^/?(home|Users)/jenkins/jobs/[^/]+/workspace/",
            r"^/?Users/distiller/[^/]+/",
            r"^/?(home|Users)/[^/]+/workspace/[^/]+/[^/]+/",

Of course, anything is possible, but these seem largely safe.

We could introduce a flag like --skip-default-transforms but I'd wait until this is actually a real world problem before introducing it.

Risk: doesn't work in practice (missed or incorrect patterns)

There are unit tests for the patterns which are good. The risk I guess is that these CI providers have different paths than the ones we've specified, and in which case, the behavior will be the same as today, which is that a strip prefix argument must be supplied. Seems ok.

Risk: isn't backwards compatible -- doesn't properly apply a customers add or strip prefix transformation

The tests in qlty-coverage/src/publish/planner.rs seem pretty good. There isn't an end to end test however. We could consider adding that.

Enhancements

Think this can largely wait, but was curious on your take:

  • Denote in debug output that paths are getting auto-transformed (bonus: which transformation was applied)
  • Print a deprecation warning if the strip prefix provided matches an established pattern

@brynary
Copy link
Member Author

brynary commented Sep 10, 2025

Thanks this is super helpful!

We can release on a weekend to limit any thundering herd of problems if we make a mistake.

One thing we should add is output like a line like this:

Auto-path fixing: Enabled

I don't think that has to be dynamic. In other words I think it's fine to render Enabled if we are attempting auto path fixing regardless of whether any paths are adjusted.

(Since path fixing is applied per path, the question of "was auto path fixing applied?" is not binary anyway. We could in theory print the counts but I don't think that's needed yet.)

@noahd1
Copy link
Member

noahd1 commented Sep 10, 2025

(Since path fixing is applied per path, the question of "was auto path fixing applied?" is not binary anyway. We could in theory print the counts but I don't think that's needed yet.)

Depends on perspective I guess -- it's binary if any path fixing was applied. That said, it's certainly simpler to state that Auto-Path fixing is enabled.

@brynary
Copy link
Member Author

brynary commented Sep 11, 2025

@noahd1 we need to add a path fix for: github.com/SOMETHING/SOMETHING which is a form that I've been seeing lately. (This isn't even a system path on disk it's virtual but we can fix it)

@brynary
Copy link
Member Author

brynary commented Sep 11, 2025

Depends on perspective I guess -- it's binary if any path fixing was applied. That said, it's certainly simpler to state that Auto-Path fixing is enabled.

Yeah, better than just "enabled" would be "Enabled but not applied" vs. "Enabled and applied"

@noahd1 noahd1 marked this pull request as ready for review September 11, 2025 23:53
@Copilot Copilot AI review requested due to automatic review settings September 11, 2025 23:53
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements automatic path fixing for code coverage reports in CI/CD environments by introducing a DefaultPathFixer transformer that removes common CI/CD path prefixes when manual path fixing options are not specified.

  • Added DefaultPathFixer transformer with support for 8+ CI/CD systems (CircleCI, GitHub Actions, Travis CI, etc.)
  • Updated both publish and transform planners to automatically apply path fixing when manual options are absent
  • Added comprehensive test coverage for the new functionality and maintained backward compatibility

Reviewed Changes

Copilot reviewed 7 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
qlty-coverage/src/transformer.rs Implements DefaultPathFixer transformer with regex patterns for CI/CD path detection and Windows path normalization
qlty-coverage/src/transform/planner.rs Conditionally applies DefaultPathFixer to transform operations when manual path options aren't provided
qlty-coverage/src/publish/planner.rs Conditionally applies DefaultPathFixer to publish operations and tracks auto-fixing status
qlty-coverage/src/publish/plan.rs Adds auto_path_fixing_enabled field to track when automatic path fixing is active
qlty-coverage/src/publish/processor.rs Passes auto_path_fixing_enabled flag from plan to report
qlty-coverage/src/publish/report.rs Adds auto_path_fixing_enabled field to Report struct
qlty-coverage/src/validate.rs Updates test helper to include auto_path_fixing_enabled field

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +211 to +217
let pattern_strings = vec![
r"^/?home/circleci/project/",
r"^/?(home|Users)/runner/work/[^/]+/[^/]+/",
r"^/?github.com/[^/]+/[^/]+/",
r"^/?(home|Users)/travis/build/[^/]+/[^/]+/",
r"^/?(home|Users)/jenkins/jobs/[^/]+/workspace/",
r"^/?Users/distiller/[^/]+/",
Copy link
Preview

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex patterns lack documentation explaining which CI/CD systems they target. Consider adding comments like // CircleCI, // GitHub Actions, etc. to clarify which pattern matches which CI system.

Suggested change
let pattern_strings = vec![
r"^/?home/circleci/project/",
r"^/?(home|Users)/runner/work/[^/]+/[^/]+/",
r"^/?github.com/[^/]+/[^/]+/",
r"^/?(home|Users)/travis/build/[^/]+/[^/]+/",
r"^/?(home|Users)/jenkins/jobs/[^/]+/workspace/",
r"^/?Users/distiller/[^/]+/",
let pattern_strings = vec![
// CircleCI
r"^/?home/circleci/project/",
// GitHub Actions
r"^/?(home|Users)/runner/work/[^/]+/[^/]+/",
// GitHub Actions (checkout path)
r"^/?github.com/[^/]+/[^/]+/",
// Travis CI
r"^/?(home|Users)/travis/build/[^/]+/[^/]+/",
// Jenkins
r"^/?(home|Users)/jenkins/jobs/[^/]+/workspace/",
// CircleCI (macOS)
r"^/?Users/distiller/[^/]+/",
// Azure Pipelines

Copilot uses AI. Check for mistakes.

return stripped.to_string();
}
// Handle any single letter drive
if path.len() >= 3 && path.chars().nth(1) == Some(':') && path.chars().nth(2) == Some('/') {
Copy link
Preview

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling path.chars().nth() multiple times is inefficient as it iterates through the string each time. Consider using path.chars().take(3).collect::<Vec<_>>() or checking bytes directly if ASCII is sufficient.

Suggested change
if path.len() >= 3 && path.chars().nth(1) == Some(':') && path.chars().nth(2) == Some('/') {
if path.as_bytes().get(1) == Some(&b':') && path.as_bytes().get(2) == Some(&b'/') {

Copilot uses AI. Check for mistakes.

Comment on lines +240 to +245
if let Some(stripped) = path.strip_prefix("C:/") {
return stripped.to_string();
}
if let Some(stripped) = path.strip_prefix("D:/") {
return stripped.to_string();
}
Copy link
Preview

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded drive letter checks for C:/ and D:/ are redundant since the generic check on lines 247-248 handles any single letter drive. Remove the specific C:/ and D:/ checks to eliminate duplication.

Suggested change
if let Some(stripped) = path.strip_prefix("C:/") {
return stripped.to_string();
}
if let Some(stripped) = path.strip_prefix("D:/") {
return stripped.to_string();
}

Copilot uses AI. Check for mistakes.

Comment on lines +57 to +59
let auto_path_fixing_enabled = transformers
.iter()
.any(|t| format!("{:?}", t).contains("DefaultPathFixer"));
Copy link
Preview

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using format!("{:?}", t).contains("DefaultPathFixer") to detect transformer type is fragile and depends on debug formatting. Consider adding a trait method like transformer_type() or using std::any::TypeId for more reliable type checking.

Copilot uses AI. Check for mistakes.

Comment on lines +87 to +89
let has_default_fixer = transformers
.iter()
.any(|t| format!("{:?}", t).contains("DefaultPathFixer"));
Copy link
Preview

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using format!("{:?}", t).contains("DefaultPathFixer") to detect transformer type is fragile and depends on debug formatting. Consider adding a trait method like transformer_type() or using std::any::TypeId for more reliable type checking.

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants