Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parser for GoCov #99

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft

Add parser for GoCov #99

wants to merge 13 commits into from

Conversation

pyieh
Copy link

@pyieh pyieh commented Apr 23, 2024

Adding a parser for GoCov coverage files.

Testing done

Testing was done in both with unit tests and a test coverage file (based on real coverage files). As well as on a local Jenkins, where a local version of the Jenkins coverage-plugin was installed to use this coverage model. Ran a simple Go pipeline and validated the coverage output was available in the UI.

Once approved and merged, the corresponding coverage-plugin change can be found here: N/A

Submitter checklist

log.logError(USAGE_ERROR_MSG);
throw new InvalidGoCoverageFormatException("Expected the coverage mode specification, but got: [" + line + "]");
}
switch(line) {

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
line
may be null at this access as suggested by
this
null guard.
String domain = split[0];
String org = split[1];
String repo = split[2];
String module = String.join("/", domain, org, repo);

Check notice

Code scanning / CodeQL

Unread local variable Note

Variable 'String module' is never read.

String[] start = split2[1].split("\\.");
String startLine = start[0];
String startCol = start[1];

Check notice

Code scanning / CodeQL

Unread local variable Note

Variable 'String startCol' is never read.
String startCol = start[1];
String[] end = split2[2].split("\\.");
String endLine = end[0];
String endCol = end[1];

Check notice

Code scanning / CodeQL

Unread local variable Note

Variable 'String endCol' is never read.
String[] end = split2[2].split("\\.");
String endLine = end[0];
String endCol = end[1];
Integer statementCount = Integer.parseInt(split[split.length-2]);

Check notice

Code scanning / CodeQL

Missing catch of NumberFormatException Note

Potential uncaught 'java.lang.NumberFormatException'.
String endCol = end[1];
Integer statementCount = Integer.parseInt(split[split.length-2]);
Integer executionCount = Integer.parseInt(split[split.length-1]);
for (Integer i = Integer.parseInt(startLine), j = Integer.parseInt(endLine); i <= j; i++) {

Check notice

Code scanning / CodeQL

Missing catch of NumberFormatException Note

Potential uncaught 'java.lang.NumberFormatException'.
String[] end = split2[2].split("\\.");
String endLine = end[0];
String endCol = end[1];
Integer statementCount = Integer.parseInt(split[split.length-2]);

Check warning

Code scanning / CodeQL

Boxed variable is never null Warning

The variable 'statementCount' is only assigned values of primitive type and is never 'null', but it is declared with the boxed type 'Integer'.
String endLine = end[0];
String endCol = end[1];
Integer statementCount = Integer.parseInt(split[split.length-2]);
Integer executionCount = Integer.parseInt(split[split.length-1]);

Check warning

Code scanning / CodeQL

Boxed variable is never null Warning

The variable 'executionCount' is only assigned values of primitive type and is never 'null', but it is declared with the boxed type 'Integer'.
String endCol = end[1];
Integer statementCount = Integer.parseInt(split[split.length-2]);
Integer executionCount = Integer.parseInt(split[split.length-1]);
for (Integer i = Integer.parseInt(startLine), j = Integer.parseInt(endLine); i <= j; i++) {

Check warning

Code scanning / CodeQL

Boxed variable is never null Warning

The variable 'i' is only assigned values of primitive type and is never 'null', but it is declared with the boxed type 'Integer'.
String endCol = end[1];
Integer statementCount = Integer.parseInt(split[split.length-2]);
Integer executionCount = Integer.parseInt(split[split.length-1]);
for (Integer i = Integer.parseInt(startLine), j = Integer.parseInt(endLine); i <= j; i++) {

Check warning

Code scanning / CodeQL

Boxed variable is never null Warning

The variable 'j' is only assigned values of primitive type and is never 'null', but it is declared with the boxed type 'Integer'.
@uhafner uhafner added the feature New features label Apr 23, 2024
Copy link
Member

@uhafner uhafner left a comment

Choose a reason for hiding this comment

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

Thanks for your pull request for this new format!

src/test/java/edu/hm/hafner/coverage/AbstractNodeTest.java Outdated Show resolved Hide resolved
@@ -0,0 +1,12 @@
mode: atomic
github.com/example/project/pkg/utils/file1.go:5.12,8.22 3 1
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain the counters? Is there no function information?

Copy link
Author

@pyieh pyieh Apr 26, 2024

Choose a reason for hiding this comment

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

The format is:
<FILE>:<start row>.<start column>,<end row>.<end column> <# of statements> <# of statements covered>

Copy link
Member

Choose a reason for hiding this comment

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

Ok, then this format actually provides only line and instruction (or in your case statement) coverage. That means the counters for the line and branch coverage are either 1/1 or 0/1 (covered/total), see next comment.

The exact numbers of statements can be used to set the instruction coverage. Since we have only files as elements this coverage needs to be put per file.

Copy link
Author

Choose a reason for hiding this comment

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

My mistake, the format is actually:
<FILE>:<start row>.<start column>,<end row>.<end column> <# of statements covered> <# of statements missed>
And so that's we have a running tallies of the number of covered & missed statements, then resolve everything at the end.

Copy link
Member

Choose a reason for hiding this comment

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

This does not make sense when I look at the test case file? All of your lines actually show a coverage > 0. Or am I missing something?

mode: atomic
github.com/example/project/pkg/utils/file1.go:5.12,8.22 3 1
github.com/example/project/pkg/utils/file1.go:10.9,15.23 5 3
github.com/example/project/pkg/utils/file1.go:19.16,20.23 2 0
github.com/example/project/pkg/utils/file1.go:24.3,25.25 1 1
github.com/example/project/pkg/utils/file1.go:30.16,32.25 1 1
github.com/example/project/pkg/db/file2.go:12.7,15.7 1 0
github.com/example/project/pkg/db/file2.go:17.7,22.14 3 2
github.com/example/project/cmd/file3.go:10.12,10.21 1 0
github.com/example/project/cmd/file3.go:15.17,17.27 3 0
github.com/example/project/pkg/test/file4.go:10.12,11.21 1 0
github.com/example/project/pkg/test/file4.go:10.12,11.21 1 1

I rewrote the test so it matches my coding style:
3ae1498

I will try to refactor the parser as well. What is important and missing: your parser produces INSTRUCTION coverage. Which implies line coverage.

Copy link
Author

Choose a reason for hiding this comment

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

Let's take the example line:

github.com/example/project/pkg/utils/file1.go:5.12,8.22 3 1

It'd be read as:
For file1.go , lines 5-8, there were 3 statements that were covered and 1 statement that was missed.

Copy link
Member

@uhafner uhafner May 16, 2024

Choose a reason for hiding this comment

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

But then I do not understand the actual test case:

Report:

github.com/example/project/pkg/db/file2.go:12.7,15.7 1 0
github.com/example/project/pkg/db/file2.go:17.7,22.14 3 2

Actual test:

 two -> assertThat(two).hasName("file2.go")
                        .hasMissedLines(12, 13, 14, 15)
                        .hasCoveredLines(17, 18, 19, 20, 21, 22)

From the counters I would expect: Lines 12-15 have one statement covered and 0 missed. So we should have:

 two -> assertThat(two).hasName("file2.go")
                        .hasCoveredLines(12, 13, 14, 15, 17, 18, 19, 20, 21, 22)

Copy link
Member

Choose a reason for hiding this comment

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

Do you need some help to get this parser finished? I think we are currently struggling at the missed/covered computation, see comment above.

@@ -521,6 +521,17 @@ public FileNode addCounters(final int lineNumber, final int covered, final int m
return this;
}

@CanIgnoreReturnValue
public FileNode addCounterIncremental(final int lineNumber, final int covered, final int missed) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think that an extra method is required. From the format of your test case it looks like you do not use branch coverage at all. So the counter should be c=1 m0 or c=0 m1. If c +n > 1 then the UI assumes that you are using branch coverage.

Copy link
Author

Choose a reason for hiding this comment

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

As addressed in previous statement, my mistake on the previous incorrect information. But because each line read in the coverage file gives N number of covered statements, and M number of missed statements, we keep running tallies of each one and then resolve everything at the end.

import static edu.hm.hafner.coverage.parser.JacocoParser.createValue;

public class GoParser extends CoverageParser {
private static final java.util.logging.Logger LOGGER = java.util.logging.Logger.getLogger(GoParser.class.getName());
Copy link
Member

Choose a reason for hiding this comment

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

Please use the provided logger that will report the information in the UI.

Copy link
Author

Choose a reason for hiding this comment

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

I'm already are doing so (example), I was using these to also log the information to Jenkins logs, but they can be removed to better align with the rest of the module, if you want.

Copy link
Member

Choose a reason for hiding this comment

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

This parser seems to be way to complex for the simple format of the test case? Are you planning to parser different reports? So I am skipping the review on this class right now. It would also make sense to keep the static analysis happy before starting a review...

@uhafner uhafner marked this pull request as draft April 23, 2024 19:09
@pyieh pyieh requested a review from uhafner May 9, 2024 22:42
@pyieh
Copy link
Author

pyieh commented May 9, 2024

@uhafner Addressed your comments and corrected an incorrect statement I made before. Hopefully the correct explanation of the Go coverage files justifies our methodology for counting coverage.


@Test
void testAtomic() {
var instructionBuilder = new Coverage.CoverageBuilder().withMetric(INSTRUCTION);

Check notice

Code scanning / CodeQL

Unread local variable Note test

Variable 'CoverageBuilder instructionBuilder' is never read.
@uhafner
Copy link
Member

uhafner commented May 13, 2024

Wouldn't it make sense to use https://github.com/AlekSi/gocov-xml? Then we simply can reuse the Cobertura parser? Wouldn't there be also method names when used in that way?

@pyieh
Copy link
Author

pyieh commented May 13, 2024

Wouldn't it make sense to use https://github.com/AlekSi/gocov-xml? Then we simply can reuse the Cobertura parser? Wouldn't there be also method names when used in that way?

@uhafner we tried avoiding using any non-organization type of libraries due to possible lack of future support reasons, but this author seems to be highly involved in the Go community and if you're okay with us using this library, I can re-implement it to do so.

@uhafner
Copy link
Member

uhafner commented May 14, 2024

Wouldn't it make sense to use https://github.com/AlekSi/gocov-xml? Then we simply can reuse the Cobertura parser? Wouldn't there be also method names when used in that way?

@uhafner we tried avoiding using any non-organization type of libraries due to possible lack of future support reasons, but this author seems to be highly involved in the Go community and if you're okay with us using this library, I can re-implement it to do so.

I'm not sure if I can follow. What I am suggesting is: rather than creating the file go-coverage-atomic.out in your build and parsing this file in Jenkins, it looks much easier to call https://github.com/AlekSi/gocov-xml in your build pipeline. Then a cobertura.xml file will be created that can be read into Jenkins without any modification in the coverage-model.

At least it would make sense to use https://github.com/AlekSi/gocov-xml in a protoype to see if the created report contains more information than the plain go-coverage-atomic.out file?

So I am not against your parser in this PR but if it is not required because a better tool already exists it would make sense to follow the other approach...

And if this does not work we need to improve your parser so that the created model is correct.

@pyieh
Copy link
Author

pyieh commented May 14, 2024

Wouldn't it make sense to use https://github.com/AlekSi/gocov-xml? Then we simply can reuse the Cobertura parser? Wouldn't there be also method names when used in that way?

@uhafner we tried avoiding using any non-organization type of libraries due to possible lack of future support reasons, but this author seems to be highly involved in the Go community and if you're okay with us using this library, I can re-implement it to do so.

I'm not sure if I can follow. What I am suggesting is: rather than creating the file go-coverage-atomic.out in your build and parsing this file in Jenkins, it looks much easier to call https://github.com/AlekSi/gocov-xml in your build pipeline. Then a cobertura.xml file will be created that can be read into Jenkins without any modification in the coverage-model.

At least it would make sense to use https://github.com/AlekSi/gocov-xml in a protoype to see if the created report contains more information than the plain go-coverage-atomic.out file?

So I am not against your parser in this PR but if it is not required because a better tool already exists it would make sense to follow the other approach...

And if this does not work we need to improve your parser so that the created model is correct.

Unfortunately, we're trying to automate and minimize the number of steps needed for our users to migrate over to this new plugin. And users are currently using that .out coverage format.
Can I ask you to clarify what's currently incorrect about my model? I thought with the updated/correct explanation of each line, my model shows the correct coverage data.

@uhafner
Copy link
Member

uhafner commented May 15, 2024

Wouldn't it make sense to use https://github.com/AlekSi/gocov-xml? Then we simply can reuse the Cobertura parser? Wouldn't there be also method names when used in that way?

@uhafner we tried avoiding using any non-organization type of libraries due to possible lack of future support reasons, but this author seems to be highly involved in the Go community and if you're okay with us using this library, I can re-implement it to do so.

I'm not sure if I can follow. What I am suggesting is: rather than creating the file go-coverage-atomic.out in your build and parsing this file in Jenkins, it looks much easier to call https://github.com/AlekSi/gocov-xml in your build pipeline. Then a cobertura.xml file will be created that can be read into Jenkins without any modification in the coverage-model.
At least it would make sense to use https://github.com/AlekSi/gocov-xml in a protoype to see if the created report contains more information than the plain go-coverage-atomic.out file?
So I am not against your parser in this PR but if it is not required because a better tool already exists it would make sense to follow the other approach...
And if this does not work we need to improve your parser so that the created model is correct.

Unfortunately, we're trying to automate and minimize the number of steps needed for our users to migrate over to this new plugin. And users are currently using that .out coverage format. Can I ask you to clarify what's currently incorrect about my model? I thought with the updated/correct explanation of each line, my model shows the correct coverage data.

See #99 (comment)

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

Successfully merging this pull request may close these issues.

2 participants