From 8c97e58405017ef7961c66902b1314378f20f374 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 30 Dec 2023 00:19:33 +0100 Subject: [PATCH 01/27] [JENKINS-72059] Add new quality gate options to alter the stage only. --- plugin/pom.xml | 5 +- .../analysis/core/model/AnalysisHistory.java | 8 +- .../analysis/core/model/AnalysisResult.java | 62 ++- .../core/model/ResetQualityGateCommand.java | 14 +- .../core/model/ResetReferenceAction.java | 8 +- .../analysis/core/model/ResultAction.java | 5 +- .../analysis/core/model/SummaryModel.java | 4 +- .../core/restapi/AnalysisResultApi.java | 6 +- .../analysis/core/steps/IssuesPublisher.java | 54 +- .../analysis/core/steps/IssuesRecorder.java | 56 +- .../core/steps/PublishIssuesStep.java | 458 +--------------- .../analysis/core/steps/RecordIssuesStep.java | 511 +----------------- .../core/steps/WarningChecksPublisher.java | 6 +- .../core/util/QualityGateEvaluator.java | 110 ---- .../analysis/core/util/QualityGateStatus.java | 6 +- .../analysis/core/util/StaticAnalysisRun.java | 14 +- ...lityGate.java => WarningsQualityGate.java} | 128 +---- .../util/WarningsQualityGateEvaluator.java | 40 ++ .../core/model/AnalysisHistoryTest.java | 101 ++-- .../core/model/AnalysisResultTest.java | 4 +- .../model/ResetQualityGateCommandTest.java | 8 +- .../analysis/core/model/SummaryModelTest.java | 11 +- .../steps/WarningChecksPublisherITest.java | 42 +- .../core/util/QualityGateEvaluatorTest.java | 219 ++++---- .../analysis/core/util/QualityGateTest.java | 19 +- .../integrations/FlexiblePublishITest.java | 13 +- .../analysis/warnings/steps/DryITest.java | 4 +- .../warnings/steps/FilesScannerITest.java | 17 +- .../steps/MiscIssuesRecorderITest.java | 6 +- .../warnings/steps/QualityGateITest.java | 88 +-- .../warnings/steps/ReferenceFinderITest.java | 103 ++-- 31 files changed, 574 insertions(+), 1556 deletions(-) delete mode 100644 plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateEvaluator.java rename plugin/src/main/java/io/jenkins/plugins/analysis/core/util/{QualityGate.java => WarningsQualityGate.java} (59%) create mode 100644 plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGateEvaluator.java diff --git a/plugin/pom.xml b/plugin/pom.xml index d59e6b1092..cbf1db7ece 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -21,7 +21,7 @@ https://github.com/jenkinsci/warnings-ng-plugin - 10.6.0 + 11.0.0 -SNAPSHOT ${project.groupId}.warnings.ng @@ -51,6 +51,7 @@ -Djava.awt.headless=true -Xmx1024m -Djenkins.test.timeout=1000 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED 1.29.0-8 + 3.9.0-rc847.81820ecdfde2 @@ -102,6 +103,7 @@ io.jenkins.plugins plugin-util-api + ${plugin-util-api.version} io.jenkins.plugins @@ -314,6 +316,7 @@ io.jenkins.plugins plugin-util-api + ${plugin-util-api.version} test tests diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisHistory.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisHistory.java index 11b0d0db76..0b54973b06 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisHistory.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisHistory.java @@ -14,8 +14,6 @@ import io.jenkins.plugins.analysis.core.charts.JenkinsBuild; import io.jenkins.plugins.analysis.core.util.AnalysisBuildResult; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.model.AnalysisHistory.JobResultEvaluationMode.*; import static io.jenkins.plugins.analysis.core.model.AnalysisHistory.QualityGateEvaluationMode.*; @@ -44,7 +42,7 @@ public class AnalysisHistory implements History { private final JobResultEvaluationMode jobResultEvaluationMode; /** - * Determines how the evaluation of the {@link QualityGateEvaluator} is taken into account when the previous result is + * Determines how the evaluation of the quality gates is taken into account when the previous result is * searched for. */ public enum QualityGateEvaluationMode { @@ -53,7 +51,7 @@ public enum QualityGateEvaluationMode { */ IGNORE_QUALITY_GATE, /** - * The quality gate result must be {@link QualityGateStatus#isSuccessful()}. I.e. the history is searched for a + * The quality gate result must be successful. I.e., the history is searched for a * build that either passed the quality gate or has deactivated the quality gate. */ SUCCESSFUL_QUALITY_GATE @@ -78,7 +76,7 @@ public enum JobResultEvaluationMode { } /** - * Creates a new instance of {@link AnalysisHistory}. This history ignores the {@link QualityGateStatus} of the + * Creates a new instance of {@link AnalysisHistory}. This history ignores the results of the * quality gate and the {@link Result} of the associated {@link Run}. * * @param baseline diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java index 9a958cfb78..4c451771c2 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java @@ -32,14 +32,17 @@ import io.jenkins.plugins.analysis.core.charts.JenkinsBuild; import io.jenkins.plugins.analysis.core.util.IssuesStatistics; import io.jenkins.plugins.analysis.core.util.IssuesStatisticsBuilder; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.analysis.core.util.StaticAnalysisRun; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; import io.jenkins.plugins.forensics.blame.Blames; import io.jenkins.plugins.forensics.blame.BlamesXmlStream; import io.jenkins.plugins.forensics.miner.RepositoryStatistics; import io.jenkins.plugins.forensics.miner.RepositoryStatisticsXmlStream; import io.jenkins.plugins.util.JenkinsFacade; +import io.jenkins.plugins.util.QualityGateEvaluator; +import io.jenkins.plugins.util.QualityGateResult; +import io.jenkins.plugins.util.QualityGateStatus; import io.jenkins.plugins.util.ValidationUtilities; /** @@ -74,18 +77,18 @@ public class AnalysisResult implements Serializable, StaticAnalysisRun { private transient Run owner; /** - * All outstanding issues: i.e. all issues, that are part of the current and reference report. + * All outstanding issues: i.e., all issues, that are part of the current and reference report. */ @CheckForNull private transient WeakReference outstandingIssuesReference; /** - * All new issues: i.e. all issues, that are part of the current report but have not been shown up in the reference + * All new issues: i.e., all issues, that are part of the current report but have not been shown up in the reference * report. */ @CheckForNull private transient WeakReference newIssuesReference; /** - * All fixed issues: i.e. all issues, that are part of the reference report but are not present in the current + * All fixed issues: i.e., all issues, that are part of the reference report but are not present in the current * report anymore. */ @CheckForNull @@ -104,13 +107,15 @@ public class AnalysisResult implements Serializable, StaticAnalysisRun { /** Determines since which build the result is successful. */ private int successfulSinceBuild; /** The result of the quality gate evaluation. */ - private final QualityGateStatus qualityGateStatus; + private transient QualityGateStatus qualityGateStatus; + /** The result of the quality gate evaluation. */ + private QualityGateResult qualityGateResult; /** * Creates a new instance of {@link AnalysisResult}. * * @param owner - * the current build as owner of this action + * the current build as the owner of this action * @param id * ID of the results * @param report @@ -119,7 +124,7 @@ public class AnalysisResult implements Serializable, StaticAnalysisRun { * author and commit information for all issues * @param totals * repository statistics for all issues - * @param qualityGateStatus + * @param qualityGateResult * the quality gate status * @param sizePerOrigin * the number of issues per origin @@ -128,10 +133,9 @@ public class AnalysisResult implements Serializable, StaticAnalysisRun { */ @SuppressWarnings("checkstyle:ParameterNumber") public AnalysisResult(final Run owner, final String id, final DeltaReport report, final Blames blames, - final RepositoryStatistics totals, final QualityGateStatus qualityGateStatus, - final Map sizePerOrigin, - final AnalysisResult previousResult) { - this(owner, id, report, blames, totals, qualityGateStatus, sizePerOrigin, true); + final RepositoryStatistics totals, final QualityGateResult qualityGateResult, + final Map sizePerOrigin, final AnalysisResult previousResult) { + this(owner, id, report, blames, totals, qualityGateResult, sizePerOrigin, true); if (report.isEmpty()) { if (previousResult.noIssuesSinceBuild == NO_BUILD) { @@ -145,8 +149,9 @@ public AnalysisResult(final Run owner, final String id, final DeltaReport noIssuesSinceBuild = NO_BUILD; } - if (this.qualityGateStatus == QualityGateStatus.PASSED) { - if (previousResult.qualityGateStatus == QualityGateStatus.PASSED) { + var overallStatus = qualityGateResult.getOverallStatus(); + if (overallStatus == QualityGateStatus.PASSED) { + if (previousResult.getQualityGateResult().getOverallStatus() == QualityGateStatus.PASSED) { successfulSinceBuild = previousResult.successfulSinceBuild; } else { @@ -162,7 +167,7 @@ public AnalysisResult(final Run owner, final String id, final DeltaReport * Creates a new instance of {@link AnalysisResult}. * * @param owner - * the current build as owner of this action + * the current build as the owner of this action * @param id * ID of the results * @param report @@ -171,15 +176,15 @@ public AnalysisResult(final Run owner, final String id, final DeltaReport * author and commit information for all issues * @param totals * repository statistics for all issues - * @param qualityGateStatus + * @param qualityGateResult * the quality gate status * @param sizePerOrigin * the number of issues per origin */ public AnalysisResult(final Run owner, final String id, final DeltaReport report, final Blames blames, - final RepositoryStatistics totals, final QualityGateStatus qualityGateStatus, + final RepositoryStatistics totals, final QualityGateResult qualityGateResult, final Map sizePerOrigin) { - this(owner, id, report, blames, totals, qualityGateStatus, sizePerOrigin, true); + this(owner, id, report, blames, totals, qualityGateResult, sizePerOrigin, true); if (report.isEmpty()) { noIssuesSinceBuild = owner.getNumber(); @@ -187,7 +192,7 @@ public AnalysisResult(final Run owner, final String id, final DeltaReport else { noIssuesSinceBuild = NO_BUILD; } - if (this.qualityGateStatus == QualityGateStatus.PASSED) { + if (qualityGateResult.getOverallStatus() == QualityGateStatus.PASSED) { successfulSinceBuild = owner.getNumber(); } else { @@ -199,7 +204,7 @@ public AnalysisResult(final Run owner, final String id, final DeltaReport * Creates a new instance of {@link AnalysisResult}. * * @param owner - * the current run as owner of this action + * the current run as the owner of this action * @param id * ID of the results * @param report @@ -208,7 +213,7 @@ public AnalysisResult(final Run owner, final String id, final DeltaReport * author and commit information for all issues * @param repositoryStatistics * source code repository statistics for all issues - * @param qualityGateStatus + * @param qualityGateResult * the quality gate status * @param sizePerOrigin * the number of issues per origin @@ -219,7 +224,7 @@ public AnalysisResult(final Run owner, final String id, final DeltaReport @SuppressWarnings("checkstyle:ParameterNumber") protected AnalysisResult(final Run owner, final String id, final DeltaReport report, final Blames blames, final RepositoryStatistics repositoryStatistics, - final QualityGateStatus qualityGateStatus, final Map sizePerOrigin, + final QualityGateResult qualityGateResult, final Map sizePerOrigin, final boolean canSerialize) { this.owner = owner; @@ -245,7 +250,7 @@ protected AnalysisResult(final Run owner, final String id, final DeltaRepo messages = new ArrayList<>(aggregatedMessages); errors = new ArrayList<>(allIssues.getErrorMessages()); - this.qualityGateStatus = qualityGateStatus; + this.qualityGateResult = qualityGateResult; blamesReference = new WeakReference<>(blames); this.repositoryStatistics = new WeakReference<>(repositoryStatistics); @@ -279,6 +284,10 @@ protected Object readResolve() { builder.setFixedSize(fixedSize); totals = builder.build(); } + if (qualityGateResult == null && qualityGateStatus != null) { + qualityGateResult = new QualityGateResult(); + qualityGateResult.add(new WarningsQualityGate(0, QualityGateType.TOTAL), qualityGateStatus, "n/a"); + } return this; } @@ -545,12 +554,17 @@ public boolean isSuccessful() { return qualityGateStatus.isSuccessful(); } - @Whitelisted @Override public QualityGateStatus getQualityGateStatus() { return qualityGateStatus; } + @Whitelisted + @Override + public QualityGateResult getQualityGateResult() { + return qualityGateResult; + } + @Override public String toString() { return getId() + " : " + getTotalSize() + " issues"; diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResetQualityGateCommand.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResetQualityGateCommand.java index 3c4c44221a..338f3d4b00 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResetQualityGateCommand.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResetQualityGateCommand.java @@ -2,14 +2,12 @@ import java.io.IOException; import java.util.List; -import java.util.Optional; import edu.hm.hafner.util.VisibleForTesting; import hudson.model.Item; import hudson.model.Run; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.util.JenkinsFacade; /** @@ -93,15 +91,11 @@ public boolean isEnabled(final Run selectedBuild, final String id) { return false; } - Optional resultAction = selectedBuild.getActions(ResultAction.class) + return selectedBuild.getActions(ResultAction.class) .stream() .filter(action -> action.getId().equals(id)) - .findAny(); - if (resultAction.isEmpty()) { - return false; - } - - QualityGateStatus status = resultAction.get().getResult().getQualityGateStatus(); - return status == QualityGateStatus.FAILED || status == QualityGateStatus.WARNING; + .findAny() + .filter(action -> !action.getResult().getQualityGateResult().isSuccessful()) + .isPresent(); } } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResetReferenceAction.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResetReferenceAction.java index 3731d0def9..75e3703ae0 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResetReferenceAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResetReferenceAction.java @@ -4,15 +4,11 @@ import hudson.model.Action; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator; - /** - * Marker for a build to indicate that this build should serve as new reference build for the {@link - * QualityGateEvaluator} evaluation of the next build. This marker helps to reset the reference build computation in - * order to restart the new issue computation. + * Marker for a build to indicate that this build should serve as a new reference build for the quality gate evaluation + * of the next build. This marker helps to reset the reference build computation to restart the new issue computation. * * @author Ullrich Hafner - * @see QualityGateEvaluator */ public class ResetReferenceAction implements Action { private final String id; diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java index 435b96403d..b1aa9c6217 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ResultAction.java @@ -22,7 +22,6 @@ import jenkins.tasks.SimpleBuildStep.LastBuildAction; import io.jenkins.plugins.analysis.core.util.HealthDescriptor; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator; import io.jenkins.plugins.analysis.core.util.TrendChartType; import io.jenkins.plugins.util.JenkinsFacade; @@ -288,12 +287,10 @@ public SummaryModel getSummaryModel() { } /** - * Returns whether the static analysis result is considered successfully with respect to the used {@link - * QualityGateEvaluator}. + * Returns whether the static analysis result is considered successfully with respect to the evaluated quality gates. * * @return {@code true} if the result is successful, {@code false} if the result has been set to {@link * Result#UNSTABLE} or {@link Result#FAILURE}. - * @see QualityGateEvaluator */ @Whitelisted public boolean isSuccessful() { diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SummaryModel.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SummaryModel.java index b4b6d67e46..74de08d591 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SummaryModel.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SummaryModel.java @@ -10,7 +10,7 @@ import hudson.model.Run; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; +import io.jenkins.plugins.util.QualityGateStatus; /** * Summary message of a static analysis run. This message is shown as part of the 'summary.jelly' information of the @@ -132,7 +132,7 @@ public int totalSize(final String origin) { } public QualityGateStatus getQualityGateStatus() { - return analysisResult.getQualityGateStatus(); + return analysisResult.getQualityGateResult().getOverallStatus(); } public boolean isResetQualityGateVisible() { diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/AnalysisResultApi.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/AnalysisResultApi.java index 75768b6ace..eac4ffbd76 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/AnalysisResultApi.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/AnalysisResultApi.java @@ -8,8 +8,8 @@ import org.kohsuke.stapler.export.ExportedBean; import hudson.model.Run; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.analysis.core.util.StaticAnalysisRun; +import io.jenkins.plugins.util.QualityGateResult; /** * Remote API for the {@link StaticAnalysisRun}. Simple Java Bean that exposes several methods of an {@link @@ -57,8 +57,8 @@ public int getSuccessfulSinceBuild() { } @Exported - public QualityGateStatus getQualityGateStatus() { - return result.getQualityGateStatus(); + public QualityGateResult getQualityGateResult() { + return result.getQualityGateResult(); } @Exported diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java index 171c8d8861..3c64b25361 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java @@ -24,13 +24,14 @@ import io.jenkins.plugins.analysis.core.model.ResultAction; import io.jenkins.plugins.analysis.core.model.ResultSelector; import io.jenkins.plugins.analysis.core.util.HealthDescriptor; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.analysis.core.util.TrendChartType; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGateEvaluator; import io.jenkins.plugins.forensics.reference.ReferenceFinder; import io.jenkins.plugins.util.JenkinsFacade; import io.jenkins.plugins.util.LogHandler; -import io.jenkins.plugins.util.StageResultHandler; +import io.jenkins.plugins.util.QualityGateResult; +import io.jenkins.plugins.util.ResultHandler; import static io.jenkins.plugins.analysis.core.model.AnalysisHistory.JobResultEvaluationMode.*; import static io.jenkins.plugins.analysis.core.model.AnalysisHistory.QualityGateEvaluationMode.*; @@ -48,35 +49,34 @@ class IssuesPublisher { private final HealthDescriptor healthDescriptor; private final String name; private final Charset sourceCodeEncoding; - private final QualityGateEvaluator qualityGate; + private final List qualityGates; private final String referenceJobName; private final String referenceBuildId; private final QualityGateEvaluationMode qualityGateEvaluationMode; private final JobResultEvaluationMode jobResultEvaluationMode; private final LogHandler logger; - private final StageResultHandler stageResultHandler; + private final ResultHandler notifier; private final boolean failOnErrors; @SuppressWarnings("ParameterNumber") IssuesPublisher(final Run run, final AnnotatedReport report, - final HealthDescriptor healthDescriptor, final QualityGateEvaluator qualityGate, + final HealthDescriptor healthDescriptor, final List qualityGates, final String name, final String referenceJobName, final String referenceBuildId, final boolean ignoreQualityGate, final boolean ignoreFailedBuilds, final Charset sourceCodeEncoding, final LogHandler logger, - final StageResultHandler stageResultHandler, final boolean failOnErrors) { - + final ResultHandler notifier, final boolean failOnErrors) { this.report = report; this.run = run; this.healthDescriptor = healthDescriptor; this.name = name; this.sourceCodeEncoding = sourceCodeEncoding; - this.qualityGate = qualityGate; + this.qualityGates = qualityGates; this.referenceJobName = referenceJobName; this.referenceBuildId = referenceBuildId; qualityGateEvaluationMode = ignoreQualityGate ? IGNORE_QUALITY_GATE : SUCCESSFUL_QUALITY_GATE; jobResultEvaluationMode = ignoreFailedBuilds ? NO_JOB_FAILURE : IGNORE_JOB_RESULT; this.logger = logger; - this.stageResultHandler = stageResultHandler; + this.notifier = notifier; this.failOnErrors = failOnErrors; } @@ -98,7 +98,7 @@ ResultAction attachAction(final TrendChartType trendChartType) { Report issues = report.getReport(); DeltaReport deltaReport = new DeltaReport(issues, createAnalysisHistory(selector, issues), run.getNumber()); - QualityGateStatus qualityGateStatus = evaluateQualityGate(issues, deltaReport); + QualityGateResult qualityGateResult = evaluateQualityGate(issues, deltaReport); reportHealth(issues); issues.logInfo("Created analysis result for %d issues (found %d new issues, fixed %d issues)", @@ -107,8 +107,7 @@ ResultAction attachAction(final TrendChartType trendChartType) { if (failOnErrors && issues.hasErrors()) { issues.logInfo("Failing build because analysis result contains errors"); - stageResultHandler.setResult(Result.FAILURE, - "Some errors have been logged during recording of issues"); + run.setResult(Result.FAILURE); } if (trendChartType == TrendChartType.AGGREGATION_TOOLS) { @@ -124,10 +123,10 @@ ResultAction attachAction(final TrendChartType trendChartType) { AnalysisResult result = new AnalysisHistory(run, selector).getResult() .map(previous -> new AnalysisResult(run, getId(), deltaReport, report.getBlames(), - report.getStatistics(), qualityGateStatus, report.getSizeOfOrigin(), + report.getStatistics(), qualityGateResult, report.getSizeOfOrigin(), previous)) .orElseGet(() -> new AnalysisResult(run, getId(), deltaReport, report.getBlames(), - report.getStatistics(), qualityGateStatus, report.getSizeOfOrigin())); + report.getStatistics(), qualityGateResult, report.getSizeOfOrigin())); ResultAction action = new ResultAction(run, result, healthDescriptor, getId(), name, sourceCodeEncoding, trendChartType); run.addAction(action); @@ -163,26 +162,11 @@ private void reportHealth(final Report filtered) { } } - private QualityGateStatus evaluateQualityGate(final Report issues, final DeltaReport deltaReport) { - QualityGateStatus qualityGateStatus; - if (qualityGate.isEnabled()) { - issues.logInfo("Evaluating quality gates"); - qualityGateStatus = qualityGate.evaluate(deltaReport.getStatistics(), issues::logInfo); - if (qualityGateStatus.isSuccessful()) { - issues.logInfo("-> All quality gates have been passed"); - } - else { - issues.logInfo("-> Some quality gates have been missed: overall result is %s", qualityGateStatus); - } - if (!qualityGateStatus.isSuccessful()) { - stageResultHandler.setResult(qualityGateStatus.getResult(), - "Some quality gates have been missed: overall result is " + qualityGateStatus.getResult()); - } - } - else { - issues.logInfo("No quality gates have been set - skipping"); - qualityGateStatus = QualityGateStatus.INACTIVE; - } + private QualityGateResult evaluateQualityGate(final Report issues, final DeltaReport deltaReport) { + var evaluator = new WarningsQualityGateEvaluator(qualityGates, deltaReport.getStatistics()); + var log = new FilteredLog("Errors while evaluating quality gates:"); + var qualityGateStatus = evaluator.evaluate(notifier, log); + issues.mergeLogMessages(log); return qualityGateStatus; } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java index 062975832f..346afaa30f 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java @@ -54,15 +54,13 @@ import io.jenkins.plugins.analysis.core.steps.WarningChecksPublisher.AnnotationScope; import io.jenkins.plugins.analysis.core.util.HealthDescriptor; import io.jenkins.plugins.analysis.core.util.ModelValidation; -import io.jenkins.plugins.analysis.core.util.QualityGate; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator; import io.jenkins.plugins.analysis.core.util.TrendChartType; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; import io.jenkins.plugins.checks.steps.ChecksInfo; import io.jenkins.plugins.prism.SourceCodeDirectory; import io.jenkins.plugins.util.JenkinsFacade; import io.jenkins.plugins.util.LogHandler; +import io.jenkins.plugins.util.ResultHandler; import io.jenkins.plugins.util.RunResultHandler; import io.jenkins.plugins.util.StageResultHandler; import io.jenkins.plugins.util.ValidationUtilities; @@ -75,7 +73,7 @@ * Additional features: *

*
    - *
  • It provides a {@link QualityGateEvaluator} that is checked after each run. If the quality gate is not passed, + *
  • It evaluates the quality gates after each run. If the quality gate is not passed, * then the build will be set to {@link Result#UNSTABLE} or {@link Result#FAILURE}, depending on the configuration * properties.
  • *
  • It provides thresholds for the build health that could be adjusted in the configuration screen. @@ -133,7 +131,7 @@ public class IssuesRecorder extends Recorder { private String id; private String name; - private List qualityGates = new ArrayList<>(); + private List qualityGates = new ArrayList<>(); private TrendChartType trendChartType = TrendChartType.AGGREGATION_TOOLS; @@ -204,26 +202,12 @@ public String getScm() { */ @SuppressWarnings("unused") // used by Stapler view data binding @DataBoundSetter - public void setQualityGates(final List qualityGates) { + public void setQualityGates(final List qualityGates) { this.qualityGates = qualityGates; } - /** - * Appends the specified quality gates to the end of the list of quality gates. - * - * @param size - * the minimum number of issues that fails the quality gate - * @param type - * the type of the quality gate - * @param result - * determines whether the quality gate is a warning or failure - */ - public void addQualityGate(final int size, final QualityGateType type, final QualityGateResult result) { - qualityGates.add(new QualityGate(size, type, result)); - } - @SuppressWarnings("unused") // used by Stapler view data binding - public List getQualityGates() { + public List getQualityGates() { return qualityGates; } @@ -354,7 +338,7 @@ public String getSourceDirectory() { } /** - * Sets the path to the directory that contains the source code. If not relative and thus not part of the workspace + * Sets the path to the directory that contains the source code. If not relative and thus not part of the workspace, * then this directory needs to be added in Jenkins global configuration to prevent accessing of forbidden resources. * * @param sourceDirectory @@ -366,7 +350,7 @@ public void setSourceDirectory(final String sourceDirectory) { } /** - * Sets the paths to the directories that contain the source code. If not relative and thus not part of the workspace + * Sets the paths to the directories that contain the source code. If not relative and thus not part of the workspace, * then these directories need to be added in Jenkins global configuration to prevent accessing of forbidden resources. * * @param sourceDirectories @@ -731,16 +715,16 @@ public boolean perform(final AbstractBuild build, final Launcher launcher, * workspace of the build * @param listener * the logger - * @param statusHandler + * @param resultHandler * reports the status for the build or for the stage * * @return the created results */ List perform(final Run run, final FilePath workspace, final TaskListener listener, - final StageResultHandler statusHandler) throws InterruptedException, IOException { + final ResultHandler resultHandler) throws InterruptedException, IOException { Result overallResult = run.getResult(); if (isEnabledForFailure || overallResult == null || overallResult.isBetterOrEqualTo(Result.UNSTABLE)) { - return record(run, workspace, listener, statusHandler); + return record(run, workspace, listener, resultHandler); } else { LogHandler logHandler = new LogHandler(listener, createLoggerPrefix()); @@ -755,7 +739,7 @@ private String createLoggerPrefix() { } private List record(final Run run, final FilePath workspace, final TaskListener listener, - final StageResultHandler statusHandler) throws IOException, InterruptedException { + final ResultHandler resultHandler) throws IOException, InterruptedException { List results = new ArrayList<>(); if (isAggregatingResults && analysisTools.size() > 1) { AnnotatedReport totalIssues = new AnnotatedReport(StringUtils.defaultIfEmpty(id, DEFAULT_ID)); @@ -763,7 +747,7 @@ private List record(final Run run, final FilePath workspac totalIssues.add(scanWithTool(run, workspace, listener, tool), tool.getActualId()); } String toolName = StringUtils.defaultIfEmpty(getName(), Messages.Tool_Default_Name()); - results.add(publishResult(run, listener, toolName, totalIssues, toolName, statusHandler)); + results.add(publishResult(run, listener, toolName, totalIssues, toolName, resultHandler)); } else { for (Tool tool : analysisTools) { @@ -778,7 +762,7 @@ private List record(final Run run, final FilePath workspac name, id); } results.add( - publishResult(run, listener, tool.getActualName(), report, getReportName(tool), statusHandler)); + publishResult(run, listener, tool.getActualName(), report, getReportName(tool), resultHandler)); } } return results; @@ -824,7 +808,7 @@ private Charset getCharset(final String encoding) { } /** - * Publishes the results as {@link Action} in the job using an {@link IssuesPublisher}. Afterwards, all affected + * Publishes the results as {@link Action} in the job using an {@link IssuesPublisher}. Afterward, all affected * files are copied to Jenkins' build folder so that they are available to show warnings in the UI. * * @param run @@ -837,15 +821,13 @@ private Charset getCharset(final String encoding) { * the analysis report to publish * @param reportName * the name of the report (might be empty) - * @param statusHandler + * @param resultHandler * the status handler to use * * @return the created results */ AnalysisResult publishResult(final Run run, final TaskListener listener, final String loggerName, - final AnnotatedReport annotatedReport, final String reportName, final StageResultHandler statusHandler) { - QualityGateEvaluator qualityGate = new QualityGateEvaluator(); - qualityGate.addAll(qualityGates); + final AnnotatedReport annotatedReport, final String reportName, final ResultHandler resultHandler) { LogHandler logHandler = new LogHandler(listener, loggerName); logHandler.setQuiet(quiet); @@ -855,9 +837,9 @@ AnalysisResult publishResult(final Run run, final TaskListener listener, f logHandler.logErrorMessages(report.getErrorMessages()); IssuesPublisher publisher = new IssuesPublisher(run, annotatedReport, - new HealthDescriptor(healthy, unhealthy, minimumSeverity), qualityGate, + new HealthDescriptor(healthy, unhealthy, minimumSeverity), qualityGates, reportName, getReferenceJobName(), getReferenceBuildId(), ignoreQualityGate, ignoreFailedBuilds, - getSourceCodeCharset(), logHandler, statusHandler, failOnError); + getSourceCodeCharset(), logHandler, resultHandler, failOnError); ResultAction action = publisher.attachAction(trendChartType); if (!skipPublishingChecks) { diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java index a2697f5dbb..6da1f008b4 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java @@ -31,15 +31,12 @@ import io.jenkins.plugins.analysis.core.model.StaticAnalysisLabelProvider; import io.jenkins.plugins.analysis.core.steps.WarningChecksPublisher.AnnotationScope; import io.jenkins.plugins.analysis.core.util.HealthDescriptor; -import io.jenkins.plugins.analysis.core.util.QualityGate; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator; import io.jenkins.plugins.analysis.core.util.TrendChartType; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; import io.jenkins.plugins.checks.steps.ChecksInfo; import io.jenkins.plugins.util.LogHandler; import io.jenkins.plugins.util.PipelineResultHandler; -import io.jenkins.plugins.util.StageResultHandler; +import io.jenkins.plugins.util.ResultHandler; import io.jenkins.plugins.util.ValidationUtilities; /** @@ -72,7 +69,7 @@ public class PublishIssuesStep extends Step implements Serializable { private int unhealthy; private Severity minimumSeverity = Severity.WARNING_LOW; - private List qualityGates = new ArrayList<>(); + private List qualityGates = new ArrayList<>(); private TrendChartType trendChartType = TrendChartType.AGGREGATION_TOOLS; @@ -188,7 +185,7 @@ public void setPublishAllIssues(final boolean publishAllIssues) { /** * If {@code true}, then the result of the quality gate is ignored when selecting a reference build. This option is - * disabled by default so a failing quality gate will be passed from build to build until the original reason for + * disabled by default, so a failing quality gate will be passed from build to build until the original reason for * the failure has been resolved. * * @param ignoreQualityGate @@ -319,7 +316,7 @@ public int getHealthy() { } /** - * Sets the healthy threshold, i.e. the number of issues when health is reported as 100%. + * Sets the healthy threshold, i.e., the number of issues when health is reported as 100%. * * @param healthy * the number of issues when health is reported as 100% @@ -334,7 +331,7 @@ public int getUnhealthy() { } /** - * Sets the healthy threshold, i.e. the number of issues when health is reported as 0%. + * Sets the healthy threshold, i.e., the number of issues when health is reported as 0%. * * @param unhealthy * the number of issues when health is reported as 0% @@ -392,445 +389,15 @@ public TrendChartType getTrendChartType() { */ @DataBoundSetter @SuppressWarnings("unused") // Used by Stapler - public void setQualityGates(final List qualityGates) { + public void setQualityGates(final List qualityGates) { this.qualityGates = qualityGates; } @SuppressWarnings("WeakerAccess") // Required by Stapler - public List getQualityGates() { + public List getQualityGates() { return qualityGates; } - /** - * Appends the specified quality gates to the end of the list of quality gates. - * - * @param size - * the minimum number of issues that fails the quality gate - * @param type - * the type of the quality gate - * @param result - * determines whether the quality gate is a warning or failure - */ - private void addQualityGate(final int size, final QualityGateType type, final QualityGateResult result) { - qualityGates.add(new QualityGate(size, type, result)); - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableTotalAll(final int size) { - addQualityGate(size, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableTotalAll() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableTotalHigh(final int size) { - addQualityGate(size, QualityGateType.TOTAL_HIGH, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableTotalHigh() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableNewAll(final int size) { - addQualityGate(size, QualityGateType.NEW, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableNewAll() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableTotalNormal(final int size) { - addQualityGate(size, QualityGateType.TOTAL_NORMAL, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableTotalNormal() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableTotalLow(final int size) { - addQualityGate(size, QualityGateType.TOTAL_LOW, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableTotalLow() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableNewHigh(final int size) { - addQualityGate(size, QualityGateType.NEW_HIGH, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableNewHigh() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableNewNormal(final int size) { - addQualityGate(size, QualityGateType.NEW_NORMAL, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableNewNormal() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableNewLow(final int size) { - addQualityGate(size, QualityGateType.NEW_LOW, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableNewLow() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedTotalAll(final int size) { - addQualityGate(size, QualityGateType.TOTAL, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedTotalAll() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedTotalHigh(final int size) { - addQualityGate(size, QualityGateType.TOTAL_HIGH, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedTotalHigh() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedTotalNormal(final int size) { - addQualityGate(size, QualityGateType.TOTAL_NORMAL, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedTotalNormal() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedTotalLow(final int size) { - addQualityGate(size, QualityGateType.TOTAL_LOW, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedTotalLow() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedNewAll(final int size) { - addQualityGate(size, QualityGateType.NEW, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedNewAll() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedNewHigh(final int size) { - addQualityGate(size, QualityGateType.NEW_HIGH, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedNewHigh() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedNewNormal(final int size) { - addQualityGate(size, QualityGateType.NEW_NORMAL, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedNewNormal() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link PublishIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedNewLow(final int size) { - addQualityGate(size, QualityGateType.NEW_LOW, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link PublishIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedNewLow() { - return 0; - } - @Override public StepExecution start(final StepContext stepContext) { return new Execution(stepContext, this); @@ -866,9 +433,6 @@ static class Execution extends AnalysisExecution { @Override protected ResultAction run() throws IOException, InterruptedException, IllegalStateException { - QualityGateEvaluator qualityGate = new QualityGateEvaluator(); - qualityGate.addAll(new ArrayList<>(step.getQualityGates())); - AnnotatedReport report; if (step.reports.size() > 1) { report = new AnnotatedReport(StringUtils.defaultIfEmpty(step.getId(), IssuesRecorder.DEFAULT_ID)); @@ -884,14 +448,14 @@ protected ResultAction run() throws IOException, InterruptedException, IllegalSt } report.addAll(step.reports); - StageResultHandler statusHandler = new PipelineResultHandler(getRun(), + ResultHandler notifier = new PipelineResultHandler(getRun(), getContext().get(FlowNode.class)); IssuesPublisher publisher = new IssuesPublisher(getRun(), report, new HealthDescriptor(step.getHealthy(), step.getUnhealthy(), - step.getMinimumSeverityAsSeverity()), qualityGate, + step.getMinimumSeverityAsSeverity()), step.getQualityGates(), StringUtils.defaultString(step.getName()), step.getReferenceJobName(), step.getReferenceBuildId(), step.getIgnoreQualityGate(), step.getIgnoreFailedBuilds(), - getCharset(step.getSourceCodeEncoding()), getLogger(report), statusHandler, step.getFailOnError()); + getCharset(step.getSourceCodeEncoding()), getLogger(report), notifier, step.getFailOnError()); ResultAction action = publisher.attachAction(step.getTrendChartType()); if (!step.isSkipPublishingChecks()) { diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java index 9de54bd653..860ecbb341 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java @@ -35,15 +35,13 @@ import io.jenkins.plugins.analysis.core.model.ResultAction; import io.jenkins.plugins.analysis.core.model.StaticAnalysisLabelProvider; import io.jenkins.plugins.analysis.core.model.Tool; -import io.jenkins.plugins.analysis.core.util.QualityGate; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator; import io.jenkins.plugins.analysis.core.util.TrendChartType; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; import io.jenkins.plugins.checks.steps.ChecksInfo; import io.jenkins.plugins.prism.SourceCodeDirectory; import io.jenkins.plugins.util.PipelineResultHandler; -import io.jenkins.plugins.util.StageResultHandler; +import io.jenkins.plugins.util.QualityGateEvaluator; +import io.jenkins.plugins.util.ResultHandler; import io.jenkins.plugins.util.ValidationUtilities; /** @@ -96,7 +94,7 @@ public class RecordIssuesStep extends Step implements Serializable { private String id; private String name; - private List qualityGates = new ArrayList<>(); + private List qualityGates = new ArrayList<>(); private TrendChartType trendChartType = TrendChartType.AGGREGATION_TOOLS; @@ -139,445 +137,15 @@ public String getScm() { */ @SuppressWarnings("unused") // used by Stapler view data binding @DataBoundSetter - public void setQualityGates(final List qualityGates) { + public void setQualityGates(final List qualityGates) { this.qualityGates = qualityGates; } - /** - * Appends the specified quality gates to the end of the list of quality gates. - * - * @param size - * the minimum number of issues that fails the quality gate - * @param type - * the type of the quality gate - * @param result - * determines whether the quality gate is a warning or failure - */ - public void addQualityGate(final int size, final QualityGateType type, final QualityGateResult result) { - qualityGates.add(new QualityGate(size, type, result)); - } - @SuppressWarnings({"unused", "WeakerAccess"}) // used by Stapler view data binding - public List getQualityGates() { + public List getQualityGates() { return qualityGates; } - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableTotalAll(final int size) { - addQualityGate(size, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableTotalAll() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableTotalHigh(final int size) { - addQualityGate(size, QualityGateType.TOTAL_HIGH, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableTotalHigh() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableNewAll(final int size) { - addQualityGate(size, QualityGateType.NEW, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableNewAll() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableTotalNormal(final int size) { - addQualityGate(size, QualityGateType.TOTAL_NORMAL, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableTotalNormal() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableTotalLow(final int size) { - addQualityGate(size, QualityGateType.TOTAL_LOW, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableTotalLow() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableNewHigh(final int size) { - addQualityGate(size, QualityGateType.NEW_HIGH, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableNewHigh() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableNewNormal(final int size) { - addQualityGate(size, QualityGateType.NEW_NORMAL, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableNewNormal() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setUnstableNewLow(final int size) { - addQualityGate(size, QualityGateType.NEW_LOW, QualityGateResult.UNSTABLE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getUnstableNewLow() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedTotalAll(final int size) { - addQualityGate(size, QualityGateType.TOTAL, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedTotalAll() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedTotalHigh(final int size) { - addQualityGate(size, QualityGateType.TOTAL_HIGH, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedTotalHigh() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedTotalNormal(final int size) { - addQualityGate(size, QualityGateType.TOTAL_NORMAL, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedTotalNormal() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedTotalLow(final int size) { - addQualityGate(size, QualityGateType.TOTAL_LOW, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedTotalLow() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedNewAll(final int size) { - addQualityGate(size, QualityGateType.NEW, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedNewAll() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedNewHigh(final int size) { - addQualityGate(size, QualityGateType.NEW_HIGH, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedNewHigh() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedNewNormal(final int size) { - addQualityGate(size, QualityGateType.NEW_NORMAL, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedNewNormal() { - return 0; - } - - /** - * Sets the quality gate. - * - * @param size - * number of issues - * - * @deprecated replaced by {@link RecordIssuesStep#addQualityGate(int, QualityGate.QualityGateType, - * QualityGate.QualityGateResult)} - */ - @Deprecated - @DataBoundSetter - public void setFailedNewLow(final int size) { - addQualityGate(size, QualityGateType.NEW_LOW, QualityGateResult.FAILURE); - } - - /** - * Gets the quality gate. - * - * @return 0 - * @deprecated replaced by {@link RecordIssuesStep#getQualityGates()} - */ - @Deprecated - public int getFailedNewLow() { - return 0; - } - /** * Defines the ID of the results. The ID is used as URL of the results and as name in UI elements. If no ID is * given, then the ID of the associated result object is used. @@ -759,7 +327,7 @@ public void setSourceDirectory(final String sourceDirectory) { /** * Sets the paths to the directories that contain the source code. If not relative and thus not part of the - * workspace then these directories need to be added in Jenkins global configuration to prevent accessing of + * workspace, then these directories need to be added in Jenkins global configuration to prevent accessing of * forbidden resources. * * @param sourceDirectories @@ -814,30 +382,6 @@ public void setQuiet(final boolean quiet) { this.quiet = quiet; } - /** - * Returns whether SCM blaming should be disabled. - * - * @return {@code true} if SCM blaming should be disabled - * @deprecated use {@link #isSkipBlames()} - */ - @SuppressWarnings("PMD.BooleanGetMethodName") - @Deprecated - public boolean getBlameDisabled() { - return isBlameDisabled; - } - - /** - * Determines whether to skip the SCM blaming. - * - * @param blameDisabled {@code true} if SCM blaming should be disabled - * @deprecated use {@link #setSkipBlames(boolean)} - */ - @Deprecated - @DataBoundSetter - public void setBlameDisabled(final boolean blameDisabled) { - isBlameDisabled = blameDisabled; - } - /** * Returns whether SCM blaming should be disabled. * @@ -852,32 +396,6 @@ public void setSkipBlames(final boolean skipBlames) { isBlameDisabled = skipBlames; } - /** - * Not used anymore. - * - * @return {@code true} if SCM forensics should be disabled - * @deprecated Forensics will be automatically skipped if the Forensics recorder is not activated. - */ - @SuppressWarnings("PMD.BooleanGetMethodName") - @Deprecated - public boolean getForensicsDisabled() { - return false; - } - - /** - * Not used anymore. - * - * @param forensicsDisabled - * not used - * - * @deprecated Forensics will be automatically skipped if the Forensics recorder is not activated. - */ - @DataBoundSetter - @Deprecated - public void setForensicsDisabled(final boolean forensicsDisabled) { - // do nothing - } - /** * Returns whether publishing checks should be skipped. * @@ -925,7 +443,7 @@ public void setEnabledForFailure(final boolean enabledForFailure) { /** * If {@code true}, then the result of the quality gate is ignored when selecting a reference build. This option is - * disabled by default so a failing quality gate will be passed from build to build until the original reason for + * disabled by default, so a failing quality gate will be passed from build to build until the original reason for * the failure has been resolved. * * @param ignoreQualityGate @@ -1039,7 +557,7 @@ public int getHealthy() { } /** - * Sets the healthy threshold, i.e. the number of issues when health is reported as 100%. + * Sets the healthy threshold, i.e., the number of issues when health is reported as 100%. * * @param healthy * the number of issues when health is reported as 100% @@ -1054,7 +572,7 @@ public int getUnhealthy() { } /** - * Sets the healthy threshold, i.e. the number of issues when health is reported as 0%. + * Sets the healthy threshold, i.e., the number of issues when health is reported as 0%. * * @param unhealthy * the number of issues when health is reported as 0% @@ -1139,7 +657,7 @@ protected List run() throws IOException, InterruptedException { recorder.setFilters(step.getFilters()); recorder.setEnabledForFailure(step.getEnabledForFailure()); recorder.setAggregatingResults(step.getAggregatingResults()); - recorder.setBlameDisabled(step.getBlameDisabled()); + recorder.setBlameDisabled(step.isSkipBlames()); recorder.setScm(step.getScm()); recorder.setSkipPublishingChecks(step.isSkipPublishingChecks()); recorder.setPublishAllIssues(step.isPublishAllIssues()); @@ -1151,13 +669,14 @@ protected List run() throws IOException, InterruptedException { recorder.setSourceDirectories(step.getAllSourceDirectories()); recorder.setChecksInfo(getContext().get(ChecksInfo.class)); recorder.setQuiet(step.isQuiet()); - StageResultHandler statusHandler = new PipelineResultHandler(getRun(), - getContext().get(FlowNode.class)); + + // FIXME: change base class + ResultHandler notifier = new PipelineResultHandler(getRun(), getContext().get(FlowNode.class)); FilePath workspace = getWorkspace(); workspace.mkdirs(); - return recorder.perform(getRun(), workspace, getTaskListener(), statusHandler); + return recorder.perform(getRun(), workspace, getTaskListener(), notifier); } } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/WarningChecksPublisher.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/WarningChecksPublisher.java index 3b0dde51e7..5dbdbb707f 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/WarningChecksPublisher.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/WarningChecksPublisher.java @@ -27,7 +27,6 @@ import io.jenkins.plugins.analysis.core.model.ResultAction; import io.jenkins.plugins.analysis.core.model.StaticAnalysisLabelProvider; import io.jenkins.plugins.analysis.core.util.IssuesStatistics; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.checks.api.ChecksAnnotation; import io.jenkins.plugins.checks.api.ChecksAnnotation.ChecksAnnotationBuilder; import io.jenkins.plugins.checks.api.ChecksAnnotation.ChecksAnnotationLevel; @@ -40,6 +39,7 @@ import io.jenkins.plugins.checks.api.ChecksStatus; import io.jenkins.plugins.checks.steps.ChecksInfo; import io.jenkins.plugins.util.JenkinsFacade; +import io.jenkins.plugins.util.QualityGateStatus; import static j2html.TagCreator.*; @@ -98,7 +98,7 @@ ChecksDetails extractChecksDetails(final AnnotationScope annotationScope) { return new ChecksDetailsBuilder() .withName(checksName) .withStatus(ChecksStatus.COMPLETED) - .withConclusion(extractChecksConclusion(result.getQualityGateStatus())) + .withConclusion(extractChecksConclusion(result.getQualityGateResult().getOverallStatus())) .withOutput(new ChecksOutputBuilder() .withTitle(extractChecksTitle(totals)) .withSummary(summary) @@ -205,7 +205,9 @@ private ChecksConclusion extractChecksConclusion(final QualityGateStatus status) case PASSED: return ChecksConclusion.SUCCESS; case FAILED: + case ERROR: case WARNING: + case NOTE: return ChecksConclusion.FAILURE; default: throw new IllegalArgumentException("Unsupported quality gate status: " + status); diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateEvaluator.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateEvaluator.java deleted file mode 100644 index e6b5ce0180..0000000000 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateEvaluator.java +++ /dev/null @@ -1,110 +0,0 @@ -package io.jenkins.plugins.analysis.core.util; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import com.google.errorprone.annotations.FormatMethod; - -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; - -/** - * Evaluates a set of quality gates for a static analysis report. - * - * @author Ullrich Hafner - */ -public class QualityGateEvaluator { - private final List qualityGates = new ArrayList<>(); - - /** - * Enforces this quality gate for the specified run. - * - * @param report - * the report to evaluate - * @param logger - * the logger that reports the passed and failed quality gate thresholds - * - * @return result of the evaluation, expressed by a build state - */ - public QualityGateStatus evaluate(final IssuesStatistics report, final FormattedLogger logger) { - if (qualityGates.isEmpty()) { - logger.print("-> INACTIVE - No quality gate defined"); - - return QualityGateStatus.INACTIVE; - } - - QualityGateStatus status = QualityGateStatus.PASSED; - - for (QualityGate qualityGate : qualityGates) { - if (qualityGate.getThreshold() > 0) { - int actualSize = qualityGate.getActualSizeMethodReference().apply(report); - if (actualSize >= qualityGate.getThreshold()) { - logger.print("-> %s - %s: %d - Quality Gate: %d", - qualityGate.getStatus(), qualityGate.getName(), actualSize, qualityGate.getThreshold()); - if (qualityGate.getStatus().isWorseThan(status)) { - status = qualityGate.getStatus(); - } - } - else { - logger.print("-> PASSED - %s: %d - Quality Gate: %d", - qualityGate.getName(), actualSize, qualityGate.getThreshold()); - } - } - } - - return status; - } - - /** - * Appends the specified quality gates to the end of the list of quality gates. - * - * @param size - * the minimum number of issues that fails the quality gate - * @param type - * the type of the quality gate - * @param strength - * determines whether the quality gate is a warning or failure - */ - public void add(final int size, final QualityGateType type, final QualityGateResult strength) { - qualityGates.add(new QualityGate(size, type, strength)); - } - - /** - * Appends all of the quality gates in the specified collection to the end of the list of quality gates. - * - * @param additionalQualityGates - * the quality gates to add - */ - public void addAll(final Collection additionalQualityGates) { - this.qualityGates.addAll(additionalQualityGates); - } - - /** - * Returns whether at least one quality gate has been added. - * - * @return {@code true} if at least one quality gate has been added, {@code false} otherwise - */ - public boolean isEnabled() { - return !qualityGates.isEmpty(); - } - - /** - * Logs results of the quality gate evaluation. - */ - @FunctionalInterface - public interface FormattedLogger { - /** - * Logs the specified message. - * - * @param format - * A format string - * @param args - * Arguments referenced by the format specifiers in the format string. If there are more arguments than - * format specifiers, the extra arguments are ignored. The number of arguments is variable and may be - * zero. - */ - @FormatMethod - void print(String format, Object... args); - } -} diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateStatus.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateStatus.java index e192839595..05ea1db7ea 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateStatus.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateStatus.java @@ -4,13 +4,13 @@ import hudson.model.BallColor; import hudson.model.Result; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator.FormattedLogger; - /** - * Result of a {@link QualityGateEvaluator#evaluate(IssuesStatistics, FormattedLogger)} call. + * Result of a quality gate evaluation (warnings plug-in older than 11.0.0). * * @author Ullrich Hafner + * @deprecated replaced by {@link io.jenkins.plugins.util.QualityGateStatus} */ +@Deprecated public enum QualityGateStatus { /** Quality gate is inactive, so result evaluation is not available. */ INACTIVE(Result.NOT_BUILT), diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/StaticAnalysisRun.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/StaticAnalysisRun.java index 924c96dd61..3fb04cd96f 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/StaticAnalysisRun.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/StaticAnalysisRun.java @@ -7,6 +7,9 @@ import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted; import hudson.model.Run; +import io.jenkins.plugins.util.QualityGateResult; +import io.jenkins.plugins.util.QualityGateStatus; + /** * Provides detailed information for the results of a static analysis run. */ @@ -48,13 +51,20 @@ public interface StaticAnalysisRun extends AnalysisBuildResult { int getSuccessfulSinceBuild(); /** - * Returns the {@link QualityGateStatus} of the {@link QualityGateEvaluator} evaluation of the static analysis run. + * Returns the {@link QualityGateStatus} of the quality gates evaluation of the static analysis run. * * @return the quality gate status */ - @Whitelisted QualityGateStatus getQualityGateStatus(); + /** + * Returns the {@link QualityGateResult} of the quality gates evaluation of the static analysis run. + * + * @return the quality gate status + */ + @Whitelisted + QualityGateResult getQualityGateResult(); + /** * Returns the reference static analysis run that has been used to compute the new issues. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGate.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java similarity index 59% rename from plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGate.java rename to plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java index 63175213d4..114bfd3129 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGate.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java @@ -1,7 +1,5 @@ package io.jenkins.plugins.analysis.core.util; -import java.io.Serializable; -import java.util.Objects; import java.util.function.Function; import edu.hm.hafner.util.VisibleForTesting; @@ -11,9 +9,7 @@ import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.verb.POST; import hudson.Extension; -import hudson.model.AbstractDescribableImpl; import hudson.model.BuildableItem; -import hudson.model.Descriptor; import hudson.model.Item; import hudson.util.FormValidation; import hudson.util.ListBoxModel; @@ -21,81 +17,49 @@ import io.jenkins.plugins.analysis.core.util.IssuesStatistics.StatisticProperties; import io.jenkins.plugins.util.JenkinsFacade; - -import static io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult.*; +import io.jenkins.plugins.util.QualityGate; /** * Defines a quality gate based on a specific threshold of issues (total, new, delta) in the current build. After a - * build has been finished, a set of {@link QualityGate quality gates} will be evaluated and the overall quality gate - * status will be reported in Jenkins UI. + * build has been finished, a set of {@link WarningsQualityGate quality gates} will be evaluated and the overall quality + * gate status will be reported in Jenkins UI. * * @author Ullrich Hafner */ -public class QualityGate extends AbstractDescribableImpl implements Serializable { - private static final long serialVersionUID = -397278599489416668L; +public class WarningsQualityGate extends QualityGate { + private static final long serialVersionUID = -3560049414586166711L; - private final int threshold; private final QualityGateType type; - private final QualityGateStatus status; /** - * Creates a new instance of {@link QualityGate}. + * Creates a new instance of {@link WarningsQualityGate}. * * @param threshold * the minimum number of issues that fails the quality gate * @param type * the type of the quality gate - * @param unstable - * determines whether the build result will be set to unstable or failed if the quality gate is failed */ @DataBoundConstructor - public QualityGate(final int threshold, final QualityGateType type, final boolean unstable) { - super(); + public WarningsQualityGate(final int threshold, final QualityGateType type) { + super(threshold); - this.threshold = threshold; this.type = type; - status = unstable ? QualityGateStatus.WARNING : QualityGateStatus.FAILED; - } - - @SuppressWarnings("PMD.BooleanGetMethodName") - public boolean getUnstable() { - return status == QualityGateStatus.WARNING; - } - - public QualityGateType getType() { - return type; - } - - @SuppressWarnings("PMD.BooleanGetMethodName") - public boolean getWarning() { - return status == QualityGateStatus.WARNING; } /** - * Creates a new instance of {@link QualityGate}. + * Creates a new instance of {@link WarningsQualityGate}. * * @param threshold * the minimum number of issues that fails the quality gate * @param type * the type of the quality gate - * @param result - * determines whether the quality gate is a warning or failure + * @param criticality the criticality of the quality gate */ - public QualityGate(final int threshold, final QualityGateType type, final QualityGateResult result) { - super(); + public WarningsQualityGate(final int threshold, final QualityGateType type, final QualityGateCriticality criticality) { + super(threshold); - this.threshold = threshold; this.type = type; - status = result.status; - } - - /** - * Returns the minimum number of issues that will fail the quality gate. - * - * @return minimum number of issues - */ - public int getThreshold() { - return threshold; + setCriticality(criticality); } /** @@ -107,31 +71,13 @@ public Function getActualSizeMethodReference() { return type.getSizeGetter(); } - /** - * Returns the human-readable name of the quality gate. - * - * @return the human-readable name - */ + @Override public String getName() { return type.getDisplayName(); } - /** - * Returns the quality gate status to set if the quality gate is failed. - * - * @return the status - */ - public QualityGateStatus getStatus() { - return status; - } - - /** - * Returns the quality gate status to set if the quality gate is failed. - * - * @return the status - */ - public QualityGateResult getResult() { - return status == QualityGateStatus.WARNING ? UNSTABLE : FAILURE; + public QualityGateType getType() { + return type; } @Override @@ -142,39 +88,15 @@ public boolean equals(final Object o) { if (o == null || getClass() != o.getClass()) { return false; } - QualityGate that = (QualityGate) o; - return threshold == that.threshold && type == that.type && status == that.status; + + WarningsQualityGate that = (WarningsQualityGate) o; + + return type == that.type; } @Override public int hashCode() { - return Objects.hash(threshold, type, status); - } - - /** - * Determines the Jenkins build result if the quality gate is failed. - */ - public enum QualityGateResult { - /** The build will be marked as unstable. */ - UNSTABLE(QualityGateStatus.WARNING), - - /** The build will be marked as failed. */ - FAILURE(QualityGateStatus.FAILED); - - private final QualityGateStatus status; - - QualityGateResult(final QualityGateStatus status) { - this.status = status; - } - - /** - * Returns the status. - * - * @return the status - */ - public QualityGateStatus getStatus() { - return status; - } + return type != null ? type.hashCode() : 0; } /** @@ -225,15 +147,15 @@ public Function getSizeGetter() { } /** - * Descriptor of the {@link QualityGate}. + * Descriptor of the {@link WarningsQualityGate}. */ @Extension - public static class QualityGateDescriptor extends Descriptor { + public static class WarningsQualityGateDescriptor extends QualityGateDescriptor { private final ModelValidation modelValidation = new ModelValidation(); private final JenkinsFacade jenkins; @VisibleForTesting - QualityGateDescriptor(final JenkinsFacade jenkinsFacade) { + WarningsQualityGateDescriptor(final JenkinsFacade jenkinsFacade) { super(); jenkins = jenkinsFacade; @@ -242,7 +164,7 @@ public static class QualityGateDescriptor extends Descriptor { /** * Creates a new descriptor. */ - public QualityGateDescriptor() { + public WarningsQualityGateDescriptor() { this(new JenkinsFacade()); } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGateEvaluator.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGateEvaluator.java new file mode 100644 index 0000000000..c7a4f721eb --- /dev/null +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGateEvaluator.java @@ -0,0 +1,40 @@ +package io.jenkins.plugins.analysis.core.util; + +import java.util.Collection; + +import io.jenkins.plugins.util.QualityGateEvaluator; +import io.jenkins.plugins.util.QualityGateResult; +import io.jenkins.plugins.util.QualityGateStatus; + +/** + * Evaluates a given set of quality gates. + * + * @author Johannes Walter + */ +public class WarningsQualityGateEvaluator extends QualityGateEvaluator { + private final IssuesStatistics statistics; + + public WarningsQualityGateEvaluator(final Collection qualityGates, + final IssuesStatistics statistics) { + super(qualityGates); + + this.statistics = statistics; + } + + @Override + protected void evaluate(final WarningsQualityGate qualityGate, final QualityGateResult result) { + if (qualityGate.getThreshold() > 0) { + int actualSize = qualityGate.getActualSizeMethodReference().apply(statistics); + var actualValue = String.valueOf(actualSize); + if (actualSize >= qualityGate.getThreshold()) { + result.add(qualityGate, qualityGate.getStatus(), actualValue); + } + else { + result.add(qualityGate, QualityGateStatus.PASSED, actualValue); + } + } + else { + result.add(qualityGate, QualityGateStatus.INACTIVE, "Threshold too small: " + qualityGate.getThreshold()); + } + } +} diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisHistoryTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisHistoryTest.java index 2430cbcf1a..07036d88d0 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisHistoryTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisHistoryTest.java @@ -17,7 +17,11 @@ import io.jenkins.plugins.analysis.core.model.AnalysisHistory.JobResultEvaluationMode; import io.jenkins.plugins.analysis.core.model.AnalysisHistory.QualityGateEvaluationMode; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; +import io.jenkins.plugins.util.QualityGate.QualityGateCriticality; +import io.jenkins.plugins.util.QualityGateResult; +import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.model.AnalysisHistory.JobResultEvaluationMode.*; import static io.jenkins.plugins.analysis.core.model.AnalysisHistory.QualityGateEvaluationMode.*; @@ -117,7 +121,7 @@ void baselineResultIsPreviousResultIfAlreadySet() { @MethodSource("createTestDataForIgnoredQualityGateAndIgnoredBuildResult") @DisplayName("Ignore job result + ignore quality gate -> history with one previous build") void shouldTestFirstIterationOfLoopIgnoreStatusAndResult(final String name, - final ExpectedResult expectedResult, final QualityGateStatus qualityGateStatus, final Result jobStatus) { + final ExpectedResult expectedResult, final QualityGateResult qualityGateStatus, final Result jobStatus) { runTest(IGNORE_QUALITY_GATE, IGNORE_JOB_RESULT, qualityGateStatus, jobStatus, expectedResult); } @@ -125,7 +129,7 @@ void shouldTestFirstIterationOfLoopIgnoreStatusAndResult(final String name, @MethodSource("createTestDataForIgnoredQualityGateAndNoFailedBuild") @DisplayName("No job failure + ignore quality gate -> history with one previous build") void shouldTestFirstIterationOfLoopIgnoreStatus(final String name, - final ExpectedResult expectedResult, final QualityGateStatus qualityGateStatus, final Result jobStatus) { + final ExpectedResult expectedResult, final QualityGateResult qualityGateStatus, final Result jobStatus) { runTest(IGNORE_QUALITY_GATE, NO_JOB_FAILURE, qualityGateStatus, jobStatus, expectedResult); } @@ -133,7 +137,7 @@ void shouldTestFirstIterationOfLoopIgnoreStatus(final String name, @MethodSource("createTestDataForSuccessfulQualityGateAndIgnoredBuildResult") @DisplayName("Ignore job result + successful quality gate -> history with one previous build") void shouldTestFirstIterationOfLoopIgnoreResult(final String name, - final ExpectedResult expectedResult, final QualityGateStatus qualityGateStatus, final Result jobStatus) { + final ExpectedResult expectedResult, final QualityGateResult qualityGateStatus, final Result jobStatus) { runTest(SUCCESSFUL_QUALITY_GATE, IGNORE_JOB_RESULT, qualityGateStatus, jobStatus, expectedResult); } @@ -141,13 +145,13 @@ void shouldTestFirstIterationOfLoopIgnoreResult(final String name, @MethodSource("createTestDataForSuccessfulQualityGateAndNoFailedBuild") @DisplayName("No job failure + successful quality gate -> history with one previous build") void shouldTestFirstIterationOfLoop(final String name, - final ExpectedResult expectedResult, final QualityGateStatus qualityGateStatus, final Result jobStatus) { + final ExpectedResult expectedResult, final QualityGateResult qualityGateStatus, final Result jobStatus) { runTest(SUCCESSFUL_QUALITY_GATE, NO_JOB_FAILURE, qualityGateStatus, jobStatus, expectedResult); } private void runTest(final QualityGateEvaluationMode qualityGateEvaluationMode, final JobResultEvaluationMode jobResultEvaluationMode, - final QualityGateStatus qualityGateStatus, final Result jobStatus, final ExpectedResult expectedResult) { + final QualityGateResult qualityGateStatus, final Result jobStatus, final ExpectedResult expectedResult) { ResultSelector resultSelector = mock(ResultSelector.class); Run baseline = createBuild(qualityGateStatus, jobStatus, resultSelector); @@ -164,13 +168,13 @@ private void runTest(final QualityGateEvaluationMode qualityGateEvaluationMode, } } - private Run createBuild(final QualityGateStatus qualityGateStatus, final Result jobStatus, + private Run createBuild(final QualityGateResult qualityGateStatus, final Result jobStatus, final ResultSelector resultSelector) { Run baseline = createBuildWithResult(jobStatus); AnalysisResult result = mock(AnalysisResult.class); when(result.getOwner()).thenAnswer(a -> baseline); - when(result.getQualityGateStatus()).thenReturn(qualityGateStatus); + when(result.getQualityGateResult()).thenReturn(qualityGateStatus); ResultAction resultAction = mock(ResultAction.class); when(resultAction.getResult()).thenReturn(result); @@ -197,48 +201,55 @@ private static Stream createTestDataForSuccessfulQualityGateAndNoFail return Stream.of( new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have analysis result (SUCCESS, quality gate is not active)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have analysis result (SUCCESS, quality gate has been passed)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.UNSTABLE) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have analysis result (UNSTABLE, quality gate is not active)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.UNSTABLE) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have analysis result (UNSTABLE, quality gate has been passed)") .build(), new BuildHistoryBuilder().setExpectedResult(NONE) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.FAILED) + .setQualityGateResult(createResult(QualityGateStatus.FAILED)) .setTestName("Job should have no analysis result if quality gate has been missed (SUCCESS)") .build(), new BuildHistoryBuilder().setExpectedResult(NONE) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.WARNING) + .setQualityGateResult(createResult(QualityGateStatus.WARNING)) .setTestName("Job should have no analysis result if quality gate has a warning (SUCCESS)") .build(), new BuildHistoryBuilder().setExpectedResult(NONE) .setJobResult(Result.FAILURE) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have no analysis result even if quality gate is not active (FAILED)") .build(), new BuildHistoryBuilder().setExpectedResult(NONE) .setJobResult(Result.FAILURE) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have no analysis result even if quality gate has been passed (FAILED)") .build() ); } + private static QualityGateResult createResult(final QualityGateStatus qualityGateStatus) { + var result = new QualityGateResult(); + result.add(new WarningsQualityGate(0, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE), + qualityGateStatus, "message"); + return result; + } + /** * Method to provide test element that return an present optional. * @@ -248,50 +259,50 @@ private static Stream createTestDataForIgnoredQualityGateAndIgnoredBu return Stream.of( new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have analysis result (SUCCESS, quality gate is not active)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have analysis result (SUCCESS, quality gate has been passed)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.UNSTABLE) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have analysis result (UNSTABLE, quality gate is not active)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.UNSTABLE) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have analysis result (UNSTABLE, quality gate has been passed)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.FAILED) + .setQualityGateResult(createResult(QualityGateStatus.FAILED)) .setTestName("Job should have analysis result if quality gate has been missed (SUCCESS)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.WARNING) + .setQualityGateResult(createResult(QualityGateStatus.WARNING)) .setTestName("Job should have analysis result if quality gate has a warning (SUCCESS)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.FAILURE) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have analysis result even if quality gate is not active (FAILED)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.FAILURE) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have analysis result even if quality gate has been passed (FAILED)") .build() ); } /** - * Method to provide test element that return an present optional. + * Method to provide test element that return a present optional. * * @return list of test data objects */ @@ -299,44 +310,44 @@ private static Stream createTestDataForSuccessfulQualityGateAndIgnore return Stream.of( new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have analysis result (SUCCESS, quality gate is not active)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have analysis result (SUCCESS, quality gate has been passed)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.UNSTABLE) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have analysis result (UNSTABLE, quality gate is not active)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.UNSTABLE) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have analysis result (UNSTABLE, quality gate has been passed)") .build(), new BuildHistoryBuilder().setExpectedResult(NONE) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.FAILED) + .setQualityGateResult(createResult(QualityGateStatus.FAILED)) .setTestName("Job should have no analysis result if quality gate has been missed (SUCCESS)") .build(), new BuildHistoryBuilder().setExpectedResult(NONE) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.WARNING) + .setQualityGateResult(createResult(QualityGateStatus.WARNING)) .setTestName("Job should have no analysis result if quality gate has a warning (SUCCESS)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.FAILURE) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have analysis result even if quality gate is not active (FAILED)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.FAILURE) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have analysis result even if quality gate has been passed (FAILED)") .build() ); @@ -351,44 +362,44 @@ private static Stream createTestDataForIgnoredQualityGateAndNoFailedB return Stream.of( new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have analysis result (SUCCESS, quality gate is not active)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have analysis result (SUCCESS, quality gate has been passed)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.UNSTABLE) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have analysis result (UNSTABLE, quality gate is not active)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.UNSTABLE) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have analysis result (UNSTABLE, quality gate has been passed)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.FAILED) + .setQualityGateResult(createResult(QualityGateStatus.FAILED)) .setTestName("Job should have analysis result if quality gate has been missed (SUCCESS)") .build(), new BuildHistoryBuilder().setExpectedResult(FIRST) .setJobResult(Result.SUCCESS) - .setQualityGateStatus(QualityGateStatus.WARNING) + .setQualityGateResult(createResult(QualityGateStatus.WARNING)) .setTestName("Job should have analysis result if quality gate has a warning (SUCCESS)") .build(), new BuildHistoryBuilder().setExpectedResult(NONE) .setJobResult(Result.FAILURE) - .setQualityGateStatus(QualityGateStatus.INACTIVE) + .setQualityGateResult(createResult(QualityGateStatus.INACTIVE)) .setTestName("Job should have no analysis result even if quality gate is not active (FAILED)") .build(), new BuildHistoryBuilder().setExpectedResult(NONE) .setJobResult(Result.FAILURE) - .setQualityGateStatus(QualityGateStatus.PASSED) + .setQualityGateResult(createResult(QualityGateStatus.PASSED)) .setTestName("Job should have no analysis result even if quality gate has been passed (FAILED)") .build() ); @@ -408,7 +419,7 @@ enum ExpectedResult { private static class BuildHistoryBuilder { private String testName; private ExpectedResult expectedResult; - private QualityGateStatus qualityGateStatus; + private QualityGateResult qualityGateResult; private Result jobResult; BuildHistoryBuilder setTestName(final String testName) { @@ -421,8 +432,8 @@ BuildHistoryBuilder setExpectedResult(final ExpectedResult expectedResult) { return this; } - BuildHistoryBuilder setQualityGateStatus(final QualityGateStatus qualityGateStatus) { - this.qualityGateStatus = qualityGateStatus; + BuildHistoryBuilder setQualityGateResult(final QualityGateResult result) { + this.qualityGateResult = result; return this; } @@ -437,7 +448,7 @@ BuildHistoryBuilder setJobResult(final Result jobResult) { * @return test arg */ Arguments build() { - return Arguments.of(testName, expectedResult, qualityGateStatus, jobResult); + return Arguments.of(testName, expectedResult, qualityGateResult, jobResult); } } } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisResultTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisResultTest.java index 3d466462e6..dc3a433bd8 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisResultTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisResultTest.java @@ -13,9 +13,9 @@ import hudson.model.Run; import hudson.util.XStream2; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.forensics.blame.Blames; import io.jenkins.plugins.forensics.miner.RepositoryStatistics; +import io.jenkins.plugins.util.QualityGateResult; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; import static org.mockito.Mockito.*; @@ -45,6 +45,6 @@ void constructorShouldThrowExceptionIfIdHasInvalidPattern() { .isThrownBy( () -> new AnalysisResult(mock(Run.class), "../../invalid-id", mock(DeltaReport.class), new Blames(), new RepositoryStatistics(), - QualityGateStatus.PASSED, Collections.emptyMap())); + new QualityGateResult(), Collections.emptyMap())); } } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/ResetQualityGateCommandTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/ResetQualityGateCommandTest.java index 224f9c7dfb..9348773b2b 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/ResetQualityGateCommandTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/ResetQualityGateCommandTest.java @@ -13,8 +13,9 @@ import hudson.model.FreeStyleProject; import hudson.model.Item; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.util.JenkinsFacade; +import io.jenkins.plugins.util.QualityGateResult; +import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.testutil.Assertions.*; import static org.mockito.Mockito.*; @@ -128,7 +129,10 @@ private ResultAction createResultAction(final QualityGateStatus status, final St ResultAction resultAction = mock(ResultAction.class); AnalysisResult result = mock(AnalysisResult.class); - when(result.getQualityGateStatus()).thenReturn(status); + var qualityGateResult = mock(QualityGateResult.class); + when(qualityGateResult.getOverallStatus()).thenReturn(status); + when(qualityGateResult.isSuccessful()).thenReturn(status.isSuccessful()); + when(result.getQualityGateResult()).thenReturn(qualityGateResult); // Reference build is set FreeStyleBuild referenceBuild = mock(FreeStyleBuild.class); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/SummaryModelTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/SummaryModelTest.java index 6d0083a3ec..c1bdbd667b 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/SummaryModelTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/SummaryModelTest.java @@ -18,7 +18,8 @@ import hudson.model.Run; import io.jenkins.plugins.analysis.core.model.SummaryModel.LabelProviderFactoryFacade; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; +import io.jenkins.plugins.util.QualityGateResult; +import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; import static org.mockito.Mockito.*; @@ -173,7 +174,9 @@ void shouldUseQualityStatusOfResult() { Lists.immutable.of(ERROR_MESSAGE), 0); QualityGateStatus qualityGateStatus = QualityGateStatus.FAILED; - when(analysisResult.getQualityGateStatus()).thenReturn(qualityGateStatus); + var result = mock(QualityGateResult.class); + when(result.getOverallStatus()).thenReturn(qualityGateStatus); + when(analysisResult.getQualityGateResult()).thenReturn(result); SummaryModel summary = createSummary(analysisResult); @@ -223,7 +226,9 @@ private AnalysisResult createAnalysisResult(final Map sizesPerO when(analysisRun.getFixedSize()).thenReturn(fixedSize); when(analysisRun.getErrorMessages()).thenReturn(errorMessages); when(analysisRun.getNoIssuesSinceBuild()).thenReturn(numberOfIssuesSinceBuild); - when(analysisRun.getQualityGateStatus()).thenReturn(QualityGateStatus.INACTIVE); + var qualityGateResult = mock(QualityGateResult.class); + when(qualityGateResult.getOverallStatus()).thenReturn(QualityGateStatus.INACTIVE); + when(analysisRun.getQualityGateResult()).thenReturn(qualityGateResult); when(analysisRun.getIssues()).thenReturn(createReport(sizesPerOrigin.keySet())); Run build = mock(Run.class); when(build.getNumber()).thenReturn(2); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/WarningChecksPublisherITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/WarningChecksPublisherITest.java index 9ffed175c8..8b3f0a946a 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/WarningChecksPublisherITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/WarningChecksPublisherITest.java @@ -20,8 +20,8 @@ import io.jenkins.plugins.analysis.core.steps.WarningChecksPublisher.AnnotationScope; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerSuite; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; import io.jenkins.plugins.analysis.warnings.CheckStyle; import io.jenkins.plugins.analysis.warnings.MsBuild; import io.jenkins.plugins.analysis.warnings.PVSStudio; @@ -37,6 +37,7 @@ import io.jenkins.plugins.checks.api.ChecksStatus; import io.jenkins.plugins.checks.util.CapturingChecksPublisher; import io.jenkins.plugins.checks.util.CapturingChecksPublisher.Factory; +import io.jenkins.plugins.util.QualityGate.QualityGateCriticality; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; @@ -111,7 +112,8 @@ private void configureScanner(final WorkflowJob job, final String fileName, fina void shouldConcludeChecksAsSuccessWhenQualityGateIsPassed() { FreeStyleProject project = createFreeStyleProjectWithWorkspaceFilesWithSuffix(NEW_CHECKSTYLE_REPORT); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(10, QualityGateType.TOTAL, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(10, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE)))); Run build = buildSuccessfully(project); WarningChecksPublisher publisher = new WarningChecksPublisher(getResultAction(build), TaskListener.NULL, null); @@ -125,7 +127,7 @@ void shouldConcludeChecksAsSuccessWhenQualityGateIsPassed() { */ @Test void shouldConcludeChecksAsFailureWhenQualityGateIsFailed() { - assertChecksConclusionIsFailureWithQualityGateResult(QualityGateResult.FAILURE); + assertChecksConclusionIsFailureWithQualityGateResult(QualityGateCriticality.FAILURE); } /** @@ -133,7 +135,7 @@ void shouldConcludeChecksAsFailureWhenQualityGateIsFailed() { */ @Test void shouldConcludeChecksAsFailureWhenQualityGateResultIsUnstable() { - assertChecksConclusionIsFailureWithQualityGateResult(QualityGateResult.UNSTABLE); + assertChecksConclusionIsFailureWithQualityGateResult(QualityGateCriticality.UNSTABLE); } /** @@ -264,10 +266,10 @@ void shouldUseDefaultChecksNamePublishIssues() { assertThat(publishedChecks).hasSize(1); - assertThat(publishedChecks.get(0).getName()).isPresent().get().isEqualTo("CheckStyle"); + assertThat(publishedChecks.get(0).getName()).contains("CheckStyle"); assertThat(publishedChecks.get(0).getOutput()).isPresent().hasValueSatisfying( - output -> assertThat(output.getTitle()).isPresent().get().isEqualTo("No new issues, 6 total")); + output -> assertThat(output.getTitle()).contains("No new issues, 6 total")); } /** @@ -295,10 +297,10 @@ private void buildCheckForNewAndOutstandingWarnings(final AnnotationScope scope, assertThat(publishedChecks).hasSize(1); ChecksDetails details = publishedChecks.get(0); - assertThat(details.getName()).isPresent().get().isEqualTo("CheckStyle"); + assertThat(details.getName()).contains("CheckStyle"); assertThat(details.getOutput()).isPresent().hasValueSatisfying( output -> { - assertThat(output.getTitle()).isPresent().get().isEqualTo("2 new issues, 6 total"); + assertThat(output.getTitle()).contains("2 new issues, 6 total"); assertThat(output.getChecksAnnotations()).hasSize(expectedSize); }); } @@ -317,9 +319,9 @@ void shouldUseDefaultChecksNameRecordIssues() { assertThat(publishedChecks).hasSize(1); ChecksDetails details = publishedChecks.get(0); - assertThat(details.getName()).isPresent().get().isEqualTo("CheckStyle"); + assertThat(details.getName()).contains("CheckStyle"); assertThat(details.getOutput()).isPresent().hasValueSatisfying( - output -> assertThat(output.getTitle()).isPresent().get().isEqualTo("No new issues, 6 total")); + output -> assertThat(output.getTitle()).contains("No new issues, 6 total")); } /** @@ -337,10 +339,10 @@ void shouldHonorWithChecksContextPublishIssues() { assertThat(publishedChecks).hasSize( 2); // First from 'In progress' check provided by withChecks, second from publishIssues - publishedChecks.forEach(check -> assertThat(check.getName()).isPresent().get().isEqualTo("Custom Checks Name")); + publishedChecks.forEach(check -> assertThat(check.getName()).contains("Custom Checks Name")); assertThat(publishedChecks.get(1).getOutput()).isPresent().hasValueSatisfying( - output -> assertThat(output.getTitle()).isPresent().get().isEqualTo("No new issues, 6 total")); + output -> assertThat(output.getTitle()).contains("No new issues, 6 total")); } /** @@ -358,10 +360,10 @@ void shouldHonorWithChecksContextRecordIssues() { assertThat(publishedChecks).hasSize( 2); // First from 'In progress' check provided by withChecks, second from recordIssues - publishedChecks.forEach(check -> assertThat(check.getName()).isPresent().get().isEqualTo("Custom Checks Name")); + publishedChecks.forEach(check -> assertThat(check.getName()).contains("Custom Checks Name")); assertThat(publishedChecks.get(1).getOutput()).isPresent().hasValueSatisfying( - output -> assertThat(output.getTitle()).isPresent().get().isEqualTo("No new issues, 6 total")); + output -> assertThat(output.getTitle()).contains("No new issues, 6 total")); } /** @@ -452,15 +454,15 @@ private IssuesRecorder enableAndConfigureCheckstyle(final AbstractProject return item; } - private void assertChecksConclusionIsFailureWithQualityGateResult(final QualityGateResult qualityGateResult) { + private void assertChecksConclusionIsFailureWithQualityGateResult(final QualityGateCriticality criticality) { FreeStyleProject project = createFreeStyleProjectWithWorkspaceFilesWithSuffix(NEW_CHECKSTYLE_REPORT); - enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(1, QualityGateType.TOTAL, qualityGateResult)); + enableAndConfigureCheckstyle(project, recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(1, QualityGateType.TOTAL, criticality)))); - Run build = buildWithResult(project, qualityGateResult.getStatus().getResult()); + Run build = buildWithResult(project, criticality.getStatus().getResult()); assertThat(getAnalysisResult(build)) .hasTotalSize(6) - .hasQualityGateStatus(qualityGateResult.getStatus()); + .hasQualityGateStatus(criticality.getStatus()); WarningChecksPublisher publisher = new WarningChecksPublisher(getResultAction(build), TaskListener.NULL, null); assertThat(publisher.extractChecksDetails(AnnotationScope.PUBLISH_NEW_ISSUES).getConclusion()) diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateEvaluatorTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateEvaluatorTest.java index e3978b4bd9..ecd532da13 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateEvaluatorTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateEvaluatorTest.java @@ -5,66 +5,65 @@ import java.util.function.Function; import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.DefaultLocale; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator.FormattedLogger; +import edu.hm.hafner.util.FilteredLog; + +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; +import io.jenkins.plugins.util.NullResultHandler; +import io.jenkins.plugins.util.QualityGate.QualityGateCriticality; +import io.jenkins.plugins.util.QualityGateResult; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; -/** - * Tests the class {@link QualityGateEvaluator}. - * - * @author Ullrich Hafner - */ @SuppressWarnings("PMD.MoreThanOneLogger") +@DefaultLocale("en") class QualityGateEvaluatorTest { @Test void shouldBeInactiveIfGatesAreEmpty() { - Logger logger = new Logger(); - IssuesStatisticsBuilder builder = new IssuesStatisticsBuilder(); - - QualityGateEvaluator qualityGate = new QualityGateEvaluator(); + var log = new FilteredLog(); + var result = evaluate(List.of(), new IssuesStatisticsBuilder(), log); - assertThat(qualityGate.evaluate(builder.build(), logger)).isEqualTo(QualityGateStatus.INACTIVE); - - assertThat(logger.getMessages()).containsExactly( - "-> INACTIVE - No quality gate defined"); + assertThat(result.getOverallStatus()) + .isEqualTo(io.jenkins.plugins.util.QualityGateStatus.INACTIVE); + assertThat(log.getInfoMessages()) + .containsExactly("No quality gates have been set - skipping"); } @Test void shouldHandleNegativeDeltaValues() { - Logger logger = new Logger(); + List qualityGates = new ArrayList<>(); + qualityGates.add(addQualityGate(1, QualityGateType.DELTA, QualityGateCriticality.UNSTABLE)); - IssuesStatisticsBuilder builder = new IssuesStatisticsBuilder(); + IssuesStatisticsBuilder builder = new IssuesStatisticsBuilder().setDeltaErrorSize(-1); - QualityGateEvaluator qualityGate = new QualityGateEvaluator(); + var result = evaluate(qualityGates, builder, new FilteredLog()); - qualityGate.add(1, QualityGateType.DELTA, QualityGateResult.UNSTABLE); - assertThat(qualityGate.evaluate(builder.setDeltaErrorSize(-1).build(), logger)).isEqualTo(QualityGateStatus.PASSED); - assertThat(logger.getMessages()).containsExactly( - "-> PASSED - " + QualityGateType.DELTA.getDisplayName() + ": -1 - Quality Gate: 1"); + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.PASSED); + assertThat(result.getMessages()).hasSize(1).first().asString() + .contains("≪Success≫", QualityGateType.DELTA.getDisplayName(), "Actual value: -1", "Quality gate: 1.00"); } @Test void shouldPassIfSizesAreZero() { - Logger logger = new Logger(); + List qualityGates = new ArrayList<>(); + qualityGates.add(addQualityGate(1, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE)); IssuesStatisticsBuilder builder = new IssuesStatisticsBuilder(); - QualityGateEvaluator qualityGate = new QualityGateEvaluator(); + var result = evaluate(qualityGates, builder, new FilteredLog()); + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.PASSED); + assertThat(result.getMessages()).hasSize(1).first().asString() + .contains("≪Success≫", QualityGateType.TOTAL.getDisplayName(), "Actual value: 0", "Quality gate: 1.00"); - qualityGate.add(1, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); - assertThat(qualityGate.evaluate(builder.build(), logger)).isEqualTo(QualityGateStatus.PASSED); - assertThat(logger.getMessages()).containsExactly( - "-> PASSED - " + QualityGateType.TOTAL.getDisplayName() + ": 0 - Quality Gate: 1"); + qualityGates.add(addQualityGate(1, QualityGateType.NEW, QualityGateCriticality.UNSTABLE)); - logger.clear(); - qualityGate.add(1, QualityGateType.NEW, QualityGateResult.UNSTABLE); - assertThat(qualityGate.evaluate(builder.build(), logger)).isEqualTo(QualityGateStatus.PASSED); - assertThat(logger.getMessages()).containsExactly( - "-> PASSED - " + QualityGateType.TOTAL.getDisplayName() + ": 0 - Quality Gate: 1", - "-> PASSED - " + QualityGateType.NEW.getDisplayName() + ": 0 - Quality Gate: 1"); + result = evaluate(qualityGates, builder, new FilteredLog()); + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.PASSED); + assertThat(result.getMessages()).hasSize(2).first().asString() + .contains("≪Success≫", QualityGateType.TOTAL.getDisplayName(), "Actual value: 0", "Quality gate: 1.00"); + assertThat(result.getMessages()).hasSize(2).last().asString() + .contains("≪Success≫", QualityGateType.NEW.getDisplayName(), "Actual value: 0", "Quality gate: 1.00"); } @Test @@ -95,107 +94,125 @@ private void evaluateQualityGateFor(final IssuesStatisticsBuilder builder, final QualityGateType type) { builder.clear(); - Logger logger = new Logger(); + List qualityGates = new ArrayList<>(); + qualityGates.add(addQualityGate(1, type, QualityGateCriticality.UNSTABLE)); - QualityGateEvaluator qualityGate = new QualityGateEvaluator(); + var result = evaluate(qualityGates, builder, new FilteredLog()); + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.PASSED); + assertThat(result.getMessages()).hasSize(1).first().asString() + .contains("≪Success≫", type.getDisplayName(), "Actual value: 0", "Quality gate: 1.00"); - qualityGate.add(1, type, QualityGateResult.UNSTABLE); - - assertThat(qualityGate.evaluate(builder.build(), logger)).isEqualTo(QualityGateStatus.PASSED); - assertThat(logger.getMessages()).containsExactly( - "-> PASSED - " + type.getDisplayName() + ": 0 - Quality Gate: 1"); - - logger.clear(); setter.apply(1); - assertThat(qualityGate.evaluate(builder.build(), logger)).isEqualTo(QualityGateStatus.WARNING); - assertThat(logger.getMessages()).containsExactly( - "-> WARNING - " + type.getDisplayName() + ": 1 - Quality Gate: 1"); + result = evaluate(qualityGates, builder, new FilteredLog()); + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.WARNING); + assertThat(result.getMessages()).hasSize(1).first().asString() + .contains("≪Unstable≫", type.getDisplayName(), "Actual value: 1", "Quality gate: 1.00"); } @Test void shouldFailIfSizeIsEqual() { - Logger logger = new Logger(); + List qualityGates = new ArrayList<>(); + qualityGates.add(addQualityGate(1, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE)); IssuesStatisticsBuilder builder = new IssuesStatisticsBuilder(); + builder.setTotalNormalSize(1); + + var result = evaluate(qualityGates, builder, new FilteredLog()); + + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.WARNING); + assertThat(result.getMessages()).hasSize(1).first().asString() + .contains("≪Unstable≫", QualityGateType.TOTAL.getDisplayName(), "Actual value: 1", "Quality gate: 1.00"); - QualityGateEvaluator qualityGate = new QualityGateEvaluator(); + qualityGates.add(addQualityGate(1, QualityGateType.NEW, QualityGateCriticality.UNSTABLE)); + builder.setNewNormalSize(1); - qualityGate.add(1, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); - assertThat(qualityGate.evaluate(builder.setTotalNormalSize(1).build(), logger)).isEqualTo(QualityGateStatus.WARNING); - assertThat(logger.getMessages()).containsExactly( - "-> WARNING - " + QualityGateType.TOTAL.getDisplayName() + ": 1 - Quality Gate: 1"); + result = evaluate(qualityGates, builder, new FilteredLog()); - logger.clear(); - qualityGate.add(1, QualityGateType.NEW, QualityGateResult.UNSTABLE); - assertThat(qualityGate.evaluate(builder.setNewNormalSize(1).build(), logger)).isEqualTo(QualityGateStatus.WARNING); - assertThat(logger.getMessages()).containsExactly( - "-> WARNING - " + QualityGateType.TOTAL.getDisplayName() + ": 1 - Quality Gate: 1", - "-> WARNING - " + QualityGateType.NEW.getDisplayName() + ": 1 - Quality Gate: 1"); + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.WARNING); + assertThat(result.getMessages()).hasSize(2).first().asString() + .contains("≪Unstable≫", QualityGateType.TOTAL.getDisplayName(), "Actual value: 1", "Quality gate: 1.00"); + assertThat(result.getMessages()).hasSize(2).last().asString() + .contains("≪Unstable≫", QualityGateType.NEW.getDisplayName(), "Actual value: 1", "Quality gate: 1.00"); + } + + private QualityGateResult evaluate(final List qualityGates, + final IssuesStatisticsBuilder builder, final FilteredLog log) { + return createEvaluator(qualityGates, builder).evaluate(new NullResultHandler(), log); + } + + private WarningsQualityGate addQualityGate(final int threshold, final QualityGateType qualityGateType, + final QualityGateCriticality qualityGateCriticality) { + return new WarningsQualityGate(threshold, qualityGateType, qualityGateCriticality); } @Test void shouldIgnoreThresholdZero() { - Logger logger = new Logger(); + List qualityGates = new ArrayList<>(); + qualityGates.add(addQualityGate(0, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE)); IssuesStatisticsBuilder builder = new IssuesStatisticsBuilder(); + builder.setTotalNormalSize(1); - QualityGateEvaluator qualityGate = new QualityGateEvaluator(); + var result = evaluate(qualityGates, builder, new FilteredLog()); + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.INACTIVE); + assertThat(result.getMessages()).hasSize(1).first().asString() + .contains("≪Not built≫", QualityGateType.TOTAL.getDisplayName(), "Actual value: Threshold too small: 0.0", "Quality gate: 0.00"); - qualityGate.add(0, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); - assertThat(qualityGate.evaluate(builder.setTotalNormalSize(1).build(), logger)).isEqualTo(QualityGateStatus.PASSED); - assertThat(logger.getMessages()).isEmpty(); + qualityGates.add(addQualityGate(0, QualityGateType.NEW, QualityGateCriticality.UNSTABLE)); + builder.setNewNormalSize(1); - qualityGate.add(0, QualityGateType.NEW, QualityGateResult.UNSTABLE); - assertThat(qualityGate.evaluate(builder.setNewNormalSize(1).build(), logger)).isEqualTo(QualityGateStatus.PASSED); - assertThat(logger.getMessages()).isEmpty(); + result = evaluate(qualityGates, builder, new FilteredLog()); + + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.INACTIVE); + assertThat(result.getMessages()).hasSize(2).first().asString() + .contains("≪Not built≫", QualityGateType.TOTAL.getDisplayName(), "Actual value: Threshold too small: 0.0", "Quality gate: 0.00"); + assertThat(result.getMessages()).hasSize(2).last().asString() + .contains("≪Not built≫", QualityGateType.NEW.getDisplayName(), "Actual value: Threshold too small: 0.0", "Quality gate: 0.00"); } @Test void shouldOverrideWarningWithFailure() { - Logger logger = new Logger(); + List qualityGates = new ArrayList<>(); + qualityGates.add(addQualityGate(1, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE)); + qualityGates.add(addQualityGate(2, QualityGateType.TOTAL, QualityGateCriticality.FAILURE)); IssuesStatisticsBuilder builder = new IssuesStatisticsBuilder(); + builder.setTotalNormalSize(1); - QualityGateEvaluator qualityGate = new QualityGateEvaluator(); + var result = evaluate(qualityGates, builder, new FilteredLog()); + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.WARNING); + assertThat(result.getMessages()).hasSize(2).first().asString() + .contains("≪Unstable≫", QualityGateType.TOTAL.getDisplayName(), "Actual value: 1", "Quality gate: 1.00"); + assertThat(result.getMessages()).hasSize(2).last().asString() + .contains("≪Success≫", QualityGateType.TOTAL.getDisplayName(), "Actual value: 1", "Quality gate: 2.00"); - qualityGate.add(1, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); - qualityGate.add(2, QualityGateType.TOTAL, QualityGateResult.FAILURE); - assertThat(qualityGate.evaluate(builder.setTotalNormalSize(1).build(), logger)).isEqualTo(QualityGateStatus.WARNING); - assertThat(logger.getMessages()).containsExactly( - "-> WARNING - " + QualityGateType.TOTAL.getDisplayName() + ": 1 - Quality Gate: 1", - "-> PASSED - " + QualityGateType.TOTAL.getDisplayName() + ": 1 - Quality Gate: 2"); + builder.setTotalNormalSize(2); - logger.clear(); - assertThat(qualityGate.evaluate(builder.setTotalNormalSize(2).build(), logger)).isEqualTo(QualityGateStatus.FAILED); - assertThat(logger.getMessages()).containsExactly( - "-> WARNING - " + QualityGateType.TOTAL.getDisplayName() + ": 2 - Quality Gate: 1", - "-> FAILED - " + QualityGateType.TOTAL.getDisplayName() + ": 2 - Quality Gate: 2"); + result = evaluate(qualityGates, builder, new FilteredLog()); - QualityGateEvaluator other = new QualityGateEvaluator(); + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.FAILED); + assertThat(result.getMessages()).hasSize(2).first().asString() + .contains("≪Unstable≫", QualityGateType.TOTAL.getDisplayName(), "Actual value: 2", "Quality gate: 1.00"); + assertThat(result.getMessages()).hasSize(2).last().asString() + .contains("≪Failed≫", QualityGateType.TOTAL.getDisplayName(), "Actual value: 2", "Quality gate: 2.00"); - other.add(2, QualityGateType.TOTAL, QualityGateResult.FAILURE); - other.add(1, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); - assertThat(other.evaluate(builder.setTotalNormalSize(1).build(), logger)).isEqualTo(QualityGateStatus.WARNING); - assertThat(other.evaluate(builder.setTotalNormalSize(2).build(), logger)).isEqualTo(QualityGateStatus.FAILED); - } + List other = new ArrayList<>(); + other.add(addQualityGate(2, QualityGateType.TOTAL, QualityGateCriticality.FAILURE)); + other.add(addQualityGate(1, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE)); - /** - * Logger for the tests that provides a way verify and clear the messages. - */ - private static class Logger implements FormattedLogger { - private final List messages = new ArrayList<>(); + builder.setTotalNormalSize(1); + result = evaluate(other, builder, new FilteredLog()); - @Override - public void print(final String format, final Object... args) { - messages.add(String.format(format, args)); - } + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.WARNING); - List getMessages() { - return messages; - } + builder.setTotalNormalSize(2); + result = evaluate(other, builder, new FilteredLog()); + + assertThat(result.getOverallStatus()).isEqualTo(io.jenkins.plugins.util.QualityGateStatus.FAILED); + } - void clear() { - messages.clear(); - } + private WarningsQualityGateEvaluator createEvaluator( + final List qualityGates, final IssuesStatisticsBuilder builder) { + return new WarningsQualityGateEvaluator(qualityGates, builder.build()); } } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateTest.java index 0aab4017bc..db65aacc26 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateTest.java @@ -7,12 +7,13 @@ import hudson.model.BuildableItem; import hudson.model.Item; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateDescriptor; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.WarningsQualityGateDescriptor; import io.jenkins.plugins.util.JenkinsFacade; +import io.jenkins.plugins.util.QualityGate; +import io.jenkins.plugins.util.QualityGate.QualityGateCriticality; -import static io.jenkins.plugins.analysis.core.testutil.Assertions.*; +import static io.jenkins.plugins.analysis.core.testutil.FormValidationAssert.*; import static org.mockito.Mockito.*; /** @@ -20,13 +21,13 @@ * * @author Ullrich Hafner */ -class QualityGateTest extends SerializableTest { +class QualityGateTest extends SerializableTest { @Test void shouldValidateThreshold() { JenkinsFacade jenkinsFacade = mock(JenkinsFacade.class); when(jenkinsFacade.hasPermission(Item.CONFIGURE, (BuildableItem) null)).thenReturn(true); - QualityGateDescriptor descriptor = new QualityGateDescriptor(jenkinsFacade); + WarningsQualityGateDescriptor descriptor = new WarningsQualityGateDescriptor(jenkinsFacade); assertThat(descriptor.doCheckThreshold(null, 0)) .isError() @@ -40,7 +41,9 @@ void shouldValidateThreshold() { } @Override - protected QualityGate createSerializable() { - return new QualityGate(1, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); + protected WarningsQualityGate createSerializable() { + var qualityGate = new WarningsQualityGate(1, QualityGateType.TOTAL); + qualityGate.setCriticality(QualityGateCriticality.UNSTABLE); + return qualityGate; } } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/FlexiblePublishITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/FlexiblePublishITest.java index 9b5d5498ac..93877c0ac1 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/FlexiblePublishITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/FlexiblePublishITest.java @@ -20,11 +20,12 @@ import io.jenkins.plugins.analysis.core.model.AnalysisResult; import io.jenkins.plugins.analysis.core.steps.IssuesRecorder; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerSuite; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; import io.jenkins.plugins.analysis.warnings.CheckStyle; import io.jenkins.plugins.analysis.warnings.Java; +import io.jenkins.plugins.util.QualityGate.QualityGateCriticality; +import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; @@ -48,14 +49,16 @@ void shouldAnalyseTwoToolsWithDifferentSettings() { checkStyle.setPattern("**/checkstyle*"); IssuesRecorder checkStyleRecorder = new IssuesRecorder(); checkStyleRecorder.setTools(checkStyle); - checkStyleRecorder.addQualityGate(6, QualityGateType.TOTAL, QualityGateResult.FAILURE); + checkStyleRecorder.setQualityGates(List.of( + new WarningsQualityGate(6, QualityGateType.TOTAL, QualityGateCriticality.FAILURE))); Java java = new Java(); java.setPattern("**/java*"); IssuesRecorder javaRecorder = new IssuesRecorder(); javaRecorder.setTools(java); javaRecorder.setEnabledForFailure(true); - javaRecorder.addQualityGate(2, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); + javaRecorder.setQualityGates(List.of( + new WarningsQualityGate(2, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE))); project.getPublishersList().add(new FlexiblePublisher(Arrays.asList( constructConditionalPublisher(checkStyleRecorder), diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/DryITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/DryITest.java index 55b4975240..4ac87326bf 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/DryITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/DryITest.java @@ -13,12 +13,12 @@ import io.jenkins.plugins.analysis.core.model.IssuesDetail; import io.jenkins.plugins.analysis.core.model.ResultAction; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerSuite; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.analysis.warnings.Cpd; import io.jenkins.plugins.analysis.warnings.DuplicateCodeScanner; import io.jenkins.plugins.analysis.warnings.DuplicateCodeScanner.DryModel.DuplicationRow; import io.jenkins.plugins.datatables.TableColumn; import io.jenkins.plugins.datatables.TableModel; +import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; @@ -179,7 +179,7 @@ void shouldDifferInAmountOfDuplicateWarningForPriorities() { } private TableModel getDryTableModel(final Run build) { - IssuesDetail issuesDetail = (IssuesDetail) build.getAction(ResultAction.class).getTarget(); + IssuesDetail issuesDetail = build.getAction(ResultAction.class).getTarget(); return issuesDetail.getTableModel("issues"); } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/FilesScannerITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/FilesScannerITest.java index 0d2b740df6..104670e32e 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/FilesScannerITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/FilesScannerITest.java @@ -5,6 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import org.junit.jupiter.api.Test; import org.opentest4j.TestAbortedException; @@ -18,10 +19,11 @@ import io.jenkins.plugins.analysis.core.model.IssueReportScanner; import io.jenkins.plugins.analysis.core.steps.IssuesRecorder; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerSuite; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; import io.jenkins.plugins.analysis.warnings.CheckStyle; +import io.jenkins.plugins.util.QualityGate.QualityGateCriticality; +import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; import static org.assertj.core.api.Assumptions.*; @@ -126,7 +128,8 @@ void filePatternDoesNotMatchAnyFile() { void findIssuesWithMultipleFiles() { FreeStyleProject project = createJobWithWorkspaceFile(MULTIPLE_FILES_WORKSPACE); IssuesRecorder recorder = enableWarnings(project, createTool(new CheckStyle(), "*.xml")); - recorder.addQualityGate(6, QualityGateType.TOTAL, QualityGateResult.FAILURE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(6, QualityGateType.TOTAL, QualityGateCriticality.FAILURE))); AnalysisResult result = scheduleBuildAndAssertStatus(project, Result.FAILURE); @@ -163,7 +166,8 @@ void findIssuesWithMultipleFilesReachableWithSymbolicLinks() { createSymbolicLinkAssumingSupported(realPath, subdirPath.resolve("link_to_actual_files")); IssuesRecorder recorder = enableWarnings(project, createTool(new CheckStyle(), false)); - recorder.addQualityGate(6, QualityGateType.TOTAL, QualityGateResult.FAILURE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(6, QualityGateType.TOTAL, QualityGateCriticality.FAILURE))); AnalysisResult result = scheduleBuildAndAssertStatus(project, Result.FAILURE); @@ -202,7 +206,8 @@ void findNoIssuesWithMultipleFilesReachableWithSymlinksWithSkipSymbolicLinks() { createSymbolicLinkAssumingSupported(realPath, subdirPath.resolve("link_to_actual_files")); IssuesRecorder recorder = enableWarnings(project, createTool(new CheckStyle(), true)); - recorder.addQualityGate(6, QualityGateType.TOTAL, QualityGateResult.FAILURE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(6, QualityGateType.TOTAL, QualityGateCriticality.FAILURE))); AnalysisResult result = scheduleBuildAndAssertStatus(project, Result.SUCCESS); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java index 6c6975779f..7f2ce6856f 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java @@ -30,7 +30,6 @@ import io.jenkins.plugins.analysis.core.portlets.PullRequestMonitoringPortlet; import io.jenkins.plugins.analysis.core.steps.IssuesRecorder; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerSuite; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.analysis.warnings.CheckStyle; import io.jenkins.plugins.analysis.warnings.Eclipse; import io.jenkins.plugins.analysis.warnings.FindBugs; @@ -38,6 +37,7 @@ import io.jenkins.plugins.analysis.warnings.Pmd; import io.jenkins.plugins.analysis.warnings.RegisteredParser; import io.jenkins.plugins.analysis.warnings.tasks.OpenTasks; +import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.*; @@ -206,9 +206,7 @@ void shouldCreateSingleActionIfAggregationEnabled() { assertThat(result).hasQualityGateStatus(QualityGateStatus.INACTIVE); assertThat(result.getIssues().getOriginReportFiles()).satisfiesExactlyInAnyOrder( first -> assertThat(first).endsWith("checkstyle-issues.txt"), - second -> assertThat(second).endsWith("pmd-warnings-issues.txt") - ); - + second -> assertThat(second).endsWith("pmd-warnings-issues.txt")); } private List runJobWithAggregation(final boolean isAggregationEnabled) { diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java index ab6ffbdf56..ae8a610cfc 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java @@ -1,5 +1,6 @@ package io.jenkins.plugins.analysis.warnings.steps; +import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -19,17 +20,18 @@ import io.jenkins.plugins.analysis.core.portlets.PullRequestMonitoringPortlet; import io.jenkins.plugins.analysis.core.steps.IssuesRecorder; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerSuite; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; -import io.jenkins.plugins.analysis.core.util.QualityGateEvaluator; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; import io.jenkins.plugins.analysis.warnings.CheckStyle; +import io.jenkins.plugins.util.QualityGate.QualityGateCriticality; +import io.jenkins.plugins.util.QualityGateEvaluator; +import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; /** * Tests the {@link QualityGateEvaluator}. The file 'checkstyle-quality-gate.xml' is being used for the tests. It - * contains 11 issues overall, from which 6 have high, 2 have normal and 3 have low severity. + * contains 11 issues overall, from which 6 have high, 2 have normal, and 3 have low severity. * * @author Michaela Reitschuster */ @@ -48,7 +50,8 @@ class QualityGateITest extends IntegrationTestWithJenkinsPerSuite { void shouldBePassedForFirstBuildWithDelta() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(11, QualityGateType.DELTA, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(11, QualityGateType.DELTA, QualityGateCriticality.UNSTABLE)))); copyMultipleFilesToWorkspaceWithSuffix(project, REPORT_FILE); scheduleBuildAndAssertStatus(project, Result.SUCCESS, QualityGateStatus.PASSED); @@ -62,7 +65,8 @@ void shouldBePassedForFirstBuildWithDelta() { void shouldBePassedForFirstBuildWithNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(11, QualityGateType.NEW, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(11, QualityGateType.NEW, QualityGateCriticality.UNSTABLE)))); copyMultipleFilesToWorkspaceWithSuffix(project, REPORT_FILE); scheduleBuildAndAssertStatus(project, Result.SUCCESS, QualityGateStatus.PASSED); @@ -76,7 +80,8 @@ void shouldBePassedForFirstBuildWithNew() { void shouldCreateUnstableResult() { FreeStyleProject project = createFreeStyleProjectWithWorkspaceFilesWithSuffix("eclipse.txt"); enableEclipseWarnings(project, - publisher -> publisher.addQualityGate(7, QualityGateType.TOTAL, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(7, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE)))); AnalysisResult result = scheduleBuildAndAssertStatus(project, Result.UNSTABLE); @@ -91,7 +96,8 @@ void shouldCreateUnstableResult() { void shouldBeUnstableWhenUnstableDeltaAllIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(11, QualityGateType.DELTA, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(11, QualityGateType.DELTA, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -102,7 +108,8 @@ void shouldBeUnstableWhenUnstableDeltaAllIsReachedNew() { void shouldBeUnstableWhenUnstableNewAllIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(11, QualityGateType.NEW, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(11, QualityGateType.NEW, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -113,7 +120,8 @@ void shouldBeUnstableWhenUnstableNewAllIsReachedNew() { void shouldBeUnstableWhenUnstableDeltaErrorIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(6, QualityGateType.DELTA_ERROR, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(6, QualityGateType.DELTA_ERROR, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -125,7 +133,8 @@ void shouldBeUnstableWhenUnstableDeltaErrorIsReachedNew() { void shouldBeUnstableWhenUnstableNewErrorIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(6, QualityGateType.NEW_ERROR, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(6, QualityGateType.NEW_ERROR, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -136,7 +145,8 @@ void shouldBeUnstableWhenUnstableNewErrorIsReachedNew() { void shouldBeUnstableWhenUnstableDeltaNormalIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(2, QualityGateType.DELTA_NORMAL, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(2, QualityGateType.DELTA_NORMAL, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -148,7 +158,8 @@ void shouldBeUnstableWhenUnstableDeltaNormalIsReachedNew() { void shouldBeUnstableWhenUnstableNewNormalIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(2, QualityGateType.NEW_NORMAL, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(2, QualityGateType.NEW_NORMAL, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -160,7 +171,8 @@ void shouldBeUnstableWhenUnstableNewNormalIsReachedNew() { void shouldBeUnstableWhenUnstableDeltaLowIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(3, QualityGateType.DELTA_LOW, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.DELTA_LOW, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -172,7 +184,8 @@ void shouldBeUnstableWhenUnstableDeltaLowIsReachedNew() { void shouldBeUnstableWhenUnstableNewLowIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(3, QualityGateType.NEW_LOW, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW_LOW, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -183,7 +196,8 @@ void shouldBeUnstableWhenUnstableNewLowIsReachedNew() { void shouldBeUnstableWhenUnstableTotalAllIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(11, QualityGateType.TOTAL, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(11, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -195,7 +209,8 @@ void shouldBeUnstableWhenUnstableTotalAllIsReachedNew() { void shouldBeUnstableWhenUnstableTotalErrorIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(6, QualityGateType.TOTAL_ERROR, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(6, QualityGateType.TOTAL_ERROR, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -207,7 +222,8 @@ void shouldBeUnstableWhenUnstableTotalErrorIsReachedNew() { void shouldBeUnstableWhenUnstableTotalNormalIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(2, QualityGateType.TOTAL_NORMAL, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(2, QualityGateType.TOTAL_NORMAL, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -219,7 +235,8 @@ void shouldBeUnstableWhenUnstableTotalNormalIsReachedNew() { void shouldBeUnstableWhenUnstableTotalLowIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(3, QualityGateType.TOTAL_LOW, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.TOTAL_LOW, QualityGateCriticality.UNSTABLE)))); runJobTwice(project, Result.UNSTABLE); } @@ -230,7 +247,8 @@ void shouldBeUnstableWhenUnstableTotalLowIsReachedNew() { void shouldBeFailureWhenFailedNewAllIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(9, QualityGateType.NEW, QualityGateResult.FAILURE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(9, QualityGateType.NEW, QualityGateCriticality.FAILURE)))); runJobTwice(project, Result.FAILURE); } @@ -242,7 +260,8 @@ void shouldBeFailureWhenFailedNewAllIsReachedNew() { void shouldBeFailureWhenFailedNewErrorIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(6, QualityGateType.NEW_ERROR, QualityGateResult.FAILURE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(6, QualityGateType.NEW_ERROR, QualityGateCriticality.FAILURE)))); runJobTwice(project, Result.FAILURE); } @@ -254,7 +273,8 @@ void shouldBeFailureWhenFailedNewErrorIsReachedNew() { void shouldBeFailureWhenFailedNewNormalIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(2, QualityGateType.NEW_NORMAL, QualityGateResult.FAILURE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(2, QualityGateType.NEW_NORMAL, QualityGateCriticality.FAILURE)))); runJobTwice(project, Result.FAILURE); } @@ -266,7 +286,8 @@ void shouldBeFailureWhenFailedNewNormalIsReachedNew() { void shouldBeFailureWhenFailedNewLowIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(3, QualityGateType.NEW_LOW, QualityGateResult.FAILURE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW_LOW, QualityGateCriticality.FAILURE)))); runJobTwice(project, Result.FAILURE); } @@ -277,7 +298,8 @@ void shouldBeFailureWhenFailedNewLowIsReachedNew() { void shouldBeFailureWhenFailureTotalAllIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(11, QualityGateType.TOTAL, QualityGateResult.FAILURE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(11, QualityGateType.TOTAL, QualityGateCriticality.FAILURE)))); runJobTwice(project, Result.FAILURE); } @@ -288,7 +310,8 @@ void shouldBeFailureWhenFailureTotalAllIsReachedNew() { void shouldBeFailureWhenFailureTotalErrorIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(6, QualityGateType.TOTAL_ERROR, QualityGateResult.FAILURE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(6, QualityGateType.TOTAL_ERROR, QualityGateCriticality.FAILURE)))); runJobTwice(project, Result.FAILURE); } @@ -300,7 +323,8 @@ void shouldBeFailureWhenFailureTotalErrorIsReachedNew() { void shouldBeFailureWhenFailureTotalNormalIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(2, QualityGateType.TOTAL_NORMAL, QualityGateResult.FAILURE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(2, QualityGateType.TOTAL_NORMAL, QualityGateCriticality.FAILURE)))); runJobTwice(project, Result.FAILURE); } @@ -311,7 +335,8 @@ void shouldBeFailureWhenFailureTotalNormalIsReachedNew() { void shouldBeFailureWhenFailureTotalLowIsReachedLow() { FreeStyleProject project = createFreeStyleProject(); enableAndConfigureCheckstyle(project, - recorder -> recorder.addQualityGate(3, QualityGateType.TOTAL_LOW, QualityGateResult.FAILURE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.TOTAL_LOW, QualityGateCriticality.FAILURE)))); runJobTwice(project, Result.FAILURE); } @@ -321,10 +346,9 @@ void shouldBeFailureWhenFailureTotalLowIsReachedLow() { @Test void shouldOverrideUnstableWhenFailureAndUnstableThresholdIsReachedNew() { FreeStyleProject project = createFreeStyleProject(); - enableAndConfigureCheckstyle(project, recorder -> { - recorder.addQualityGate(1, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); - recorder.addQualityGate(3, QualityGateType.TOTAL_LOW, QualityGateResult.FAILURE); - }); + enableAndConfigureCheckstyle(project, recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(1, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE), + new WarningsQualityGate(3, QualityGateType.TOTAL_LOW, QualityGateCriticality.FAILURE)))); runJobTwice(project, Result.FAILURE); } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ReferenceFinderITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ReferenceFinderITest.java index 0d7d1bcc6a..93c78ec5f8 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ReferenceFinderITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ReferenceFinderITest.java @@ -1,5 +1,6 @@ package io.jenkins.plugins.analysis.warnings.steps; +import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -18,10 +19,11 @@ import io.jenkins.plugins.analysis.core.model.ResetQualityGateCommand; import io.jenkins.plugins.analysis.core.steps.IssuesRecorder; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerTest; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateResult; -import io.jenkins.plugins.analysis.core.util.QualityGate.QualityGateType; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; import io.jenkins.plugins.analysis.warnings.Java; +import io.jenkins.plugins.util.QualityGate.QualityGateCriticality; +import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; @@ -39,8 +41,8 @@ class ReferenceFinderITest extends IntegrationTestWithJenkinsPerTest { private static final String PUBLISH_ISSUES_STEP = "publishIssues issues:[issues]"; /** - * Creates a reference job and starts a build having 2 warnings. Then two builds for the job. Then another job is created that - * uses the first build as a reference. Verifies that the association is correctly stored. + * Creates a reference job and starts a build having 2 warnings. Then two builds for the job. Then another job is + * created that uses the first build as a reference. Verifies that the association is correctly stored. */ // TODO: The functionality within this test is deprecated and will be removed in a future release @Test @@ -108,8 +110,8 @@ void shouldUseOtherJobAsReference() { } /** - * Creates a reference job without builds, then builds another job, referring to the reference job that does - * not contain a valid build. + * Creates a reference job without builds, then builds another job, referring to the reference job that does not + * contain a valid build. */ @Test void shouldHandleMissingJobBuildAsReference() { @@ -142,7 +144,8 @@ void shouldHandleMissingJobBuildAsReference() { void shouldResetReference() { // #1 SUCCESS FreeStyleProject project = createJob(JOB_NAME, "eclipse2Warnings.txt"); - enableWarnings(project, recorder -> recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE)); + enableWarnings(project, recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE)))); scheduleBuildAndAssertStatus(project, Result.SUCCESS, analysisResult -> assertThat(analysisResult) .hasTotalSize(2) @@ -206,7 +209,8 @@ void shouldResetReference() { void shouldCreateSuccessResultWithIgnoredUnstableInBetween() { // #1 SUCCESS FreeStyleProject project = createJob(JOB_NAME, "eclipse2Warnings.txt"); - enableWarnings(project, recorder -> recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE)); + enableWarnings(project, recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE)))); Run expectedReference = scheduleBuildAndAssertStatus(project, Result.SUCCESS, analysisResult -> assertThat(analysisResult) .hasTotalSize(2) @@ -244,7 +248,8 @@ private void createResetAction(final Run unstable, final String id) { void shouldCreateUnstableResultWithIgnoredUnstableInBetween() { // #1 SUCCESS FreeStyleProject project = createJob(JOB_NAME, "eclipse2Warnings.txt"); - enableWarnings(project, recorder -> recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE)); + enableWarnings(project, recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE)))); Run expectedReference = scheduleBuildAndAssertStatus(project, Result.SUCCESS, analysisResult -> assertThat(analysisResult).hasTotalSize(2) .hasNewSize(0) @@ -275,7 +280,8 @@ void shouldCreateSuccessResultWithNotIgnoredUnstableInBetween() { FreeStyleProject project = createJob(JOB_NAME, "eclipse2Warnings.txt"); enableWarnings(project, recorder -> { recorder.setIgnoreQualityGate(true); - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); }); scheduleBuildAndAssertStatus(project, Result.SUCCESS, analysisResult -> assertThat(analysisResult).hasTotalSize(2) @@ -313,15 +319,18 @@ void shouldCreateUnstableResultWithNotIgnoredUnstableInBetween() { // #2 UNSTABLE cleanAndCopy(project, "eclipse4Warnings.txt"); - issuesRecorder.addQualityGate(3, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); + issuesRecorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE))); Run expectedReference = scheduleBuildAndAssertStatus(project, Result.UNSTABLE, analysisResult -> assertThat(analysisResult).hasTotalSize(4) .hasQualityGateStatus(QualityGateStatus.WARNING)).getOwner(); // #3 UNSTABLE (Reference #2) cleanAndCopy(project, "eclipse8Warnings.txt"); - issuesRecorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); - issuesRecorder.addQualityGate(9, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); + issuesRecorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); + issuesRecorder.setQualityGates(List.of( + new WarningsQualityGate(9, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE))); scheduleBuildAndAssertStatus(project, Result.UNSTABLE, analysisResult -> assertThat(analysisResult) .hasTotalSize(8) @@ -341,7 +350,8 @@ void shouldCreateUnstableResultWithOverAllMustBeSuccess() { enableWarnings(project, recorder -> { recorder.setIgnoreFailedBuilds(true); recorder.setEnabledForFailure(true); - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); }); Run expectedReference = scheduleBuildAndAssertStatus(project, Result.SUCCESS, analysisResult -> assertThat(analysisResult).hasTotalSize(2) @@ -385,7 +395,8 @@ void shouldCreateSuccessResultWithOverAllMustBeSuccess() { // #2 FAILURE cleanAndCopy(project, "eclipse2Warnings.txt"); - issuesRecorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + issuesRecorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); Builder failureStep = addFailureStep(project); scheduleBuildAndAssertStatus(project, Result.FAILURE, analysisResult -> assertThat(analysisResult).hasTotalSize(2) @@ -421,7 +432,8 @@ void shouldCreateUnstableResultWithOverAllMustNotBeSuccess() { // #2 FAILURE cleanAndCopy(project, "eclipse2Warnings.txt"); - issuesRecorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + issuesRecorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); Builder failureStep = addFailureStep(project); Run expectedReference = scheduleBuildAndAssertStatus(project, Result.FAILURE, analysisResult -> assertThat(analysisResult).hasTotalSize(2) @@ -450,7 +462,8 @@ void shouldCreateSuccessResultWithOverAllMustNotBeSuccess() { enableWarnings(project, recorder -> { recorder.setIgnoreFailedBuilds(false); recorder.setEnabledForFailure(true); - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); }); scheduleBuildAndAssertStatus(project, Result.SUCCESS, analysisResult -> assertThat(analysisResult).hasTotalSize(2) @@ -484,7 +497,8 @@ void shouldCreateSuccessResultWithIgnoredUnstableInBetweenWithReferenceBuild() { // #1 SUCCESS FreeStyleProject reference = createJob(REFERENCE_JOB_NAME, "eclipse2Warnings.txt"); enableWarnings(reference, - recorder -> recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE)); + recorder -> recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE)))); Run expectedReference = scheduleBuildAndAssertStatus(reference, Result.SUCCESS, analysisResult -> assertThat(analysisResult).hasTotalSize(2) .hasNewSize(0) @@ -500,9 +514,11 @@ void shouldCreateSuccessResultWithIgnoredUnstableInBetweenWithReferenceBuild() { // #1 SUCCESS (Reference #1) FreeStyleProject project = createJob(JOB_NAME, "eclipse4Warnings.txt"); enableWarnings(project, recorder -> { - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); recorder.setReferenceJobName(REFERENCE_JOB_NAME); - recorder.addQualityGate(7, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(7, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE))); }); scheduleBuildAndAssertStatus(project, Result.SUCCESS, analysisResult -> assertThat(analysisResult) .hasTotalSize(4) @@ -520,7 +536,8 @@ void shouldCreateUnstableResultWithIgnoredUnstableInBetweenWithReferenceBuild() // #1 SUCCESS FreeStyleProject reference = createJob(REFERENCE_JOB_NAME, "eclipse2Warnings.txt"); enableWarnings(reference, recorder -> { - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); recorder.setIgnoreQualityGate(false); }); Run expectedReference = scheduleBuildAndAssertStatus(reference, Result.SUCCESS, @@ -538,7 +555,8 @@ void shouldCreateUnstableResultWithIgnoredUnstableInBetweenWithReferenceBuild() // #1 SUCCESS (Reference #1) FreeStyleProject project = createJob(JOB_NAME, "eclipse8Warnings.txt"); enableWarnings(project, recorder -> { - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); recorder.setReferenceJobName(REFERENCE_JOB_NAME); recorder.setIgnoreQualityGate(false); }); @@ -560,7 +578,8 @@ void shouldCreateSuccessResultWithNotIgnoredUnstableInBetweenWithReferenceBuild( FreeStyleProject reference = createJob(REFERENCE_JOB_NAME, "eclipse2Warnings.txt"); enableWarnings(reference, recorder -> { recorder.setIgnoreQualityGate(true); - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); }); scheduleBuildAndAssertStatus(reference, Result.SUCCESS, analysisResult -> assertThat(analysisResult).hasTotalSize(2) @@ -577,7 +596,8 @@ void shouldCreateSuccessResultWithNotIgnoredUnstableInBetweenWithReferenceBuild( // #1 SUCCESS (Reference #2) FreeStyleProject project = createJob(JOB_NAME, "eclipse8Warnings.txt"); enableWarnings(project, recorder -> { - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); recorder.setReferenceJobName(REFERENCE_JOB_NAME); recorder.setIgnoreQualityGate(true); }); @@ -604,7 +624,8 @@ void shouldCreateUnstableResultWithNotIgnoredUnstableInBetweenWithReferenceBuild // #2 UNSTABLE cleanAndCopy(reference, "eclipse4Warnings.txt"); - issuesRecorder.addQualityGate(3, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); + issuesRecorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE))); Run expectedReference = scheduleBuildAndAssertStatus(reference, Result.UNSTABLE, analysisResult -> assertThat(analysisResult).hasTotalSize(4) @@ -614,10 +635,12 @@ void shouldCreateUnstableResultWithNotIgnoredUnstableInBetweenWithReferenceBuild // #1 SUCCESS (Reference #2) FreeStyleProject project = createJob(JOB_NAME, "eclipse8Warnings.txt"); enableWarnings(project, recorder -> { - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); recorder.setReferenceJobName(REFERENCE_JOB_NAME); recorder.setIgnoreQualityGate(true); - recorder.addQualityGate(9, QualityGateType.TOTAL, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(9, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE))); }); scheduleBuildAndAssertStatus(project, Result.UNSTABLE, analysisResult -> assertThat(analysisResult) .hasTotalSize(8) @@ -637,7 +660,8 @@ void shouldCreateUnstableResultWithOverAllMustBeSuccessWithReferenceBuild() { enableWarnings(reference, recorder -> { recorder.setIgnoreFailedBuilds(true); recorder.setEnabledForFailure(true); - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); }); Run expectedReference = scheduleBuildAndAssertStatus(reference, Result.SUCCESS, analysisResult -> assertThat(analysisResult).hasTotalSize(2) @@ -656,7 +680,8 @@ void shouldCreateUnstableResultWithOverAllMustBeSuccessWithReferenceBuild() { // #1 SUCCESS (Reference #1) FreeStyleProject project = createJob(JOB_NAME, "eclipse6Warnings.txt"); enableWarnings(project, recorder -> { - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); recorder.setReferenceJobName(REFERENCE_JOB_NAME); recorder.setIgnoreFailedBuilds(true); recorder.setEnabledForFailure(true); @@ -687,7 +712,8 @@ void shouldCreateSuccessResultWithOverAllMustBeSuccessWithReferenceBuild() { // #2 FAILURE cleanAndCopy(reference, "eclipse2Warnings.txt"); - issuesRecorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + issuesRecorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); addFailureStep(reference); scheduleBuildAndAssertStatus(reference, Result.FAILURE, analysisResult -> assertThat(analysisResult).hasTotalSize(2) @@ -697,7 +723,8 @@ void shouldCreateSuccessResultWithOverAllMustBeSuccessWithReferenceBuild() { // #1 SUCCESS (Reference #1) FreeStyleProject project = createJob(JOB_NAME, "eclipse6Warnings.txt"); enableWarnings(project, recorder -> { - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); recorder.setReferenceJobName(REFERENCE_JOB_NAME); recorder.setIgnoreFailedBuilds(true); recorder.setEnabledForFailure(true); @@ -728,7 +755,8 @@ void shouldCreateUnstableResultWithOverAllMustNotBeSuccessWithReferenceBuild() { // #2 FAILURE cleanAndCopy(reference, "eclipse2Warnings.txt"); - issuesRecorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + issuesRecorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); Builder failureStep = addFailureStep(reference); Run expectedReference = scheduleBuildAndAssertStatus(reference, Result.FAILURE, analysisResult -> assertThat(analysisResult).hasTotalSize(2) @@ -739,7 +767,8 @@ void shouldCreateUnstableResultWithOverAllMustNotBeSuccessWithReferenceBuild() { // #1 UNSTABLE (Reference #2) FreeStyleProject project = createJob(JOB_NAME, "eclipse6Warnings.txt"); enableWarnings(project, recorder -> { - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); recorder.setReferenceJobName(REFERENCE_JOB_NAME); recorder.setIgnoreFailedBuilds(false); recorder.setEnabledForFailure(true); @@ -762,7 +791,8 @@ void shouldCreateSuccessResultWithOverAllMustNotBeSuccessWithReferenceBuild() { enableWarnings(reference, recorder -> { recorder.setIgnoreFailedBuilds(false); recorder.setEnabledForFailure(true); - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); }); scheduleBuildAndAssertStatus(reference, Result.SUCCESS, analysisResult -> assertThat(analysisResult).hasTotalSize(2) @@ -782,7 +812,8 @@ void shouldCreateSuccessResultWithOverAllMustNotBeSuccessWithReferenceBuild() { // #1 UNSTABLE (Reference #2) FreeStyleProject project = createJob(JOB_NAME, "eclipse6Warnings.txt"); enableWarnings(project, recorder -> { - recorder.addQualityGate(3, QualityGateType.NEW, QualityGateResult.UNSTABLE); + recorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); recorder.setReferenceJobName(REFERENCE_JOB_NAME); recorder.setIgnoreFailedBuilds(false); recorder.setEnabledForFailure(true); From afb3d2af0b742b6779de567310baefcd90342c60 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Mon, 1 Jan 2024 22:54:13 +0100 Subject: [PATCH 02/27] Fix some tests. --- plugin/pom.xml | 2 +- .../jenkins/plugins/analysis/core/model/AnalysisResult.java | 6 +++--- .../plugins/analysis/warnings/steps/FilesScannerITest.java | 4 +++- .../plugins/analysis/warnings/steps/QualityGateITest.java | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/plugin/pom.xml b/plugin/pom.xml index cbf1db7ece..7a823dd158 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -51,7 +51,7 @@ -Djava.awt.headless=true -Xmx1024m -Djenkins.test.timeout=1000 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED 1.29.0-8 - 3.9.0-rc847.81820ecdfde2 + 3.9.0-rc848.2d5e600a_712a_ diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java index 4c451771c2..358ad20497 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java @@ -52,7 +52,7 @@ * @author Ullrich Hafner */ @SuppressFBWarnings(value = "SE, DESERIALIZATION_GADGET", justification = "transient fields are restored using a Jenkins callback (or are checked for null)") -@SuppressWarnings({"PMD.TooManyFields", "PMD.ExcessiveClassLength", "PMD.GodClass", "checkstyle:ClassFanOutComplexity"}) +@SuppressWarnings({"PMD.TooManyFields", "PMD.ExcessiveClassLength", "PMD.GodClass", "checkstyle:ClassFanOutComplexity", "checkstyle:ClassDataAbstractionCoupling"}) public class AnalysisResult implements Serializable, StaticAnalysisRun { private static final long serialVersionUID = 1110545450292087475L; @@ -551,12 +551,12 @@ public int getSuccessfulSinceBuild() { * @see QualityGateEvaluator */ public boolean isSuccessful() { - return qualityGateStatus.isSuccessful(); + return qualityGateResult.isSuccessful(); } @Override public QualityGateStatus getQualityGateStatus() { - return qualityGateStatus; + return qualityGateResult.getOverallStatus(); } @Whitelisted diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/FilesScannerITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/FilesScannerITest.java index 104670e32e..ec2545fd00 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/FilesScannerITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/FilesScannerITest.java @@ -213,7 +213,9 @@ void findNoIssuesWithMultipleFilesReachableWithSymlinksWithSkipSymbolicLinks() { assertThat(result).hasTotalSize(0); assertThat(result).hasInfoMessages( - "-> PASSED - Total (any severity): 0 - Quality Gate: 6"); + "-> All quality gates have been passed", + "-> Details for each quality gate:", + " - [Total (any severity)]: ≪Success≫ - (Actual value: 0, Quality gate: 6.00)"); } private AnalysisModelParser createTool(final AnalysisModelParser tool, final boolean skipSymbolicLinks) { diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java index ae8a610cfc..cfb159aba1 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java @@ -385,7 +385,7 @@ private void scheduleBuildAndAssertStatus(final AbstractProject job, final try { Run build = getJenkins().assertBuildStatus(result, job.scheduleBuild2(0)); ResultAction action = build.getAction(ResultAction.class); - assertThat(action.getResult()).hasQualityGateStatus(expectedQualityGateStatus); + assertThat(action.getResult().getQualityGateResult().getOverallStatus()).isEqualTo(expectedQualityGateStatus); PullRequestMonitoringPortlet portlet = new PullRequestMonitoringPortlet(action); assertThat(portlet.hasQualityGate()).isTrue(); From 69d72f51ed5f0b98ebebe3564f3c8bb2b91c493d Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Tue, 2 Jan 2024 10:36:16 +0100 Subject: [PATCH 03/27] Make API method of quality result accessible. --- plugin/pom.xml | 2 +- .../core/util/WarningsQualityGate.java | 25 +++++++++++++++++-- .../warnings/steps/ReferenceFinderITest.java | 8 +++--- .../analysis/warnings/steps/StepsITest.java | 6 ++--- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/plugin/pom.xml b/plugin/pom.xml index 7a823dd158..10ab5a5f5f 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -51,7 +51,7 @@ -Djava.awt.headless=true -Xmx1024m -Djenkins.test.timeout=1000 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED 1.29.0-8 - 3.9.0-rc848.2d5e600a_712a_ + 3.9.0-rc849.401125b_7782e diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java index 114bfd3129..e89d895eeb 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java @@ -6,6 +6,7 @@ import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.verb.POST; import hudson.Extension; @@ -53,15 +54,35 @@ public WarningsQualityGate(final int threshold, final QualityGateType type) { * the minimum number of issues that fails the quality gate * @param type * the type of the quality gate - * @param criticality the criticality of the quality gate + * @param criticality + * the criticality of the quality gate */ - public WarningsQualityGate(final int threshold, final QualityGateType type, final QualityGateCriticality criticality) { + public WarningsQualityGate(final int threshold, final QualityGateType type, + final QualityGateCriticality criticality) { super(threshold); this.type = type; setCriticality(criticality); } + /** + * Sets the criticality of the quality gate. + * + * @param unstable + * the criticality of the quality gate + * @deprecated use {@link #setCriticality(QualityGateCriticality)} instead + */ + @DataBoundSetter + @Deprecated + public void setUnstable(final boolean unstable) { + if (unstable) { + setCriticality(QualityGateCriticality.UNSTABLE); + } + else { + setCriticality(QualityGateCriticality.FAILURE); + } + } + /** * Returns the method that should be used to determine the actual number of issues in the build. * diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ReferenceFinderITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ReferenceFinderITest.java index 93c78ec5f8..e76b9aa276 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ReferenceFinderITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/ReferenceFinderITest.java @@ -328,8 +328,7 @@ void shouldCreateUnstableResultWithNotIgnoredUnstableInBetween() { // #3 UNSTABLE (Reference #2) cleanAndCopy(project, "eclipse8Warnings.txt"); issuesRecorder.setQualityGates(List.of( - new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); - issuesRecorder.setQualityGates(List.of( + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE), new WarningsQualityGate(9, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE))); scheduleBuildAndAssertStatus(project, Result.UNSTABLE, analysisResult -> assertThat(analysisResult) @@ -636,11 +635,10 @@ void shouldCreateUnstableResultWithNotIgnoredUnstableInBetweenWithReferenceBuild FreeStyleProject project = createJob(JOB_NAME, "eclipse8Warnings.txt"); enableWarnings(project, recorder -> { recorder.setQualityGates(List.of( - new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE))); + new WarningsQualityGate(3, QualityGateType.NEW, QualityGateCriticality.UNSTABLE), + new WarningsQualityGate(9, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE))); recorder.setReferenceJobName(REFERENCE_JOB_NAME); recorder.setIgnoreQualityGate(true); - recorder.setQualityGates(List.of( - new WarningsQualityGate(9, QualityGateType.TOTAL, QualityGateCriticality.UNSTABLE))); }); scheduleBuildAndAssertStatus(project, Result.UNSTABLE, analysisResult -> assertThat(analysisResult) .hasTotalSize(8) diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java index f7cd5aa80e..bdfbe7f787 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java @@ -274,7 +274,7 @@ private void configurePublisher(final WorkflowJob job, final String fileName, fi + " echo '[name=' + action.getDisplayName() + ']' \n" + " echo '[isSuccessful=' + action.isSuccessful() + ']' \n" + " def result = action.getResult()\n" - + " def status = result.getQualityGateStatus()\n" + + " def status = result.getQualityGateResult()\n" + " echo '[status=' + status + ']' \n" + " echo '[isSuccessfulQualityGate=' + status.isSuccessful() + ']' \n" + " def totals = result.getTotals()\n" @@ -1101,7 +1101,7 @@ void publishIssuesShouldMarkStepWithWarningAction() { assertThat(publishIssuesNode).isNotNull(); WarningAction warningAction = publishIssuesNode.getPersistentAction(WarningAction.class); assertThat(warningAction).isNotNull(); - assertThat(warningAction.getMessage()).isEqualTo( + assertThat(warningAction.getMessage()).endsWith( "Some quality gates have been missed: overall result is UNSTABLE"); } @@ -1123,7 +1123,7 @@ void recordIssuesShouldMarkStepWithWarningAction() { assertThat(publishIssuesNode).isNotNull(); WarningAction warningAction = publishIssuesNode.getPersistentAction(WarningAction.class); assertThat(warningAction).isNotNull(); - assertThat(warningAction.getMessage()).isEqualTo( + assertThat(warningAction.getMessage()).endsWith( "Some quality gates have been missed: overall result is UNSTABLE"); } From 58afecfa97ca00bb553298b976c107de3c30619d Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Tue, 2 Jan 2024 14:29:30 +0100 Subject: [PATCH 04/27] Fix reference of old quality gate status. --- .../PullRequestMonitoringPortlet.java | 2 +- .../core/util/WarningsQualityGate.java | 2 +- .../analysis/warnings/steps/StepsITest.java | 55 +------------------ 3 files changed, 4 insertions(+), 55 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/PullRequestMonitoringPortlet.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/PullRequestMonitoringPortlet.java index 856cea2e8b..803604b19d 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/PullRequestMonitoringPortlet.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/PullRequestMonitoringPortlet.java @@ -20,9 +20,9 @@ import io.jenkins.plugins.analysis.core.model.AnalysisResult; import io.jenkins.plugins.analysis.core.model.ResultAction; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.monitoring.MonitorPortlet; import io.jenkins.plugins.monitoring.MonitorPortletFactory; +import io.jenkins.plugins.util.QualityGateStatus; /** * A portlet that can be used for the diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java index e89d895eeb..23ee3aa3fa 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java @@ -82,7 +82,7 @@ public void setUnstable(final boolean unstable) { setCriticality(QualityGateCriticality.FAILURE); } } - + /** * Returns the method that should be used to determine the actual number of issues in the build. * diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java index bdfbe7f787..288f3afd08 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java @@ -160,7 +160,7 @@ private void configureRecorder(final WorkflowJob job, final String fileName) { + " echo '[totalSize=' + result.getTotals().getTotalSize() + ']' \n" + " echo '[newSize=' + result.getTotals().getNewSize() + ']' \n" + " echo '[fixedSize=' + result.getTotals().getFixedSize() + ']' \n" - + " echo '[qualityGate=' + result.getQualityGateStatus() + ']' \n" + + " echo '[qualityGate=' + result.getQualityGateResult() + ']' \n" + " echo '[id=' + result.getId() + ']' \n" + " result.getIssues().each { issue ->\n" + " echo issue.toString()\n" @@ -194,8 +194,7 @@ void shouldSkipBlaming() { job.setDefinition(new CpsFlowDefinition("node {\n" + " stage ('Integration Test') {\n" - + " recordIssues forensicsDisabled: true, skipBlames: true, tool: checkStyle(pattern: '**/" - + "checkstyle1" + "*')\n" + + " recordIssues skipBlames: true, tool: checkStyle(pattern: '**/checkstyle1" + "*')\n" + " }\n" + "}", true)); Run baseline = buildSuccessfully(job); @@ -573,56 +572,6 @@ void shouldFailBuildWhenFailBuildOnErrorsIsSet() { scheduleBuildAndAssertStatus(job, Result.FAILURE); } - /** Runs the JavaDoc parser and enforces quality gates. */ - @Test - @org.jvnet.hudson.test.Issue("JENKINS-58253") - void shouldSupportDeprecatedAttributesInRecord() { - WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix("javadoc.txt"); - - job.setDefinition(asStage( - "recordIssues tool: javaDoc(pattern:'**/*issues.txt', reportEncoding:'UTF-8'), " - + "unstableTotalAll: 6")); - buildWithResult(job, Result.UNSTABLE); - - job.setDefinition(asStage( - "recordIssues tool: javaDoc(pattern:'**/*issues.txt', reportEncoding:'UTF-8'), " - + "failedTotalAll: 6")); - buildWithResult(job, Result.FAILURE); - - job.setDefinition(asStage( - "recordIssues tool: javaDoc(pattern:'**/*issues.txt', reportEncoding:'UTF-8'), " - + "unstableTotalNormal: 6")); - buildWithResult(job, Result.UNSTABLE); - - job.setDefinition(asStage( - "recordIssues tool: javaDoc(pattern:'**/*issues.txt', reportEncoding:'UTF-8'), " - + "failedTotalNormal: 6")); - buildWithResult(job, Result.FAILURE); - } - - /** Runs the JavaDoc parser and enforces quality gates. */ - @Test - @org.jvnet.hudson.test.Issue("JENKINS-58253") - void shouldSupportDeprecatedAttributesInPublish() { - WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix("javadoc.txt"); - - job.setDefinition(asStage(createScanForIssuesStep(new JavaDoc(), "java"), - "publishIssues issues:[java], unstableTotalAll: 6")); - buildWithResult(job, Result.UNSTABLE); - - job.setDefinition(asStage(createScanForIssuesStep(new JavaDoc(), "java"), - "publishIssues issues:[java], failedTotalAll: 6")); - buildWithResult(job, Result.FAILURE); - - job.setDefinition(asStage(createScanForIssuesStep(new JavaDoc(), "java"), - "publishIssues issues:[java], unstableTotalNormal: 6")); - buildWithResult(job, Result.UNSTABLE); - - job.setDefinition(asStage(createScanForIssuesStep(new JavaDoc(), "java"), - "publishIssues issues:[java], failedTotalNormal: 6")); - buildWithResult(job, Result.FAILURE); - } - /** * Runs the all Java parsers on three output files: the build should report issues of all tools. The results should * be aggregated into a new action with the specified ID. Since no name is given the default name is used. From 0ec09b2aa9377693e6ceaa63ed2fec95fde3ae97 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Tue, 2 Jan 2024 14:48:49 +0100 Subject: [PATCH 05/27] Fix quality gate definition in Job DSL. --- .../analysis/warnings/integrations/JobDslITest.java | 13 +++++++++---- .../warnings/integrations/job-dsl-warnings-ng.yaml | 5 ++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/JobDslITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/JobDslITest.java index 1eb6509b2a..0d4fecd15c 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/JobDslITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/JobDslITest.java @@ -15,9 +15,11 @@ import io.jenkins.plugins.analysis.core.steps.IssuesRecorder; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerTest; import io.jenkins.plugins.analysis.core.util.TrendChartType; +import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; import io.jenkins.plugins.analysis.warnings.Java; import io.jenkins.plugins.casc.ConfigurationAsCode; import io.jenkins.plugins.casc.ConfiguratorException; +import io.jenkins.plugins.util.QualityGate.QualityGateCriticality; import static org.assertj.core.api.Assertions.*; @@ -107,12 +109,15 @@ void shouldCreateFreestyleJobUsingJobDslAndVerifyIssueRecorderWithValuesSet() { assertThat(recorder.getSourceCodeEncoding()).isEqualTo("UTF-8"); assertThat(recorder.getUnhealthy()).isEqualTo(50); assertThat(recorder.getReferenceJobName()).isEqualTo("test-job"); - assertThat(recorder.getQualityGates()).hasSize(1); + assertThat(recorder.getQualityGates()).hasSize(1) + .first().satisfies(gate -> { + assertThat(gate.getThreshold()).isEqualTo(10.0); + assertThat(gate.getType()).isEqualTo(QualityGateType.TOTAL); + assertThat(gate.getCriticality()).isEqualTo(QualityGateCriticality.FAILURE); + }); List tools = recorder.getTools(); - assertThat(tools).hasSize(2); - assertThat(tools.get(0)).isInstanceOf(Java.class); - + assertThat(tools).hasSize(2).first().isInstanceOf(Java.class); } /** diff --git a/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/job-dsl-warnings-ng.yaml b/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/job-dsl-warnings-ng.yaml index bef04ad70f..f32069f8b3 100644 --- a/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/job-dsl-warnings-ng.yaml +++ b/plugin/src/test/resources/io/jenkins/plugins/analysis/warnings/integrations/job-dsl-warnings-ng.yaml @@ -6,7 +6,6 @@ jobs: trendChartType('NONE') aggregatingResults(true) blameDisabled(true) - forensicsDisabled(true) enabledForFailure(true) quiet(false) healthy(10) @@ -20,10 +19,10 @@ jobs: unhealthy(50) referenceJobName('test-job') qualityGates { - qualityGate { + warningsQualityGate { threshold(10) type('TOTAL') - unstable(true) + criticality('FAILURE') } } tools { From 1b3707442c8da581bc865b1645144dcb84b8c5bd Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Tue, 2 Jan 2024 15:57:01 +0100 Subject: [PATCH 06/27] Fix quality gates in remote API. --- doc/Documentation.md | 5 ++- plugin/pom.xml | 2 +- .../core/restapi/AnalysisResultApi.java | 8 ++--- .../config.jelly | 6 ++-- .../config.properties | 0 .../WarningsQualityGate/help-criticality.html | 31 +++++++++++++++++++ .../help-threshold.html | 2 +- .../help-type.html | 4 +-- .../warnings/steps/RemoteApiITest.java | 12 ++++--- 9 files changed, 52 insertions(+), 18 deletions(-) rename plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/{QualityGate => WarningsQualityGate}/config.jelly (63%) rename plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/{QualityGate => WarningsQualityGate}/config.properties (100%) create mode 100644 plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/help-criticality.html rename plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/{QualityGate => WarningsQualityGate}/help-threshold.html (70%) rename plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/{QualityGate => WarningsQualityGate}/help-type.html (90%) diff --git a/doc/Documentation.md b/doc/Documentation.md index 0c24e41d5c..9038beb195 100644 --- a/doc/Documentation.md +++ b/doc/Documentation.md @@ -462,12 +462,11 @@ the number of issues that will fail a given quality gate. An example pipeline with these options is shown in the following snippet: ```groovy -recordIssues tool: java(pattern: '*.log'), qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]] +recordIssues tool: java(pattern: '*.log'), qualityGates: [[threshold: 1, type: 'TOTAL', criticality: 'FAILURE']] ``` The type determines the property that will be picked to evaluate the quality gate. Refer to the enum -[QualityGateType](../plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGate.java) to see which different -types are supported. +[QualityGateType](../plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java) to see which different types are supported. ### Health report configuration diff --git a/plugin/pom.xml b/plugin/pom.xml index 10ab5a5f5f..f84e2d43bb 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -51,7 +51,7 @@ -Djava.awt.headless=true -Xmx1024m -Djenkins.test.timeout=1000 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED 1.29.0-8 - 3.9.0-rc849.401125b_7782e + 3.9.0-rc851.d8f87eccd5b_e diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/AnalysisResultApi.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/AnalysisResultApi.java index eac4ffbd76..89442c768b 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/AnalysisResultApi.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/AnalysisResultApi.java @@ -9,7 +9,7 @@ import hudson.model.Run; import io.jenkins.plugins.analysis.core.util.StaticAnalysisRun; -import io.jenkins.plugins.util.QualityGateResult; +import io.jenkins.plugins.util.QualityGateResult.QualityGateResultApi; /** * Remote API for the {@link StaticAnalysisRun}. Simple Java Bean that exposes several methods of an {@link @@ -56,9 +56,9 @@ public int getSuccessfulSinceBuild() { return result.getSuccessfulSinceBuild(); } - @Exported - public QualityGateResult getQualityGateResult() { - return result.getQualityGateResult(); + @Exported(inline = true) + public QualityGateResultApi getQualityGates() { + return new QualityGateResultApi(result.getQualityGateResult()); } @Exported diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/config.jelly b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/config.jelly similarity index 63% rename from plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/config.jelly rename to plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/config.jelly index d77aa6daf2..ceacb08c79 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/config.jelly +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/config.jelly @@ -2,15 +2,15 @@ - + - - + + diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/config.properties b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/config.properties similarity index 100% rename from plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/config.properties rename to plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/config.properties diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/help-criticality.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/help-criticality.html new file mode 100644 index 0000000000..8655cba352 --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/help-criticality.html @@ -0,0 +1,31 @@ +
    + When a quality gate has been missed, this property determines whether the result of the associated step + or the overall build will be marked as unstable or failure. + + The following enum values are possible for freestyle jobs: + +
    +
    UNSTABLE
    +
    + Set the step and build status to unstable if the quality gate has been missed. +
    +
    FAILURE
    +
    + Fail the step and build if the quality gate has been missed. +
    +
    + + For Pipelines two additional fine-grained options are available, that allow setting the status of the step without + touching the overall build status: + +
    +
    NOTE
    +
    + Set the step to unstable if the quality gate has been missed. +
    +
    ERROR
    +
    + Fail the step if the quality gate has been missed. +
    +
    +
    diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/help-threshold.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/help-threshold.html similarity index 70% rename from plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/help-threshold.html rename to plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/help-threshold.html index 9483446c77..1ff7a911a3 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/help-threshold.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/help-threshold.html @@ -1,4 +1,4 @@
    - The threshold defines the minimum number of warnings that will fail a build. Values less or equal zero are ignored. + The threshold defines the minimum number of warnings that will miss the quality gate. Values less or equal zero are ignored. So if you want to fail a build that has one warning, set this field to 1.
    diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/help-type.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/help-type.html similarity index 90% rename from plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/help-type.html rename to plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/help-type.html index ab00ee452f..ce8a214390 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/QualityGate/help-type.html +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/WarningsQualityGate/help-type.html @@ -6,12 +6,12 @@
    Selects the total number of issues in the current build.
    New
    Selects the total number of new issues in the current build with respect to the reference build. - New issues will be calculated by a sophisticated algorithm, + New issues will be calculated by a sophisticated algorithm that tries to track issues from build to build, even if the source code has been modified. Note that this algorithm sometimes detects outstanding warnings as new, e.g., if a source file has been refactored heavily.
    Delta
    -
    Selects the difference of the total number of issues of the current build subtracted by the total +
    Selects the difference between the total number of issues in the current build subtracted by the total number of issues in the reference build. This is a simple subtraction, so if you have a build that adds a new warning and removes a completely different warning, then the result will be zero.
    diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/RemoteApiITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/RemoteApiITest.java index 9ae0a1f9f3..2715af5ae6 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/RemoteApiITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/RemoteApiITest.java @@ -48,7 +48,7 @@ class RemoteApiITest extends IntegrationTestWithJenkinsPerSuite { */ @Test void shouldReturnSummaryForTopLevelApiCall() { - // Skip elements with absolute paths or other platform specific information + // Skip elements with absolute paths or other platform-specific information verifyRemoteApi("/checkstyle/api/xml" + "?exclude=/*/errorMessage" + "&exclude=/*/infoMessage" @@ -85,10 +85,14 @@ private void assertThatRemoteApiEquals(final Run build, final String url, void assertXmlApiWithXPathNavigationMatchesExpected() { Run build = buildCheckStyleJob(); - Document actualDocument = callXmlRemoteApi(build.getUrl() + "/checkstyle/api/xml?xpath=/*/qualityGateStatus"); + Document actualDocument = callXmlRemoteApi(build.getUrl() + "/checkstyle/api/xml?xpath=/*/qualityGates"); - assertThat(actualDocument.getDocumentElement().getTagName()).isEqualTo("qualityGateStatus"); - assertThat(actualDocument.getDocumentElement().getFirstChild().getNodeValue()).isEqualTo("INACTIVE"); + var documentElement = actualDocument.getDocumentElement(); + assertThat(documentElement.getTagName()).isEqualTo("qualityGates"); + + var result = documentElement.getFirstChild(); + assertThat(result.getNodeName()).isEqualTo("overallResult"); + assertThat(result.getTextContent()).isEqualTo("INACTIVE"); } /** From b8408673a79ef4dfbc68c31ca30586493e694b10 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Tue, 2 Jan 2024 23:01:52 +0100 Subject: [PATCH 07/27] Add integer based setter fpr threshold. --- plugin/pom.xml | 2 +- .../core/util/WarningsQualityGate.java | 15 +++ .../core/model/ResultAction/summary.jelly | 6 +- .../util/WarningsQualityGate/config.jelly | 2 +- .../src/main/resources/issues/integer.jelly | 116 ++++++++++++++++++ .../analysis/warnings/IssuesRecorder.java | 61 +++++---- .../FreeStyleConfigurationUiTest.java | 6 +- .../warnings/SnippetGeneratorUiTest.java | 4 +- .../warnings/WarningsPluginUiTest.java | 6 +- 9 files changed, 179 insertions(+), 39 deletions(-) create mode 100644 plugin/src/main/resources/issues/integer.jelly diff --git a/plugin/pom.xml b/plugin/pom.xml index f84e2d43bb..08748cdfa6 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -51,7 +51,7 @@ -Djava.awt.headless=true -Xmx1024m -Djenkins.test.timeout=1000 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED 1.29.0-8 - 3.9.0-rc851.d8f87eccd5b_e + 3.9.0-rc852.80b_65e3a_1549 diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java index 23ee3aa3fa..7232e2bc94 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java @@ -65,6 +65,21 @@ public WarningsQualityGate(final int threshold, final QualityGateType type, setCriticality(criticality); } + public int getIntegerThreshold() { + return Double.valueOf(getThreshold()).intValue(); + } + + /** + * Sets the threshold of the quality gate. + * + * @param threshold + * the threshold of the quality gate + */ + @DataBoundSetter + public void setIntegerThreshold(final int threshold) { + setThreshold(threshold); + } + /** * Sets the criticality of the quality gate. * diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ResultAction/summary.jelly b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ResultAction/summary.jelly index 79fa243bd8..91128ff87f 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ResultAction/summary.jelly +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ResultAction/summary.jelly @@ -1,6 +1,6 @@ - + @@ -81,10 +81,10 @@
  • +
  • ${%Quality gate}: ${s.qualityGateStatus.description} -
  • - -
  • - ${%Quality gate}: ${s.qualityGateStatus.description} - - - - - -
  • + + + + + +
diff --git a/plugin/src/main/resources/issues/integer.jelly b/plugin/src/main/resources/issues/integer.jelly deleted file mode 100644 index 912914ba7e..0000000000 --- a/plugin/src/main/resources/issues/integer.jelly +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Generates an input field <input type="number" ... /> to be - used inside <f:entry/> - - - Used for databinding. TBD. - - tag. - If @field is specified, this value is inferred from it. - ]]> - - tag. - If @field is specified, the current property from the "instance" object - will be set as the initial value automatically, - which is the recommended approach. - ]]> - - - The default value of the text box, in case both @value is and 'instance[field]' is null. - - - A number that specifies the granularity that the value must adhere to. - - tag. - This will work only @clazz is 'number', 'number-required', 'non-negative-number-required', - 'positive-number', 'positive-number-required'. - If specified, the @value should be greater than this value, or errors will be rendered under the text field. - If this value contains non-digit characters, it will not work. - If @max is specified and @max is less than this value, both @min and @max will not work. - ]]> - - tag. - This will work only @clazz is 'number', 'number-required', 'non-negative-number-required', - 'positive-number', 'positive-number-required'. - If specified, the @value should be less than this value, or errors will be rendered under the text field. - If this value contains non-digit characters, it will not work. - If @min is specified and @min is greater than this value, both @min and @max will not work. - ]]> - - - - Additional CSS class(es) to add (such as client-side validation clazz="required", - "number" or "positive-number"; these may be combined, as clazz="required number"). - - - Override the default error message when client-side validation fails, - as with clazz="required", etc. - - - If specified, the value entered in this input field will be checked (via AJAX) - against this URL, and errors will be rendered under the text field. - - If @field is specified, this will be inferred automatically, - which is the recommended approach. - - - Specify 'get' (must be lowercase) to change the HTTP method used for the AJAX requests to @checkUrl from a POST to a GET. - If any other value is specified then requests will use POST. - The historical default was GET and 'post' had to be specified to change that, but this was changed in Jenkins 2.285. - - - - - - - - - - - - - - - ${customizedFields.add(name)} - - diff --git a/plugin/src/main/resources/issues/number.jelly b/plugin/src/main/resources/issues/number.jelly deleted file mode 100644 index 87910a60b3..0000000000 --- a/plugin/src/main/resources/issues/number.jelly +++ /dev/null @@ -1,64 +0,0 @@ - - - - Generates an input field <input type="number" ... /> to be used inside <f:entry/>. - The minimum number is set to 0, the step size to 10. - - - Used for databinding. TBD. - - - This becomes @name of the <input> tag. - If @field is specified, this value is inferred from it. - - - The initial value of the field. This becomes the @value of the <input> tag. - If @field is specified, the current property from the "instance" object - will be set as the initial value automatically, - which is the recommended approach. - - - The default value of the text box, in case both @value is and 'instance[field]' is null. - - - - Additional CSS class(es) to add (such as client-side validation clazz="required", - "number" or "positive-number"; these may be combined, as clazz="required number"). - - - Override the default error message when client-side validation fails, - as with clazz="required", etc. - - - If specified, the value entered in this input field will be checked (via AJAX) - against this URL, and errors will be rendered under the text field. - - If @field is specified, this will be inferred automatically, - which is the recommended approach. - - - - - - - - - - - - - - ${customizedFields.add(name)} - - diff --git a/plugin/src/main/resources/issues/publish-parameters.jelly b/plugin/src/main/resources/issues/publish-parameters.jelly index 2cdf738dac..98128fb369 100644 --- a/plugin/src/main/resources/issues/publish-parameters.jelly +++ b/plugin/src/main/resources/issues/publish-parameters.jelly @@ -53,11 +53,11 @@ - + - + diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/warnings/SnippetGeneratorUiTest.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/warnings/SnippetGeneratorUiTest.java index 0d8a4e6cf3..49d2faa29b 100644 --- a/ui-tests/src/test/java/io/jenkins/plugins/analysis/warnings/SnippetGeneratorUiTest.java +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/warnings/SnippetGeneratorUiTest.java @@ -130,7 +130,7 @@ public void shouldHandleComplexConfiguration() { assertThat(script).contains("filters: [excludeType('*toExclude*')]"); assertThat(script).contains("ignoreFailedBuilds: false"); assertThat(script).contains("ignoreQualityGate: true"); - assertThat(script).contains("qualityGates: [[integerThreshold: 1, type: 'NEW', criticality: 'FAILURE']]"); + assertThat(script).contains("qualityGates: [[criticality: 'FAILURE', integerThreshold: 1, type: 'NEW']]"); assertThat(script).contains("pattern: 'firstText'"); assertThat(script).contains("sourceCodeEncoding: 'otherText'"); From 6290ceeec5efafd98ca3752c81d9eaa2d11f7c41 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 6 Jan 2024 14:51:45 +0100 Subject: [PATCH 16/27] Use new data-bound properties of QualityGate. --- plugin/pom.xml | 2 +- .../analysis/core/model/AnalysisResult.java | 5 +- .../model/StaticAnalysisLabelProvider.java | 46 ---------- .../analysis/core/util/QualityGateStatus.java | 92 ------------------- .../core/util/WarningsQualityGate.java | 10 +- .../core/util/QualityGateStatusTest.java | 48 ---------- .../analysis/core/util/QualityGateTest.java | 3 +- .../warnings/steps/QualityGateITest.java | 26 +++++- .../analysis/warnings/steps/StepsITest.java | 5 +- 9 files changed, 33 insertions(+), 204 deletions(-) delete mode 100644 plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateStatus.java delete mode 100644 plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateStatusTest.java diff --git a/plugin/pom.xml b/plugin/pom.xml index 63c85c843a..6601cfe5ce 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -51,7 +51,7 @@ -Djava.awt.headless=true -Xmx1024m -Djenkins.test.timeout=1000 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED 1.29.0-8 - 3.9.0-rc853.384b_719b_9ff9 + 3.9.0-rc855.756b_f8df78a_1 diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java index 358ad20497..1f5be0a55b 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java @@ -33,8 +33,6 @@ import io.jenkins.plugins.analysis.core.util.IssuesStatistics; import io.jenkins.plugins.analysis.core.util.IssuesStatisticsBuilder; import io.jenkins.plugins.analysis.core.util.StaticAnalysisRun; -import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; -import io.jenkins.plugins.analysis.core.util.WarningsQualityGate.QualityGateType; import io.jenkins.plugins.forensics.blame.Blames; import io.jenkins.plugins.forensics.blame.BlamesXmlStream; import io.jenkins.plugins.forensics.miner.RepositoryStatistics; @@ -285,8 +283,7 @@ protected Object readResolve() { totals = builder.build(); } if (qualityGateResult == null && qualityGateStatus != null) { - qualityGateResult = new QualityGateResult(); - qualityGateResult.add(new WarningsQualityGate(0, QualityGateType.TOTAL), qualityGateStatus, "n/a"); + qualityGateResult = new QualityGateResult(qualityGateStatus); } return this; } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/StaticAnalysisLabelProvider.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/StaticAnalysisLabelProvider.java index 1438a87786..7af5864c6e 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/StaticAnalysisLabelProvider.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/StaticAnalysisLabelProvider.java @@ -17,8 +17,6 @@ import org.jvnet.localizer.Localizable; import hudson.model.Run; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; - import static j2html.TagCreator.*; /** @@ -289,50 +287,6 @@ private ContainerTag emptyElementForDeprecatedMethod() { return div(); } - /** - * Returns the HTML text showing the result of the quality gate. - * - * @param qualityGateStatus - * the status of the quality gate - * - * @return the legend of the trend chart - * @deprecated rendering of the summary is now done on the client side with the new model {@link SummaryModel} - */ - @Deprecated - public DomContent getQualityGateResult(final QualityGateStatus qualityGateStatus) { - return emptyElementForDeprecatedMethod(); - } - - /** - * Returns the HTML text showing the result of the quality gate. - * - * @param qualityGateStatus - * the status of the quality gate - * @param hasResetLink - * determines whether the reset reference link is shown - * - * @return the legend of the trend chart - * @deprecated rendering of the summary is now done on the client side with the new model {@link SummaryModel} - */ - @Deprecated - public DomContent getQualityGateResult(final QualityGateStatus qualityGateStatus, final boolean hasResetLink) { - return emptyElementForDeprecatedMethod(); - } - - /** - * Returns the HTML text showing a link to the reference build. - * - * @param referenceBuild - * the reference build - * - * @return the legend of the trend chart - * @deprecated rendering of the summary is now done on the client side with the new model {@link SummaryModel} - */ - @Deprecated - public DomContent getReferenceBuild(final Run referenceBuild) { - return emptyElementForDeprecatedMethod(); - } - /** * Returns a short description describing the total number of issues. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateStatus.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateStatus.java deleted file mode 100644 index 05ea1db7ea..0000000000 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/QualityGateStatus.java +++ /dev/null @@ -1,92 +0,0 @@ -package io.jenkins.plugins.analysis.core.util; - -import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted; -import hudson.model.BallColor; -import hudson.model.Result; - -/** - * Result of a quality gate evaluation (warnings plug-in older than 11.0.0). - * - * @author Ullrich Hafner - * @deprecated replaced by {@link io.jenkins.plugins.util.QualityGateStatus} - */ -@Deprecated -public enum QualityGateStatus { - /** Quality gate is inactive, so result evaluation is not available. */ - INACTIVE(Result.NOT_BUILT), - - /** Quality gate has been passed. */ - PASSED(Result.SUCCESS), - - /** Quality gate has been missed: severity is a warning. */ - WARNING(Result.UNSTABLE), - - /** Quality gate has been missed: severity is an error. */ - FAILED(Result.FAILURE); - - private final Result result; - - QualityGateStatus(final Result result) { - this.result = result; - } - - /** - * Returns the associated {@link Result} color. - * - * @return Jenkins' {@link Result} color - * @deprecated BallColor is not used anymore, build status icons are now rendered on the UI side only - */ - @Deprecated - public BallColor getColor() { - return result.color; - } - - /** - * Returns the associated {@link Result} icon class to be used in the UI. - * - * @return Jenkins' {@link Result} icon class - */ - public String getIconClass() { - return getColor().getIconClassName(); - } - - /** - * Returns the localized description be used in the UI. - * - * @return the localized description - */ - public String getDescription() { - return getColor().getDescription(); - } - - /** - * Returns whether the quality gate has been passed (or has not been activated at all). - * - * @return {@code true} if the quality gate has been passed, {@code false} otherwise - */ - @Whitelisted - public boolean isSuccessful() { - return this == PASSED || this == INACTIVE; - } - - /** - * Returns the associated {@link Result}. - * - * @return the associated {@link Result} - */ - public Result getResult() { - return result; - } - - /** - * Returns whether this status is worse than the specified status. - * - * @param other - * the other status - * - * @return {@code true} if this status is worse than the other status, {@code false} otherwise - */ - public boolean isWorseThan(final QualityGateStatus other) { - return ordinal() > other.ordinal(); - } -} diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java index a43e33f910..f0f4dbfd8e 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java @@ -35,15 +35,11 @@ public class WarningsQualityGate extends QualityGate { /** * Creates a new instance of {@link WarningsQualityGate}. * - * @param integerThreshold - * the minimum number of issues that fails the quality gate * @param type * the type of the quality gate */ @DataBoundConstructor - public WarningsQualityGate(final int integerThreshold, final QualityGateType type) { - super(integerThreshold); - + public WarningsQualityGate(final QualityGateType type) { this.type = type; } @@ -59,9 +55,9 @@ public WarningsQualityGate(final int integerThreshold, final QualityGateType typ */ public WarningsQualityGate(final int threshold, final QualityGateType type, final QualityGateCriticality criticality) { - super(threshold); - this.type = type; + + setIntegerThreshold(threshold); setCriticality(criticality); } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateStatusTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateStatusTest.java deleted file mode 100644 index c1438389b3..0000000000 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateStatusTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.jenkins.plugins.analysis.core.util; - -import org.junit.jupiter.api.Test; - -import hudson.model.Result; - -import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; - -/** - * Tests the class {@link QualityGateStatus}. - * - * @author Ullrich Hafner - */ -class QualityGateStatusTest { - @Test - void shouldIdentifySuccessfulStatus() { - assertThat(QualityGateStatus.PASSED).isSuccessful().hasColor(Result.SUCCESS.color); - assertThat(QualityGateStatus.INACTIVE).isSuccessful().hasColor(Result.NOT_BUILT.color); - assertThat(QualityGateStatus.WARNING).isNotSuccessful().hasColor(Result.UNSTABLE.color); - assertThat(QualityGateStatus.FAILED).isNotSuccessful().hasColor(Result.FAILURE.color); - } - - @Test - void shouldDefineOrder() { - assertThat(QualityGateStatus.FAILED.isWorseThan(QualityGateStatus.INACTIVE)).isTrue(); - assertThat(QualityGateStatus.FAILED.isWorseThan(QualityGateStatus.PASSED)).isTrue(); - assertThat(QualityGateStatus.FAILED.isWorseThan(QualityGateStatus.WARNING)).isTrue(); - - assertThat(QualityGateStatus.FAILED.isWorseThan(QualityGateStatus.FAILED)).isFalse(); - - assertThat(QualityGateStatus.WARNING.isWorseThan(QualityGateStatus.INACTIVE)).isTrue(); - assertThat(QualityGateStatus.WARNING.isWorseThan(QualityGateStatus.PASSED)).isTrue(); - - assertThat(QualityGateStatus.WARNING.isWorseThan(QualityGateStatus.FAILED)).isFalse(); - assertThat(QualityGateStatus.WARNING.isWorseThan(QualityGateStatus.WARNING)).isFalse(); - - assertThat(QualityGateStatus.PASSED.isWorseThan(QualityGateStatus.INACTIVE)).isTrue(); - - assertThat(QualityGateStatus.PASSED.isWorseThan(QualityGateStatus.PASSED)).isFalse(); - assertThat(QualityGateStatus.PASSED.isWorseThan(QualityGateStatus.FAILED)).isFalse(); - assertThat(QualityGateStatus.PASSED.isWorseThan(QualityGateStatus.WARNING)).isFalse(); - - assertThat(QualityGateStatus.INACTIVE.isWorseThan(QualityGateStatus.INACTIVE)).isFalse(); - assertThat(QualityGateStatus.INACTIVE.isWorseThan(QualityGateStatus.PASSED)).isFalse(); - assertThat(QualityGateStatus.INACTIVE.isWorseThan(QualityGateStatus.FAILED)).isFalse(); - assertThat(QualityGateStatus.INACTIVE.isWorseThan(QualityGateStatus.WARNING)).isFalse(); - } -} diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateTest.java index db65aacc26..f49f5cc327 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/QualityGateTest.java @@ -42,8 +42,9 @@ void shouldValidateThreshold() { @Override protected WarningsQualityGate createSerializable() { - var qualityGate = new WarningsQualityGate(1, QualityGateType.TOTAL); + var qualityGate = new WarningsQualityGate(QualityGateType.TOTAL); qualityGate.setCriticality(QualityGateCriticality.UNSTABLE); + qualityGate.setIntegerThreshold(1); return qualityGate; } } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java index cfb159aba1..4579ea98a2 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/QualityGateITest.java @@ -10,6 +10,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; import hudson.model.AbstractProject; import hudson.model.FreeStyleProject; import hudson.model.Result; @@ -25,6 +27,7 @@ import io.jenkins.plugins.analysis.warnings.CheckStyle; import io.jenkins.plugins.util.QualityGate.QualityGateCriticality; import io.jenkins.plugins.util.QualityGateEvaluator; +import io.jenkins.plugins.util.QualityGateResult.QualityGateResultItem; import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; @@ -35,12 +38,33 @@ * * @author Michaela Reitschuster */ -// TODO: add some tests for severity HIGH class QualityGateITest extends IntegrationTestWithJenkinsPerSuite { private static final Map RESULT_TO_STATUS_MAPPING = Maps.fixedSize.of(Result.UNSTABLE, QualityGateStatus.WARNING, Result.FAILURE, QualityGateStatus.FAILED); private static final String REPORT_FILE = "checkstyle-quality-gate.xml"; + @Test + void shouldUseTwoQualityGates() { + WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix("checkstyle1.xml", "checkstyle2.xml"); + + job.setDefinition(new CpsFlowDefinition("node {\n" + + " stage ('Integration Test') {\n" + + " recordIssues tools: [checkStyle(pattern: '**/*issues.txt')],\n" + + " qualityGates: [\n" + + " [threshold: 3, type: 'TOTAL', criticality: 'NOTE'],\n" + + " [threshold: 7, type: 'TOTAL', criticality: 'ERROR']]\n" + + " }\n" + + "}", true)); + + AnalysisResult result = scheduleSuccessfulBuild(job); + assertThat(result).hasTotalSize(6); + assertThat(result).hasQualityGateStatus(QualityGateStatus.NOTE); + + assertThat(result.getQualityGateResult().getResultItems()).hasSize(2) + .extracting(QualityGateResultItem::getStatus) + .containsExactly(QualityGateStatus.NOTE, QualityGateStatus.PASSED); + } + /** * Verifies that the first build is always considered stable if the quality gate is set up for delta warnings - even * if there is a warning. diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java index 288f3afd08..6d5a4b8036 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java @@ -43,7 +43,6 @@ import io.jenkins.plugins.analysis.core.steps.PublishIssuesStep; import io.jenkins.plugins.analysis.core.steps.ScanForIssuesStep; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerSuite; -import io.jenkins.plugins.analysis.core.util.QualityGateStatus; import io.jenkins.plugins.analysis.warnings.CheckStyle; import io.jenkins.plugins.analysis.warnings.Eclipse; import io.jenkins.plugins.analysis.warnings.FindBugs; @@ -53,6 +52,7 @@ import io.jenkins.plugins.analysis.warnings.Pmd; import io.jenkins.plugins.analysis.warnings.groovy.GroovyParser; import io.jenkins.plugins.analysis.warnings.groovy.ParserConfiguration; +import io.jenkins.plugins.util.QualityGateStatus; import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.*; @@ -68,9 +68,6 @@ class StepsITest extends IntegrationTestWithJenkinsPerSuite { private static final String NO_QUALITY_GATE = ""; - /** - * Runs a pipeline and verifies the {@code scanForIssues} step has some allowlisted methods. - */ @Test void shouldParseCheckstyleUsingTheParserRegistry() { WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix("checkstyle1.xml", "checkstyle2.xml"); From f7a00f8b7d41aaf84d1db76cc0086f72d4022d85 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 6 Jan 2024 15:24:30 +0100 Subject: [PATCH 17/27] Use new output of snippet generator. --- .github/workflows/ui-tests.yml | 64 ++++++++++++++----- .../warnings/SnippetGeneratorUiTest.java | 2 +- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index fd9d124717..37203c3b13 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -13,13 +13,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '21' check-latest: true cache: 'maven' + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.6 - name: Build warnings plugin and download dependencies run: mvn -V --color always -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip - name: Run UI tests for the details tabs @@ -33,13 +37,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '21' check-latest: true cache: 'maven' + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.6 - name: Build warnings plugin and download dependencies run: mvn -V --color always -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip - name: Run UI tests for the dashboard view @@ -53,13 +61,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '21' check-latest: true cache: 'maven' + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.6 - name: Build warnings plugin and download dependencies run: mvn -V --color always -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip - name: Run UI tests for the freestyle configuration @@ -73,13 +85,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '21' check-latest: true cache: 'maven' + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.6 - name: Build warnings plugin and download dependencies run: mvn -V --color always -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip - name: Run UI tests for the pipeline snippet configurator @@ -93,13 +109,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '21' check-latest: true cache: 'maven' + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.6 - name: Build warnings plugin and download dependencies run: mvn -V --color always -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip - name: Run UI tests for Jenkins' global configuration @@ -113,13 +133,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '21' check-latest: true cache: 'maven' + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.6 - name: Build warnings plugin and download dependencies run: mvn -V --color always -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip - name: Run UI tests for the issues column @@ -133,13 +157,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '21' check-latest: true cache: 'maven' + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.6 - name: Build warnings plugin and download dependencies run: mvn -V --color always -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip - name: Run UI tests for the trend charts @@ -153,13 +181,17 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 11 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: '11' + java-version: '21' check-latest: true cache: 'maven' + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.6 - name: Build warnings plugin and download dependencies run: mvn -V --color always -ntp verify -Pskip --file plugin/pom.xml -Dgpg.skip - name: Run miscellaneous UI tests of the warnings plugin diff --git a/ui-tests/src/test/java/io/jenkins/plugins/analysis/warnings/SnippetGeneratorUiTest.java b/ui-tests/src/test/java/io/jenkins/plugins/analysis/warnings/SnippetGeneratorUiTest.java index 49d2faa29b..34e3edc164 100644 --- a/ui-tests/src/test/java/io/jenkins/plugins/analysis/warnings/SnippetGeneratorUiTest.java +++ b/ui-tests/src/test/java/io/jenkins/plugins/analysis/warnings/SnippetGeneratorUiTest.java @@ -130,7 +130,7 @@ public void shouldHandleComplexConfiguration() { assertThat(script).contains("filters: [excludeType('*toExclude*')]"); assertThat(script).contains("ignoreFailedBuilds: false"); assertThat(script).contains("ignoreQualityGate: true"); - assertThat(script).contains("qualityGates: [[criticality: 'FAILURE', integerThreshold: 1, type: 'NEW']]"); + assertThat(script).contains("qualityGates: [[criticality: 'FAILURE', integerThreshold: 1, threshold: 1.0, type: 'NEW']]"); assertThat(script).contains("pattern: 'firstText'"); assertThat(script).contains("sourceCodeEncoding: 'otherText'"); From 33ce1b8ad76d6c0e7e68758fa0ffbe8ceea1d7a2 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Wed, 10 Jan 2024 09:15:20 +0100 Subject: [PATCH 18/27] Remove deprecated code. --- .../core/model/AggregatedTrendAction.java | 12 --- .../analysis/core/model/AnalysisHistory.java | 2 +- .../analysis/core/model/AnalysisResult.java | 87 ++++--------------- .../analysis/core/model/IssuesDetail.java | 66 +------------- .../analysis/core/model/JobAction.java | 26 ------ .../analysis/core/model/ReportLocations.java | 16 ---- .../model/StaticAnalysisLabelProvider.java | 78 ----------------- .../core/portlets/IssuesChartPortlet.java | 11 --- .../PullRequestMonitoringPortlet.java | 12 --- .../analysis/core/restapi/ToolApi.java | 21 ----- .../core/steps/AnalysisStepDescriptor.java | 18 ---- .../analysis/core/steps/IssuesRecorder.java | 42 --------- .../analysis/core/steps/IssuesScanner.java | 2 - .../core/steps/ScanForIssuesStep.java | 26 ------ .../core/util/AffectedFilesResolver.java | 24 ----- .../core/util/IssuesStatisticsBuilder.java | 42 --------- .../core/model/DetailsTableModelTest.java | 2 +- .../analysis/core/model/JobActionTest.java | 2 +- .../core/steps/IssuesAggregatorTest.java | 2 +- .../integrations/TimeStamperPluginITest.java | 2 +- .../steps/AffectedFilesResolverITest.java | 2 +- .../steps/MiscIssuesRecorderITest.java | 4 +- .../warnings/steps/PackageDetectorsITest.java | 2 +- .../analysis/warnings/steps/StepsITest.java | 12 +-- .../warnings/steps/StepsOnAgentITest.java | 4 +- .../warnings/tasks/TaskScannerTest.java | 4 +- 26 files changed, 39 insertions(+), 482 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AggregatedTrendAction.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AggregatedTrendAction.java index 96f9ae8b5a..d0ec002c39 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AggregatedTrendAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AggregatedTrendAction.java @@ -31,7 +31,6 @@ */ public class AggregatedTrendAction implements Action, AsyncConfigurableTrendChart { private static final int MIN_TOOLS = 2; - private static final String EMPTY = "{}"; private final Job owner; @@ -76,17 +75,6 @@ private Set createBuildHistory() { } } - /** - * Returns the trend chart model that renders the aggregated build results. - * - * @return the trend chart - * @deprecated replaced {@link #getConfigurableBuildTrendModel(String)} - */ - @Deprecated - public String getBuildTrendModel() { - return getConfigurableBuildTrendModel(EMPTY); - } - /** * Returns the trend chart model that renders the aggregated build results. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisHistory.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisHistory.java index 0b54973b06..8e536222bd 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisHistory.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisHistory.java @@ -63,7 +63,7 @@ public enum QualityGateEvaluationMode { */ public enum JobResultEvaluationMode { /** - * Only those jobs are considered that did not fail. I.e. jobs with result {@link Result#UNSTABLE} or {@link + * Only those jobs are considered that did not fail. I.e., jobs with result {@link Result#UNSTABLE} or {@link * Result#SUCCESS}. */ NO_JOB_FAILURE, diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java index 1f5be0a55b..7974561733 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java @@ -31,7 +31,6 @@ import io.jenkins.plugins.analysis.core.charts.JenkinsBuild; import io.jenkins.plugins.analysis.core.util.IssuesStatistics; -import io.jenkins.plugins.analysis.core.util.IssuesStatisticsBuilder; import io.jenkins.plugins.analysis.core.util.StaticAnalysisRun; import io.jenkins.plugins.forensics.blame.Blames; import io.jenkins.plugins.forensics.blame.BlamesXmlStream; @@ -60,7 +59,7 @@ public class AnalysisResult implements Serializable, StaticAnalysisRun { private final String id; - private IssuesStatistics totals; + private final IssuesStatistics totals; private final Map sizePerOrigin; private final List errors; @@ -266,22 +265,6 @@ protected AnalysisResult(final Run owner, final String id, final DeltaRepo * @return this */ protected Object readResolve() { - if (totals == null) { - IssuesStatisticsBuilder builder = new IssuesStatisticsBuilder(); - - builder.setTotalErrorSize(sizePerSeverity.getOrDefault(Severity.ERROR, 0)); - builder.setTotalHighSize(sizePerSeverity.getOrDefault(Severity.WARNING_HIGH, 0)); - builder.setTotalNormalSize(sizePerSeverity.getOrDefault(Severity.WARNING_NORMAL, 0)); - builder.setTotalLowSize(sizePerSeverity.getOrDefault(Severity.WARNING_LOW, 0)); - - builder.setNewErrorSize(newSizePerSeverity.getOrDefault(Severity.ERROR, 0)); - builder.setNewHighSize(newSizePerSeverity.getOrDefault(Severity.WARNING_HIGH, 0)); - builder.setNewNormalSize(newSizePerSeverity.getOrDefault(Severity.WARNING_NORMAL, 0)); - builder.setNewLowSize(newSizePerSeverity.getOrDefault(Severity.WARNING_LOW, 0)); - - builder.setFixedSize(fixedSize); - totals = builder.build(); - } if (qualityGateResult == null && qualityGateStatus != null) { qualityGateResult = new QualityGateResult(qualityGateStatus); } @@ -445,7 +428,7 @@ public boolean hasNoNewWarnings() { } /** - * Returns all outstanding issues of the associated static analysis run. I.e. all issues, that are part of the + * Returns all outstanding issues of the associated static analysis run. I.e., all issues that are part of the * current and previous report. * * @return all outstanding issues @@ -457,7 +440,7 @@ public Report getOutstandingIssues() { } /** - * Returns all new issues of the associated static analysis run. I.e. all issues, that are part of the current + * Returns all new issues of the associated static analysis run. I.e., all issues that are part of the current * report but have not been shown up in the previous report. * * @return all new issues @@ -469,7 +452,7 @@ public Report getNewIssues() { } /** - * Returns all fixed issues of the associated static analysis run. I.e. all issues, that are part of the previous + * Returns all fixed issues of the associated static analysis run. I.e., all issues that are part of the previous * report but are not present in the current report anymore. * * @return all fixed issues @@ -480,6 +463,7 @@ public Report getFixedIssues() { "fixed"); } + @CheckForNull private WeakReference getOutstandingIssuesReference() { return outstandingIssuesReference; } @@ -488,6 +472,7 @@ private void setOutstandingIssuesReference(final WeakReference outstandi this.outstandingIssuesReference = outstandingIssuesReference; } + @CheckForNull private WeakReference getNewIssuesReference() { return newIssuesReference; } @@ -496,6 +481,7 @@ private void setNewIssuesReference(final WeakReference newIssuesReferenc this.newIssuesReference = newIssuesReference; } + @CheckForNull private WeakReference getFixedIssuesReference() { return fixedIssuesReference; } @@ -623,9 +609,9 @@ public int getTotalErrorsSize() { } /** - * Returns the total number of high severity issues in this analysis run. + * Returns the total number of high-severity issues in this analysis run. * - * @return total number of high severity issues + * @return total number of high-severity issues */ public int getTotalHighPrioritySize() { return getTotalSizeOf(Severity.WARNING_HIGH); @@ -641,9 +627,9 @@ public int getTotalNormalPrioritySize() { } /** - * Returns the total number of low severity issues in this analysis run. + * Returns the total number of low-severity issues in this analysis run. * - * @return total number of low severity of issues + * @return total number of low-severity issues */ public int getTotalLowPrioritySize() { return getTotalSizeOf(Severity.WARNING_LOW); @@ -669,27 +655,27 @@ public int getNewErrorSize() { } /** - * Returns the number of new high severity issues in this analysis run. + * Returns the number of new high-severity issues in this analysis run. * - * @return number of new high severity issues + * @return number of new high-severity issues */ public int getNewHighPrioritySize() { return getNewSizeOf(Severity.WARNING_HIGH); } /** - * Returns the number of new normal severity issues in this analysis run. + * Returns the number of new normal-severity issues in this analysis run. * - * @return number of new normal severity issues + * @return number of new normal-severity issues */ public int getNewNormalPrioritySize() { return getNewSizeOf(Severity.WARNING_NORMAL); } /** - * Returns the number of new low severity issues in this analysis run. + * Returns the number of new low-severity issues in this analysis run. * - * @return number of new low severity of issues + * @return number of new low-severity issues */ public int getNewLowPrioritySize() { return getNewSizeOf(Severity.WARNING_LOW); @@ -704,45 +690,6 @@ public int getDeltaSize() { return totals.getDeltaSize(); } - /** - * Old serialization item. - * - * @deprecated Replaced by {@link AnalysisResult#totals}. - */ - @Deprecated @SuppressFBWarnings("SS_SHOULD_BE_STATIC") - private final transient int size = 0; - /** - * Old serialization item. - * - * @deprecated Replaced by {@link AnalysisResult#totals}. - */ - @Deprecated @SuppressFBWarnings("SS_SHOULD_BE_STATIC") - private final transient int newSize = 0; - /** - * Old serialization item. - * - * @deprecated Replaced by {@link AnalysisResult#totals}. - */ - @Deprecated - @SuppressWarnings("DeprecatedIsStillUsed") - private transient int fixedSize; - /** - * Old serialization item. - * - * @deprecated Replaced by {@link AnalysisResult#totals}. - */ - @Deprecated - @SuppressWarnings({"DeprecatedIsStillUsed", "MismatchedQueryAndUpdateOfCollection"}) - private final Map sizePerSeverity = new HashMap<>(); - /** - * Old serialization item. - * - * @deprecated Replaced by {@link AnalysisResult#totals}. - */ - @Deprecated - @SuppressWarnings({"DeprecatedIsStillUsed", "MismatchedQueryAndUpdateOfCollection"}) - private final Map newSizePerSeverity = new HashMap<>(); - public Build getBuild() { return new JenkinsBuild(getOwner()); } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java index 68b706ff04..a2814b2c67 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java @@ -319,21 +319,6 @@ public String getTrendModel() { return JACKSON_FACADE.toJson(new NewVersusFixedPieChart().create(newIssues, outstandingIssues, fixedIssues)); } - /** - * Returns the UI model for an ECharts line chart that shows the issues stacked by severity. - * - * @param isBuildOnXAxis - * determines whether the Jenkins build number should be used on the X-axis or the date - * - * @return the UI model as JSON - * @deprecated replaced by {@link #getBuildTrend(String)} - */ - @Deprecated - @SuppressWarnings("unused") - public String getBuildTrend(final boolean isBuildOnXAxis) { - return createTrendAsJson(new SeverityTrendChart(), DEFAULT_CONFIGURATION); - } - /** * Returns the UI model for an ECharts line chart that shows the issues stacked by severity. * @@ -348,21 +333,6 @@ public String getBuildTrend(final String configuration) { return createTrendAsJson(new SeverityTrendChart(), configuration); } - /** - * Returns the UI model for an ECharts line chart that shows the issues by tool. - * - * @param isBuildOnXAxis - * determines whether the Jenkins build number should be used on the X-axis or the date - * - * @return the UI model as JSON - * @deprecated replaced by {@link #getToolsTrend(String)} - */ - @Deprecated - @SuppressWarnings("unused") - public String getToolsTrend(final boolean isBuildOnXAxis) { - return createTrendAsJson(new ToolsTrendChart(), DEFAULT_CONFIGURATION); - } - /** * Returns the UI model for an ECharts line chart that shows the issues by tool. * @@ -377,21 +347,6 @@ public String getToolsTrend(final String configuration) { return createTrendAsJson(new ToolsTrendChart(), configuration); } - /** - * Returns the UI model for an ECharts line chart that shows the new and fixed issues. - * - * @param isBuildOnXAxis - * determines whether the Jenkins build number should be used on the X-axis or the date - * - * @return the UI model as JSON - * @deprecated replaced by {@link #getNewVersusFixedTrend(String)} - */ - @Deprecated - @SuppressWarnings("unused") - public String getNewVersusFixedTrend(final boolean isBuildOnXAxis) { - return createTrendAsJson(new NewVersusFixedTrendChart(), DEFAULT_CONFIGURATION); - } - /** * Returns the UI model for an ECharts line chart that shows the new and fixed issues. * @@ -406,21 +361,6 @@ public String getNewVersusFixedTrend(final String configuration) { return createTrendAsJson(new NewVersusFixedTrendChart(), configuration); } - /** - * Returns the UI model for an ECharts line chart that shows the issues by tool. - * - * @param isBuildOnXAxis - * determines whether the Jenkins build number should be used on the X-axis or the date - * - * @return the UI model as JSON - * @deprecated replaced by {@link #getHealthTrend(String)} - */ - @Deprecated - @SuppressWarnings("unused") - public String getHealthTrend(final boolean isBuildOnXAxis) { - return createTrendAsJson(new HealthTrendChart(healthDescriptor), DEFAULT_CONFIGURATION); - } - /** * Returns the UI model for an ECharts line chart that shows the issues by tool. * @@ -466,7 +406,7 @@ public Report getIssues() { } /** - * Returns all new issues of the associated static analysis run. I.e. all issues, that are part of the current + * Returns all new issues of the associated static analysis run. I.e., all issues that are part of the current * report but have not been shown up in the previous report. * * @return all new issues @@ -477,7 +417,7 @@ public Report getNewIssues() { } /** - * Returns all fixed issues of the associated static analysis run. I.e. all issues, that are part of the previous + * Returns all fixed issues of the associated static analysis run. I.e., all issues that are part of the previous * report but are not present in the current report anymore. * * @return all fixed issues @@ -488,7 +428,7 @@ public Report getFixedIssues() { } /** - * Returns all outstanding issues of the associated static analysis run. I.e. all issues, that are part of the + * Returns all outstanding issues of the associated static analysis run. I.e., all issues that are part of the * current and previous report. * * @return all outstanding issues diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/JobAction.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/JobAction.java index 4439075a24..0c1e54feb2 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/JobAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/JobAction.java @@ -37,21 +37,6 @@ public class JobAction implements Action, AsyncConfigurableTrendChart { private final int numberOfTools; private final TrendChartType trendChartType; - /** - * Creates a new instance of {@link JobAction}. - * - * @param owner - * the job that owns this action - * @param labelProvider - * the label provider - * - * @deprecated use {@link #JobAction(Job, StaticAnalysisLabelProvider, int)} - */ - @Deprecated - public JobAction(final Job owner, final StaticAnalysisLabelProvider labelProvider) { - this(owner, labelProvider, 1); - } - /** * Creates a new instance of {@link JobAction}. * @@ -181,17 +166,6 @@ public Optional getLatestAction() { return createBuildHistory().getBaselineAction(); } - /** - * Returns the trend chart model that renders the aggregated build results. - * - * @return the trend chart - * @deprecated replaced {@link #getConfigurableBuildTrendModel(String)} - */ - @Deprecated - public String getBuildTrendModel() { - return getConfigurableBuildTrendModel("{}"); - } - /** * Returns the trend chart model that renders the build results for a specific action. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ReportLocations.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ReportLocations.java index 8ed574973d..8a3cda57df 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ReportLocations.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/ReportLocations.java @@ -22,20 +22,4 @@ public FileLocations toFileLocations(final Report report) { report.stream().forEach(i -> fileLocations.addLine(i.getFileName(), i.getLineStart())); return fileLocations; } - - /** - * Returns the affected file locations in the report. - * - * @param report - * the report to get the affected files from - * @param workspace - * the workspace to get the SCM repository from - * - * @return the affected file locations - * @deprecated use {@link #toFileLocations(Report)} - */ - @Deprecated - public FileLocations toFileLocations(final Report report, @SuppressWarnings("unused") final String workspace) { - return toFileLocations(report); - } } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/StaticAnalysisLabelProvider.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/StaticAnalysisLabelProvider.java index 7af5864c6e..df4a13e373 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/StaticAnalysisLabelProvider.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/StaticAnalysisLabelProvider.java @@ -11,9 +11,6 @@ import edu.hm.hafner.util.VisibleForTesting; import edu.umd.cs.findbugs.annotations.CheckForNull; -import j2html.tags.ContainerTag; -import j2html.tags.DomContent; - import org.jvnet.localizer.Localizable; import hudson.model.Run; @@ -179,17 +176,6 @@ public String toString() { * @return the name of the side panel link */ public String getLinkName() { - return getRawLinkName(); - } - - /** - * Returns the name of the link to the results. - * - * @return the name of the side panel link - * @deprecated use {@link #getLinkName()} - */ - @Deprecated @Generated - public String getRawLinkName() { if (StringUtils.isNotBlank(name)) { return Messages.Tool_Link_Name(name); } @@ -223,70 +209,6 @@ public String getLargeIconUrl() { return ANALYSIS_SVG_ICON; } - /** - * Returns the title for the small information box in the corresponding build page. - * - * @param result - * the result - * @param hasErrors - * indicates if an error has been reported - * - * @return the title div - * @deprecated rendering of the summary is now done on the client side with the new model {@link SummaryModel} - */ - @Deprecated - public ContainerTag getTitle(final AnalysisResult result, final boolean hasErrors) { - return emptyElementForDeprecatedMethod(); - } - - /** - * Returns the HTML label for the link to the new issues of the build. - * - * @param newSize - * the number of new issues - * - * @return the legend of the trend chart - * @deprecated rendering of the summary is now done on the client side with the new model {@link SummaryModel} - */ - @Deprecated - public ContainerTag getNewIssuesLabel(final int newSize) { - return emptyElementForDeprecatedMethod(); - } - - /** - * Returns the HTML label for the link to the fixed issues of the build. - * - * @param fixedSize - * the number of fixed issues - * - * @return the legend of the trend chart - * @deprecated rendering of the summary is now done on the client side with the new model {@link SummaryModel} - */ - @Deprecated - public ContainerTag getFixedIssuesLabel(final int fixedSize) { - return emptyElementForDeprecatedMethod(); - } - - /** - * Returns the HTML text showing the number of builds since the project has no issues. - * - * @param currentBuild - * the current build number - * @param noIssuesSinceBuild - * the build since there are no issues - * - * @return the legend of the trend chart - * @deprecated rendering of the summary is now done on the client side with the new model {@link SummaryModel} - */ - @Deprecated - public DomContent getNoIssuesSinceLabel(final int currentBuild, final int noIssuesSinceBuild) { - return emptyElementForDeprecatedMethod(); - } - - private ContainerTag emptyElementForDeprecatedMethod() { - return div(); - } - /** * Returns a short description describing the total number of issues. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/IssuesChartPortlet.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/IssuesChartPortlet.java index ab6007b59e..83a1ded346 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/IssuesChartPortlet.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/IssuesChartPortlet.java @@ -139,17 +139,6 @@ public String getBuildTrendModel() { severityChart.aggregate(histories, new ChartModelConfiguration(AxisType.DATE))); } - /** - * Returns the UI model for an ECharts line chart that shows the issues stacked by severity. - * - * @return the UI model as JSON - * @deprecated replaced by {@link #getBuildTrendModel()} which is called from JS file - */ - @JavaScriptMethod @Deprecated @SuppressWarnings("unused") // Called by jelly view - public String getTrend() { - return getBuildTrendModel(); - } - /** * Registers the specified jobs in this portlet. These jobs will be used to render the trend chart. Note that * rendering of the trend chart is done using an Ajax call later on. diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/PullRequestMonitoringPortlet.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/PullRequestMonitoringPortlet.java index 803604b19d..6e16f0ee06 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/PullRequestMonitoringPortlet.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/portlets/PullRequestMonitoringPortlet.java @@ -154,18 +154,6 @@ public boolean hasQualityGate() { return !result.getQualityGateStatus().equals(QualityGateStatus.INACTIVE); } - /** - * Get the icon of the quality gate. - * - * @return - * the image url of the icon. - * @deprecated replaced by {@link #getQualityGateResultClass()} - */ - @Deprecated - public String getQualityGateResultIconUrl() { - return result.getQualityGateStatus().getResult().color.getImageOf("16x16"); - } - /** * Get the icon class of the quality gate. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/ToolApi.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/ToolApi.java index f0d833d277..8b99813cdf 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/ToolApi.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/restapi/ToolApi.java @@ -1,6 +1,5 @@ package io.jenkins.plugins.analysis.core.restapi; -import java.util.Collections; import java.util.Map; import edu.hm.hafner.analysis.Severity; @@ -23,25 +22,6 @@ public class ToolApi { private final int size; private final Map sizePerSeverity; - /** - * Creates a new instance of {@link ToolApi}. - * - * @param id - * unique ID of the tool - * @param name - * human-readable name of the tool - * @param latestUrl - * the URL to the latest results - * @param size - * the number of warnings - * @deprecated - * use {@link #ToolApi(String, String, String, int, Map)} instead. - */ - @Deprecated - public ToolApi(final String id, final String name, final String latestUrl, final int size) { - this(name, id, latestUrl, size, Collections.emptyMap()); - } - /** * Creates a new instance of {@link ToolApi}. * @@ -103,5 +83,4 @@ public int getNormalSize() { public int getLowSize() { return sizePerSeverity.getOrDefault(Severity.WARNING_LOW, 0); } - } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java index bc8bda4f9e..ea8f747488 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java @@ -96,24 +96,6 @@ public ListBoxModel doFillMinimumSeverityItems() { return model.getAllSeverityFilters(); } return new ListBoxModel(); - - } - - /** - * Returns the model with the possible reference jobs. - * - * @param project - * the project that is configured - * @return the model with the possible reference jobs - * @deprecated not used anymore, part of forensics plugin - */ - @Deprecated - @POST - public ComboBoxModel doFillReferenceJobNameItems(@AncestorInPath final BuildableItem project) { - if (JENKINS.hasPermission(Item.CONFIGURE, project)) { - return model.getAllJobs(); - } - return new ComboBoxModel(); } /** diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java index 346afaa30f..93b3580d74 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java @@ -62,7 +62,6 @@ import io.jenkins.plugins.util.LogHandler; import io.jenkins.plugins.util.ResultHandler; import io.jenkins.plugins.util.RunResultHandler; -import io.jenkins.plugins.util.StageResultHandler; import io.jenkins.plugins.util.ValidationUtilities; /** @@ -426,32 +425,6 @@ public void setSkipBlames(final boolean skipBlames) { isBlameDisabled = skipBlames; } - /** - * Not used anymore. - * - * @return {@code true} if SCM forensics should be disabled - * @deprecated Forensics will be automatically skipped if the Forensics recorder is not activated. - */ - @SuppressWarnings("PMD.BooleanGetMethodName") - @Deprecated - public boolean getForensicsDisabled() { - return isForensicsDisabled; - } - - /** - * Not used anymore. - * - * @param forensicsDisabled - * not used - * - * @deprecated Forensics will be automatically skipped if the Forensics recorder is not activated. - */ - @DataBoundSetter - @Deprecated - public void setForensicsDisabled(final boolean forensicsDisabled) { - isForensicsDisabled = forensicsDisabled; - } - /** * Returns whether publishing checks should be skipped. * @@ -705,21 +678,6 @@ public boolean perform(final AbstractBuild build, final Launcher launcher, return true; } - /** - * Executes the build step. Used from {@link RecordIssuesStep} to provide a {@link StageResultHandler} that has - * Pipeline-specific behavior. - * - * @param run - * the run of the pipeline or freestyle job - * @param workspace - * workspace of the build - * @param listener - * the logger - * @param resultHandler - * reports the status for the build or for the stage - * - * @return the created results - */ List perform(final Run run, final FilePath workspace, final TaskListener listener, final ResultHandler resultHandler) throws InterruptedException, IOException { Result overallResult = run.getResult(); diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java index 7c3d216de8..c93e0969d4 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java @@ -170,7 +170,6 @@ private Blamer createBlamer(final Report report) { report.logInfo("-> Filtering SCMs by key '%s'", scm); } Blamer blamer = BlamerFactory.findBlamer(scm, run, workspace, listener, log); - log.logSummary(); report.mergeLogMessages(log); return blamer; @@ -283,7 +282,6 @@ private Blames blame(final Report filtered, final FileLocations fileLocations) { } FilteredLog log = new FilteredLog("Errors while extracting author and commit information from Git:"); Blames blames = blamer.blame(fileLocations, log); - log.logSummary(); filtered.mergeLogMessages(log); return blames; } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java index 501a5afb8d..48ad2cca77 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java @@ -127,32 +127,6 @@ public void setBlameDisabled(final boolean blameDisabled) { isBlameDisabled = blameDisabled; } - /** - * Not used anymore. - * - * @return {@code true} if SCM forensics should be disabled - * @deprecated Forensics will be automatically skipped if the Forensics recorder is not activated. - */ - @SuppressWarnings("PMD.BooleanGetMethodName") - @Deprecated - public boolean getForensicsDisabled() { - return false; - } - - /** - * Not used anymore. - * - * @param forensicsDisabled - * not used - * - * @deprecated Forensics will be automatically skipped if the Forensics recorder is not activated. - */ - @DataBoundSetter - @Deprecated - public void setForensicsDisabled(final boolean forensicsDisabled) { - // do nothing - } - @CheckForNull public String getSourceCodeEncoding() { return sourceCodeEncoding; diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AffectedFilesResolver.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AffectedFilesResolver.java index a4366099d5..292ac6173a 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AffectedFilesResolver.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AffectedFilesResolver.java @@ -15,7 +15,6 @@ import hudson.FilePath; import hudson.model.Run; -import hudson.remoting.VirtualChannel; import io.jenkins.plugins.prism.FilePermissionEnforcer; @@ -111,28 +110,6 @@ public void copyAffectedFilesToBuildFolder(final Report report, final FilePath w copyAffectedFilesToBuildFolder(report, new RemoteFacade(workspace, permittedSourceDirectories, buildFolder)); } - /** - * Copies all files with issues from the workspace to the build folder. - * - * @param report - * the issues - * @param channel - * virtual channel to access the files on the agent - * @param buildFolder - * directory to store the copied files in - * @param permittedSourceDirectories - * paths to the affected files on the agent - * - * @throws InterruptedException - * if the user cancels the processing - * @deprecated use {@link #copyAffectedFilesToBuildFolder(Report, FilePath, Set, FilePath)} - */ - @Deprecated - public void copyAffectedFilesToBuildFolder(final Report report, final VirtualChannel channel, - final FilePath buildFolder, final Set permittedSourceDirectories) throws InterruptedException { - // do nothing - } - @VisibleForTesting @SuppressWarnings("PMD.CognitiveComplexity") void copyAffectedFilesToBuildFolder(final Report report, final RemoteFacade remoteFacade) @@ -170,7 +147,6 @@ void copyAffectedFilesToBuildFolder(final Report report, final RemoteFacade remo log.getErrorMessages().forEach(report::logError); report.logInfo("-> %d copied, %d not in workspace, %d not-found, %d with I/O error", copied, notInWorkspace, notFound, log.size()); - log.logSummary(); } static class RemoteFacade { diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsBuilder.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsBuilder.java index da969e2fd4..e3bb6d0984 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsBuilder.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsBuilder.java @@ -113,46 +113,4 @@ void clear() { fixedSize = 0; } - - /** - * Computed automatically since 6.1.0. - * - * @param unused - * not used - * - * @return this - * @deprecated Computed automatically. - */ - @Deprecated - public IssuesStatisticsBuilder setTotalSize(final int unused) { - return this; - } - - /** - * Computed automatically since 6.1.0. - * - * @param unused - * not used - * - * @return this - * @deprecated Computed automatically. - */ - @Deprecated - public IssuesStatisticsBuilder setNewSize(final int unused) { - return this; - } - - /** - * Computed automatically since 6.1.0. - * - * @param unused - * not used - * - * @return this - * @deprecated Computed automatically. - */ - @Deprecated - public IssuesStatisticsBuilder setDeltaSize(final int unused) { - return this; - } } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/DetailsTableModelTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/DetailsTableModelTest.java index e3d7aa58c7..5a7033d222 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/DetailsTableModelTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/DetailsTableModelTest.java @@ -16,7 +16,7 @@ */ class DetailsTableModelTest extends AbstractDetailsModelTest { @Test - @org.jvnet.hudson.test.Issue("JENKINS-64051") + @org.junitpioneer.jupiter.Issue("JENKINS-64051") void shouldNotRemoveWhitespace() { try (IssueBuilder builder = new IssueBuilder()) { builder.setMessage("project: Defaults to NumberGroupSeparator on .NET Core except on Windows."); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/JobActionTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/JobActionTest.java index 27e757ead5..aa8a2a2a7e 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/JobActionTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/JobActionTest.java @@ -32,7 +32,7 @@ class JobActionTest { void shouldUseLabelProvider() { StaticAnalysisLabelProvider labelProvider = mock(StaticAnalysisLabelProvider.class); when(labelProvider.getLinkName()).thenReturn(LINK_NAME); - when(labelProvider.getRawLinkName()).thenReturn(LINK_NAME); + when(labelProvider.getLinkName()).thenReturn(LINK_NAME); when(labelProvider.getTrendName()).thenReturn(TREND_NAME); when(labelProvider.getId()).thenReturn(ID); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java index 8fc53cd44a..cb6f79e2f1 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java @@ -74,7 +74,7 @@ void shouldCollectSingleResultForSingleAxis() { verify(recorder).publishResult(any(), any(), anyString(), any(), anyString(), any()); } - @Test @org.jvnet.hudson.test.Issue("JENKINS-59178") + @Test @org.junitpioneer.jupiter.Issue("JENKINS-59178") void shouldCollectDifferentResultsForTwoAxes() { IssuesRecorder recorder = mock(IssuesRecorder.class); IssuesAggregator aggregator = createIssueAggregator(recorder); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/TimeStamperPluginITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/TimeStamperPluginITest.java index ffe284ad69..c0b51ba631 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/TimeStamperPluginITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/TimeStamperPluginITest.java @@ -65,7 +65,7 @@ private String getWorkspacePath(final WorkflowJob project, final String fileName /** * Tests JENKINS-56484: Error while parsing clang errors with active timestamper plugin. */ - @Test @org.jvnet.hudson.test.Issue("JENKINS-56484") + @Test @org.junitpioneer.jupiter.Issue("JENKINS-56484") void shouldCorrectlyParseClangErrors() { WorkflowJob project = createPipeline(); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java index 04129d9a1c..ebfbc79869 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java @@ -210,7 +210,7 @@ void shouldShowNoFilesOutsideWorkspace() { * Verifies that a source code file will be copied from outside the workspace if configured correspondingly. */ @Test - @org.jvnet.hudson.test.Issue("JENKINS-55998") + @org.junitpioneer.jupiter.Issue("JENKINS-55998") void shouldShowFileOutsideWorkspaceIfConfigured() { FreeStyleProject job = createFreeStyleProject(); prepareGccLog(job); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java index 7f2ce6856f..ccb593bba5 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/MiscIssuesRecorderITest.java @@ -57,7 +57,7 @@ class MiscIssuesRecorderITest extends IntegrationTestWithJenkinsPerSuite { /** * Verifies that {@link FindBugs} handles the different severity mapping modes ({@link PriorityProperty}). */ - @Test @org.jvnet.hudson.test.Issue("JENKINS-55514") + @Test @org.junitpioneer.jupiter.Issue("JENKINS-55514") void shouldMapSeverityFilterForFindBugs() { FreeStyleProject project = createFreeStyleProjectWithWorkspaceFilesWithSuffix("findbugs-severities.xml"); @@ -256,7 +256,7 @@ void shouldHaveOriginsIfBuildContainsWarnings() { * Verifies that a report that contains errors (since the report pattern does not find some files), * will fail the step if the property {@link IssuesRecorder#setFailOnError(boolean)} is enabled. */ - @Test @org.jvnet.hudson.test.Issue("JENKINS-58056") + @Test @org.junitpioneer.jupiter.Issue("JENKINS-58056") void shouldFailBuildWhenFailBuildOnErrorsIsSet() { FreeStyleProject job = createFreeStyleProject(); IssuesRecorder recorder = enableEclipseWarnings(job); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/PackageDetectorsITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/PackageDetectorsITest.java index 631f937a79..8dedc87849 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/PackageDetectorsITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/PackageDetectorsITest.java @@ -42,7 +42,7 @@ class PackageDetectorsITest extends IntegrationTestWithJenkinsPerSuite { * in the expected HTML output. */ @Test - @org.jvnet.hudson.test.Issue("JENKINS-58538") + @org.junitpioneer.jupiter.Issue("JENKINS-58538") void shouldShowFolderDistributionRatherThanPackageDistribution() { FreeStyleProject project = createFreeStyleProject(); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java index 6d5a4b8036..a96e4f1f74 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsITest.java @@ -109,7 +109,7 @@ void shouldWhitelistScannerApi() { * Runs a pipeline and verifies the {@code recordIssues} step has some allowlisted methods. */ @Test - @org.jvnet.hudson.test.Issue("JENKINS-63109") + @org.junitpioneer.jupiter.Issue("JENKINS-63109") void shouldWhitelistRecorderApi() { WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix("checkstyle1.xml", "checkstyle2.xml"); @@ -554,7 +554,7 @@ void shouldEnforceQualityGate() { /** Runs the JavaDoc parser and enforces quality gates. */ @Test - @org.jvnet.hudson.test.Issue("JENKINS-58253") + @org.junitpioneer.jupiter.Issue("JENKINS-58253") void shouldFailBuildWhenFailBuildOnErrorsIsSet() { WorkflowJob job = createPipeline(); @@ -623,7 +623,7 @@ private void assertThatJavaIssuesArePublished(final AnalysisResult result) { * for the origin field as well. */ @Test - @org.jvnet.hudson.test.Issue("JENKINS-57638") + @org.junitpioneer.jupiter.Issue("JENKINS-57638") void shouldUseCustomIdsForOrigin() { verifyCustomIdsForOrigin(asStage( "def java = scanForIssues tool: java(pattern:'**/*issues.txt', reportEncoding:'UTF-8', id:'id1', name:'name1')", @@ -636,7 +636,7 @@ void shouldUseCustomIdsForOrigin() { * for the origin field as well. */ @Test - @org.jvnet.hudson.test.Issue("JENKINS-57638") + @org.junitpioneer.jupiter.Issue("JENKINS-57638") void shouldUseCustomIdsForOriginSimpleStep() { verifyCustomIdsForOrigin(asStage( "recordIssues(\n" @@ -1036,7 +1036,7 @@ void shouldHandleMissingJobBuildIdAsReference() { * @see Issue 39203 */ @Test - @org.jvnet.hudson.test.Issue("JENKINS-39203") + @org.junitpioneer.jupiter.Issue("JENKINS-39203") void publishIssuesShouldMarkStepWithWarningAction() { WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix("javac.txt"); job.setDefinition(asStage(createScanForIssuesStep(new Java(), "java"), @@ -1058,7 +1058,7 @@ void publishIssuesShouldMarkStepWithWarningAction() { * @see Issue 39203 */ @Test - @org.jvnet.hudson.test.Issue("JENKINS-39203") + @org.junitpioneer.jupiter.Issue("JENKINS-39203") void recordIssuesShouldMarkStepWithWarningAction() { WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix("javac.txt"); job.setDefinition(asStage("recordIssues(tool: java(pattern:'**/*issues.txt', reportEncoding:'UTF-8')," diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsOnAgentITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsOnAgentITest.java index 08044c132f..766f27de2a 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsOnAgentITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/StepsOnAgentITest.java @@ -35,7 +35,7 @@ class StepsOnAgentITest extends IntegrationTestWithJenkinsPerTest { * active, see JENKINS-56007 for details. */ @Test - @org.jvnet.hudson.test.Issue("JENKINS-56007") + @org.junitpioneer.jupiter.Issue("JENKINS-56007") void shouldCopySourcesIfMasterAgentSecurityIsActive() { Slave agent = createAgentWithEnabledSecurity("agent"); @@ -58,7 +58,7 @@ void shouldCopySourcesIfMasterAgentSecurityIsActive() { } private String getSourceCode(final AnalysisResult result, final int rowIndex) { - IssuesDetail target = (IssuesDetail) result.getOwner().getAction(ResultAction.class).getTarget(); + IssuesDetail target = result.getOwner().getAction(ResultAction.class).getTarget(); String sourceCodeUrl = new FileNameRenderer(result.getOwner()).getSourceCodeUrl( result.getIssues().get(rowIndex)); SourceCodeViewModel dynamic = (SourceCodeViewModel) target.getDynamic( diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/tasks/TaskScannerTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/tasks/TaskScannerTest.java index 56d7c264c2..7f6a9aec5a 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/tasks/TaskScannerTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/tasks/TaskScannerTest.java @@ -118,7 +118,7 @@ void shouldParseRegularExpressionsIssue17225() { * * @see Issue 64622 */ - @Test @org.jvnet.hudson.test.Issue("JENKINS-64622") + @Test @org.junitpioneer.jupiter.Issue("JENKINS-64622") void shouldHandleEmptyMatchWithRegExp() { Report tasks = new TaskScannerBuilder() .setHighTasks("(a)?(b)?.*") @@ -134,7 +134,7 @@ void shouldHandleEmptyMatchWithRegExp() { * * @see Issue 22744 */ - @Test @org.jvnet.hudson.test.Issue("JENKINS-22744") + @Test @org.junitpioneer.jupiter.Issue("JENKINS-22744") void issue22744() { Report tasks = new TaskScannerBuilder() .setHighTasks("FIXME") From cc2f21e1a2be76aee39090e62ccda4e4e61f2276 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 12 Jan 2024 17:10:21 +0100 Subject: [PATCH 19/27] Remove global configuration of source paths. --- .../model/WarningsPluginConfiguration.java | 118 ------------------ .../analysis/core/steps/IssuesScanner.java | 12 +- .../analysis/warnings/steps/DryITest.java | 6 +- 3 files changed, 4 insertions(+), 132 deletions(-) delete mode 100644 plugin/src/main/java/io/jenkins/plugins/analysis/core/model/WarningsPluginConfiguration.java diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/WarningsPluginConfiguration.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/WarningsPluginConfiguration.java deleted file mode 100644 index eab56fa970..0000000000 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/WarningsPluginConfiguration.java +++ /dev/null @@ -1,118 +0,0 @@ -package io.jenkins.plugins.analysis.core.model; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.commons.lang3.StringUtils; - -import edu.hm.hafner.util.PathUtil; -import edu.hm.hafner.util.VisibleForTesting; - -import org.kohsuke.stapler.DataBoundSetter; -import org.jenkinsci.Symbol; -import hudson.Extension; -import hudson.FilePath; -import jenkins.model.GlobalConfiguration; - -import io.jenkins.plugins.util.GlobalConfigurationFacade; -import io.jenkins.plugins.util.GlobalConfigurationItem; - -/** - * Global system configuration of the warnings plugin. These configuration options are used globally for all jobs and - * require administrator permissions. - * - * @author Ullrich Hafner - */ -@Extension -@Symbol("warningsPlugin") -public class WarningsPluginConfiguration extends GlobalConfigurationItem { - private static final PathUtil PATH_UTIL = new PathUtil(); - private List sourceDirectories = Collections.emptyList(); - private Set normalizedSourceDirectories = Collections.emptySet(); - - /** - * Creates the global configuration for the warnings plugins. - */ - public WarningsPluginConfiguration() { - super(); - - load(); - } - - @VisibleForTesting - WarningsPluginConfiguration(final GlobalConfigurationFacade facade) { - super(facade); - - load(); - } - - @Override - protected void clearRepeatableProperties() { - setSourceDirectories(new ArrayList<>()); - } - - /** - * Returns the singleton instance of this {@link WarningsPluginConfiguration}. - * - * @return the singleton instance - */ - public static WarningsPluginConfiguration getInstance() { - return GlobalConfiguration.all().get(WarningsPluginConfiguration.class); - } - - /** - * Returns the list of source directories that contain the affected files.. - * - * @return the source root folders - */ - public List getSourceDirectories() { - return sourceDirectories; - } - - /** - * Sets the list of source directories to the specified elements. Previously set directories will be removed. - * - * @param sourceDirectories - * the source directories that contain the affected files - */ - @DataBoundSetter - public void setSourceDirectories(final List sourceDirectories) { - this.sourceDirectories = new ArrayList<>(sourceDirectories); - - normalizedSourceDirectories = sourceDirectories.stream() - .map(SourceDirectory::getPath) - .map(PATH_UTIL::getAbsolutePath) - .collect(Collectors.toSet()); - - save(); - } - - /** - * Filters the specified collection of additional directories so that only permitted source directories will be - * returned. Permitted source directories are absolute paths that have been registered using {@link - * #setSourceDirectories(List)} or relative paths in the workspace. - * - * @param workspace - * the workspace containing the affected files - * @param sourceDirectory - * additional source directly (might be empty): a relative path in the workspace or an absolute path - * - * @return the permitted source directory - or as a fallback the the workspace path - */ - public FilePath getPermittedSourceDirectory(final FilePath workspace, final String sourceDirectory) { - PathUtil pathUtil = new PathUtil(); - String normalized = pathUtil.getAbsolutePath(sourceDirectory); - if (pathUtil.isAbsolute(normalized)) { - if (normalizedSourceDirectories.contains(normalized)) { // skip not registered absolute paths - return workspace.child(normalized); - } - } - else if (StringUtils.isNotBlank(sourceDirectory) && !"-".equals(sourceDirectory)) { - return workspace.child(normalized); - } - return workspace; - } -} diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java index 8f2e6ce8d6..36bf1f3708 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java @@ -37,9 +37,7 @@ import io.jenkins.plugins.analysis.core.filter.RegexpFilter; import io.jenkins.plugins.analysis.core.model.ReportLocations; -import io.jenkins.plugins.analysis.core.model.SourceDirectory; import io.jenkins.plugins.analysis.core.model.Tool; -import io.jenkins.plugins.analysis.core.model.WarningsPluginConfiguration; import io.jenkins.plugins.analysis.core.util.AffectedFilesResolver; import io.jenkins.plugins.analysis.core.util.ConsoleLogHandler; import io.jenkins.plugins.analysis.core.util.FileFinder; @@ -151,19 +149,11 @@ private ReportPostProcessor createPostProcessor(final Report report) { } private Set getPermittedSourceDirectories() { - Set permittedSourceDirectories = PrismConfiguration.getInstance() + return PrismConfiguration.getInstance() .getSourceDirectories() .stream() .map(PermittedSourceCodeDirectory::getPath) .collect(Collectors.toSet()); - List permittedSourceCodeDirectoriesOfWarningsPlugin - = WarningsPluginConfiguration.getInstance() - .getSourceDirectories() - .stream() - .map(SourceDirectory::getPath) - .collect(Collectors.toList()); - permittedSourceDirectories.addAll(permittedSourceCodeDirectoriesOfWarningsPlugin); - return permittedSourceDirectories; } private Blamer createBlamer(final Report report) { diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/DryITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/DryITest.java index 4ac87326bf..ee570eb49c 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/DryITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/DryITest.java @@ -40,7 +40,7 @@ class DryITest extends IntegrationTestWithJenkinsPerSuite { private static final String CPD_REPORT = FOLDER + "cpd.xml"; /** - * Verifies that the right amount of duplicate code warnings are detected. + * Verifies that the right number of duplicate code warnings is detected. */ @Test void shouldHaveDuplicateCodeWarnings() { @@ -61,7 +61,7 @@ void shouldHaveDuplicateCodeWarnings() { } /** - * Verifies that the priority of the duplicate code warnings are changed corresponding to the defined thresholds for + * Verifies that the priority of the duplicate code warnings is changed corresponding to the defined thresholds for * cpd warnings. */ @Test @@ -115,7 +115,7 @@ void shouldConfigureSeverityThresholdTo5InJobConfigurationForCpd() { } /** - * Verifies that the priority of the duplicate code warnings are changed corresponding to the defined thresholds for + * Verifies that the priority of the duplicate code warnings is changed corresponding to the defined thresholds for * cpd warnings. */ @Test From d1fad88275d6bb160c502b605092d44706f17f20 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 12 Jan 2024 17:48:50 +0100 Subject: [PATCH 20/27] Remove source directory. --- plugin/pom.xml | 6 ++ .../analysis/core/model/SourceDirectory.java | 55 ------------------- .../steps/AffectedFilesResolverITest.java | 10 +--- 3 files changed, 9 insertions(+), 62 deletions(-) delete mode 100644 plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SourceDirectory.java diff --git a/plugin/pom.xml b/plugin/pom.xml index 7ed22eb70a..d25d156d3e 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -372,6 +372,12 @@ parasoft-findings 10.7.1 test + + + warnings-ng + io.jenkins.plugins + +
org.jenkins-ci.plugins diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SourceDirectory.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SourceDirectory.java deleted file mode 100644 index fab68b386a..0000000000 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SourceDirectory.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.jenkins.plugins.analysis.core.model; - -import java.io.Serializable; - -import org.apache.commons.lang3.StringUtils; - -import edu.umd.cs.findbugs.annotations.NonNull; - -import org.kohsuke.stapler.DataBoundConstructor; -import hudson.Extension; -import hudson.model.AbstractDescribableImpl; -import hudson.model.Descriptor; - -/** - * Directory that contains the source files that have issues. - * - * @author Ullrich Hafner - */ -public class SourceDirectory extends AbstractDescribableImpl implements Serializable { - private static final long serialVersionUID = -3864564528382064924L; - - private final String path; - - /** - * Creates a new instance of {@link SourceDirectory}. - * - * @param path - * the name of the folder - */ - @DataBoundConstructor - public SourceDirectory(final String path) { - super(); - - this.path = path; - } - - public String getPath() { - return path; - } - - /** - * Descriptor to validate {@link SourceDirectory}. - * - * @author Ullrich Hafner - */ - @Extension - public static class DescriptorImpl extends Descriptor { - @NonNull - @Override - public String getDisplayName() { - return StringUtils.EMPTY; - } - } -} - diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java index ebfbc79869..e7e47144c9 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java @@ -25,8 +25,6 @@ import io.jenkins.plugins.analysis.core.model.IssuesDetail; import io.jenkins.plugins.analysis.core.model.IssuesModel.IssuesRow; import io.jenkins.plugins.analysis.core.model.ResultAction; -import io.jenkins.plugins.analysis.core.model.SourceDirectory; -import io.jenkins.plugins.analysis.core.model.WarningsPluginConfiguration; import io.jenkins.plugins.analysis.core.steps.IssuesRecorder; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerSuite; import io.jenkins.plugins.analysis.core.util.AffectedFilesResolver; @@ -222,15 +220,13 @@ void shouldShowFileOutsideWorkspaceIfConfigured() { // First build: copying the affected file is forbidden buildAndVerifyFilesResolving(job, ColumnLink.SHOULD_NOT_HAVE_LINK, "0 copied", "1 not in workspace", "0 not-found", "0 with I/O error"); - // Use source directories of old Warnings plugin configuration - WarningsPluginConfiguration.getInstance().setSourceDirectories( - Collections.singletonList(new SourceDirectory(buildsFolder))); + PrismConfiguration.getInstance().setSourceDirectories( + Collections.singletonList(new PermittedSourceCodeDirectory(buildsFolder))); // Second build: copying the affected file is permitted buildAndVerifyFilesResolving(job, ColumnLink.SHOULD_HAVE_LINK, "1 copied", "0 not in workspace", "0 not-found", "0 with I/O error"); - // Use source directories of new Prism plugin configuration - WarningsPluginConfiguration.getInstance().setSourceDirectories(new ArrayList<>()); + PrismConfiguration.getInstance().setSourceDirectories(new ArrayList<>()); // Third build: copying the affected file is forbidden again buildAndVerifyFilesResolving(job, ColumnLink.SHOULD_NOT_HAVE_LINK, "0 copied", "1 not in workspace", "0 not-found", "0 with I/O error"); From e4b3c2f5916a2de2c6a18666458618b30301f096 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 12 Jan 2024 17:52:30 +0100 Subject: [PATCH 21/27] Remove source directory. --- .../core/model/TabLabelProviderTest.java | 1 - .../WarningsPluginConfigurationTest.java | 99 ------------------- .../ConfigurationAsCodeITest.java | 15 --- 3 files changed, 115 deletions(-) delete mode 100644 plugin/src/test/java/io/jenkins/plugins/analysis/core/model/WarningsPluginConfigurationTest.java diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/TabLabelProviderTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/TabLabelProviderTest.java index 807868be84..0d2d31b743 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/TabLabelProviderTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/TabLabelProviderTest.java @@ -14,7 +14,6 @@ * @author Tobias Redl */ class TabLabelProviderTest { - private TabLabelProvider createTabLabelProvider(final String fileName) { Issue issue = mock(Issue.class); when(issue.getFileName()).thenReturn(fileName); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/WarningsPluginConfigurationTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/WarningsPluginConfigurationTest.java deleted file mode 100644 index 09b53c9ade..0000000000 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/WarningsPluginConfigurationTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.jenkins.plugins.analysis.core.model; - -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import edu.hm.hafner.util.PathUtil; - -import hudson.FilePath; -import hudson.remoting.VirtualChannel; - -import io.jenkins.plugins.util.GlobalConfigurationFacade; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -/** - * Tests the class {@link WarningsPluginConfiguration}. - * - * @author Ullrich Hafner - */ -class WarningsPluginConfigurationTest { - private static final PathUtil PATH_UTIL = new PathUtil(); - - private static final String FIRST = "/One"; - private static final String SECOND = "/Two"; - private static final String ABSOLUTE_NOT_EXISTING = "/Three"; - private static final String RELATIVE = "Relative"; - - private static final String NORMALIZED = PATH_UTIL.getAbsolutePath("/workspace"); - - private static final List SOURCE_ROOTS - = Arrays.asList(new SourceDirectory(FIRST), new SourceDirectory(SECOND)); - - @Test - void shouldHaveNoRootFoldersWhenCreated() { - WarningsPluginConfiguration configuration = createConfiguration(); - - assertThat(configuration.getSourceDirectories()).isEmpty(); - - assertThat(get(configuration, "")).isEqualTo(NORMALIZED); - assertThat(get(configuration, "-")).isEqualTo(NORMALIZED); - assertThat(get(configuration, ABSOLUTE_NOT_EXISTING)).isEqualTo(NORMALIZED); - assertThat(get(configuration, RELATIVE)).isEqualTo(getWorkspaceChild(RELATIVE)); - } - - @Test - void shouldSaveConfigurationIfFoldersAreAdded() { - GlobalConfigurationFacade facade = mock(GlobalConfigurationFacade.class); - WarningsPluginConfiguration configuration = new WarningsPluginConfiguration(facade); - - configuration.setSourceDirectories(SOURCE_ROOTS); - - verify(facade).save(); - assertThat(configuration.getSourceDirectories()).isEqualTo(SOURCE_ROOTS); - - assertThat(get(configuration, FIRST)).isEqualTo(FIRST); - assertThat(get(configuration, RELATIVE)).isEqualTo(getWorkspaceChild(RELATIVE)); - assertThat(get(configuration, ABSOLUTE_NOT_EXISTING)).isEqualTo(NORMALIZED); - } - - @Test - void shouldNormalizePath() { - WarningsPluginConfiguration configuration = createConfiguration(); - - configuration.setSourceDirectories( - Arrays.asList(new SourceDirectory("/absolute/unix"), new SourceDirectory("C:\\absolute\\windows"))); - - String relativeUnix = "relative/unix"; - String relativeWindows = "relative\\windows"; - String absoluteUnix = "/absolute/unix"; - String absoluteWindows = "C:\\absolute\\windows"; - String absoluteWindowsNormalized = "C:/absolute/windows"; - - assertThat(get(configuration, relativeUnix)).isEqualTo(getWorkspaceChild(relativeUnix)); - assertThat(get(configuration, relativeWindows)).isEqualTo(getWorkspaceChild(relativeWindows)); - assertThat(get(configuration, absoluteUnix)).isEqualTo(absoluteUnix); - assertThat(get(configuration, absoluteWindows)).isEqualTo(normalize(absoluteWindows)); - assertThat(get(configuration, absoluteWindowsNormalized)).isEqualTo(absoluteWindowsNormalized); - } - - private String getWorkspaceChild(final String expected) { - return PATH_UTIL.createAbsolutePath(NORMALIZED, expected); - } - - private String normalize(final String remote) { - return PATH_UTIL.getAbsolutePath(remote); - } - - private String get(final WarningsPluginConfiguration configuration, final String absoluteUnix) { - FilePath path = new FilePath((VirtualChannel) null, NORMALIZED); - return normalize(configuration.getPermittedSourceDirectory(path, absoluteUnix).getRemote()); - } - - private WarningsPluginConfiguration createConfiguration() { - return new WarningsPluginConfiguration(mock(GlobalConfigurationFacade.class)); - } -} diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/ConfigurationAsCodeITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/ConfigurationAsCodeITest.java index a35f25b924..4f8f485e78 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/ConfigurationAsCodeITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/integrations/ConfigurationAsCodeITest.java @@ -8,9 +8,7 @@ import hudson.model.FreeStyleProject; import hudson.model.TopLevelItem; -import io.jenkins.plugins.analysis.core.model.SourceDirectory; import io.jenkins.plugins.analysis.core.model.Tool; -import io.jenkins.plugins.analysis.core.model.WarningsPluginConfiguration; import io.jenkins.plugins.analysis.core.steps.IssuesRecorder; import io.jenkins.plugins.analysis.core.testutil.IntegrationTestWithJenkinsPerTest; import io.jenkins.plugins.analysis.warnings.Java; @@ -48,19 +46,6 @@ void shouldImportParserSettingsFromYaml() { assertThat(parser.getScript()).isEqualTo("script"); } - /** - * Reads the YAML file with permitted source code directories and verifies that the directories have been loaded. - */ - @Test - void shouldImportSourceDirectoriesFromYaml() { - configureJenkins("sourceDirectories.yaml"); - - List parsers = WarningsPluginConfiguration.getInstance().getSourceDirectories(); - assertThat(parsers.stream().map(SourceDirectory::getPath)) - .hasSize(2) - .containsExactlyInAnyOrder("C:\\Windows", "/absolute"); - } - /** * Reads the YAML file with a freestyle job and verifies that the job has been created. */ From c9e0b4ad0961251862dbf7718f16badf3715db61 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 13 Jan 2024 14:00:52 +0100 Subject: [PATCH 22/27] Restore totals field when `null`. --- .../jenkins/plugins/analysis/core/model/AnalysisResult.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java index 7974561733..3d548dc6e7 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java @@ -31,6 +31,7 @@ import io.jenkins.plugins.analysis.core.charts.JenkinsBuild; import io.jenkins.plugins.analysis.core.util.IssuesStatistics; +import io.jenkins.plugins.analysis.core.util.IssuesStatisticsBuilder; import io.jenkins.plugins.analysis.core.util.StaticAnalysisRun; import io.jenkins.plugins.forensics.blame.Blames; import io.jenkins.plugins.forensics.blame.BlamesXmlStream; @@ -59,7 +60,7 @@ public class AnalysisResult implements Serializable, StaticAnalysisRun { private final String id; - private final IssuesStatistics totals; + private IssuesStatistics totals; private final Map sizePerOrigin; private final List errors; @@ -268,6 +269,9 @@ protected Object readResolve() { if (qualityGateResult == null && qualityGateStatus != null) { qualityGateResult = new QualityGateResult(qualityGateStatus); } + if (totals == null) { + totals = new IssuesStatisticsBuilder().build(); + } return this; } From 16139ca983b9a5236eb642fa63c61e197eac5739 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Mon, 15 Jan 2024 09:24:51 +0100 Subject: [PATCH 23/27] Remove test for old serialization. --- .../analysis/core/model/AnalysisResultTest.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisResultTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisResultTest.java index dc3a433bd8..fd40f666fb 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisResultTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/AnalysisResultTest.java @@ -1,7 +1,5 @@ package io.jenkins.plugins.analysis.core.model; -import java.io.IOException; -import java.nio.file.Path; import java.util.Collections; import org.junit.jupiter.api.Test; @@ -9,9 +7,7 @@ import edu.hm.hafner.util.ResourceTest; -import hudson.XmlFile; import hudson.model.Run; -import hudson.util.XStream2; import io.jenkins.plugins.forensics.blame.Blames; import io.jenkins.plugins.forensics.miner.RepositoryStatistics; @@ -26,18 +22,6 @@ * @author Ullrich Hafner */ class AnalysisResultTest extends ResourceTest { - @Test - void shouldRestoreResultBeforeIssuesStatisticsField() throws IOException { - XStream2 reportXmlStream = new XStream2(); - - Path xml = getResourceAsFile("result.xml"); - XmlFile xmlFile = new XmlFile(reportXmlStream, xml.toFile()); - AnalysisResult restored = (AnalysisResult) xmlFile.read(); - - assertThat(restored).hasTotalSize(14).hasNewSize(9).hasFixedSize(0); - assertThat(restored.getTotals()).hasTotalSize(14).hasNewSize(9).hasFixedSize(0); - } - @Test @Issue("SECURITY-2090") void constructorShouldThrowExceptionIfIdHasInvalidPattern() { From 8f0571fd87fd27bef53e72e3d3638d93ff016406 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 12 Jan 2024 18:24:08 +0100 Subject: [PATCH 24/27] [JENKINS-58167] Use source code retention strategy of Prism. --- .../core/steps/AnalysisStepDescriptor.java | 15 ++++ .../analysis/core/steps/IssuesRecorder.java | 68 ++++++++++--------- .../analysis/core/steps/IssuesScanner.java | 22 ++++-- .../analysis/core/steps/RecordIssuesStep.java | 40 +++++------ .../core/steps/ScanForIssuesStep.java | 49 +++++++------ .../help-sourceCodeRetention.html | 21 ++++++ .../help-sourceCodeRetention.html | 21 ++++++ .../help-sourceCodeRetention.html | 21 ++++++ .../resources/issues/scan-parameters.jelly | 4 ++ .../issues/scan-parameters.properties | 2 +- .../steps/AffectedFilesResolverITest.java | 57 +++++++++++++--- 11 files changed, 225 insertions(+), 95 deletions(-) create mode 100644 plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-sourceCodeRetention.html create mode 100644 plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-sourceCodeRetention.html create mode 100644 plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep/help-sourceCodeRetention.html diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java index ea8f747488..c1ee37322e 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/AnalysisStepDescriptor.java @@ -16,6 +16,7 @@ import jenkins.model.Jenkins; import io.jenkins.plugins.analysis.core.util.ModelValidation; +import io.jenkins.plugins.prism.SourceCodeRetention; import io.jenkins.plugins.util.JenkinsFacade; import io.jenkins.plugins.util.ValidationUtilities; @@ -85,6 +86,20 @@ public FormValidation doCheckSourceCodeEncoding(@AncestorInPath final BuildableI return VALIDATION_UTILITIES.validateCharset(sourceCodeEncoding); } + /** + * Returns a model with all {@link SourceCodeRetention} strategies. + * + * @return a model with all {@link SourceCodeRetention} strategies. + */ + @POST + @SuppressWarnings("unused") // used by Stapler view data binding + public ListBoxModel doFillSourceCodeRetentionItems() { + if (JENKINS.hasPermission(Jenkins.READ)) { + return SourceCodeRetention.fillItems(); + } + return new ListBoxModel(); + } + /** * Returns a model with all available severity filters. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java index e055132858..a90077115e 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java @@ -59,6 +59,7 @@ import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; import io.jenkins.plugins.checks.steps.ChecksInfo; import io.jenkins.plugins.prism.SourceCodeDirectory; +import io.jenkins.plugins.prism.SourceCodeRetention; import io.jenkins.plugins.util.JenkinsFacade; import io.jenkins.plugins.util.LogHandler; import io.jenkins.plugins.util.ResultHandler; @@ -92,8 +93,8 @@ public class IssuesRecorder extends Recorder { private List analysisTools = new ArrayList<>(); private String sourceCodeEncoding = StringUtils.EMPTY; - private String sourceDirectory = StringUtils.EMPTY; private Set sourceDirectories = new HashSet<>(); // @since 9.11.0 + private SourceCodeRetention sourceCodeRetention = SourceCodeRetention.EVERY_BUILD; private boolean ignoreQualityGate = false; // by default, a successful quality gate is mandatory; private boolean ignoreFailedBuilds = true; // by default, failed builds are ignored; @@ -114,14 +115,6 @@ public class IssuesRecorder extends Recorder { private boolean quiet = false; private boolean isBlameDisabled; - /** - * Not used anymore. - * - * @deprecated since 8.5.0 - */ - @Deprecated - private transient boolean isForensicsDisabled; - private boolean skipPublishingChecks; // by default, checks will be published private boolean publishAllIssues; // by default, only new issues will be published @@ -156,14 +149,8 @@ public IssuesRecorder() { * @return this */ protected Object readResolve() { - if (sourceDirectory == null) { - sourceDirectory = StringUtils.EMPTY; - } if (sourceDirectories == null) { sourceDirectories = new HashSet<>(); - if (StringUtils.isNotBlank(sourceDirectory)) { - sourceDirectories.add(new SourceCodeDirectory(sourceDirectory)); - } } if (trendChartType == null) { trendChartType = TrendChartType.AGGREGATION_TOOLS; @@ -177,6 +164,9 @@ protected Object readResolve() { if (scm == null) { scm = StringUtils.EMPTY; } + if (sourceCodeRetention == null) { + sourceCodeRetention = SourceCodeRetention.EVERY_BUILD; + } return this; } @@ -335,22 +325,6 @@ public void setSourceCodeEncoding(final String sourceCodeEncoding) { this.sourceCodeEncoding = sourceCodeEncoding; } - public String getSourceDirectory() { - return sourceDirectory; - } - - /** - * Sets the path to the directory that contains the source code. If not relative and thus not part of the workspace, - * then this directory needs to be added in Jenkins global configuration to prevent accessing of forbidden resources. - * - * @param sourceDirectory - * directory containing the source code - */ - @DataBoundSetter - public void setSourceDirectory(final String sourceDirectory) { - this.sourceDirectory = sourceDirectory; - } - /** * Sets the paths to the directories that contain the source code. If not relative and thus not part of the workspace, * then these directories need to be added in Jenkins global configuration to prevent accessing of forbidden resources. @@ -367,6 +341,21 @@ public List getSourceDirectories() { return new ArrayList<>(sourceDirectories); } + /** + * Defines the retention strategy for source code files. + * + * @param sourceCodeRetention + * the retention strategy for source code files + */ + @DataBoundSetter + public void setSourceCodeRetention(final SourceCodeRetention sourceCodeRetention) { + this.sourceCodeRetention = sourceCodeRetention; + } + + public SourceCodeRetention getSourceCodeRetention() { + return sourceCodeRetention; + } + /** * Returns whether the results for each configured static analysis result should be aggregated into a single result * or if every tool should get an individual result. @@ -764,7 +753,8 @@ private String getReportName(final Tool tool) { private AnnotatedReport scanWithTool(final Run run, final FilePath workspace, final TaskListener listener, final Tool tool) throws IOException, InterruptedException { IssuesScanner issuesScanner = new IssuesScanner(tool, getFilters(), getSourceCodeCharset(), - workspace, getSourceCodePaths(), run, new FilePath(run.getRootDir()), listener, + workspace, getSourceCodePaths(), getSourceCodeRetention(), + run, new FilePath(run.getRootDir()), listener, scm, isBlameDisabled ? BlameMode.DISABLED : BlameMode.ENABLED, skipPostProcessing ? PostProcessingMode.DISABLED : PostProcessingMode.ENABLED, quiet); @@ -891,6 +881,20 @@ public ComboBoxModel doFillSourceCodeEncodingItems(@AncestorInPath final Buildab return new ComboBoxModel(); } + /** + * Returns a model with all {@link SourceCodeRetention} strategies. + * + * @return a model with all {@link SourceCodeRetention} strategies. + */ + @POST + @SuppressWarnings("unused") // used by Stapler view data binding + public ListBoxModel doFillSourceCodeRetentionItems() { + if (JENKINS.hasPermission(Jenkins.READ)) { + return SourceCodeRetention.fillItems(); + } + return new ListBoxModel(); + } + /** * Returns a model with all available severity filters. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java index 36bf1f3708..4b0dbd415c 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesScanner.java @@ -50,6 +50,7 @@ import io.jenkins.plugins.forensics.miner.RepositoryStatistics; import io.jenkins.plugins.prism.PermittedSourceCodeDirectory; import io.jenkins.plugins.prism.PrismConfiguration; +import io.jenkins.plugins.prism.SourceCodeRetention; import io.jenkins.plugins.prism.SourceDirectoryFilter; import io.jenkins.plugins.util.LogHandler; @@ -64,6 +65,7 @@ class IssuesScanner { private final FilePath workspace; private final Set sourceDirectories; + private final SourceCodeRetention sourceCodeRetention; private final Run run; private final FilePath jenkinsRootDir; private final Charset sourceCodeEncoding; @@ -85,7 +87,8 @@ enum PostProcessingMode { @SuppressWarnings("checkstyle:ParameterNumber") IssuesScanner(final Tool tool, final List filters, final Charset sourceCodeEncoding, - final FilePath workspace, final Set sourceDirectories, final Run run, + final FilePath workspace, final Set sourceDirectories, + final SourceCodeRetention sourceCodeRetention, final Run run, final FilePath jenkinsRootDir, final TaskListener listener, final String scm, final BlameMode blameMode, final PostProcessingMode postProcessingMode, final boolean quiet) { @@ -94,6 +97,7 @@ enum PostProcessingMode { this.tool = tool; this.workspace = workspace; this.sourceDirectories = sourceDirectories; + this.sourceCodeRetention = sourceCodeRetention; this.run = run; this.jenkinsRootDir = jenkinsRootDir; this.listener = listener; @@ -177,12 +181,18 @@ private Blamer createBlamer(final Report report) { private void copyAffectedFiles(final Report report, final FilePath buildFolder) throws InterruptedException { - report.logInfo("Copying affected files to Jenkins' build folder '%s'", buildFolder); + var log = new FilteredLog("Errors while processing affected files"); + if (sourceCodeRetention != SourceCodeRetention.NEVER) { + report.logInfo("Copying affected files to Jenkins' build folder '%s'", buildFolder); + + Set permittedSourceDirectories = getPermittedSourceDirectories(); + permittedSourceDirectories.add(workspace.getRemote()); + new AffectedFilesResolver().copyAffectedFilesToBuildFolder( + report, workspace, permittedSourceDirectories, buildFolder); + } + sourceCodeRetention.cleanup(run, AFFECTED_FILES_FOLDER_NAME, log); - Set permittedSourceDirectories = getPermittedSourceDirectories(); - permittedSourceDirectories.add(workspace.getRemote()); - new AffectedFilesResolver().copyAffectedFilesToBuildFolder( - report, workspace, permittedSourceDirectories, buildFolder); + report.mergeLogMessages(log); } private FilePath createAffectedFilesFolder(final Report report) throws InterruptedException { diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java index aed1c67aad..82b73a10a5 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep.java @@ -39,6 +39,7 @@ import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; import io.jenkins.plugins.checks.steps.ChecksInfo; import io.jenkins.plugins.prism.SourceCodeDirectory; +import io.jenkins.plugins.prism.SourceCodeRetention; import io.jenkins.plugins.util.PipelineResultHandler; import io.jenkins.plugins.util.QualityGateEvaluator; import io.jenkins.plugins.util.ResultHandler; @@ -69,8 +70,8 @@ public class RecordIssuesStep extends Step implements Serializable { private List analysisTools = new ArrayList<>(); private String sourceCodeEncoding = StringUtils.EMPTY; - private String sourceDirectory = StringUtils.EMPTY; private Set sourceDirectories = new HashSet<>(); // @since 9.11.0 + private SourceCodeRetention sourceCodeRetention = SourceCodeRetention.EVERY_BUILD; private boolean ignoreQualityGate = false; // by default, a successful quality gate is mandatory; private boolean ignoreFailedBuilds = true; // by default, failed builds are ignored; @@ -310,22 +311,6 @@ public void setSourceCodeEncoding(final String sourceCodeEncoding) { this.sourceCodeEncoding = sourceCodeEncoding; } - public String getSourceDirectory() { - return sourceDirectory; - } - - /** - * Sets the path to the folder that contains the source code. If not relative and thus not part of the workspace - * then this folder needs to be added in Jenkins global configuration. - * - * @param sourceDirectory - * a folder containing the source code - */ - @DataBoundSetter - public void setSourceDirectory(final String sourceDirectory) { - this.sourceDirectory = sourceDirectory; - } - /** * Sets the paths to the directories that contain the source code. If not relative and thus not part of the * workspace, then these directories need to be added in Jenkins global configuration to prevent accessing of @@ -344,11 +329,22 @@ public List getSourceDirectories() { } private List getAllSourceDirectories() { - Set directories = new HashSet<>(getSourceDirectories()); - if (StringUtils.isNotBlank(getSourceDirectory())) { - directories.add(new SourceCodeDirectory(getSourceDirectory())); - } - return new ArrayList<>(directories); + return new ArrayList<>(new HashSet<>(getSourceDirectories())); + } + + /** + * Defines the retention strategy for source code files. + * + * @param sourceCodeRetention + * the retention strategy for source code files + */ + @DataBoundSetter + public void setSourceCodeRetention(final SourceCodeRetention sourceCodeRetention) { + this.sourceCodeRetention = sourceCodeRetention; + } + + public SourceCodeRetention getSourceCodeRetention() { + return sourceCodeRetention; } /** diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java index 3bd857a530..6d8ac823cf 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep.java @@ -30,6 +30,7 @@ import io.jenkins.plugins.analysis.core.steps.IssuesScanner.BlameMode; import io.jenkins.plugins.analysis.core.steps.IssuesScanner.PostProcessingMode; import io.jenkins.plugins.prism.SourceCodeDirectory; +import io.jenkins.plugins.prism.SourceCodeRetention; /** * Scan files or the console log for issues. @@ -39,8 +40,8 @@ public class ScanForIssuesStep extends Step { private Tool tool; private String sourceCodeEncoding = StringUtils.EMPTY; - private String sourceDirectory = StringUtils.EMPTY; private Set sourceDirectories = new HashSet<>(); // @since 9.11.0 + private SourceCodeRetention sourceCodeRetention = SourceCodeRetention.EVERY_BUILD; private boolean isBlameDisabled; private boolean skipPostProcessing; // @since 10.6.0: by default, post-processing will be enabled private boolean quiet; @@ -159,25 +160,9 @@ public void setSourceCodeEncoding(final String sourceCodeEncoding) { this.sourceCodeEncoding = sourceCodeEncoding; } - public String getSourceDirectory() { - return sourceDirectory; - } - - /** - * Sets the path to the folder that contains the source code. If not relative and thus not part of the workspace - * then this folder needs to be added in Jenkins global configuration. - * - * @param sourceDirectory - * a folder containing the source code - */ - @DataBoundSetter - public void setSourceDirectory(final String sourceDirectory) { - this.sourceDirectory = sourceDirectory; - } - /** * Sets the paths to the directories that contain the source code. If not relative and thus not part of the - * workspace then these directories need to be added in Jenkins global configuration to prevent accessing of + * workspace, then these directories need to be added in Jenkins global configuration to prevent accessing of * forbidden resources. * * @param sourceDirectories @@ -193,14 +178,24 @@ public List getSourceDirectories() { } private Set getAllSourceDirectories() { - Set directories = new HashSet<>(); - if (StringUtils.isNotBlank(getSourceDirectory())) { - directories.add(getSourceDirectory()); - } - directories.addAll(getSourceDirectories().stream() + return getSourceDirectories().stream() .map(SourceCodeDirectory::getPath) - .collect(Collectors.toSet())); - return directories; + .collect(Collectors.toSet()); + } + + /** + * Defines the retention strategy for source code files. + * + * @param sourceCodeRetention + * the retention strategy for source code files + */ + @DataBoundSetter + public void setSourceCodeRetention(final SourceCodeRetention sourceCodeRetention) { + this.sourceCodeRetention = sourceCodeRetention; + } + + public SourceCodeRetention getSourceCodeRetention() { + return sourceCodeRetention; } @Override @@ -223,6 +218,7 @@ static class Execution extends AnalysisExecution { private final Set sourceDirectories; private final String scm; private final boolean quiet; + private final SourceCodeRetention sourceCodeRetention; /** * Creates a new instance of the step execution object. @@ -240,6 +236,7 @@ static class Execution extends AnalysisExecution { isBlameDisabled = step.getBlameDisabled(); filters = step.getFilters(); sourceDirectories = step.getAllSourceDirectories(); + sourceCodeRetention = step.getSourceCodeRetention(); scm = step.getScm(); skipPostProcessing = step.isSkipPostProcessing(); quiet = step.isQuiet(); @@ -252,7 +249,7 @@ protected AnnotatedReport run() throws IOException, InterruptedException, Illega IssuesScanner issuesScanner = new IssuesScanner(tool, filters, getCharset(sourceCodeEncoding), workspace, sourceDirectories, - getRun(), new FilePath(getRun().getRootDir()), listener, + sourceCodeRetention, getRun(), new FilePath(getRun().getRootDir()), listener, scm, isBlameDisabled ? BlameMode.DISABLED : BlameMode.ENABLED, skipPostProcessing ? PostProcessingMode.DISABLED : PostProcessingMode.ENABLED, quiet); diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-sourceCodeRetention.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-sourceCodeRetention.html new file mode 100644 index 0000000000..c004607b03 --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/IssuesRecorder/help-sourceCodeRetention.html @@ -0,0 +1,21 @@ +
+ Select the strategy that should be used to store the affected source code files. + Storing the affected source code files along with the issues consumes a lot of space on your hard disk for large + projects. So if your server has not enough free space available to store the sources for all builds, + it might make more sense to store only the sources of the last build. + In this case, the plugin will automatically discard old results before the new sources are stored. + If you do not need the source files at all, you can deactivate the storing of source code files. + + The following options are supported: + +
+
NEVER
+
Never store source code files.
+
LAST_BUILD
+
Store source code files of the last build, delete older artifacts.
+
EVERY_BUILD
+
Store source code files for all builds, never delete those files automatically.
+
MODIFIED
+
Store only changed source code files for all builds, never delete those files automatically.
+
+
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-sourceCodeRetention.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-sourceCodeRetention.html new file mode 100644 index 0000000000..c004607b03 --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/RecordIssuesStep/help-sourceCodeRetention.html @@ -0,0 +1,21 @@ +
+ Select the strategy that should be used to store the affected source code files. + Storing the affected source code files along with the issues consumes a lot of space on your hard disk for large + projects. So if your server has not enough free space available to store the sources for all builds, + it might make more sense to store only the sources of the last build. + In this case, the plugin will automatically discard old results before the new sources are stored. + If you do not need the source files at all, you can deactivate the storing of source code files. + + The following options are supported: + +
+
NEVER
+
Never store source code files.
+
LAST_BUILD
+
Store source code files of the last build, delete older artifacts.
+
EVERY_BUILD
+
Store source code files for all builds, never delete those files automatically.
+
MODIFIED
+
Store only changed source code files for all builds, never delete those files automatically.
+
+
diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep/help-sourceCodeRetention.html b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep/help-sourceCodeRetention.html new file mode 100644 index 0000000000..c004607b03 --- /dev/null +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/steps/ScanForIssuesStep/help-sourceCodeRetention.html @@ -0,0 +1,21 @@ +
+ Select the strategy that should be used to store the affected source code files. + Storing the affected source code files along with the issues consumes a lot of space on your hard disk for large + projects. So if your server has not enough free space available to store the sources for all builds, + it might make more sense to store only the sources of the last build. + In this case, the plugin will automatically discard old results before the new sources are stored. + If you do not need the source files at all, you can deactivate the storing of source code files. + + The following options are supported: + +
+
NEVER
+
Never store source code files.
+
LAST_BUILD
+
Store source code files of the last build, delete older artifacts.
+
EVERY_BUILD
+
Store source code files for all builds, never delete those files automatically.
+
MODIFIED
+
Store only changed source code files for all builds, never delete those files automatically.
+
+
diff --git a/plugin/src/main/resources/issues/scan-parameters.jelly b/plugin/src/main/resources/issues/scan-parameters.jelly index 670b18b776..d1b198bb67 100644 --- a/plugin/src/main/resources/issues/scan-parameters.jelly +++ b/plugin/src/main/resources/issues/scan-parameters.jelly @@ -20,6 +20,10 @@ + + + + diff --git a/plugin/src/main/resources/issues/scan-parameters.properties b/plugin/src/main/resources/issues/scan-parameters.properties index 1db1120aa6..83e5a2be04 100644 --- a/plugin/src/main/resources/issues/scan-parameters.properties +++ b/plugin/src/main/resources/issues/scan-parameters.properties @@ -8,4 +8,4 @@ title.skipPostProcessing=Disable detection of missing package and module names title.filter=Issue Filters description.filter=Issues will be matched with all the specified filters. If no filter is \ defined, then all issues will be published. Filters with empty regular expression will be ignored. - +sourceCodeRetention.title=Source Code Retention Strategy diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java index e7e47144c9..cca4b2b59d 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/AffectedFilesResolverITest.java @@ -33,6 +33,7 @@ import io.jenkins.plugins.prism.PermittedSourceCodeDirectory; import io.jenkins.plugins.prism.PrismConfiguration; import io.jenkins.plugins.prism.SourceCodeDirectory; +import io.jenkins.plugins.prism.SourceCodeRetention; import static org.assertj.core.api.Assertions.*; @@ -51,6 +52,7 @@ class AffectedFilesResolverITest extends IntegrationTestWithJenkinsPerSuite { private static final String ECLIPSE_REPORT = FOLDER + "/eclipseOneAffectedAndThreeNotExistingFiles.txt"; private static final String ECLIPSE_REPORT_ONE_AFFECTED_AFFECTED_FILE = FOLDER + "/eclipseOneAffectedFile.txt"; private static final int ROW_NUMBER_ACTUAL_AFFECTED_FILE = 0; + private static final String COPY_FILES = "Copying affected files to Jenkins' build folder"; /** * Verifies that the affected source code is copied and shown in the source code view. If the file is deleted in the @@ -76,8 +78,8 @@ private FreeStyleProject createEclipseProject() { return project; } - private void enableEclipseWarnings(final FreeStyleProject project) { - enableWarnings(project, createTool(new Eclipse(), "**/*.txt")); + private IssuesRecorder enableEclipseWarnings(final FreeStyleProject project) { + return enableWarnings(project, createTool(new Eclipse(), "**/*.txt")); } private FreeStyleProject getJobWithWorkspaceFiles() { @@ -220,6 +222,7 @@ void shouldShowFileOutsideWorkspaceIfConfigured() { // First build: copying the affected file is forbidden buildAndVerifyFilesResolving(job, ColumnLink.SHOULD_NOT_HAVE_LINK, "0 copied", "1 not in workspace", "0 not-found", "0 with I/O error"); + // Use source directories of old Warnings plugin configuration PrismConfiguration.getInstance().setSourceDirectories( Collections.singletonList(new PermittedSourceCodeDirectory(buildsFolder))); @@ -238,12 +241,39 @@ void shouldShowFileOutsideWorkspaceIfConfigured() { buildAndVerifyFilesResolving(job, ColumnLink.SHOULD_HAVE_LINK, "1 copied", "0 not in workspace", "0 not-found", "0 with I/O error"); } - private void buildAndVerifyFilesResolving(final FreeStyleProject job, final ColumnLink columnLink, + @Test + void shouldDeleteSourceCodeFilesOfPreviousBuilds() { + FreeStyleProject job = createFreeStyleProject(); + prepareGccLog(job); + + IssuesRecorder recorder = enableWarnings(job, createTool(new Gcc4(), "**/gcc.log")); + recorder.setSourceCodeRetention(SourceCodeRetention.LAST_BUILD); + + String buildsFolder = job.getRootDir().getAbsolutePath(); + + PrismConfiguration.getInstance().setSourceDirectories( + Collections.singletonList(new PermittedSourceCodeDirectory(buildsFolder))); + + Run first = buildAndVerifyFilesResolving(job, ColumnLink.SHOULD_HAVE_LINK, + "1 copied", "0 not in workspace", "0 not-found", "0 with I/O error"); + Run second = buildAndVerifyFilesResolving(job, ColumnLink.SHOULD_HAVE_LINK, + "1 copied", "0 not in workspace", "0 not-found", "0 with I/O error"); + + verifyResolving(ColumnLink.SHOULD_NOT_HAVE_LINK, getAnalysisResult(first)); + assertThat(getConsoleLog(second)).contains("Deleting source code files of build #1"); + } + + private Run buildAndVerifyFilesResolving(final FreeStyleProject job, final ColumnLink columnLink, final String... expectedResolveMessages) { AnalysisResult result = scheduleBuildAndAssertStatus(job, Result.SUCCESS); - assertThat(getConsoleLog(result)).contains(expectedResolveMessages); + verifyResolving(columnLink, result); + + return result.getOwner(); + } + + private void verifyResolving(final ColumnLink columnLink, final AnalysisResult result) { assertThat(result.getIssues()).hasSize(1); IssuesRow firstRow = getIssuesModel(result, 0); @@ -280,14 +310,25 @@ private void prepareGccLog(final FreeStyleProject job) { } } - /** - * Verifies that the {@link AffectedFilesResolver} can find one existing file. - */ @Test void shouldFindOneAffectedFile() { AnalysisResult result = buildEclipseProject(ECLIPSE_REPORT_ONE_AFFECTED_AFFECTED_FILE, SOURCE_AFFECTED_FILE); - assertThat(getConsoleLog(result)).contains("1 copied", "0 not-found", "0 with I/O error"); + assertThat(getConsoleLog(result)) + .contains(COPY_FILES, "1 copied", "0 not-found", "0 with I/O error"); + } + + @Test + void shouldSkipStoringOfAffectedFiles() { + FreeStyleProject project = createFreeStyleProject(); + copyMultipleFilesToWorkspace(project, ECLIPSE_REPORT_ONE_AFFECTED_AFFECTED_FILE, SOURCE_AFFECTED_FILE); + var recorder = enableEclipseWarnings(project); + recorder.setSourceCodeRetention(SourceCodeRetention.NEVER); + + AnalysisResult result = scheduleBuildAndAssertStatus(project, Result.SUCCESS); + + assertThat(getConsoleLog(result)) + .doesNotContain(COPY_FILES, " copied", " not-found", " with I/O error"); } private AnalysisResult buildEclipseProject(final String... files) { From 74228347e956321b9ac0a11d7a4661320a603bf0 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Sat, 13 Jan 2024 17:34:57 +0100 Subject: [PATCH 25/27] ZIP all source files before copying into build folder. --- .../core/model/PropertyStatistics.java | 14 ++-- .../core/util/AffectedFilesResolver.java | 70 +++++++++++++++++-- 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/PropertyStatistics.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/PropertyStatistics.java index 7669696a42..2181260f92 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/PropertyStatistics.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/PropertyStatistics.java @@ -35,7 +35,7 @@ public class PropertyStatistics { * @param property * the property to show the details for * @param propertyFormatter - * the formatter that show the property + * the formatter that shows the property */ PropertyStatistics(final Report report, final Report newIssues, final String property, final Function propertyFormatter) { @@ -57,9 +57,9 @@ public int getTotal() { } /** - * Returns the amount of issues introduced since the last build. + * Returns the number of issues introduced since the last build. * - * @return the amount of new issues + * @return the number of new issues */ public int getTotalNewIssues() { return totalNewIssues; @@ -151,7 +151,7 @@ public long getNewCount(final String key) { * @param key * the property instance * - * @return the number of high severity issues + * @return the number of high-severity issues */ public long getErrorsCount(final String key) { return getReportFor(key).getSizeOf(Severity.ERROR); @@ -163,7 +163,7 @@ public long getErrorsCount(final String key) { * @param key * the property instance * - * @return the number of high severity issues + * @return the number of high-severity issues */ public long getHighCount(final String key) { return getReportFor(key).getSizeOf(Severity.WARNING_HIGH); @@ -175,7 +175,7 @@ public long getHighCount(final String key) { * @param key * the property instance * - * @return the number of normal severity issues + * @return the number of normal-severity issues */ public long getNormalCount(final String key) { return getReportFor(key).getSizeOf(Severity.WARNING_NORMAL); @@ -187,7 +187,7 @@ public long getNormalCount(final String key) { * @param key * the property instance * - * @return the number of low severity issues + * @return the number of low-severity issues */ public long getLowCount(final String key) { return getReportFor(key).getSizeOf(Severity.WARNING_LOW); diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AffectedFilesResolver.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AffectedFilesResolver.java index 292ac6173a..be2c91efa9 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AffectedFilesResolver.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AffectedFilesResolver.java @@ -7,6 +7,9 @@ import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; + import edu.hm.hafner.analysis.Issue; import edu.hm.hafner.analysis.Report; import edu.hm.hafner.util.FilteredLog; @@ -25,8 +28,10 @@ * @author Ullrich Hafner */ public class AffectedFilesResolver { - /** Sub folder with the affected files. */ + /** Folder with the affected files within Jenkins' build results. */ public static final String AFFECTED_FILES_FOLDER_NAME = "files-with-issues"; + private static final String ZIP_EXTENSION = ".zip"; + private static final String TEXT_EXTENSION = ".tmp"; /** * Returns whether the affected file in Jenkins' build folder does exist and is readable. @@ -39,7 +44,8 @@ public class AffectedFilesResolver { * @return the file */ public static boolean hasAffectedFile(final Run run, final Issue issue) { - return canAccess(getFile(run, issue.getFileName())); + return canAccess(getFile(run, issue.getFileName())) + || canAccess(getZipFile(run, issue.getFileName())); } private static boolean canAccess(final Path file) { @@ -59,7 +65,35 @@ private static boolean canAccess(final Path file) { * if the file could not be found */ static InputStream asStream(final Run build, final String fileName) throws IOException { - return Files.newInputStream(getFile(build, fileName)); + try { + var file = getFile(build, fileName); + if (canAccess(file)) { + return Files.newInputStream(file); + } + + return extractFromZip(build, fileName); + } + catch (InterruptedException e) { + throw new IOException(e); + } + } + + private static InputStream extractFromZip(final Run build, final String fileName) + throws IOException, InterruptedException { + Path tempDir = Files.createTempDirectory(AFFECTED_FILES_FOLDER_NAME); + FilePath unzippedSourcesDir = new FilePath(tempDir.toFile()); + try { + var zipFile = getZipFile(build, fileName); + FilePath inputZipFile = new FilePath(zipFile.toFile()); + inputZipFile.unzip(unzippedSourcesDir); + StringUtils.removeEnd(zipFile.toString(), ZIP_EXTENSION); + var sourceFile = tempDir.resolve(FilenameUtils.getName(fileName)); + + return Files.newInputStream(sourceFile); + } + finally { + unzippedSourcesDir.deleteRecursive(); + } } /** @@ -73,9 +107,27 @@ static InputStream asStream(final Run build, final String fileName) throws * @return the file */ public static Path getFile(final Run run, final String fileName) { + return getPath(run, getTempName(fileName)); // Warnings plugin < 11.0.0 + } + + /** + * Returns the affected file in Jenkins' build folder. + * + * @param run + * the run referencing the build folder + * @param fileName + * the file name in the folder of affected files + * + * @return the file + */ + public static Path getZipFile(final Run run, final String fileName) { + return getPath(run, getZipName(fileName)); + } + + private static Path getPath(final Run run, final String zipName) { return run.getRootDir().toPath() .resolve(AFFECTED_FILES_FOLDER_NAME) - .resolve(getTempName(fileName)); + .resolve(zipName); } /** @@ -87,7 +139,11 @@ public static Path getFile(final Run run, final String fileName) { * @return the temporary name */ private static String getTempName(final String fileName) { - return Integer.toHexString(fileName.hashCode()) + ".tmp"; + return Integer.toHexString(fileName.hashCode()) + TEXT_EXTENSION; + } + + private static String getZipName(final String fileName) { + return getTempName(fileName) + ZIP_EXTENSION; } /** @@ -194,7 +250,7 @@ boolean isInWorkspace(final String fileName) { } public void copy(final String from, final String to) throws IOException, InterruptedException { - createFile(from).copyTo(computeBuildFolderFileName(to)); + createFile(from).zip(computeBuildFolderFileName(to)); } public boolean existsInBuildFolder(final String fileName) { @@ -207,7 +263,7 @@ public boolean existsInBuildFolder(final String fileName) { } private FilePath computeBuildFolderFileName(final String fileName) { - return buildFolder.child(getTempName(fileName)); + return buildFolder.child(getZipName(fileName)); } } } From 9320d92c00b8ff689b0f050b0254c89bc7d4bbba Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 26 Jan 2024 11:21:58 +0100 Subject: [PATCH 26/27] Make issues in modified code visible. --- plugin/pom.xml | 60 ++++++------ .../core/charts/CompositeBuildResult.java | 57 ++++++----- .../core/charts/ModifiedCodePieChart.java | 32 +++++++ .../core/model/AggregatedTrendAction.java | 8 +- .../analysis/core/model/AnalysisResult.java | 1 + .../analysis/core/model/DeltaReport.java | 16 ++-- .../analysis/core/model/DetailFactory.java | 11 ++- .../analysis/core/model/IssuesDetail.java | 12 +++ .../analysis/core/model/SummaryModel.java | 12 +++ .../analysis/core/steps/IssuesAggregator.java | 4 +- .../analysis/core/steps/IssuesPublisher.java | 64 +++++++++++-- .../analysis/core/steps/IssuesRecorder.java | 18 ++-- .../core/steps/PublishIssuesStep.java | 25 ++++- .../core/util/AnalysisBuildResult.java | 7 ++ .../analysis/core/util/IssuesStatistics.java | 65 +++++++++++-- .../core/util/IssuesStatisticsBuilder.java | 18 +++- .../core/util/WarningsQualityGate.java | 6 +- .../analysis/core/charts/Messages.properties | 3 + .../analysis/core/model/Messages.properties | 1 + .../core/model/ResultAction/summary.jelly | 19 ++++ .../analysis/core/util/Messages.properties | 2 + .../core/charts/BuildResultStubs.java | 29 +++++- .../core/charts/CompositeBuildResultTest.java | 38 ++++---- .../charts/NewVersusFixedPieChartTest.java | 26 ++--- .../charts/NewVersusFixedTrendChartTest.java | 2 +- .../core/charts/SeverityTrendChartTest.java | 13 +-- .../core/charts/ToolsTrendChartTest.java | 9 +- .../analysis/core/model/DeltaReportTest.java | 8 +- .../core/steps/IssuesAggregatorTest.java | 10 +- .../core/util/IssuesStatisticsTest.java | 57 ++++++++++- .../warnings/steps/GitForensicsITest.java | 96 +++++++++++++++---- 31 files changed, 550 insertions(+), 179 deletions(-) create mode 100644 plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/ModifiedCodePieChart.java diff --git a/plugin/pom.xml b/plugin/pom.xml index d25d156d3e..5c498fed4e 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -1,11 +1,12 @@ - + 4.0.0 org.jvnet.hudson.plugins analysis-pom - 6.17.0 - + 7.0.0 + io.jenkins.plugins @@ -25,14 +26,12 @@ -SNAPSHOT ${project.groupId}.warnings.ng - 11.14.0 - ${analysis-model-api.version} + 11.20.0-rc789.31c8340705ef + 11.6.0 1.7.8 9.2.0 - 20231013 - 2.9.1 1.17.2 @@ -47,12 +46,15 @@ 0.15.2 1.11 - - -Djava.awt.headless=true -Xmx1024m -Djenkins.test.timeout=1000 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED - 1.29.0-8 - 3.9.0-rc855.756b_f8df78a_1 + 3.9.0-rc856.7a_4b_8d226cb_2 + 2.4.0-rc1530.ccd6a_b_012b_c7 + + -Djava.awt.headless=true -Xmx1024m -Djenkins.test.timeout=1000 --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens + java.base/java.util.concurrent=ALL-UNNAMED + @@ -87,6 +89,12 @@ net.minidev json-smart ${json-smart.version} + + + org.json + json + +
org.jsoup @@ -171,6 +179,11 @@ io.jenkins.plugins forensics-api + ${forensics-api.version} + + + io.jenkins.plugins + json-api @@ -183,9 +196,8 @@ apache-httpcomponents-client-4-api - com.google.code.gson - gson - 2.10.1 + io.jenkins.plugins + gson-api @@ -284,12 +296,6 @@
- - org.json - json - ${json.version} - test - org.jenkins-ci.main jenkins-test-harness-tools @@ -614,13 +620,13 @@ - - fast - - true - true - - + + fast + + true + true + + diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/CompositeBuildResult.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/CompositeBuildResult.java index 0a7a25e290..62b7397daf 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/CompositeBuildResult.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/CompositeBuildResult.java @@ -1,44 +1,42 @@ package io.jenkins.plugins.analysis.core.charts; +import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import edu.hm.hafner.analysis.Severity; import io.jenkins.plugins.analysis.core.util.AnalysisBuildResult; +import io.jenkins.plugins.analysis.core.util.IssuesStatistics; +import io.jenkins.plugins.analysis.core.util.IssuesStatisticsBuilder; /** * A build result that is composed of a series of other builds results simply by summing up the number of issues. * * @author Ullrich Hafner */ -// FIXME: IssueStatistics? public class CompositeBuildResult implements AnalysisBuildResult { - private final Map sizesPerOrigin = new HashMap<>(); - private final Map sizesPerSeverity = new HashMap<>(); - private final Map newSizesPerSeverity = new HashMap<>(); - - private int fixedSize = 0; + private final Map sizesPerOrigin; + private final IssuesStatistics totals; /** - * Adds the specified results to this composition. Adds the new value of each property to the existing value of the + * Creates a composition of the specified results. Adds the new value of each property to the existing value of the * same property. * - * @param additionalResults - * the additional results to add - * - * @return returns this to simplify call chains + * @param results + * the results to add */ - public CompositeBuildResult add(final AnalysisBuildResult... additionalResults) { - for (AnalysisBuildResult another : additionalResults) { - sizesPerOrigin.putAll(another.getSizePerOrigin()); - for (Severity severity : Severity.getPredefinedValues()) { - sizesPerSeverity.merge(severity, another.getTotalSizeOf(severity), Integer::sum); - newSizesPerSeverity.merge(severity, another.getNewSizeOf(severity), Integer::sum); - } - fixedSize += another.getFixedSize(); - } - return this; + public CompositeBuildResult(final Collection results) { + totals = results.stream() + .map(AnalysisBuildResult::getTotals) + .map(Objects::requireNonNull) + .reduce(new IssuesStatisticsBuilder().build(), IssuesStatistics::aggregate); + sizesPerOrigin = results.stream().map(AnalysisBuildResult::getSizePerOrigin) + .reduce(new HashMap<>(), (first, second) -> { + second.forEach((key, value) -> first.merge(key, value, Integer::sum)); + return first; + }); } @Override @@ -48,30 +46,31 @@ public Map getSizePerOrigin() { @Override public int getFixedSize() { - return fixedSize; + return getTotals().getFixedSize(); } @Override public int getTotalSize() { - return sum(sizesPerSeverity); + return getTotals().getTotalSize(); } @Override public int getTotalSizeOf(final Severity severity) { - return sizesPerSeverity.getOrDefault(severity, 0); + return getTotals().getTotalSizeOf(severity); } @Override public int getNewSize() { - return sum(newSizesPerSeverity); + return getTotals().getNewSize(); } - private Integer sum(final Map map) { - return map.values().stream().reduce(0, Integer::sum); + @Override + public int getNewSizeOf(final Severity severity) { + return getTotals().getNewSizeOf(severity); } @Override - public int getNewSizeOf(final Severity severity) { - return newSizesPerSeverity.getOrDefault(severity, 0); + public IssuesStatistics getTotals() { + return totals; } } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/ModifiedCodePieChart.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/ModifiedCodePieChart.java new file mode 100644 index 0000000000..f690046837 --- /dev/null +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/ModifiedCodePieChart.java @@ -0,0 +1,32 @@ +package io.jenkins.plugins.analysis.core.charts; + +import edu.hm.hafner.analysis.Report; +import edu.hm.hafner.echarts.PieChartModel; +import edu.hm.hafner.echarts.PieData; + +import io.jenkins.plugins.echarts.JenkinsPalette; + +/** + * Builds the model for a pie chart showing the number of issues in modified code. + * + * @author Ullrich Hafner + */ +public class ModifiedCodePieChart { + /** + * Creates the chart for the specified result. + * + * @param issues + * all issues + * + * @return the chart model + */ + public PieChartModel create(final Report issues) { + PieChartModel model = new PieChartModel(Messages.NewVersusFixed_Name()); + + var totals = issues.size(); + model.add(new PieData(Messages.Modified_Code_Warnings_Short(), totals), JenkinsPalette.RED.normal()); + model.add(new PieData(Messages.Unchanged_Code_Warnings_Short(), totals), JenkinsPalette.YELLOW.normal()); + + return model; + } +} diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AggregatedTrendAction.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AggregatedTrendAction.java index d0ec002c39..167d045fcb 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AggregatedTrendAction.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AggregatedTrendAction.java @@ -145,7 +145,7 @@ public boolean hasNext() { @Override public BuildResult next() { - if (!latestAction.isPresent()) { + if (latestAction.isEmpty()) { throw new NoSuchElementException("No more build results available"); } Run run = latestAction.get(); @@ -155,11 +155,7 @@ public BuildResult next() { .stream() .map(ResultAction::getResult) .collect(Collectors.toSet()); - CompositeBuildResult compositeBuildResult = new CompositeBuildResult(); - for (AnalysisResult result : results) { - compositeBuildResult.add(result); - } - return new BuildResult<>(new JenkinsBuild(run), compositeBuildResult); + return new BuildResult<>(new JenkinsBuild(run), new CompositeBuildResult(results)); } } } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java index 3d548dc6e7..818024a0c4 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/AnalysisResult.java @@ -565,6 +565,7 @@ public String toString() { return new JenkinsFacade().getBuild(referenceBuildId); } + @Override @Whitelisted public IssuesStatistics getTotals() { return totals; diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DeltaReport.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DeltaReport.java index 953293756d..53e6360bcc 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DeltaReport.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DeltaReport.java @@ -57,7 +57,7 @@ public DeltaReport(final Report report, final History history, final int current else { report.logInfo("No valid reference build found that meets the criteria (%s)", history); report.logInfo("All reported issues will be considered outstanding"); - report.forEach(issue -> issue.setReference(String.valueOf(currentBuildNumber))); + report.setReference(String.valueOf(currentBuildNumber)); outstandingIssues = report; referenceIssues = EMPTY_REPORT; newIssues = EMPTY_REPORT; @@ -94,7 +94,7 @@ public Report getAllIssues() { } /** - * Returns all outstanding issues: i.e. all issues, that are part of the current and reference report. + * Returns all outstanding issues: i.e., all issues, that are part of the current and reference report. * * @return the outstanding issues */ @@ -103,7 +103,7 @@ public Report getOutstandingIssues() { } /** - * Returns all new issues: i.e. all issues, that are part of the current report but have not been shown up in the + * Returns all new issues: i.e., all issues, that are part of the current report but have not been shown up in the * reference report. * * @return the new issues @@ -113,7 +113,7 @@ public Report getNewIssues() { } /** - * Returns all fixed issues: i.e. all issues, that are part of the reference report but are not present in the + * Returns all fixed issues: i.e., all issues, that are part of the reference report but are not present in the * current report anymore. * * @return the fixed issues @@ -124,7 +124,7 @@ public Report getFixedIssues() { /** * Returns statistics for the number of issues (total, new, delta). - * + * * @return the issues statistics */ public IssuesStatistics getStatistics() { @@ -132,11 +132,13 @@ public IssuesStatistics getStatistics() { builder.setTotalErrorSize(allIssues.getSizeOf(ERROR)) .setTotalHighSize(allIssues.getSizeOf(WARNING_HIGH)) .setTotalNormalSize(allIssues.getSizeOf(WARNING_NORMAL)) - .setTotalLowSize(allIssues.getSizeOf(WARNING_LOW)); + .setTotalLowSize(allIssues.getSizeOf(WARNING_LOW)) + .setTotalModifiedSize(allIssues.getInModifiedCode().size()); builder.setNewErrorSize(newIssues.getSizeOf(ERROR)) .setNewHighSize(newIssues.getSizeOf(WARNING_HIGH)) .setNewNormalSize(newIssues.getSizeOf(WARNING_NORMAL)) - .setNewLowSize(newIssues.getSizeOf(WARNING_LOW)); + .setNewLowSize(newIssues.getSizeOf(WARNING_LOW)) + .setNewModifiedSize(newIssues.getInModifiedCode().size()); builder.setFixedSize(fixedIssues.size()); if (!referenceBuildId.isEmpty()) { builder.setDeltaErrorSize(allIssues.getSizeOf(ERROR) - referenceIssues.getSizeOf(ERROR)) diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DetailFactory.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DetailFactory.java index 2d16d4f54b..30ff9a00b8 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DetailFactory.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DetailFactory.java @@ -151,7 +151,7 @@ private Marker asMarker(final Issue issue, final String description, final Strin .withColumnEnd(issue.getColumnEnd()).build(); } - @SuppressWarnings("checkstyle:ParameterNumber") + @SuppressWarnings({"checkstyle:ParameterNumber", "PMD.CyclomaticComplexity"}) @SuppressFBWarnings("IMPROPER_UNICODE") private Object createNewDetailView(final String link, final Run owner, final AnalysisResult result, final Report allIssues, final Report newIssues, final Report outstandingIssues, final Report fixedIssues, @@ -162,6 +162,11 @@ private Object createNewDetailView(final String link, final Run owner, fin return new IssuesDetail(owner, result, allIssues, newIssues, outstandingIssues, fixedIssues, labelProvider.getLinkName(), url, labelProvider, sourceEncoding); } + if ("modified".equalsIgnoreCase(link)) { + return new IssuesDetail(owner, result, filterModified(allIssues), filterModified(newIssues), + filterModified(outstandingIssues), EMPTY, + Messages.Modified_Warnings_Header(), url, labelProvider, sourceEncoding); + } if ("fixed".equalsIgnoreCase(link)) { return new FixedWarningsDetail(owner, result, fixedIssues, url, labelProvider, sourceEncoding); } @@ -191,6 +196,10 @@ private Object createNewDetailView(final String link, final Run owner, fin throw new NoSuchElementException(String.format("There is no URL mapping for %s and %s", parent.getUrl(), link)); } + private Report filterModified(final Report report) { + return report.filter(Issue::isPartOfModifiedCode); + } + private Predicate createPropertyFilter(final String plainLink, final String property) { return issue -> plainLink.equals(String.valueOf( Issue.getPropertyValueAsString(issue, property).hashCode())); diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java index a2814b2c67..0face0f6b9 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java @@ -29,6 +29,7 @@ import hudson.model.Run; import io.jenkins.plugins.analysis.core.charts.HealthTrendChart; +import io.jenkins.plugins.analysis.core.charts.ModifiedCodePieChart; import io.jenkins.plugins.analysis.core.charts.NewVersusFixedPieChart; import io.jenkins.plugins.analysis.core.charts.NewVersusFixedTrendChart; import io.jenkins.plugins.analysis.core.charts.SeverityPieChart; @@ -319,6 +320,17 @@ public String getTrendModel() { return JACKSON_FACADE.toJson(new NewVersusFixedPieChart().create(newIssues, outstandingIssues, fixedIssues)); } + /** + * Returns the UI model for an ECharts doughnut chart that shows the issues in modified code. + * + * @return the UI model as JSON + */ + @JavaScriptMethod + @SuppressWarnings("unused") // Called by jelly view + public String getModifiedModel() { + return JACKSON_FACADE.toJson(new ModifiedCodePieChart().create(report)); + } + /** * Returns the UI model for an ECharts line chart that shows the issues stacked by severity. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SummaryModel.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SummaryModel.java index 74de08d591..aefe320383 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SummaryModel.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/SummaryModel.java @@ -108,6 +108,18 @@ public int getFixedSize() { return analysisResult.getFixedSize(); } + public int getModifiedSize() { + return analysisResult.getTotals().getTotalModifiedSize(); + } + + public int getModifiedNewSize() { + return analysisResult.getTotals().getNewModifiedSize(); + } + + public int getModifiedOutstandingSize() { + return getModifiedSize() - getModifiedNewSize(); + } + /** * Returns the tools that contribute to this result. * diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregator.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregator.java index 1c2a32e912..f3bcda2e9c 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregator.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregator.java @@ -88,8 +88,8 @@ private AnnotatedReport createReport(final String id, final AnalysisResult resul public boolean endBuild() { resultsPerTool.forEachKeyMultiValues((tool, reports) -> { AnnotatedReport aggregatedReport = new AnnotatedReport(tool, reports); - recorder.publishResult(build, listener, Messages.Tool_Default_Name(), aggregatedReport, StringUtils.EMPTY, - new RunResultHandler(build)); + recorder.publishResult(build, build.getWorkspace(), listener, Messages.Tool_Default_Name(), + aggregatedReport, StringUtils.EMPTY, new RunResultHandler(build)); }); return true; } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java index 3c64b25361..62b42a9a20 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesPublisher.java @@ -3,7 +3,12 @@ import java.nio.charset.Charset; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + +import edu.hm.hafner.analysis.Issue; +import edu.hm.hafner.analysis.IssuesInModifiedCodeMarker; import edu.hm.hafner.analysis.Report; import edu.hm.hafner.util.FilteredLog; @@ -27,6 +32,8 @@ import io.jenkins.plugins.analysis.core.util.TrendChartType; import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; import io.jenkins.plugins.analysis.core.util.WarningsQualityGateEvaluator; +import io.jenkins.plugins.forensics.delta.DeltaCalculator; +import io.jenkins.plugins.forensics.delta.FileChanges; import io.jenkins.plugins.forensics.reference.ReferenceFinder; import io.jenkins.plugins.util.JenkinsFacade; import io.jenkins.plugins.util.LogHandler; @@ -42,10 +49,11 @@ * * @author Ullrich Hafner */ -@SuppressWarnings({"PMD.ExcessiveImports", "checkstyle:ClassFanOutComplexity"}) +@SuppressWarnings({"PMD.ExcessiveImports", "checkstyle:ClassFanOutComplexity", "checkstyle:ClassDataAbstractionCoupling"}) class IssuesPublisher { private final AnnotatedReport report; private final Run run; + private final DeltaCalculator deltaCalculator; private final HealthDescriptor healthDescriptor; private final String name; private final Charset sourceCodeEncoding; @@ -59,14 +67,15 @@ class IssuesPublisher { private final boolean failOnErrors; @SuppressWarnings("ParameterNumber") - IssuesPublisher(final Run run, final AnnotatedReport report, + IssuesPublisher(final Run run, final AnnotatedReport report, final DeltaCalculator deltaCalculator, final HealthDescriptor healthDescriptor, final List qualityGates, final String name, final String referenceJobName, final String referenceBuildId, - final boolean ignoreQualityGate, - final boolean ignoreFailedBuilds, final Charset sourceCodeEncoding, final LogHandler logger, - final ResultHandler notifier, final boolean failOnErrors) { + final boolean ignoreQualityGate, final boolean ignoreFailedBuilds, + final Charset sourceCodeEncoding, final LogHandler logger, final ResultHandler notifier, + final boolean failOnErrors) { this.report = report; this.run = run; + this.deltaCalculator = deltaCalculator; this.healthDescriptor = healthDescriptor; this.name = name; this.sourceCodeEncoding = sourceCodeEncoding; @@ -97,7 +106,12 @@ ResultAction attachAction(final TrendChartType trendChartType) { ResultSelector selector = ensureThatIdIsUnique(); Report issues = report.getReport(); - DeltaReport deltaReport = new DeltaReport(issues, createAnalysisHistory(selector, issues), run.getNumber()); + + var analysisHistory = createAnalysisHistory(selector, issues); + DeltaReport deltaReport = new DeltaReport(issues, analysisHistory, run.getNumber()); + + markIssuesInModifiedFiles(analysisHistory, issues, deltaReport); + QualityGateResult qualityGateResult = evaluateQualityGate(issues, deltaReport); reportHealth(issues); @@ -138,6 +152,44 @@ ResultAction attachAction(final TrendChartType trendChartType) { return action; } + private void markIssuesInModifiedFiles(final History history, final Report issues, final DeltaReport deltaReport) { + if (history.getBuild().isPresent() && issues.isNotEmpty()) { + report.logInfo("Detect all issues that are part of modified code"); + Run referenceBuild = history.getBuild().get(); + + var log = new FilteredLog("Errors while computing delta: "); + var delta = deltaCalculator.calculateDelta(run, referenceBuild, StringUtils.EMPTY, log); // get rid of SCM + 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 { + report.logInfo("Skip detection of issues in modified code"); + } + } + + private long count(final Report issues) { + return issues.stream().filter(Issue::isPartOfModifiedCode).count(); + } + private ResultSelector ensureThatIdIsUnique() { ResultSelector selector = new ByIdResultSelector(getId()); Optional other = selector.get(run); diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java index a90077115e..114ed2863d 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/IssuesRecorder.java @@ -12,6 +12,7 @@ import org.apache.commons.lang3.StringUtils; import edu.hm.hafner.analysis.Severity; +import edu.hm.hafner.util.FilteredLog; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; @@ -58,6 +59,7 @@ import io.jenkins.plugins.analysis.core.util.TrendChartType; import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; import io.jenkins.plugins.checks.steps.ChecksInfo; +import io.jenkins.plugins.forensics.delta.DeltaCalculatorFactory; import io.jenkins.plugins.prism.SourceCodeDirectory; import io.jenkins.plugins.prism.SourceCodeRetention; import io.jenkins.plugins.util.JenkinsFacade; @@ -711,7 +713,7 @@ private List record(final Run run, final FilePath workspac totalIssues.add(scanWithTool(run, workspace, listener, tool), tool.getActualId()); } String toolName = StringUtils.defaultIfEmpty(getName(), Messages.Tool_Default_Name()); - results.add(publishResult(run, listener, toolName, totalIssues, toolName, resultHandler)); + results.add(publishResult(run, workspace, listener, toolName, totalIssues, toolName, resultHandler)); } else { for (Tool tool : analysisTools) { @@ -726,7 +728,7 @@ private List record(final Run run, final FilePath workspac name, id); } results.add( - publishResult(run, listener, tool.getActualName(), report, getReportName(tool), resultHandler)); + publishResult(run, workspace, listener, tool.getActualName(), report, getReportName(tool), resultHandler)); } } return results; @@ -779,6 +781,8 @@ private Charset getCharset(final String encoding) { * * @param run * the run + * @param workspace + * the workspace * @param listener * the listener * @param loggerName @@ -792,17 +796,19 @@ private Charset getCharset(final String encoding) { * * @return the created results */ - AnalysisResult publishResult(final Run run, final TaskListener listener, final String loggerName, + AnalysisResult publishResult(final Run run, final FilePath workspace, final TaskListener listener, final String loggerName, final AnnotatedReport annotatedReport, final String reportName, final ResultHandler resultHandler) { - LogHandler logHandler = new LogHandler(listener, loggerName); - + var logHandler = new LogHandler(listener, loggerName); logHandler.setQuiet(quiet); var report = annotatedReport.getReport(); logHandler.logInfoMessages(report.getInfoMessages()); logHandler.logErrorMessages(report.getErrorMessages()); - IssuesPublisher publisher = new IssuesPublisher(run, annotatedReport, + var deltaCalculator = DeltaCalculatorFactory + .findDeltaCalculator(scm, run, workspace, listener, new FilteredLog()); + + IssuesPublisher publisher = new IssuesPublisher(run, annotatedReport, deltaCalculator, new HealthDescriptor(healthy, unhealthy, minimumSeverity), qualityGates, reportName, getReferenceJobName(), getReferenceBuildId(), ignoreQualityGate, ignoreFailedBuilds, getSourceCodeCharset(), logHandler, resultHandler, failOnError); diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java index 6da1f008b4..23a61eaec8 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/steps/PublishIssuesStep.java @@ -10,6 +10,7 @@ import org.eclipse.collections.impl.factory.Sets; import edu.hm.hafner.analysis.Severity; +import edu.hm.hafner.util.FilteredLog; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -34,6 +35,7 @@ import io.jenkins.plugins.analysis.core.util.TrendChartType; import io.jenkins.plugins.analysis.core.util.WarningsQualityGate; import io.jenkins.plugins.checks.steps.ChecksInfo; +import io.jenkins.plugins.forensics.delta.DeltaCalculatorFactory; import io.jenkins.plugins.util.LogHandler; import io.jenkins.plugins.util.PipelineResultHandler; import io.jenkins.plugins.util.ResultHandler; @@ -75,6 +77,7 @@ public class PublishIssuesStep extends Step implements Serializable { private String id = StringUtils.EMPTY; private String name = StringUtils.EMPTY; + private String scm = StringUtils.EMPTY; /** * Creates a new instance of {@link PublishIssuesStep}. @@ -135,6 +138,22 @@ public String getName() { return name; } + /** + * Sets the SCM that should be used to find the reference build for. The reference recorder will select the SCM + * based on a substring comparison, there is no need to specify the full name. + * + * @param scm + * the ID of the SCM to use (a substring of the full ID) + */ + @DataBoundSetter + public void setScm(final String scm) { + this.scm = scm; + } + + public String getScm() { + return scm; + } + /** * Determines whether to fail the step on errors during the step of recording issues. * @@ -450,8 +469,12 @@ protected ResultAction run() throws IOException, InterruptedException, IllegalSt ResultHandler notifier = new PipelineResultHandler(getRun(), getContext().get(FlowNode.class)); + + var deltaCalculator = DeltaCalculatorFactory + .findDeltaCalculator(step.scm, getRun(), getWorkspace(), getTaskListener(), new FilteredLog()); + IssuesPublisher publisher = new IssuesPublisher(getRun(), report, - new HealthDescriptor(step.getHealthy(), step.getUnhealthy(), + deltaCalculator, new HealthDescriptor(step.getHealthy(), step.getUnhealthy(), step.getMinimumSeverityAsSeverity()), step.getQualityGates(), StringUtils.defaultString(step.getName()), step.getReferenceJobName(), step.getReferenceBuildId(), step.getIgnoreQualityGate(), step.getIgnoreFailedBuilds(), diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AnalysisBuildResult.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AnalysisBuildResult.java index 9fb9a4dc6b..e5ee1de7c0 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AnalysisBuildResult.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/AnalysisBuildResult.java @@ -56,4 +56,11 @@ public interface AnalysisBuildResult { * @return total number of issues */ int getNewSizeOf(Severity severity); + + /** + * Returns the total number of issues (by severity, new, total, fixed and delta) in a build. + * + * @return the totals + */ + IssuesStatistics getTotals(); } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatistics.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatistics.java index 6c1d5a0970..9d1171642c 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatistics.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatistics.java @@ -29,11 +29,13 @@ public class IssuesStatistics implements Serializable { private final int totalHighSize; private final int totalNormalSize; private final int totalLowSize; + private final int totalModifiedSize; // @since 11.0.0 private final int newErrorSize; private final int newHighSize; private final int newNormalSize; private final int newLowSize; + private final int newModifiedSize; // @since 11.0.0 private final int deltaErrorSize; private final int deltaHighSize; @@ -48,25 +50,31 @@ public class IssuesStatistics implements Serializable { @SuppressWarnings("checkstyle:ParameterNumber") IssuesStatistics( final int totalErrorSize, final int totalHighSize, final int totalNormalSize, final int totalLowSize, + final int totalModifiedSize, final int newErrorSize, final int newHighSize, final int newNormalSize, final int newLowSize, + final int newModifiedSize, final int deltaErrorSize, final int deltaHighSize, final int deltaNormalSize, final int deltaLowSize, final int fixedSize) { this.totalErrorSize = totalErrorSize; - totalSizeBySeverity.put(Severity.ERROR, totalErrorSize); this.totalHighSize = totalHighSize; - totalSizeBySeverity.put(Severity.WARNING_HIGH, totalHighSize); this.totalNormalSize = totalNormalSize; - totalSizeBySeverity.put(Severity.WARNING_NORMAL, totalNormalSize); this.totalLowSize = totalLowSize; + this.totalModifiedSize = totalModifiedSize; + + totalSizeBySeverity.put(Severity.ERROR, totalErrorSize); + totalSizeBySeverity.put(Severity.WARNING_HIGH, totalHighSize); + totalSizeBySeverity.put(Severity.WARNING_NORMAL, totalNormalSize); totalSizeBySeverity.put(Severity.WARNING_LOW, totalLowSize); this.newErrorSize = newErrorSize; - newSizeBySeverity.put(Severity.ERROR, newErrorSize); this.newHighSize = newHighSize; - newSizeBySeverity.put(Severity.WARNING_HIGH, newHighSize); this.newNormalSize = newNormalSize; - newSizeBySeverity.put(Severity.WARNING_NORMAL, newNormalSize); this.newLowSize = newLowSize; + this.newModifiedSize = newModifiedSize; + + newSizeBySeverity.put(Severity.ERROR, newErrorSize); + newSizeBySeverity.put(Severity.WARNING_HIGH, newHighSize); + newSizeBySeverity.put(Severity.WARNING_NORMAL, newNormalSize); newSizeBySeverity.put(Severity.WARNING_LOW, newLowSize); this.deltaErrorSize = deltaErrorSize; @@ -77,16 +85,51 @@ public class IssuesStatistics implements Serializable { this.fixedSize = fixedSize; } + /** + * Aggregates the specified statistics with this statistics to a new instance, that contains all totals summed up. + * + * @param other the other statistics to aggregate + * @return the aggregated statistics + */ + public IssuesStatistics aggregate(final IssuesStatistics other) { + return new IssuesStatistics( + totalErrorSize + other.totalErrorSize, + totalHighSize + other.totalHighSize, + totalNormalSize + other.totalNormalSize, + totalLowSize + other.totalLowSize, + totalModifiedSize + other.totalModifiedSize, + newErrorSize + other.newErrorSize, + newHighSize + other.newHighSize, + newNormalSize + other.newNormalSize, + newLowSize + other.newLowSize, + newModifiedSize + other.newModifiedSize, + deltaErrorSize + other.deltaErrorSize, + deltaHighSize + other.deltaHighSize, + deltaNormalSize + other.deltaNormalSize, + deltaLowSize + other.deltaLowSize, + fixedSize + other.fixedSize); + } + @Whitelisted public int getTotalSize() { return totalErrorSize + totalHighSize + totalNormalSize + totalLowSize; } + @Whitelisted + public int getTotalModifiedSize() { + return totalModifiedSize; + } + @Whitelisted public int getNewSize() { return newErrorSize + newHighSize + newNormalSize + newLowSize; } + @Whitelisted + public int getNewModifiedSize() { + return newModifiedSize; + } + @Whitelisted public int getDeltaSize() { return deltaErrorSize + deltaHighSize + deltaNormalSize + deltaLowSize; @@ -212,10 +255,12 @@ public boolean equals(final Object o) { && totalHighSize == that.totalHighSize && totalNormalSize == that.totalNormalSize && totalLowSize == that.totalLowSize + && totalModifiedSize == that.totalModifiedSize && newErrorSize == that.newErrorSize && newHighSize == that.newHighSize && newNormalSize == that.newNormalSize && newLowSize == that.newLowSize + && newModifiedSize == that.newModifiedSize && deltaErrorSize == that.deltaErrorSize && deltaHighSize == that.deltaHighSize && deltaNormalSize == that.deltaNormalSize @@ -225,8 +270,10 @@ public boolean equals(final Object o) { @Override public int hashCode() { - return Objects.hash(totalErrorSize, totalHighSize, totalNormalSize, totalLowSize, newErrorSize, newHighSize, - newNormalSize, newLowSize, deltaErrorSize, deltaHighSize, deltaNormalSize, deltaLowSize, fixedSize); + return Objects.hash( + totalErrorSize, totalHighSize, totalNormalSize, totalLowSize, totalModifiedSize, + newErrorSize, newHighSize, newNormalSize, newLowSize, totalModifiedSize, + deltaErrorSize, deltaHighSize, deltaNormalSize, deltaLowSize, fixedSize); } /** @@ -238,12 +285,14 @@ public enum StatisticProperties { TOTAL_HIGH(Messages._Statistics_Total_High(), IssuesStatistics::getTotalHighSize, "high"), TOTAL_NORMAL(Messages._Statistics_Total_Normal(), IssuesStatistics::getTotalNormalSize, "normal"), TOTAL_LOW(Messages._Statistics_Total_Low(), IssuesStatistics::getTotalLowSize, "low"), + TOTAL_MODIFIED(Messages._Statistics_Total_Modified(), IssuesStatistics::getTotalModifiedSize, "modified"), NEW(Messages._Statistics_New(), IssuesStatistics::getNewSize, "new"), NEW_ERROR(Messages._Statistics_New_Error(), IssuesStatistics::getNewErrorSize, "new/error"), NEW_HIGH(Messages._Statistics_New_High(), IssuesStatistics::getNewHighSize, "new/high"), NEW_NORMAL(Messages._Statistics_New_Normal(), IssuesStatistics::getNewNormalSize, "new/normal"), NEW_LOW(Messages._Statistics_New_Low(), IssuesStatistics::getNewLowSize, "new/low"), + NEW_MODIFIED(Messages._Statistics_New_Modified(), IssuesStatistics::getNewModifiedSize, "new/modified"), DELTA(Messages._Statistics_Delta(), IssuesStatistics::getDeltaSize, StringUtils.EMPTY), DELTA_ERROR(Messages._Statistics_Delta_Error(), IssuesStatistics::getDeltaErrorSize, StringUtils.EMPTY), diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsBuilder.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsBuilder.java index e3bb6d0984..1253d415f8 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsBuilder.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsBuilder.java @@ -20,6 +20,8 @@ public class IssuesStatisticsBuilder { private int totalLowSize; private int newLowSize; private int deltaLowSize; + private int totalModifiedSize; // @since 11.0.0 + private int newModifiedSize; // @since 11.0.0 private int fixedSize; public IssuesStatisticsBuilder setTotalErrorSize(final int totalErrorSize) { @@ -57,6 +59,11 @@ public IssuesStatisticsBuilder setTotalNormalSize(final int totalNormalSize) { return this; } + public IssuesStatisticsBuilder setTotalModifiedSize(final int totalModifiedSize) { + this.totalModifiedSize = totalModifiedSize; + return this; + } + public IssuesStatisticsBuilder setNewNormalSize(final int newNormalSize) { this.newNormalSize = newNormalSize; return this; @@ -77,6 +84,11 @@ public IssuesStatisticsBuilder setNewLowSize(final int newLowSize) { return this; } + public IssuesStatisticsBuilder setNewModifiedSize(final int newModifiedSize) { + this.newModifiedSize = newModifiedSize; + return this; + } + public IssuesStatisticsBuilder setDeltaLowSize(final int deltaLowSize) { this.deltaLowSize = deltaLowSize; return this; @@ -89,8 +101,8 @@ public IssuesStatisticsBuilder setFixedSize(final int fixedSize) { public IssuesStatistics build() { return new IssuesStatistics( - totalErrorSize, totalHighSize, totalNormalSize, totalLowSize, - newErrorSize, newHighSize, newNormalSize, newLowSize, + totalErrorSize, totalHighSize, totalNormalSize, totalLowSize, totalModifiedSize, + newErrorSize, newHighSize, newNormalSize, newLowSize, newModifiedSize, deltaErrorSize, deltaHighSize, deltaNormalSize, deltaLowSize, fixedSize); } @@ -100,11 +112,13 @@ void clear() { totalHighSize = 0; totalNormalSize = 0; totalLowSize = 0; + totalModifiedSize = 0; newErrorSize = 0; newHighSize = 0; newNormalSize = 0; newLowSize = 0; + newModifiedSize = 0; deltaErrorSize = 0; deltaHighSize = 0; diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java index f0f4dbfd8e..463b573e36 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/util/WarningsQualityGate.java @@ -40,6 +40,8 @@ public class WarningsQualityGate extends QualityGate { */ @DataBoundConstructor public WarningsQualityGate(final QualityGateType type) { + super(); + this.type = type; } @@ -55,7 +57,7 @@ public WarningsQualityGate(final QualityGateType type) { */ public WarningsQualityGate(final int threshold, final QualityGateType type, final QualityGateCriticality criticality) { - this.type = type; + this(type); setIntegerThreshold(threshold); setCriticality(criticality); @@ -130,12 +132,14 @@ public enum QualityGateType { TOTAL_HIGH(StatisticProperties.TOTAL_HIGH), TOTAL_NORMAL(StatisticProperties.TOTAL_NORMAL), TOTAL_LOW(StatisticProperties.TOTAL_LOW), + TOTAL_MODIFIED(StatisticProperties.TOTAL_MODIFIED), 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), DELTA(StatisticProperties.DELTA), DELTA_ERROR(StatisticProperties.DELTA_ERROR), diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/charts/Messages.properties b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/charts/Messages.properties index 48c12ae205..2df17670b1 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/charts/Messages.properties +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/charts/Messages.properties @@ -8,3 +8,6 @@ NewVersusFixed.Name=Reference Comparison New.Warnings.Short=New Outstanding.Warnings.Short=Outstanding Fixed.Warnings.Short=Fixed + +Modified.Code.Warnings.Short=Modified +Unchanged.Code.Warnings.Short=Unchanged diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/Messages.properties b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/Messages.properties index f0a9081600..ff41fbb18a 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/Messages.properties +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/Messages.properties @@ -46,6 +46,7 @@ Summary.ShowDetails=Show info and error messages ConsoleLog.Name=Console Output ReportScanningTool.PatternIsEmptyAndConsoleParsingDisabled=A pattern must be specified. This parser is unable to parse the console output. +Modified.Warnings.Header=Warnings in Modified Code Fixed.Warnings.Header=Fixed Warnings New.Warnings.Header=New Warnings Outstanding.Warnings.Header=Outstanding Warnings diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ResultAction/summary.jelly b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ResultAction/summary.jelly index 415da6ec6e..6ac50b9b57 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ResultAction/summary.jelly +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/ResultAction/summary.jelly @@ -76,6 +76,25 @@ ${s.newSize} + +
  • + + + ${%Issues in modified code}: + ${s.modifiedSize} + (${%new}: ${s.modifiedNewSize}, ${%outstanding}: ${s.modifiedOutstandingSize}) + + + ${%Outstanding issues in modified code}: + ${s.modifiedSize} + + + ${%New issues in modified code}: + ${s.modifiedSize} + + +
  • +
  • diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/Messages.properties b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/Messages.properties index 3f08223de5..7ca18f0ec7 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/Messages.properties +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/util/Messages.properties @@ -39,12 +39,14 @@ Statistics.Total.Error=Total (errors only) Statistics.Total.High=Total (severity high only) Statistics.Total.Normal=Total (severity normal only) Statistics.Total.Low=Total (severity low only) +Statistics.Total.Modified=Total (in modified code) Statistics.New=New (any severity) Statistics.New.Error=New (errors only) Statistics.New.High=New (severity high only) Statistics.New.Normal=New (severity normal only) Statistics.New.Low=New (severity low only) +Statistics.New.Modified=New (in modified code) Statistics.Delta=Delta (any severity) Statistics.Delta.Error=Delta (errors only) diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/BuildResultStubs.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/BuildResultStubs.java index cabbf1b1c7..f84b22bec9 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/BuildResultStubs.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/BuildResultStubs.java @@ -8,6 +8,7 @@ import edu.hm.hafner.util.VisibleForTesting; import io.jenkins.plugins.analysis.core.util.AnalysisBuildResult; +import io.jenkins.plugins.analysis.core.util.IssuesStatisticsBuilder; import static org.mockito.Mockito.*; @@ -33,6 +34,15 @@ static AnalysisBuildResult createAnalysisBuildResult(final int errors, final int when(buildResult.getTotalSizeOf(Severity.WARNING_NORMAL)).thenReturn(normal); when(buildResult.getTotalSizeOf(Severity.WARNING_LOW)).thenReturn(low); when(buildResult.getTotalSize()).thenReturn(low + normal + high + errors); + + var statistics = new IssuesStatisticsBuilder(); + statistics.setTotalErrorSize(errors); + statistics.setTotalHighSize(high); + statistics.setTotalNormalSize(normal); + statistics.setTotalLowSize(low); + + when(buildResult.getTotals()).thenReturn(statistics.build()); + return buildResult; } @@ -59,6 +69,14 @@ static AnalysisBuildResult createAnalysisBuildResultWithNew(final int errors, fi when(buildResult.getNewSizeOf(Severity.WARNING_LOW)).thenReturn(low); when(buildResult.getNewSize()).thenReturn(low + normal + high + errors); + var statistics = new IssuesStatisticsBuilder(); + statistics.setNewErrorSize(errors); + statistics.setNewHighSize(high); + statistics.setNewNormalSize(normal); + statistics.setNewLowSize(low); + + when(buildResult.getTotals()).thenReturn(statistics.build()); + return buildResult; } @@ -75,19 +93,20 @@ static AnalysisBuildResult createAnalysisBuildResultWithNewAndFixedIssues(final when(buildResult.getFixedSize()).thenReturn(fixedSize); when(buildResult.getNewSize()).thenReturn(newSize); - return buildResult; - } + var statistics = new IssuesStatisticsBuilder(); + statistics.setFixedSize(fixedSize); + statistics.setNewNormalSize(newSize); - static BuildResult createResult(final int buildNumber, final String toolId, final int total) { - AnalysisBuildResult buildResult = createAnalysisBuildResult(toolId, total); + when(buildResult.getTotals()).thenReturn(statistics.build()); - return createBuildResult(buildNumber, buildResult); + return buildResult; } static AnalysisBuildResult createAnalysisBuildResult(final String toolId, final int total) { AnalysisBuildResult buildResult = mock(AnalysisBuildResult.class); when(buildResult.getSizePerOrigin()).thenReturn(Maps.mutable.of(toolId, total)); + when(buildResult.getTotals()).thenReturn(new IssuesStatisticsBuilder().build()); return buildResult; } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/CompositeBuildResultTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/CompositeBuildResultTest.java index 5ce8212219..946a3f4b76 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/CompositeBuildResultTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/CompositeBuildResultTest.java @@ -1,5 +1,7 @@ package io.jenkins.plugins.analysis.core.charts; +import java.util.List; + import org.junit.jupiter.api.Test; import edu.hm.hafner.analysis.Severity; @@ -20,11 +22,11 @@ void shouldMergeNumberOfIssuesByOrigin() { AnalysisBuildResult first = createAnalysisBuildResult("first", 1); AnalysisBuildResult second = createAnalysisBuildResult("second", 2); - CompositeBuildResult run = new CompositeBuildResult(); + CompositeBuildResult run = new CompositeBuildResult(List.of()); assertThat(run.getSizePerOrigin()).isEmpty(); - assertThat(run.add(first).getSizePerOrigin()).containsExactly(entry("first", 1)); - assertThat(run.add(second).getSizePerOrigin()).containsExactly(entry("first", 1), entry("second", 2)); + assertThat(new CompositeBuildResult(List.of(first)).getSizePerOrigin()).containsExactly(entry("first", 1)); + assertThat(new CompositeBuildResult(List.of(first, second)).getSizePerOrigin()).containsExactly(entry("first", 1), entry("second", 2)); } @Test @@ -32,11 +34,11 @@ void shouldMergeNumberOfTotalIssues() { AnalysisBuildResult first = createAnalysisBuildResult(1, 2, 3, 4); AnalysisBuildResult second = createAnalysisBuildResult(5, 6, 7, 8); - CompositeBuildResult run = new CompositeBuildResult(); + CompositeBuildResult run = new CompositeBuildResult(List.of()); assertThat(run).hasTotalSize(0); - assertThat(run.add(first)).hasTotalSize(10); - assertThat(run.add(second)).hasTotalSize(36); + assertThat(new CompositeBuildResult(List.of(first))).hasTotalSize(10); + assertThat(new CompositeBuildResult(List.of(first, second))).hasTotalSize(36); } @Test @@ -44,11 +46,11 @@ void shouldMergeNumberOfFixedIssues() { AnalysisBuildResult first = createAnalysisBuildResultWithNewAndFixedIssues(0, 2); AnalysisBuildResult second = createAnalysisBuildResultWithNewAndFixedIssues(0, 3); - CompositeBuildResult run = new CompositeBuildResult(); + CompositeBuildResult run = new CompositeBuildResult(List.of()); assertThat(run).hasFixedSize(0); - assertThat(run.add(first)).hasFixedSize(2); - assertThat(run.add(second)).hasFixedSize(5); + assertThat(new CompositeBuildResult(List.of(first))).hasFixedSize(2); + assertThat(new CompositeBuildResult(List.of(first, second))).hasFixedSize(5); } @Test @@ -56,11 +58,11 @@ void shouldMergeNumberOfNewIssues() { AnalysisBuildResult first = createAnalysisBuildResultWithNew(0, 5, 2, 6); AnalysisBuildResult second = createAnalysisBuildResultWithNew(0, 4, 2, 7); - CompositeBuildResult run = new CompositeBuildResult(); + CompositeBuildResult run = new CompositeBuildResult(List.of()); assertThat(run).hasNewSize(0); - assertThat(run.add(first)).hasNewSize(13); - assertThat(run.add(second)).hasNewSize(26); + assertThat(new CompositeBuildResult(List.of(first))).hasNewSize(13); + assertThat(new CompositeBuildResult(List.of(first, second))).hasNewSize(26); } @Test @@ -68,20 +70,20 @@ void shouldMergeTotalNumberOfIssuesBySeverity() { AnalysisBuildResult first = createAnalysisBuildResult(7, 1, 2, 3); AnalysisBuildResult second = createAnalysisBuildResult(8, 4, 5, 6); - CompositeBuildResult run = new CompositeBuildResult(); + CompositeBuildResult run = new CompositeBuildResult(List.of()); assertThat(run.getTotalSizeOf(Severity.ERROR)).isEqualTo(0); assertThat(run.getTotalSizeOf(Severity.WARNING_HIGH)).isEqualTo(0); assertThat(run.getTotalSizeOf(Severity.WARNING_NORMAL)).isEqualTo(0); assertThat(run.getTotalSizeOf(Severity.WARNING_LOW)).isEqualTo(0); - run.add(first); + run = new CompositeBuildResult(List.of(first)); assertThat(run.getTotalSizeOf(Severity.ERROR)).isEqualTo(7); assertThat(run.getTotalSizeOf(Severity.WARNING_HIGH)).isEqualTo(1); assertThat(run.getTotalSizeOf(Severity.WARNING_NORMAL)).isEqualTo(2); assertThat(run.getTotalSizeOf(Severity.WARNING_LOW)).isEqualTo(3); - run.add(second); + run = new CompositeBuildResult(List.of(first, second)); assertThat(run.getTotalSizeOf(Severity.ERROR)).isEqualTo(15); assertThat(run.getTotalSizeOf(Severity.WARNING_HIGH)).isEqualTo(5); assertThat(run.getTotalSizeOf(Severity.WARNING_NORMAL)).isEqualTo(7); @@ -93,20 +95,20 @@ void shouldMergeNumberOfNewIssuesBySeverity() { AnalysisBuildResult first = createAnalysisBuildResultWithNew(7, 2, 6, 2); AnalysisBuildResult second = createAnalysisBuildResultWithNew(8, 5, 2, 2); - CompositeBuildResult run = new CompositeBuildResult(); + CompositeBuildResult run = new CompositeBuildResult(List.of()); assertThat(run.getNewSizeOf(Severity.ERROR)).isEqualTo(0); assertThat(run.getNewSizeOf(Severity.WARNING_HIGH)).isEqualTo(0); assertThat(run.getNewSizeOf(Severity.WARNING_NORMAL)).isEqualTo(0); assertThat(run.getNewSizeOf(Severity.WARNING_LOW)).isEqualTo(0); - run.add(first); + run = new CompositeBuildResult(List.of(first)); assertThat(run.getNewSizeOf(Severity.ERROR)).isEqualTo(7); assertThat(run.getNewSizeOf(Severity.WARNING_HIGH)).isEqualTo(2); assertThat(run.getNewSizeOf(Severity.WARNING_NORMAL)).isEqualTo(6); assertThat(run.getNewSizeOf(Severity.WARNING_LOW)).isEqualTo(2); - run.add(second); + run = new CompositeBuildResult(List.of(first, second)); assertThat(run.getNewSizeOf(Severity.ERROR)).isEqualTo(15); assertThat(run.getNewSizeOf(Severity.WARNING_HIGH)).isEqualTo(7); assertThat(run.getNewSizeOf(Severity.WARNING_NORMAL)).isEqualTo(8); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/NewVersusFixedPieChartTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/NewVersusFixedPieChartTest.java index c3c38519f1..3e5a148554 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/NewVersusFixedPieChartTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/NewVersusFixedPieChartTest.java @@ -1,46 +1,34 @@ package io.jenkins.plugins.analysis.core.charts; -import java.util.List; - import org.junit.jupiter.api.Test; import edu.hm.hafner.analysis.Report; import edu.hm.hafner.echarts.PieChartModel; import edu.hm.hafner.echarts.PieData; -import io.jenkins.plugins.echarts.JenkinsPalette; - import static io.jenkins.plugins.analysis.core.charts.Messages.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; /** - * Tests the class {@link NewVersusFixedPieChart}. + * Tests the class {@link ModifiedCodePieChart}. * * @author Veronika Zwickenpflug */ class NewVersusFixedPieChartTest { @Test void testCreate() { - int[] sizes = {2, 3, 4}; - String[] names = {New_Warnings_Short(), Outstanding_Warnings_Short(), Fixed_Warnings_Short()}; - JenkinsPalette[] colors = {JenkinsPalette.RED, JenkinsPalette.YELLOW, JenkinsPalette.GREEN}; - NewVersusFixedPieChart chart = new NewVersusFixedPieChart(); - PieChartModel model = chart.create(createReportStub(sizes[0]), createReportStub(sizes[1]), createReportStub(sizes[2])); - List data = model.getData(); - assertThat(model.getData().size()).isEqualTo(3); - assertThat(model.getColors().size()).isEqualTo(3); + PieChartModel model = chart.create(createReportWithSize(2), createReportWithSize(3), createReportWithSize(4)); - for (int i = 0; i < 3; i++) { - assertThat(data.get(i).getName()).isEqualTo(names[i]); - assertThat(data.get(i).getValue()).isEqualTo(sizes[i]); - assertThat(model.getColors().get(i)).isEqualTo(colors[i].normal()); - } + assertThat(model.getData()).map(PieData::getName).containsExactly( + New_Warnings_Short(), Outstanding_Warnings_Short(), Fixed_Warnings_Short()); + assertThat(model.getData()).map(PieData::getValue).containsExactly( + 2, 3, 4); } - private Report createReportStub(final int size) { + private Report createReportWithSize(final int size) { Report report = mock(Report.class); when(report.size()).thenReturn(size); return report; diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/NewVersusFixedTrendChartTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/NewVersusFixedTrendChartTest.java index 97b0bc35f9..e87f102f91 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/NewVersusFixedTrendChartTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/NewVersusFixedTrendChartTest.java @@ -23,7 +23,7 @@ */ class NewVersusFixedTrendChartTest { @Test - void shouldCreateALinesChartModel() { + void shouldCreateLinesChartModel() { NewVersusFixedTrendChart chart = new NewVersusFixedTrendChart(); List> results = new ArrayList<>(); diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/SeverityTrendChartTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/SeverityTrendChartTest.java index e43dfb07a5..d400faec66 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/SeverityTrendChartTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/SeverityTrendChartTest.java @@ -32,12 +32,14 @@ void shouldCreatePriorityChartWithDifferentDisplayNames() { SeverityTrendChart chart = new SeverityTrendChart(); List> compositeResults = new ArrayList<>(); - compositeResults.add(new BuildResult<>(new Build(2), new CompositeBuildResult().add( - createAnalysisBuildResult(0, 2, 4, 6), - createAnalysisBuildResult(0, 12, 14, 16)))); - compositeResults.add(new BuildResult<>(new Build(1), new CompositeBuildResult().add( + var first = createAnalysisBuildResult(0, 2, 4, 6); + var second = createAnalysisBuildResult(0, 12, 14, 16); + compositeResults.add(new BuildResult<>(new Build(2), new CompositeBuildResult(List.of( + first, + second)))); + compositeResults.add(new BuildResult<>(new Build(1), new CompositeBuildResult(List.of( createAnalysisBuildResult(3, 1, 2, 3), - createAnalysisBuildResult(1, 11, 12, 13)))); + createAnalysisBuildResult(1, 11, 12, 13))))); LinesChartModel model = chart.create(compositeResults, new ChartModelConfiguration()); @@ -162,5 +164,4 @@ private void verifySeries(final LineSeries series, final Severity severity, fina assertThatJson(series).node("data").isArray().hasSize(values.length).contains(value); } } - } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/ToolsTrendChartTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/ToolsTrendChartTest.java index 769e2ede16..eed4b10e47 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/ToolsTrendChartTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/charts/ToolsTrendChartTest.java @@ -35,10 +35,10 @@ void shouldCreateToolsChartForMultipleActions() { ToolsTrendChart chart = new ToolsTrendChart(); List> compositeResults = new ArrayList<>(); - compositeResults.add(new BuildResult<>(new Build(1), new CompositeBuildResult() - .add(createAnalysisBuildResult(CHECK_STYLE, 1), createAnalysisBuildResult(SPOT_BUGS, 3)))); - compositeResults.add(new BuildResult<>(new Build(2), new CompositeBuildResult() - .add(createAnalysisBuildResult(CHECK_STYLE, 2), createAnalysisBuildResult(SPOT_BUGS, 4)))); + compositeResults.add(new BuildResult<>(new Build(1), new CompositeBuildResult(List.of( + createAnalysisBuildResult(CHECK_STYLE, 1), createAnalysisBuildResult(SPOT_BUGS, 3))))); + compositeResults.add(new BuildResult<>(new Build(2), new CompositeBuildResult(List.of( + createAnalysisBuildResult(CHECK_STYLE, 2), createAnalysisBuildResult(SPOT_BUGS, 4))))); LinesChartModel model = chart.create(compositeResults, new ChartModelConfiguration()); @@ -89,7 +89,6 @@ void shouldCreateLineChartWithDuplicateColors() { boolean modelHasDuplicateColors = hasDuplicates(lineColors); assertThat(modelHasDuplicateColors).isTrue(); - } private boolean hasDuplicates(final List list) { diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/DeltaReportTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/DeltaReportTest.java index 82b40b6447..2652cb3a12 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/DeltaReportTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/model/DeltaReportTest.java @@ -113,10 +113,14 @@ void shouldCreateIssuesStatistics() { } private Issue getIssue(final String name) { - return new IssueBuilder().setFileName(name).setFingerprint(name).build(); + try (var builder = new IssueBuilder()) { + return builder.setFileName(name).setFingerprint(name).build(); + } } private Issue getIssueWithSeverity(final String name, final Severity severity) { - return new IssueBuilder().setFileName(name).setFingerprint(name).setSeverity(severity).build(); + try (var builder = new IssueBuilder()) { + return builder.setFileName(name).setFingerprint(name).setSeverity(severity).build(); + } } } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java index cb6f79e2f1..581d8086d6 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/steps/IssuesAggregatorTest.java @@ -50,7 +50,7 @@ void shouldHandleBuildWithoutActions() { aggregator.endBuild(); - verify(recorder, never()).publishResult(any(), any(), anyString(), any(), anyString(), any()); + verify(recorder, never()).publishResult(any(), any(), any(), anyString(), any(), anyString(), any()); } @Test @@ -71,7 +71,7 @@ void shouldCollectSingleResultForSingleAxis() { aggregator.endBuild(); - verify(recorder).publishResult(any(), any(), anyString(), any(), anyString(), any()); + verify(recorder).publishResult(any(), any(), any(), anyString(), any(), anyString(), any()); } @Test @org.junitpioneer.jupiter.Issue("JENKINS-59178") @@ -96,7 +96,7 @@ void shouldCollectDifferentResultsForTwoAxes() { aggregator.endBuild(); - verify(recorder, times(2)).publishResult(any(), any(), anyString(), any(), anyString(), any()); + verify(recorder, times(2)).publishResult(any(), any(), any(), anyString(), any(), anyString(), any()); } @Test @@ -120,7 +120,7 @@ void shouldCollectMultipleToolsOneAxis() { aggregator.endBuild(); - verify(recorder, times(2)).publishResult(any(), any(), anyString(), any(), anyString(), any()); + verify(recorder, times(2)).publishResult(any(), any(), any(), anyString(), any(), anyString(), any()); } @Test @@ -148,7 +148,7 @@ void shouldCollectOneToolMultipleAxes() { aggregator.endBuild(); - verify(recorder).publishResult(any(), any(), anyString(), any(), anyString(), any()); + verify(recorder).publishResult(any(), any(), any(), anyString(), any(), anyString(), any()); } private Issue createIssue(final String pmd) { diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsTest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsTest.java index 0c9e8db862..d67c7422c2 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/core/util/IssuesStatisticsTest.java @@ -10,7 +10,8 @@ import io.jenkins.plugins.analysis.core.util.IssuesStatistics.StatisticProperties; -import static io.jenkins.plugins.analysis.core.testutil.Assertions.*; +import static io.jenkins.plugins.analysis.core.assertions.Assertions.*; +import static org.mockito.Mockito.*; /** * Tests the class {@link IssuesStatistics}. @@ -27,6 +28,8 @@ void shouldCreateStatistics() { assertThat(StatisticProperties.TOTAL_HIGH.get(statistics)).isEqualTo(2); assertThat(StatisticProperties.TOTAL_NORMAL.get(statistics)).isEqualTo(3); assertThat(StatisticProperties.TOTAL_LOW.get(statistics)).isEqualTo(4); + assertThat(StatisticProperties.TOTAL_MODIFIED.get(statistics)).isEqualTo(14); + assertThat(statistics.getTotalSizeOf(Severity.ERROR)).isEqualTo(1); assertThat(statistics.getTotalSizeOf(Severity.WARNING_HIGH)).isEqualTo(2); assertThat(statistics.getTotalSizeOf(Severity.WARNING_NORMAL)).isEqualTo(3); @@ -37,6 +40,8 @@ void shouldCreateStatistics() { assertThat(StatisticProperties.NEW_HIGH.get(statistics)).isEqualTo(6); assertThat(StatisticProperties.NEW_NORMAL.get(statistics)).isEqualTo(7); assertThat(StatisticProperties.NEW_LOW.get(statistics)).isEqualTo(8); + assertThat(StatisticProperties.NEW_MODIFIED.get(statistics)).isEqualTo(15); + assertThat(statistics.getNewSizeOf(Severity.ERROR)).isEqualTo(5); assertThat(statistics.getNewSizeOf(Severity.WARNING_HIGH)).isEqualTo(6); assertThat(statistics.getNewSizeOf(Severity.WARNING_NORMAL)).isEqualTo(7); @@ -57,6 +62,47 @@ void shouldCreateStatistics() { entry(Severity.WARNING_LOW, 4)); } + @Test + void shouldAggregateTotals() { + var serializable = createSerializable(); + + assertThat(serializable.aggregate(serializable)).hasTotalErrorSize(2) + .hasTotalHighSize(4) + .hasTotalNormalSize(6) + .hasTotalLowSize(8) + .hasTotalModifiedSize(28) + .hasNewErrorSize(10) + .hasNewHighSize(12) + .hasNewNormalSize(14) + .hasNewLowSize(16) + .hasNewModifiedSize(30) + .hasDeltaErrorSize(18) + .hasDeltaHighSize(20) + .hasDeltaNormalSize(22) + .hasDeltaLowSize(24) + .hasFixedSize(26); + + assertThat(serializable).isEqualTo(serializable); + } + + @Test + void shouldResetBuilder() { + IssuesStatisticsBuilder builder = createBuilder(); + + builder.clear(); + + assertThat(builder).usingRecursiveComparison().isEqualTo(new IssuesStatisticsBuilder()); + } + + @Test + void shouldBlub() { + var statistics = mock(IssuesStatistics.class); + + when(statistics.getFixedSize()).thenReturn(15); + + assertThat(statistics.getFixedSize()).isEqualTo(15); + } + @Test void shouldRejectUnsupportedSeverities() { IssuesStatistics statistics = createSerializable(); @@ -69,22 +115,27 @@ void shouldRejectUnsupportedSeverities() { @Override protected IssuesStatistics createSerializable() { + return createBuilder().build(); + } + + private IssuesStatisticsBuilder createBuilder() { IssuesStatisticsBuilder builder = new IssuesStatisticsBuilder(); builder.setTotalErrorSize(1) .setTotalHighSize(2) .setTotalNormalSize(3) .setTotalLowSize(4) + .setTotalModifiedSize(14) .setNewErrorSize(5) .setNewHighSize(6) .setNewNormalSize(7) .setNewLowSize(8) + .setNewModifiedSize(15) .setDeltaErrorSize(9) .setDeltaHighSize(10) .setDeltaNormalSize(11) .setDeltaLowSize(12) .setFixedSize(13); - - return builder.build(); + return builder; } } diff --git a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/GitForensicsITest.java b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/GitForensicsITest.java index 56a65e96d3..eb93e88f02 100644 --- a/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/GitForensicsITest.java +++ b/plugin/src/test/java/io/jenkins/plugins/analysis/warnings/steps/GitForensicsITest.java @@ -29,12 +29,20 @@ class GitForensicsITest extends IntegrationTestWithJenkinsPerSuite { private static final String JAVA_ONE_WARNING = "java-start-rev0.txt"; private static final String PUBLISH_ISSUES_STEP = "publishIssues issues:[issues]"; private static final String FORENSICS_API_PLUGIN = "https://github.com/jenkinsci/forensics-api-plugin.git"; + private static final String OLD_COMMIT = "3097ea1037fb809c84d2752d7f98f71eedcfe97a"; private static final String COMMIT = "a6d0ef09ab3c418e370449a884da99b8190ae950"; - private static final String CHECKOUT_FORENSICS_API = "checkout([$class: 'GitSCM', " - + "branches: [[name: '" + COMMIT + "' ]],\n" - + "userRemoteConfigs: [[url: '" + FORENSICS_API_PLUGIN + "']],\n" - + "extensions: [[$class: 'RelativeTargetDirectory', \n" - + " relativeTargetDir: 'forensics-api']]])"; + private static final String CHECKOUT_FORENSICS_API = checkout(COMMIT); + private static final String MODIFIED_FILE = "src/main/java/io/jenkins/plugins/forensics/blame/Blames.java"; + + private static String checkout(final String commit) { + return "checkout([$class: 'GitSCM', " + + "branches: [[name: '" + commit + + "' ]],\n" + + "userRemoteConfigs: [[url: '" + FORENSICS_API_PLUGIN + "']],\n" + + "extensions: [[$class: 'RelativeTargetDirectory', \n" + + " relativeTargetDir: 'forensics-api']]])"; + } + private static final String MINE_REPOSITORY = "mineRepository()"; private static final String SCM_RESOLVER = "src/main/java/io/jenkins/plugins/forensics/util/ScmResolver.java"; private static final int AFFECTED_LINE = 20; @@ -45,9 +53,9 @@ class GitForensicsITest extends IntegrationTestWithJenkinsPerSuite { */ @Test void shouldObtainBlamesAndForensicsWithScanAndPublishIssuesSteps() { - runStepAndVerifyBlamesAndForensics(createScanForIssuesStep("sourceDirectory: 'forensics-api'")); runStepAndVerifyBlamesAndForensics(createScanForIssuesStep("sourceDirectories: [[path: 'forensics-api']]")); - runStepAndVerifyBlamesAndForensics(createScanForIssuesStep("sourceDirectories: [[path: 'does-not-exist'], [path: 'forensics-api']]")); + runStepAndVerifyBlamesAndForensics( + createScanForIssuesStep("sourceDirectories: [[path: 'does-not-exist'], [path: 'forensics-api']]")); } private String createScanForIssuesStep(final String sourceDirectories) { @@ -63,9 +71,10 @@ private String createScanForIssuesStep(final String sourceDirectories) { */ @Test void shouldObtainBlamesAndForensicsWithRecordIssuesStep() { - runStepAndVerifyBlamesAndForensics(createRecordIssuesStep("sourceDirectory: 'forensics-api'")); - runStepAndVerifyBlamesAndForensics(createRecordIssuesStep("sourceDirectories: [[path: 'forensics-api']]")); - runStepAndVerifyBlamesAndForensics(createRecordIssuesStep("sourceDirectories: [[path: 'does-not-exist'], [path: 'forensics-api']]")); + runStepAndVerifyBlamesAndForensics( + createRecordIssuesStep("sourceDirectories: [[path: 'forensics-api']]")); + runStepAndVerifyBlamesAndForensics( + createRecordIssuesStep("sourceDirectories: [[path: 'does-not-exist'], [path: 'forensics-api']]")); } private String createRecordIssuesStep(final String sourceDirectories) { @@ -75,8 +84,8 @@ private String createRecordIssuesStep(final String sourceDirectories) { } /** - * Checks out an existing Git repository and starts a freestyle job. Verifies that the - * Git forensics plugin is correctly invoked. + * Checks out an existing Git repository and starts a freestyle job. Verifies that the Git forensics plugin is + * correctly invoked. */ @Test void shouldObtainBlamesAndForensicsInFreestyleJob() throws IOException { @@ -97,14 +106,38 @@ void shouldObtainBlamesAndForensicsInFreestyleJob() throws IOException { private void runStepAndVerifyBlamesAndForensics(final String step) { WorkflowJob job = createPipelineWithWorkspaceFilesWithSuffix(); - createFileInWorkspace(job, "java-issues.txt", createJavaWarning(SCM_RESOLVER, AFFECTED_LINE)); + createFileInWorkspace(job, "java-issues.txt", + createJavaWarning(MODIFIED_FILE, 111) + + createJavaWarning(SCM_RESOLVER, AFFECTED_LINE)); + + job.setDefinition(asStage(checkout(OLD_COMMIT), MINE_REPOSITORY, step)); + + buildSuccessfully(job); // reference build + + createFileInWorkspace(job, "java-issues.txt", + createJavaWarning(MODIFIED_FILE, 111) // outstanding and modified + + createJavaWarning(MODIFIED_FILE, 112) // new and modified + + createJavaWarning(MODIFIED_FILE, 113) // new and modified + + createJavaWarning(SCM_RESOLVER, AFFECTED_LINE) // outstanding (not modified) + + createJavaWarning(MODIFIED_FILE, 2)); // new (not modified) job.setDefinition(asStage(CHECKOUT_FORENSICS_API, MINE_REPOSITORY, step)); - verifyBlaming(job); + var result = verifyBlamingWithModifiedFiles(job); + + assertThat(getConsoleLog(result)).contains( + "Detect all issues that are part of modified code", + "-> Using commit 'a6d0ef0' as latest commit for build", + "-> Using commit '3097ea1' as latest commit for build", + "-> Invoking Git delta calculator for determining the changes between commits 'a6d0ef0' and '3097ea1'", + "-> Start scanning for differences between commits...", + "-> 121 files contain changes", + "-> Creating the Git diff file", + "-> Git code delta successfully calculated", + "Issues in modified code: 3 (new: 2, outstanding: 1)"); } - private void verifyBlaming(final ParameterizedJob job) { + private AnalysisResult verifyBlaming(final ParameterizedJob job) { AnalysisResult result = scheduleSuccessfulBuild(job); assertThat(result).hasTotalSize(1).hasNewSize(0).hasFixedSize(0); @@ -121,6 +154,31 @@ private void verifyBlaming(final ParameterizedJob job) { "-> blamed authors of issues in 1 files", "Extracting repository forensics for 1 affected files (files in repository: 121)", "-> 1 affected files processed"); + + return result; + } + + private AnalysisResult verifyBlamingWithModifiedFiles(final ParameterizedJob job) { + AnalysisResult result = scheduleSuccessfulBuild(job); + + assertThat(result).hasTotalSize(5).hasNewSize(3).hasFixedSize(0); + assertThat(result.getTotals()).hasNewModifiedSize(2).hasTotalModifiedSize(3); + + assertThat(result.getBlames().contains(SCM_RESOLVER)).isTrue(); + + FileBlame blame = result.getBlames().getBlame(SCM_RESOLVER); + assertThat(blame.getFileName()).isEqualTo(SCM_RESOLVER); + assertThat(blame.getEmail(AFFECTED_LINE)).isEqualTo("ullrich.hafner@gmail.com"); + assertThat(blame.getCommit(AFFECTED_LINE)).isEqualTo("43dde5d4f7a06122216494a896c51830ed684572"); + + assertThat(getConsoleLog(result)).contains( + "Invoking Git blamer to create author and commit information for 2 affected files", + "Git commit ID = 'a6d0ef09ab3c418e370449a884da99b8190ae950'", + "-> blamed authors of issues in 2 files", + "Extracting repository forensics for 2 affected files (files in repository: 121)", + "-> 2 affected files processed"); + + return result; } /** @@ -130,7 +188,7 @@ private void verifyBlaming(final ParameterizedJob job) { @Test void shouldSkipBlamesAndForensicsWithScanAndPublishIssuesSteps() { runStepAndVerifyScmSkipping("def issues = scanForIssues " - + "sourceDirectory: 'forensics-api'," + + "sourceDirectories: [[path: 'forensics-api']]," + "scm: 'nothing', " + "tool: java(pattern:'**/*issues.txt', reportEncoding:'UTF-8')\n" + PUBLISH_ISSUES_STEP); @@ -143,14 +201,14 @@ void shouldSkipBlamesAndForensicsWithScanAndPublishIssuesSteps() { @Test void shouldSkipBlamesAndForensicsWithRecordIssuesStep() { runStepAndVerifyScmSkipping("recordIssues " - + "sourceDirectory: 'forensics-api'," + + "sourceDirectories: [[path: 'forensics-api']]," + "scm: 'nothing', " + "tool: java(pattern:'**/*issues.txt', reportEncoding:'UTF-8')"); } /** - * Checks out an existing Git repository and starts a freestyle job. Verifies that the Git forensics - * plugin is correctly skipped. + * Checks out an existing Git repository and starts a freestyle job. Verifies that the Git forensics plugin is + * correctly skipped. */ @Test void shouldSkipBlamesAndForensicsInFreestyleJob() throws IOException { From a30f12ef4867bb7e687103c9b80651be9da30706 Mon Sep 17 00:00:00 2001 From: Ulli Hafner Date: Fri, 26 Jan 2024 12:44:16 +0100 Subject: [PATCH 27/27] Add a pie chart that shows the issues in modified code. --- .../analysis/core/charts/ModifiedCodePieChart.java | 5 +++-- .../plugins/analysis/core/model/DetailFactory.java | 11 ++++++++++- .../plugins/analysis/core/model/IssuesDetail.java | 9 +++++++++ .../analysis/core/model/IssuesDetail/filled.jelly | 5 +++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/ModifiedCodePieChart.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/ModifiedCodePieChart.java index f690046837..1d63ca4215 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/ModifiedCodePieChart.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/charts/ModifiedCodePieChart.java @@ -24,8 +24,9 @@ public PieChartModel create(final Report issues) { PieChartModel model = new PieChartModel(Messages.NewVersusFixed_Name()); var totals = issues.size(); - model.add(new PieData(Messages.Modified_Code_Warnings_Short(), totals), JenkinsPalette.RED.normal()); - model.add(new PieData(Messages.Unchanged_Code_Warnings_Short(), totals), JenkinsPalette.YELLOW.normal()); + var modified = issues.getInModifiedCode().size(); + model.add(new PieData(Messages.Modified_Code_Warnings_Short(), modified), JenkinsPalette.RED.normal()); + model.add(new PieData(Messages.Unchanged_Code_Warnings_Short(), totals - modified), JenkinsPalette.YELLOW.normal()); return model; } diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DetailFactory.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DetailFactory.java index 30ff9a00b8..322f905cf8 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DetailFactory.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/DetailFactory.java @@ -151,7 +151,7 @@ private Marker asMarker(final Issue issue, final String description, final Strin .withColumnEnd(issue.getColumnEnd()).build(); } - @SuppressWarnings({"checkstyle:ParameterNumber", "PMD.CyclomaticComplexity"}) + @SuppressWarnings({"checkstyle:ParameterNumber", "PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) @SuppressFBWarnings("IMPROPER_UNICODE") private Object createNewDetailView(final String link, final Run owner, final AnalysisResult result, final Report allIssues, final Report newIssues, final Report outstandingIssues, final Report fixedIssues, @@ -167,6 +167,11 @@ private Object createNewDetailView(final String link, final Run owner, fin filterModified(outstandingIssues), EMPTY, Messages.Modified_Warnings_Header(), url, labelProvider, sourceEncoding); } + if ("unchanged".equalsIgnoreCase(link)) { + return new IssuesDetail(owner, result, filterUnchanged(allIssues), filterUnchanged(newIssues), + filterUnchanged(outstandingIssues), EMPTY, + Messages.Modified_Warnings_Header(), url, labelProvider, sourceEncoding); + } if ("fixed".equalsIgnoreCase(link)) { return new FixedWarningsDetail(owner, result, fixedIssues, url, labelProvider, sourceEncoding); } @@ -200,6 +205,10 @@ private Report filterModified(final Report report) { return report.filter(Issue::isPartOfModifiedCode); } + private Report filterUnchanged(final Report report) { + return report.filter(Predicate.not(Issue::isPartOfModifiedCode)); + } + private Predicate createPropertyFilter(final String plainLink, final String property) { return issue -> plainLink.equals(String.valueOf( Issue.getPropertyValueAsString(issue, property).hashCode())); diff --git a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java index 0face0f6b9..f9f9b89644 100644 --- a/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java +++ b/plugin/src/main/java/io/jenkins/plugins/analysis/core/model/IssuesDetail.java @@ -407,6 +407,15 @@ private AnalysisHistory createHistory() { return new AnalysisHistory(owner, new ByIdResultSelector(result.getId())); } + /** + * Returns whether there are any issues in the associated static analysis run that are part of modified code. + * + * @return {@code true} if there are issues in modified code, {@code false} otherwise + */ + public boolean hasIssuesInModifiedCode() { + return result.getTotals().getTotalModifiedSize() > 0; + } + /** * Returns all issues of the associated static analysis run. * diff --git a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/IssuesDetail/filled.jelly b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/IssuesDetail/filled.jelly index 9b92999572..7f71ecdb2d 100644 --- a/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/IssuesDetail/filled.jelly +++ b/plugin/src/main/resources/io/jenkins/plugins/analysis/core/model/IssuesDetail/filled.jelly @@ -14,6 +14,11 @@ + + +