Skip to content

Commit

Permalink
Changes probe result status meaning (#355)
Browse files Browse the repository at this point in the history
Co-authored-by: Antoine Neveux <[email protected]>
  • Loading branch information
alecharp and aneveux authored Oct 6, 2023
1 parent 88eb1ca commit 674a3e0
Show file tree
Hide file tree
Showing 90 changed files with 1,885 additions and 1,978 deletions.
10 changes: 5 additions & 5 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<parent>
<groupId>io.jenkins.pluginhealth.scoring</groupId>
<artifactId>plugin-health-scoring-parent</artifactId>
<version>2.8.1-SNAPSHOT</version>
<version>3.0.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>

Expand All @@ -52,6 +52,10 @@
<artifactId>org.eclipse.jgit</artifactId>
<version>6.7.0.202309050840-r</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
Expand Down Expand Up @@ -102,9 +106,5 @@
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public Map<String, ProbeResult> getDetails() {

public Plugin addDetails(ProbeResult newProbeResult) {
this.details.compute(newProbeResult.id(), (s, previousProbeResult) ->
newProbeResult.status() == ResultStatus.ERROR ?
newProbeResult.status() == ProbeResult.Status.ERROR ?
null :
Objects.equals(previousProbeResult, newProbeResult) ? previousProbeResult : newProbeResult
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,41 +27,49 @@
import java.time.ZonedDateTime;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonAlias;

/**
* Represents the result of one analyze performed by a {@link io.jenkins.pluginhealth.scoring.probes.Probe} implementation on a {@link Plugin}
*
* @param id represent the ID of the {@link io.jenkins.pluginhealth.scoring.probes.Probe}
* @param message represents a summary of the result
* @param status represents the state of the analyze performed
* @param id represent the ID of the {@link io.jenkins.pluginhealth.scoring.probes.Probe}
* @param message represents a summary of the result
* @param status represents the state of the performed analysis
* @param timestamp when the probe generated this result
* @param probeVersion the version of the probe which generated this result
*/
public record ProbeResult(String id, String message, ResultStatus status, ZonedDateTime timestamp) {
public ProbeResult(String id, String message, ResultStatus status) {
this(id, message, status, ZonedDateTime.now());
public record ProbeResult(String id, String message, Status status, ZonedDateTime timestamp, long probeVersion) {
public ProbeResult(String id, String message, Status status, long probeVersion) {
this(id, message, status, ZonedDateTime.now(), probeVersion);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProbeResult that = (ProbeResult) o;
return id.equals(that.id) && message.equals(that.message) && status == that.status;
return id.equals(that.id) && message.equals(that.message) && status == that.status && probeVersion == that.probeVersion;
}

@Override
public int hashCode() {
return Objects.hash(id, status);
}

public static ProbeResult success(String id, String message) {
return new ProbeResult(id, message, ResultStatus.SUCCESS);
public static ProbeResult success(String id, String message, long probeVersion) {
return new ProbeResult(id, message, Status.SUCCESS, probeVersion);
}

public static ProbeResult failure(String id, String message) {
return new ProbeResult(id, message, ResultStatus.FAILURE);
public static ProbeResult error(String id, String message, long probeVersion) {
return new ProbeResult(id, message, Status.ERROR, probeVersion);
}

public static ProbeResult error(String id, String message) {
return new ProbeResult(id, message, ResultStatus.ERROR);
/*
* To handle the probe status migration from 2.x.y to 3.x.y, let consider the FAILURE status as an ERROR to force
* the execution of the probe.
*/
public enum Status {
SUCCESS, @JsonAlias("FAILURE") ERROR
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ public long getValue() {

private void computeValue() {
var sum = details.stream()
.flatMapToDouble(res -> DoubleStream.of(res.value() * res.coefficient()))
.flatMapToDouble(res -> DoubleStream.of(res.value() * res.weight()))
.sum();
var coefficient = details.stream()
.flatMapToDouble(res -> DoubleStream.of(res.coefficient()))
.flatMapToDouble(res -> DoubleStream.of(res.weight()))
.sum();
this.value = Math.round(100 * (sum / coefficient));
this.value = Math.round((sum / coefficient));
}

public void addDetail(ScoreResult result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,19 @@
package io.jenkins.pluginhealth.scoring.model;

import java.util.Objects;
import java.util.Set;

public record ScoreResult(String key, float value, float coefficient) {
import com.fasterxml.jackson.annotation.JsonAlias;

/**
* @param key the identifier of the scoring implementation which produced this result
* @param value the score granted to the plugin by a specific scoring implementation, out of 100 (one hundred).
* @param weight the importance of the score facing the others
* @param componentsResults a list of {@link ScoringComponentResult} which explain the score
*/
public record ScoreResult(String key, int value, @JsonAlias("coefficient") float weight, Set<ScoringComponentResult> componentsResults) {
public ScoreResult {
if (value > 1 || coefficient > 1) {
if (weight > 1) {
throw new IllegalArgumentException("Value and Coefficient must be less or equal to 1.");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@

package io.jenkins.pluginhealth.scoring.model;

import java.util.List;

import io.jenkins.pluginhealth.scoring.scores.ScoringComponent;

/**
* Represents the state of the analyze performed by any {@link io.jenkins.pluginhealth.scoring.probes.Probe} implementation
* Describes the evaluation from a {@link ScoringComponent} on a specific plugin.
*
* @param score the score representing the points granted to the plugin, out of 100 (one hundred).
* @param weight the weight of the score
* @param reasons the list of string explaining the score granted to the plugin
*/
public enum ResultStatus {
SUCCESS, FAILURE, ERROR
public record ScoringComponentResult(int score, float weight, List<String> reasons) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,24 @@ public abstract class AbstractDependencyBotConfigurationProbe extends Probe {

@Override
protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
final Path scmRepository = context.getScmRepository();
if (context.getScmRepository().isEmpty()) {
return this.error("There is no local repository for plugin " + plugin.getName() + ".");
}
final Path scmRepository = context.getScmRepository().get();
final Path githubConfig = scmRepository.resolve(".github");
if (Files.notExists(githubConfig)) {
LOGGER.error("No GitHub configuration folder at {} ", key());
return ProbeResult.failure(key(), "No GitHub configuration folder found");
LOGGER.trace("No GitHub configuration folder at {} ", key());
return this.success("No GitHub configuration folder found.");
}

try (Stream<Path> paths = Files.find(githubConfig, 1, (path, $) ->
Files.isRegularFile(path) && path.getFileName().toString().startsWith(getBotName()))) {
return paths.findFirst()
.map(file -> ProbeResult.success(key(), String.format("%s is configured", getBotName())))
.orElseGet(() -> ProbeResult.failure(key(), String.format("%s is not configured", getBotName())));
.map(file -> this.success(String.format("%s is configured.", getBotName())))
.orElseGet(() -> this.success(String.format("%s is not configured.", getBotName())));
} catch (IOException ex) {
LOGGER.error("Could not browse the plugin folder during probe {}", key(), ex);
return ProbeResult.error(key(), "Could not browse the plugin folder");
return this.error("Could not browse the plugin folder");
}
}

Expand All @@ -74,9 +77,4 @@ protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
protected boolean isSourceCodeRelated() {
return true;
}

@Override
public String[] getProbeResultRequirement() {
return new String[] { SCMLinkValidationProbe.KEY, LastCommitDateProbe.KEY };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@ public abstract class AbstractGitHubWorkflowProbe extends Probe {

@Override
protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
final Path repository = context.getScmRepository();
if (context.getScmRepository().isEmpty()) {
return this.error("There is no local repository for plugin " + plugin.getName() + ".");
}
final Path repository = context.getScmRepository().get();
final Path workflowPath = repository.resolve(WORKFLOWS_DIRECTORY);

if (!Files.exists(workflowPath)) {
return ProbeResult.failure(key(), "Plugin has no GitHub Action configured");
return this.success("Plugin has no GitHub Action configured.");
}

try (Stream<Path> files = Files.find(workflowPath, 1, (path, $) -> Files.isRegularFile(path))) {
Expand All @@ -67,11 +70,11 @@ protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
.anyMatch(jobDefinition -> jobDefinition.startsWith(getWorkflowDefinition()));

return isWorkflowConfigured ?
ProbeResult.success(key(), getSuccessMessage()) :
ProbeResult.failure(key(), getFailureMessage());
this.success(getValidMessage()) :
this.success(getInvalidMessage());
} catch (IOException e) {
LOGGER.warn("Couldn't not read file for plugin {} during probe {}", plugin.getName(), key(), e);
return ProbeResult.error(key(), e.getMessage());
return this.error(e.getMessage());
}
}

Expand Down Expand Up @@ -111,14 +114,6 @@ private record WorkflowDefinition(Map<String, WorkflowJobDefinition> jobs) {
private record WorkflowJobDefinition(String uses) {
}

/**
* @return a String array of probes that should be executed before AbstractGitHubWorkflowProbe
*/
@Override
public String[] getProbeResultRequirement() {
return new String[] { SCMLinkValidationProbe.KEY, LastCommitDateProbe.KEY };
}

@Override
protected boolean isSourceCodeRelated() {
return true;
Expand All @@ -132,12 +127,14 @@ protected boolean isSourceCodeRelated() {
public abstract String getWorkflowDefinition();

/**
* @return a failure message
* @return a message used when the probe could browse the workflows configured on the repository,
* but not the one expected by {@link this#getWorkflowDefinition()}.
*/
public abstract String getFailureMessage();
public abstract String getInvalidMessage();

/**
* @return a success message
* @return a message used when the probe could find the expected workflow from {@link this#getWorkflowDefinition()}
* configured on the plugin repository.
*/
public abstract String getSuccessMessage();
public abstract String getValidMessage();
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
* This probe is looking for the Code Coverage records, using GitHub API for Checks, on the default branch of the plugin.
*/
@Component
@Order(CodeCoverageProbe.ORDER)
public class CodeCoverageProbe extends Probe {
private static final Logger LOGGER = LoggerFactory.getLogger(CodeCoverageProbe.class);
private static final int LINE_COVERAGE_THRESHOLD = 70;
private static final int BRANCH_COVERAGE_THRESHOLD = 60;

private static final String COVERAGE_TITLE_REGEXP =
"^Line(?: Coverage)?: (?<line>\\d{1,2}(?:\\.\\d{1,2})?)%(?: \\(.+\\))?. Branch(?: Coverage)?: (?<branch>\\d{1,2}(?:\\.\\d{1,2})?)%(?: \\(.+\\))?\\.?$";
Expand All @@ -60,14 +61,17 @@ protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
final io.jenkins.pluginhealth.scoring.model.updatecenter.Plugin ucPlugin =
context.getUpdateCenter().plugins().get(plugin.getName());
final String defaultBranch = ucPlugin.defaultBranch();
if (defaultBranch == null || defaultBranch.isBlank()) {
return this.error("No default branch configured for the plugin.");
}
try {
final Optional<String> repositoryName = context.getRepositoryName(plugin.getScm());
final Optional<String> repositoryName = context.getRepositoryName();
if (repositoryName.isPresent()) {
final GHRepository ghRepository = context.getGitHub().getRepository(repositoryName.get());
final List<GHCheckRun> ghCheckRuns =
ghRepository.getCheckRuns(defaultBranch, Map.of("check_name", "Code Coverage")).toList();
if (ghCheckRuns.size() == 0) {
return ProbeResult.error(key(), "Could not determine code coverage for plugin");
if (ghCheckRuns.isEmpty()) {
return this.success("Could not determine code coverage for the plugin.");
}

double overall_line_coverage = 100;
Expand All @@ -81,18 +85,14 @@ protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
overall_branch_coverage = Math.min(overall_branch_coverage, branch_coverage);
}
}
return overall_line_coverage >= LINE_COVERAGE_THRESHOLD && overall_branch_coverage >= BRANCH_COVERAGE_THRESHOLD ?
ProbeResult.success(key(), "Line coverage is above " + LINE_COVERAGE_THRESHOLD + "%. Branch coverage is above " + BRANCH_COVERAGE_THRESHOLD + "%.") :
ProbeResult.failure(key(),
"Line coverage is " + (overall_line_coverage < LINE_COVERAGE_THRESHOLD ? "below " : "above ") + LINE_COVERAGE_THRESHOLD + "%. " +
"Branch coverage is " + (overall_branch_coverage < BRANCH_COVERAGE_THRESHOLD ? "below " : "above ") + BRANCH_COVERAGE_THRESHOLD + "%."
);

return this.success("Line coverage: " + overall_line_coverage + "%. Branch coverage: " + overall_branch_coverage + "%.");
} else {
return ProbeResult.error(key(), "Cannot determine plugin repository");
return this.error("Cannot determine plugin repository.");
}
} catch (IOException e) {
LOGGER.warn("Could not get Coverage check for {}", plugin.getName(), e);
return ProbeResult.error(key(), "Could not get coverage check");
return this.error("Could not get coverage check");
}
}

Expand All @@ -112,12 +112,7 @@ protected boolean isSourceCodeRelated() {
}

@Override
public String[] getProbeResultRequirement() {
return new String[]{
SCMLinkValidationProbe.KEY,
JenkinsfileProbe.KEY,
UpdateCenterPluginPublicationProbe.KEY,
LastCommitDateProbe.KEY,
};
public long getVersion() {
return 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,17 @@ public String getWorkflowDefinition() {
}

@Override
public String getFailureMessage() {
return "Could not find JEP-229 workflow definition";
public String getInvalidMessage() {
return "Could not find JEP-229 workflow definition.";
}

@Override
public String getSuccessMessage() {
return "JEP-229 workflow definition found";
public String getValidMessage() {
return "JEP-229 workflow definition found.";
}

@Override
public long getVersion() {
return 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,20 @@ public class ContributingGuidelinesProbe extends Probe {

@Override
protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
final Path repository = context.getScmRepository();
if (context.getScmRepository().isEmpty()) {
return this.error("There is no local repository for plugin " + plugin.getName() + ".");
}

final Path repository = context.getScmRepository().get();
try (Stream<Path> paths = Files.find(repository, 2,
(file, basicFileAttributes) -> Files.isReadable(file)
&& ("CONTRIBUTING.md".equalsIgnoreCase(file.getFileName().toString())
|| "CONTRIBUTING.adoc".equalsIgnoreCase(file.getFileName().toString())))) {
return paths.findAny()
.map(file -> ProbeResult.success(key(), "Contributing guidelines found"))
.orElseGet(() -> ProbeResult.failure(key(), "No contributing guidelines found"));
.map(file -> this.success("Contributing guidelines found."))
.orElseGet(() -> this.success("No contributing guidelines found."));
} catch (IOException e) {
return ProbeResult.error(key(), e.getMessage());
return this.error(e.getMessage());
}
}

Expand All @@ -72,7 +76,7 @@ protected boolean isSourceCodeRelated() {
}

@Override
public String[] getProbeResultRequirement() {
return new String[]{SCMLinkValidationProbe.KEY, LastCommitDateProbe.KEY};
public long getVersion() {
return 1;
}
}
Loading

0 comments on commit 674a3e0

Please sign in to comment.