Skip to content

Improve detection of paths in GO_COV reports - #265

Open
akash-manna-sky wants to merge 30 commits into
jenkinsci:mainfrom
akash-manna-sky:issue-263
Open

akash-manna-sky wants to merge 30 commits into
jenkinsci:mainfrom
akash-manna-sky:issue-263

Conversation

@akash-manna-sky

@akash-manna-sky akash-manna-sky commented Jan 28, 2026

Copy link
Copy Markdown
Contributor

GO_COV issues with resolving paths

Issue #263 reported that Go coverage reports were losing their directory structure and failing to locate source files. For example, example.com/project/internal/alpha.go was incorrectly parsed as just alpha.go. To resolve this, I implemented a ModuleRegistry() solution that parses go.mod files to extract module-to-directory mappings. It then uses a longest-prefix matching approach to accurately translate coverage paths to file system paths (e.g., ext/sum.go./hack/ext/sum.go). When go.mod files are not available, the implementation falls back to intelligent heuristics to infer the correct path structure.

From the demo coverage profile:

ext/sum.go                    => ./hack/ext/sum.go
ext/stat/minmax.go       => ./hack/ext/stat/minmax.go
ext/sub/sub.go              => ./hack/extsub/sub.go
hello.test/main.go         => ./main.go
hello.test/cpu/main.go  => ./cpu/main.go

Fixes #263

Testing done

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests that demonstrate the feature works or the issue is fixed

@akash-manna-sky
akash-manna-sky marked this pull request as ready for review January 29, 2026 18:03
@uhafner uhafner added the bug Bugs or performance problems label Feb 12, 2026

@uhafner uhafner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good, I will ask in #263 if we might get a more realistic report example.

Comment on lines +193 to +195
private String buildPackagePath(final List<String> parts, final int startIndex) {
return parts.subList(startIndex, parts.size() - 1).stream()
.collect(Collectors.joining("."));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Replace with

String.join(".", parts.subList(startIndex, parts.size() - 1));

And extract "." as parameter so you don't need to duplicate with "/"

if (parts.size() == 1) {
return new PathInfo(StringUtils.EMPTY, StringUtils.EMPTY, 0);
}
if (parts.size() == PATH_LENGTH_TWO) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if (parts.size() == PATH_LENGTH_TWO) {
if (parts.size() == 2) {

if (parts.size() == PATH_LENGTH_TWO) {
return new PathInfo(parts.get(0), parts.get(0), 1);
}
if (parts.size() == PATH_LENGTH_THREE) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

PMD is sometimes too silly: add a suppression for this method.

Suggested change
if (parts.size() == PATH_LENGTH_THREE) {
if (parts.size() == 3) {

Comment on lines +133 to +135
var module = new ModuleNode(moduleName);
modules.add(module);
return module;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

While that seems to work technically, it is a bad practice to modify the elements of the parameter.

Can you add the result in the caller?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This one still is open? Never ever change a parameter.

@uhafner uhafner changed the title GO_COV issues with resolving paths Improve detection of paths in GO_COV reports Feb 12, 2026
@github-actions
github-actions Bot requested a review from uhafner February 12, 2026 11:17
@uhafner

uhafner commented Feb 12, 2026

Copy link
Copy Markdown
Member

Can you check, if the new example in #263 works?

}

@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
private PathInfo determinePathStructure(final List<String> parts) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This logic does not look correct, because the module name can be from "a" to "a/b/c/d/e/f/g" etc. The only way to determine what it actually is, is based on the information parsed from go.mod.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PS: this approach as a fallback when go.mod is not available would be of course fine.

ext/stat/minmax.go:9.27,11.3 1 1
ext/stat/minmax.go:12.2,12.24 1 1
hello.test/main.go:3.13,5.2 1 1
hello.test/cpu/main.go:3.13,5.2 1 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Note, I updated the example to have an additional corner case for determining, which module to use.

@akash-manna-sky

Copy link
Copy Markdown
Contributor Author

Please have a look. @uhafner

@uhafner

uhafner commented Feb 12, 2026

Copy link
Copy Markdown
Member

Please have a look. @uhafner

Did you have a chance to look at the comments from @egonelbre?

@akash-manna-sky

Copy link
Copy Markdown
Contributor Author

Please have a look. @uhafner

Did you have a chance to look at the comments from @egonelbre?

Yes, I looked his concern. My approach is basically heuristic-based approach. I was waiting for your suggestion. Anyway, I have a plan, will push the changes shortly.

@egonelbre

Copy link
Copy Markdown

@akash-manna-sky https://github.com/egonelbre/exp/blob/main/coverage_example/parse/main.go contains Go code on how to handle module files and use them to parse coverage. Extracting the module name can be done via regex easily as well, because the go.mod doesn't have block comments.

Comment thread src/main/java/edu/hm/hafner/coverage/parser/GoCovParser.java
@akash-manna-sky

Copy link
Copy Markdown
Contributor Author

Please review the changes @uhafner

@github-actions
github-actions Bot requested a review from uhafner March 2, 2026 18:58
@akash-manna-sky

Copy link
Copy Markdown
Contributor Author

Is there anything need to update? Please review. @uhafner

@uhafner uhafner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks almost good now. I still find a hard to see what is working now differently. Can you please add more text to the description in the PR as the current text does not say anything useful for users.

private static final long serialVersionUID = -4511292826873362408L;

private static final PathUtil PATH_UTIL = new PathUtil();
private static final Pattern PATH_SEPARATOR = Pattern.compile("/");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This makes no sense, see below.

if (org == null) {
private String determineContainerName(final String fullPath) {
var normalizedPath = fullPath.replace('\\', '/');
var parts = Arrays.stream(PATH_SEPARATOR.split(normalizedPath)).toList();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
var parts = Arrays.stream(PATH_SEPARATOR.split(normalizedPath)).toList();
var parts = StringUtils.split(normalizedPath, PATH_SEPARATOR);

Comment on lines +129 to +137
if (parts.isEmpty()) {
return StringUtils.EMPTY;
}
return org + "/";

if (parts.size() >= 3 && parts.get(0).contains(".")) {
return parts.get(0) + "/" + parts.get(1);
}

return parts.get(0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It makes no sense to convert to a list, just use the array

Comment on lines +133 to +135
var module = new ModuleNode(moduleName);
modules.add(module);
return module;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This one still is open? Never ever change a parameter.

return createPathPartsFromModule(normalizedPath, moduleMatch);
}

var parts = Arrays.stream(PATH_SEPARATOR.split(normalizedPath)).toList();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See above

@github-actions
github-actions Bot requested a review from uhafner March 4, 2026 16:44
@akash-manna-sky

Copy link
Copy Markdown
Contributor Author

I added a short PR description regarding this PR. I also implemented your requested changes. Please review the changes. @uhafner

* Creates an empty module registry.
*/
ModuleRegistry() {
this.modules = new ArrayList<>();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The module registry does not make sense. It has a list of modules that is always empty. Can you check what the registry is good for?

@github-actions
github-actions Bot requested a review from uhafner March 5, 2026 04:56

@egonelbre egonelbre left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It looks like the latest change completely removes the module handling code, without module handling an accurate path cannot be resolved.

I should mention there does not seem to be any tests for having multiple module files and a corresponding coverprofile. This should make it easier to see when essential pieces are being removed.

https://github.com/egonelbre/exp/tree/main/coverage_example/demo contains a decent example that should work with the expected output shown in #263 (comment)

@akash-manna-sky

Copy link
Copy Markdown
Contributor Author

What should I do now? @uhafner

@uhafner

uhafner commented Mar 5, 2026

Copy link
Copy Markdown
Member

What should I do now? @uhafner

Did you already have a look at the example?
https://github.com/egonelbre/exp/tree/main/coverage_example/demo

Does your code correctly resolve the files?

I did not find time yet to make me familiar with the reports. Maybe it makes sense to go a step back and re-evaluate the problem? I try to find some time to look into the problem a little be deeper.

@uhafner

uhafner commented Mar 5, 2026

Copy link
Copy Markdown
Member

Another option would be to ask in the corresponding channels of the GoCov project, how the mapping should be and why they do not include the corresponding information in the report format. All other coverage tools in the world report their files correctly with absolute or relative paths, I see no reason why GoCov should not follow the same approach.

@egonelbre

Copy link
Copy Markdown

The relevant change was done in https://go-review.googlesource.com/c/go/+/122478. The main reasoning for using package path instead of filenames is that a lot of go tooling use package path as input. Also, the package path allows to transfer the coverprofile to other computers and have it still correctly resolve to correct absolute path (assuming you are on the exact same commit).

As for finding the correct mapping -- one approach I didn't think of earlier is to let people build the "module name" -> "directory" mapping with go list, e.g.

$ go list -m -f "{{.Path}} >> {{.Dir}}" all

// example output from my command
hello.test >> /Users/egon/code/github.com/egonelbre/exp/coverage_example/demo
ext >> /Users/egon/code/github.com/egonelbre/exp/coverage_example/demo/hack/ext
ext/sub >> /Users/egon/code/github.com/egonelbre/exp/coverage_example/demo/hack/extsub

// but it may also contain things from module cache
loov.dev/diagram >> /Users/egon/go/pkg/mod/loov.dev/diagram@v0.0.0-20250912145245-d31ac3c2406b

Of course that would be a little bit more inconvenient to setup than passing in the relevant go.mod files. The conversion logic from package path to absolute path would still stay the same though as I previously suggested.

@akash-manna-sky

Copy link
Copy Markdown
Contributor Author

What should I do now? @uhafner

Did you already have a look at the example? https://github.com/egonelbre/exp/tree/main/coverage_example/demo

Does your code correctly resolve the files?

I did not find time yet to make me familiar with the reports. Maybe it makes sense to go a step back and re-evaluate the problem? I try to find some time to look into the problem a little be deeper.

Yes, it seems my code resolve the files. And please take your time and look into the problem. I will commit the changes after your suggestions. I am trying to figure out the solutions and looking into the ModuleRegistry() method more deeply to figure out what the best approach might be. Thanks!

@uhafner
uhafner marked this pull request as draft March 31, 2026 20:53
@akash-manna-sky
akash-manna-sky marked this pull request as ready for review August 4, 2026 05:17
assertThat(report.getAllFileNodes()).map(FileNode::getFileName).containsExactlyInAnyOrder(
"sum.go", "minmax.go", "main.go", "main.go");
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It looks like the test case I outlined in #263 (comment) is missing. Similarly I don't see any go.mod in the testsuite or module parsing, which are necessary to correctly resolve paths.

@github-actions

Copy link
Copy Markdown

☀️   Quality Monitor

Tests

   JUnit   Unit Tests: ✅ successful — 453 passed $\color{green}{\textsf{(+11)}}$
   ⛔   Architecture Tests: ✅ successful — 10 passed $\textsf{(±0)}$

Coverage for New Code

   〰️   Line Coverage: 97.35% — 4 missed lines
   ➰   Branch Coverage: 100.00% — perfect 🎉
   PIT   Mutation Coverage: 92.86% — 5 survived mutations
   💪   Test Strength: 100.00% — perfect 🎉

Coverage for Whole Project

   〰️   Line Coverage: 95.96% $\color{green}{\textsf{(+0.02)}}$ — 132 missed lines
   ➰   Branch Coverage: 91.68% $\color{green}{\textsf{(+0.21)}}$ — 115 missed branches
   PIT   Mutation Coverage: 89.74% $\color{green}{\textsf{(+0.09)}}$ — 165 survived mutations
   💪   Test Strength: 92.80% $\color{green}{\textsf{(+0.05)}}$ — 112 survived mutations in tested code

Style

   CheckStyle   CheckStyle: No warnings $\textsf{(±0)}$
   PMD   PMD: No warnings $\textsf{(±0)}$
   ☕   Java Compiler: No warnings $\textsf{(±0)}$

Bugs

   SpotBugs   SpotBugs: No bugs $\textsf{(±0)}$
   🐛   Error Prone: No bugs $\textsf{(±0)}$

API Problems

   🚫   Revapi: No warnings $\textsf{(±0)}$

Vulnerabilities

   🛡️   OWASP Dependency Check: No vulnerabilities $\textsf{(±0)}$

Software Metrics

   🌀   Cyclomatic Complexity: 1019 (total)
   💭   Cognitive Complexity: 782 (total)
   ➿   N-Path Complexity: 1578 (total)
   📏   Lines of Code: 10279 (total)
   📝   Non Commenting Source Statements: 4103 (total)
   🔗   Class Cohesion: 54.55% (maximum)
   ⚖️   Weight of Class: 100.00% (maximum)

📌 Reference Results

Delta reports computed against the reference results of 20d104b in workflow run 31372098981.

🚦 Quality Gates

Overall Status: ✅ SUCCESS

✅ Passed Gates

  • ✅ Overall Tests Success Rate: 100.00 >= 100.00
  • ✅ Line Coverage in New Code: 97.35 >= 90.00
  • ✅ Branch Coverage in New Code: 100.00 >= 90.00
  • ✅ Mutation Coverage in New Code: 92.86 >= 90.00
  • ✅ Potential Bugs in Whole Project: 0.00 <= 0.00
  • ✅ Style Violation in Whole Project: 0.00 <= 0.00

Created by Quality Monitor v4.15.0 (#82d77af). More details are shown in the GitHub Checks Result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bugs or performance problems

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GO_COV issues with resolving paths

3 participants