Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import edu.hm.hafner.analysis.IssuesInModifiedCodeMarker;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.util.FilteredLog;
import edu.hm.hafner.util.VisibleForTesting;

import java.nio.charset.Charset;
import java.util.List;
Expand Down Expand Up @@ -42,7 +43,7 @@
*
* @author Ullrich Hafner
*/
@SuppressWarnings({"checkstyle:ClassFanOutComplexity", "checkstyle:ClassDataAbstractionCoupling"})
@SuppressWarnings({"checkstyle:ClassFanOutComplexity", "checkstyle:ClassDataAbstractionCoupling", "PMD.CouplingBetweenObjects"})
class IssuesPublisher {
private final AnnotatedReport report;
private final Run<?, ?> run;
Expand Down Expand Up @@ -99,9 +100,10 @@
run.removeAction(existingAction.get());
}

var deltaReport = computeDelta(issues, selector);
var delta = computeDelta(issues, selector);
var deltaReport = delta.report();

var qualityGateResult = evaluateQualityGate(issues, deltaReport);
var qualityGateResult = evaluateQualityGate(issues, deltaReport, delta.isModifiedCodeAvailable());
reportHealth(issues);

issues.logInfo("Created analysis result for %d issues (found %d new issues, fixed %d issues)",
Expand Down Expand Up @@ -146,7 +148,7 @@
return issues.stream().filter(Issue::isPartOfModifiedCode).count();
}

private DeltaReport computeDelta(final Report issues, final ResultSelector selector) {
private DeltaResult computeDelta(final Report issues, final ResultSelector selector) {
var possibleReferenceBuild = findReferenceBuild(selector, issues);
if (possibleReferenceBuild.isPresent()) {
Run<?, ?> build = possibleReferenceBuild.get();
Expand All @@ -155,46 +157,69 @@

var deltaReport = new DeltaReport(issues, build, run.getNumber(), resultAction.getResult().getIssues());

markIssuesInModifiedFiles(build, issues, deltaReport);

return deltaReport;
return new DeltaResult(deltaReport, markIssuesInModifiedFiles(build, issues, deltaReport));
}
else {
return new DeltaReport(issues, run.getNumber());
return new DeltaResult(new DeltaReport(issues, run.getNumber()), true);
}
}

private void markIssuesInModifiedFiles(final Run<?, ?> referenceBuild, final Report issues, final DeltaReport deltaReport) {
if (issues.isNotEmpty()) {
report.logInfo("Detect all issues that are part of modified code");

var log = new FilteredLog("Errors while computing delta: ");
var delta = deltaCalculator.calculateDelta(run, referenceBuild, log);
issues.mergeLogMessages(log);

if (delta.isPresent()) {
var changes = delta.get().getFileChangesMap().values().stream()
.collect(Collectors.toMap(
FileChanges::getFileName,
FileChanges::getModifiedLines,
(left, right) -> {
left.addAll(right);
return left;
}));
var marker = new IssuesInModifiedCodeMarker();
marker.markIssuesInModifiedCode(issues, changes);
report.logInfo("Issues in modified code: %d (new: %d, outstanding: %d)",
count(deltaReport.getAllIssues()),
count(deltaReport.getNewIssues()),
count(deltaReport.getOutstandingIssues()));
}
else {
report.logInfo("No relevant modified code found");
}
}
else {
/**
* Marks all issues that are part of the modified code of this build. The modified code is determined by computing
* the SCM delta between this build and the specified reference build.
*
* @param referenceBuild
* the reference build to compare with
* @param issues
* the issues to mark
* @param deltaReport
* the delta report that provides the new and outstanding issues
*
* @return {@code true} if the modified code of this build is known, {@code false} if the SCM delta could not be
* computed so that the modified code is unknown
*/
@VisibleForTesting
boolean markIssuesInModifiedFiles(final Run<?, ?> referenceBuild, final Report issues, final DeltaReport deltaReport) {
if (issues.isEmpty()) {
report.logInfo("Skip detection of issues in modified code");

return true;
}

report.logInfo("Detect all issues that are part of modified code");

var log = new FilteredLog("Errors while computing delta: ");
var delta = deltaCalculator.calculateDelta(run, referenceBuild, log);
issues.mergeLogMessages(log);

if (delta.isPresent()) {
var changes = delta.get().getFileChangesMap().values().stream()
.collect(Collectors.toMap(
FileChanges::getFileName,
FileChanges::getModifiedLines,
(left, right) -> {
left.addAll(right);
return left;

Check warning on line 202 in plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java

View workflow job for this annotation

GitHub Actions / Quality Monitor

Not covered lines

Lines 201-202 are not covered by tests

Check warning on line 202 in plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java

View workflow job for this annotation

GitHub Actions / Quality Monitor

Not covered lines

Lines 201-202 are not covered by tests
}));
var marker = new IssuesInModifiedCodeMarker();
marker.markIssuesInModifiedCode(issues, changes);
report.logInfo("Issues in modified code: %d (new: %d, outstanding: %d)",
count(deltaReport.getAllIssues()),
count(deltaReport.getNewIssues()),
count(deltaReport.getOutstandingIssues()));

return true;
}

if (log.hasErrors()) {
report.logInfo("Skipping detection of issues in modified code since the SCM delta is not available");

return false;
}

report.logInfo("No relevant modified code found");

return true;
}

private void reportHealth(final Report filtered) {
Expand All @@ -211,8 +236,10 @@
}
}

private QualityGateResult evaluateQualityGate(final Report issues, final DeltaReport deltaReport) {
var evaluator = new WarningsQualityGateEvaluator(qualityGates, deltaReport.getStatistics());
private QualityGateResult evaluateQualityGate(final Report issues, final DeltaReport deltaReport,
final boolean isModifiedCodeAvailable) {
var evaluator = new WarningsQualityGateEvaluator(qualityGates, deltaReport.getStatistics(),
isModifiedCodeAvailable);
var log = new FilteredLog("Errors while evaluating quality gates:");
var qualityGateStatus = evaluator.evaluate(notifier, log);
issues.mergeLogMessages(log);
Expand Down Expand Up @@ -301,4 +328,15 @@
private QualityGateEvaluationMode determineQualityGateEvaluationMode() {
return qualityGateEvaluationMode;
}

/**
* The delta of this build with respect to the reference build.
*
* @param report
* the delta report that contains the new, outstanding, and fixed issues
* @param isModifiedCodeAvailable
* determines whether the modified code of this build is known
*/
private record DeltaResult(DeltaReport report, boolean isModifiedCodeAvailable) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -891,9 +891,11 @@ AnalysisResult publishResult(final Run<?, ?> run, final FilePath workspace, fina
logHandler.logInfoMessages(report.getInfoMessages());
logHandler.logErrorMessages(report.getErrorMessages());

var deltaLog = new FilteredLog("Errors while creating the SCM delta calculator:");
var deltaCalculator = isSkipDeltaCalculation()
? new NullDeltaCalculator()
: DeltaCalculatorFactory.findDeltaCalculator(scm, run, workspace, listener, new FilteredLog());
: DeltaCalculatorFactory.findDeltaCalculator(scm, run, workspace, listener, deltaLog);
report.mergeLogMessages(deltaLog);

var publisher = new IssuesPublisher(run, annotatedReport, deltaCalculator,
new HealthDescriptor(healthy, unhealthy, minimumSeverity), qualityGates,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,12 +470,15 @@ protected ResultAction run() throws IOException, InterruptedException, IllegalSt
}
report.addAll(step.reports);

var logHandler = getLogger(report);

var workspace = getContext().get(FilePath.class);
var deltaLog = new FilteredLog("Errors while creating the SCM delta calculator:");
var deltaCalculator = workspace == null || step.isSkipDeltaCalculation()
? new DeltaCalculator.NullDeltaCalculator()
: DeltaCalculatorFactory.findDeltaCalculator(step.scm, getRun(), workspace, getTaskListener(), new FilteredLog());
: DeltaCalculatorFactory.findDeltaCalculator(step.scm, getRun(), workspace, getTaskListener(), deltaLog);
report.getReport().mergeLogMessages(deltaLog);

var logHandler = getLogger(report);
var publisher = new IssuesPublisher(getRun(), report,
deltaCalculator, new HealthDescriptor(step.getHealthy(), step.getUnhealthy(),
step.getMinimumSeverityAsSeverity()), step.getQualityGates(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,14 @@ public enum QualityGateType {
TOTAL_HIGH(StatisticProperties.TOTAL_HIGH),
TOTAL_NORMAL(StatisticProperties.TOTAL_NORMAL),
TOTAL_LOW(StatisticProperties.TOTAL_LOW),
TOTAL_MODIFIED(StatisticProperties.TOTAL_MODIFIED),
TOTAL_MODIFIED(StatisticProperties.TOTAL_MODIFIED, true),

NEW(StatisticProperties.NEW),
NEW_ERROR(StatisticProperties.NEW_ERROR),
NEW_HIGH(StatisticProperties.NEW_HIGH),
NEW_NORMAL(StatisticProperties.NEW_NORMAL),
NEW_LOW(StatisticProperties.NEW_LOW),
NEW_MODIFIED(StatisticProperties.NEW_MODIFIED),
NEW_MODIFIED(StatisticProperties.NEW_MODIFIED, true),

DELTA(StatisticProperties.DELTA),
DELTA_ERROR(StatisticProperties.DELTA_ERROR),
Expand All @@ -150,9 +150,15 @@ public enum QualityGateType {
DELTA_LOW(StatisticProperties.DELTA_LOW);

private final StatisticProperties properties;
private final boolean basedOnModifiedCode;

QualityGateType(final StatisticProperties statisticProperties) {
this(statisticProperties, false);
}

QualityGateType(final StatisticProperties statisticProperties, final boolean basedOnModifiedCode) {
properties = statisticProperties;
this.basedOnModifiedCode = basedOnModifiedCode;
}

/**
Expand All @@ -164,6 +170,16 @@ public String getDisplayName() {
return properties.getDisplayName();
}

/**
* Returns whether this type counts issues in modified code only. Such quality gates can be evaluated only if
* the code delta between the current build and the reference build has been computed successfully.
*
* @return {@code true} if this type requires the SCM code delta, {@code false} otherwise
*/
public boolean isBasedOnModifiedCode() {
return basedOnModifiedCode;
}

/**
* Returns the method that should be used to determine the actual number of issues in the build.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
* @author Johannes Walter
*/
public class WarningsQualityGateEvaluator extends QualityGateEvaluator<WarningsQualityGate> {
static final String MODIFIED_CODE_UNKNOWN_MESSAGE
= "Modified code is unknown: the SCM code delta could not be computed";

private final IssuesStatistics statistics;
private final boolean isModifiedCodeAvailable;

/**
* Creates a new instance of {@link WarningsQualityGateEvaluator}.
Expand All @@ -24,14 +28,36 @@ public class WarningsQualityGateEvaluator extends QualityGateEvaluator<WarningsQ
*/
public WarningsQualityGateEvaluator(final Collection<? extends WarningsQualityGate> qualityGates,
final IssuesStatistics statistics) {
this(qualityGates, statistics, true);
}

/**
* Creates a new instance of {@link WarningsQualityGateEvaluator}.
*
* @param qualityGates
* the quality gates to evaluate
* @param statistics
* the statistics to evaluate
* @param isModifiedCodeAvailable
* determines whether the modified code of the current build is known, i.e., whether the SCM code delta
* between the current build and the reference build has been computed successfully. If the code delta is
* not available, then all quality gates that are based on the modified code will be skipped rather than
* silently evaluated against a size of zero.
*/
public WarningsQualityGateEvaluator(final Collection<? extends WarningsQualityGate> qualityGates,
final IssuesStatistics statistics, final boolean isModifiedCodeAvailable) {
super(qualityGates);

this.statistics = statistics;
this.isModifiedCodeAvailable = isModifiedCodeAvailable;
}

@Override
protected void evaluate(final WarningsQualityGate qualityGate, final QualityGateResult result) {
if (qualityGate.getThreshold() > 0) {
if (!isModifiedCodeAvailable && qualityGate.getType().isBasedOnModifiedCode()) {
result.add(qualityGate, QualityGateStatus.INACTIVE, MODIFIED_CODE_UNKNOWN_MESSAGE);
}
else if (qualityGate.getThreshold() > 0) {
int actualSize = qualityGate.getActualSizeMethodReference().apply(statistics);
var actualValue = String.valueOf(actualSize);
if (actualSize >= qualityGate.getThreshold()) {
Expand Down
Loading
Loading