Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
372e4d8
adds model classes
aelfric-m Jul 18, 2023
9ca1af4
adds LineCoverageViewModel class
aelfric-m Jul 18, 2023
f7070e9
Add test environment for coverage remote api model calculation
Jul 18, 2023
2738d94
adds LineCoverageViewModel class
aelfric-m Jul 18, 2023
b8e59bc
Merge branch 'dev' of github.com:aelfric-m/code-coverage-api-plugin i…
aelfric-m Jul 18, 2023
b0940cf
fixes LineCoverageViewModel class
aelfric-m Jul 26, 2023
102fb85
adds test for LineCoverageViewModel class
aelfric-m Jul 27, 2023
ad3192a
adds first working model of ChangedAndCoveredLinesApi
aelfric-m Jul 27, 2023
eb1dc94
adds first working model of CoolApi
aelfric-m Jul 28, 2023
2c99275
backup
aelfric-m Jul 28, 2023
8095485
LineCoverageViewModel can now be passed as an argument to an Api
aelfric-m Jul 28, 2023
eca4317
properly names LineCoverageTest and removes unused code
aelfric-m Jul 31, 2023
3076954
refacors model classes to better reflect their usage
Aug 3, 2023
3ab8736
makes model class attributes final and removes associated setters
Aug 3, 2023
fdc71c9
separates data from LineCoverageViewModel into LineCoverageApi
Aug 3, 2023
bbba7fc
changes unit test assertions to AssertJ and removes redundant test ro…
Aug 7, 2023
c3f0d7c
adds documentation to classes associated with the line coverage API
Aug 7, 2023
928e0e1
fixed out of bounds exception
Aug 8, 2023
7a779d7
fixes annotations for ModifiedLinesBlock class
Aug 16, 2023
ef4a9c9
fixes jenkins CheckStyle
Aug 22, 2023
45e5664
more checks fixes
Aug 23, 2023
68e4cde
reformatting and import optimization
Aug 24, 2023
de9da44
refactors the LineCoverageViewModel to CoverageApiUtil
Aug 24, 2023
165d80e
renames function to better reflect its job
Aug 25, 2023
b758d6d
improves unit tests and fixes some type-os
Sep 4, 2023
c56cf62
adds FileWithModifiedLinesTest class, moves some methods into parent …
Sep 4, 2023
dc1dd4b
integrates the logic from CoverageApiUtil into the Api class. More re…
Sep 6, 2023
07ab227
Adds restapi package to design.puml. Some renaming
Sep 7, 2023
94fefc4
Adds GerritCoverageController class for Gerrit fronteend communication
Sep 7, 2023
1531f76
Adds GerritCoverageController class for Gerrit fronteend communication
Sep 7, 2023
4366b71
Merge remote-tracking branch 'origin/dev' into dev
Sep 21, 2023
6d09435
removes GerritCoverageController class
Sep 21, 2023
cc8cdbd
visibility changes in restapi package
Sep 21, 2023
0328e16
Data provided by ModifiedLinesCoverageApi is now sorted by starting l…
Sep 28, 2023
b21c729
Data provided by ModifiedLinesCoverageApi is now sorted by starting l…
Sep 28, 2023
b16555d
Merge remote-tracking branch 'origin/dev' into dev
Sep 28, 2023
e5e5470
Merge branch 'jenkinsci:master' into dev
fo-code Oct 6, 2023
5d3f660
Add modified lines API integration test
fo-code Oct 7, 2023
972649d
Coverage API test
fo-code Oct 7, 2023
617cf8e
Coverage API test
fo-code Oct 7, 2023
9b96984
Implemented coverage API test and added README
fo-code Oct 7, 2023
54389a9
Better implementation for calculating line blocks for REST API
fo-code Oct 13, 2023
0edbb21
§Improved calculation of modified line blocks with the same coverage …
fo-code Oct 14, 2023
04dfec4
Fix checkstyle
fo-code Oct 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,12 @@ Examples:

## Remote API

We provide a remote API to retrieve coverage data, using the following URL: `https://[jenkins-url]/job/[job-name]/[build-number]/coverage/api/json?pretty=true`.
We provide a remote API to retrieve a coverage overview, using the following URL: `https://[jenkins-url]/job/[job-name]/[build-number]/coverage/api/json?pretty=true`.

Example output:
```json
{
"_class" : "io.jenkins.plugins.coverage.metrics.steps.CoverageApi",
"_class" : "io.jenkins.plugins.coverage.metrics.restapi.CoverageApi",
"modifiedFilesDelta" : {
"branch" : "+1.72%",
"class" : "-3.54%",
Expand Down Expand Up @@ -278,3 +278,34 @@ Example output:
"referenceBuild" : "<a href=\"http://localhost:8080/job/coverage-model-history/10/\" class=\"model-link inside\">coverage-model-history #10</a>"
}
```

More concrete, the line coverage for modified code lines is provided per modified file, using the following URL: `https://[jenkins-url]/job/[job-name]/[build-number]/coverage/modified/api/json?pretty=true`.

Example output:
```json
{
"_class": "io.jenkins.plugins.coverage.metrics.restapi.ModifiedLinesCoverageApi",
"files": [
{
"fullyQualifiedFileName": "io/jenkins/plugins/coverage/metrics/restapi/ModifiedLinesCoverageApi.java",
"modifiedLinesBlocks": [
{
"startLine": 30,
"endLine": 35,
"type": "MISSED"
}
]
},
{
"fullyQualifiedFileName": "io/jenkins/plugins/coverage/metrics/restapi/ModifiedLinesBlocks.java",
"modifiedLinesBlocks": [
{
"startLine": 80,
"endLine": 81,
"type": "COVERED"
}
]
}
]
}
```
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.jenkins.plugins.coverage.metrics.steps;
package io.jenkins.plugins.coverage.metrics.restapi;

import java.util.Collection;
import java.util.Locale;
Expand Down Expand Up @@ -30,7 +30,17 @@ public class CoverageApi {
private final QualityGateResult qualityGateResult;
private final String referenceBuild;

CoverageApi(final CoverageStatistics statistics, final QualityGateResult qualityGateResult,
/**
* Creates a new instance of {@link CoverageApi}.
*
* @param statistics
* the coverage statistics of the build.
* @param qualityGateResult
* the quality gate result of the build.
* @param referenceBuild
* the build referenced for comparison purposes.
*/
public CoverageApi(final CoverageStatistics statistics, final QualityGateResult qualityGateResult,
final String referenceBuild) {
this.statistics = statistics;
this.qualityGateResult = qualityGateResult;
Expand Down Expand Up @@ -136,7 +146,10 @@ public Result getOverallResult() {

@Exported(inline = true)
public Collection<QualityGateItemApi> getResultItems() {
return qualityGateResult.getResultItems().stream().map(QualityGateItemApi::new).collect(Collectors.toList());
return qualityGateResult.getResultItems()
.stream()
.map(QualityGateItemApi::new)
.collect(Collectors.toList());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.jenkins.plugins.coverage.metrics.restapi;

import java.util.Objects;
import java.util.SortedSet;

import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

/**
* Model class that describes the {@link LineCoverageType coverage} for modified code lines of a file. The file is
* described by its fully qualified name.
*/
@ExportedBean
class FileWithModifiedLines {
private final String fullyQualifiedFileName;
private final SortedSet<ModifiedLinesBlock> modifiedLinesBlocks;

FileWithModifiedLines(final String fullyQualifiedFileName,
final SortedSet<ModifiedLinesBlock> modifiedLinesBlocks) {
this.fullyQualifiedFileName = fullyQualifiedFileName;
this.modifiedLinesBlocks = modifiedLinesBlocks;
}

@Exported(inline = true)
public String getFullyQualifiedFileName() {
return fullyQualifiedFileName;
}

@Exported(inline = true)
public SortedSet<ModifiedLinesBlock> getModifiedLinesBlocks() {
return modifiedLinesBlocks;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FileWithModifiedLines file = (FileWithModifiedLines) o;
return Objects.equals(this.getFullyQualifiedFileName(), file.getFullyQualifiedFileName())
&& Objects.equals(this.getModifiedLinesBlocks(), file.getModifiedLinesBlocks());
}

@Override
public int hashCode() {
return Objects.hash(getFullyQualifiedFileName(), getModifiedLinesBlocks());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.jenkins.plugins.coverage.metrics.restapi;

/**
* Defines the type of line coverage.
*/
enum LineCoverageType {
COVERED,
MISSED,
PARTIALLY_COVERED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.jenkins.plugins.coverage.metrics.restapi;

import java.util.Objects;

import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

/**
* Model class containing data pertaining to consecutive lines of modified code. Each object possesses a starting and
* ending line number and the type of coverage of the block. Each object is associated with a
* {@link FileWithModifiedLines} object. The class implements {@link Comparable} and is ordered by the start line. Empty
* lines and comments are also included in blocks.
*/
@ExportedBean
class ModifiedLinesBlock implements Comparable<ModifiedLinesBlock> {
private final int startLine;
private final int endLine;
private final LineCoverageType type;

ModifiedLinesBlock(final int startLine, final int endLine, final LineCoverageType type) {
this.startLine = startLine;
this.endLine = endLine;
this.type = type;
}

@Exported
public int getStartLine() {
return startLine;
}

@Exported
public int getEndLine() {
return endLine;
}

@Exported
public LineCoverageType getType() {
return type;
}

@Override
public int compareTo(final ModifiedLinesBlock other) {
return this.startLine - other.startLine;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ModifiedLinesBlock that = (ModifiedLinesBlock) o;
return getStartLine() == that.getStartLine() && getEndLine() == that.getEndLine()
&& getType() == that.getType();
}

@Override
public int hashCode() {
return Objects.hash(getStartLine(), getEndLine(), getType());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package io.jenkins.plugins.coverage.metrics.restapi;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import edu.hm.hafner.coverage.FileNode;
import edu.hm.hafner.coverage.Node;

import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

/**
* Remote API to list the details of modified line coverage results.
*/
@ExportedBean
class ModifiedLinesCoverageApi {
private final List<FileWithModifiedLines> filesWithModifiedLines;

ModifiedLinesCoverageApi(final Node node) {
this.filesWithModifiedLines = createListOfFilesWithModifiedLines(node);
}

@Exported(inline = true, name = "files")
public List<FileWithModifiedLines> getFilesWithModifiedLines() {
return filesWithModifiedLines;
}

/**
* Finds all files with modified lines of code in a passed {@link Node} object.
*
* @param node
* containing the file tree.
*
* @return a list of {@link FileWithModifiedLines} objects.
*/
private List<FileWithModifiedLines> createListOfFilesWithModifiedLines(final Node node) {
var result = new ArrayList<FileWithModifiedLines>();

for (FileNode fileNode : node.filterByModifiedLines().getAllFileNodes()) {
var modifiedLines = new ArrayList<>(fileNode.getModifiedLines());
var modifiedLinesWithoutCoverage = modifiedLines.stream()
.filter(line -> !fileNode.getLinesWithCoverage().contains(line))
.collect(Collectors.toList());

var missedLines = filterByModifiedLines(modifiedLines, fileNode.getMissedLines());
var partiallyCoveredLines =
filterByModifiedLines(modifiedLines, fileNode.getPartiallyCoveredLines().keySet());
var coveredLines = fileNode.getLinesWithCoverage().stream()
.filter(line -> fileNode.getMissedOfLine(line) == 0)
.filter(modifiedLines::contains)
.collect(Collectors.toList());

var modifiedLinesBlocks = new TreeSet<ModifiedLinesBlock>();

modifiedLinesBlocks.addAll(
calculateModifiedLineBlocks(coveredLines, modifiedLinesWithoutCoverage, LineCoverageType.COVERED));
modifiedLinesBlocks.addAll(
calculateModifiedLineBlocks(missedLines, modifiedLinesWithoutCoverage, LineCoverageType.MISSED));
modifiedLinesBlocks.addAll(calculateModifiedLineBlocks(partiallyCoveredLines, modifiedLinesWithoutCoverage,
LineCoverageType.PARTIALLY_COVERED));

var changedFile = new FileWithModifiedLines(fileNode.getRelativePath(), modifiedLinesBlocks);
result.add(changedFile);
}

return result;
}

/**
* Filters the given lines to only contain the given modified lines.
*
* @param modifiedLines
* the lines that have been modified
* @param lines
* the lines that should be filtered for modified lines only
*
* @return the filtered lines
*/
private List<Integer> filterByModifiedLines(final Collection<Integer> modifiedLines,
final Collection<Integer> lines) {
return lines.stream().filter(modifiedLines::contains).collect(Collectors.toList());
}

/**
* This method parses over the modified lines of a file and combines consecutive line numbers with the same coverage
* type into a {@link ModifiedLinesBlock} object. If there are empty lines without coverage information between
* lines with the same type, they will be included in the block. The result is sorted by the starting line number.
*
* @param modifiedLines
* list containing the integer numbers of modified lines.
* @param modifiedLinesWithoutCoverage
* lines that have been modified but have no coverage information
* @param type
* type of coverage pertaining to each line of code ({@link LineCoverageType#COVERED},
* {@link LineCoverageType#MISSED}, or {@link LineCoverageType#PARTIALLY_COVERED})
*
* @return the list of {@link ModifiedLinesBlock}
*/
private List<ModifiedLinesBlock> calculateModifiedLineBlocks(final List<Integer> modifiedLines,
final List<Integer> modifiedLinesWithoutCoverage, final LineCoverageType type) {
var modifiedLinesBlocks = new ArrayList<ModifiedLinesBlock>();
if (modifiedLines.isEmpty()) {
return modifiedLinesBlocks;
}

int start = modifiedLines.get(0);
int last = start;
if (modifiedLines.size() > 1) {
for (int line : modifiedLines.subList(1, modifiedLines.size())) {
if (line > last + 1
&& hasAnyLinesWithCoverageBetween(last, line, modifiedLinesWithoutCoverage)) {
var modifiedLinesBlock = new ModifiedLinesBlock(start, last, type);
modifiedLinesBlocks.add(modifiedLinesBlock);
start = line;
}
last = line;
}
}
var modifiedLinesBlock = new ModifiedLinesBlock(start, last, type);
modifiedLinesBlocks.add(modifiedLinesBlock);

return modifiedLinesBlocks;
}

/**
* Checks whether the range between the first and the second given line contains any lines with coverage
* information.
*
* @param start
* the first line number
* @param end
* the second line number (must be greater than the first)
* @param modifiedLinesWithoutCoverage
* the modified lines without coverage information
*
* @return {@code true} whether there are any lines within the given line range that contains coverage information,
* else {@code false}
*/
private boolean hasAnyLinesWithCoverageBetween(final int start, final int end,
final List<Integer> modifiedLinesWithoutCoverage) {
return IntStream.range(start + 1, end).anyMatch(line -> !modifiedLinesWithoutCoverage.contains(line));
}
}
Loading