From 454f14e4688f677877ce61bb0c9c5256638375a9 Mon Sep 17 00:00:00 2001 From: Daniel Schwarz Date: Wed, 4 Oct 2017 09:42:38 +0200 Subject: [PATCH] SONAR-9352 add "ignoredConditions" to response example of api/qualitygates/project_status --- .../QualityGateDetailsData.java | 6 +- .../step/QualityGateMeasuresStep.java | 22 ++++- .../SmallChangesetQualityGateSpecialCase.java | 28 +++---- .../qualitygate/ws/ProjectStatusAction.java | 6 +- .../ws/QualityGateDetailsFormatter.java | 10 +++ .../ws/project_status-example.json | 1 + .../QualityGateDetailsDataTest.java | 18 ++++- .../step/QualityGateMeasuresStepTest.java | 8 +- ...llChangesetQualityGateSpecialCaseTest.java | 42 ++++++---- .../ws/ProjectStatusActionTest.java | 12 +++ .../src/main/protobuf/ws-qualitygates.proto | 1 + .../v2-1020-lines/src/sample/Sample.xoo.scm | 2 +- .../QualityGateForSmallChangesetsTest.java | 81 ++++++++++++++++++- 13 files changed, 191 insertions(+), 46 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/measure/qualitygatedetails/QualityGateDetailsData.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/measure/qualitygatedetails/QualityGateDetailsData.java index 4c4af01b977..e157d5aa650 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/measure/qualitygatedetails/QualityGateDetailsData.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/measure/qualitygatedetails/QualityGateDetailsData.java @@ -32,13 +32,16 @@ import static java.util.Objects.requireNonNull; @Immutable public class QualityGateDetailsData { private static final String FIELD_LEVEL = "level"; + private static final String FIELD_IGNORED_CONDITIONS = "ignoredConditions"; private final Measure.Level level; private final List conditions; + private final boolean ignoredConditions; - public QualityGateDetailsData(Measure.Level level, Iterable conditions) { + public QualityGateDetailsData(Measure.Level level, Iterable conditions, boolean ignoredConditions) { this.level = requireNonNull(level); this.conditions = from(conditions).toList(); + this.ignoredConditions = ignoredConditions; } public String toJson() { @@ -49,6 +52,7 @@ public class QualityGateDetailsData { conditionResults.add(toJson(condition)); } details.add("conditions", conditionResults); + details.addProperty(FIELD_IGNORED_CONDITIONS, ignoredConditions); return details.toString(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java index 22a0b04aa2f..8bfffcac931 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java @@ -174,6 +174,7 @@ public class QualityGateMeasuresStep implements ComputationStep { private void updateMeasures(Component project, Set conditions, QualityGateDetailsDataBuilder builder) { Multimap conditionsPerMetric = conditions.stream().collect(MoreCollectors.index(Condition::getMetric, java.util.function.Function.identity())); + boolean ignoredConditions = false; for (Map.Entry> entry : conditionsPerMetric.asMap().entrySet()) { Metric metric = entry.getKey(); Optional measure = measureRepository.getRawMeasure(project, metric); @@ -182,7 +183,13 @@ public class QualityGateMeasuresStep implements ComputationStep { } final MetricEvaluationResult metricEvaluationResult = evaluateQualityGate(measure.get(), entry.getValue()); - final MetricEvaluationResult finalMetricEvaluationResult = smallChangesetQualityGateSpecialCase.applyIfNeeded(project, metricEvaluationResult); + final MetricEvaluationResult finalMetricEvaluationResult; + if (smallChangesetQualityGateSpecialCase.appliesTo(project, metricEvaluationResult)) { + finalMetricEvaluationResult = smallChangesetQualityGateSpecialCase.apply(metricEvaluationResult); + ignoredConditions = true; + } else { + finalMetricEvaluationResult = metricEvaluationResult; + } String text = evaluationResultTextConverter.asText(finalMetricEvaluationResult.condition, finalMetricEvaluationResult.evaluationResult); builder.addLabel(text); @@ -193,6 +200,7 @@ public class QualityGateMeasuresStep implements ComputationStep { builder.addEvaluatedCondition(finalMetricEvaluationResult); } + builder.setIgnoredConditions(ignoredConditions); } private static MetricEvaluationResult evaluateQualityGate(Measure measure, Collection conditions) { @@ -214,7 +222,7 @@ public class QualityGateMeasuresStep implements ComputationStep { Metric metric = metricRepository.getByKey(CoreMetrics.ALERT_STATUS_KEY); measureRepository.add(project, metric, globalMeasure); - String detailMeasureValue = new QualityGateDetailsData(builder.getGlobalLevel(), builder.getEvaluatedConditions()).toJson(); + String detailMeasureValue = new QualityGateDetailsData(builder.getGlobalLevel(), builder.getEvaluatedConditions(), builder.isIgnoredConditions()).toJson(); Measure detailsMeasure = Measure.newMeasureBuilder().create(detailMeasureValue); Metric qgDetailsMetric = metricRepository.getByKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY); measureRepository.add(project, qgDetailsMetric, detailsMeasure); @@ -229,6 +237,7 @@ public class QualityGateMeasuresStep implements ComputationStep { private Measure.Level globalLevel = Measure.Level.OK; private List labels = new ArrayList<>(); private List evaluatedConditions = new ArrayList<>(); + private boolean ignoredConditions; public Measure.Level getGlobalLevel() { return globalLevel; @@ -259,6 +268,15 @@ public class QualityGateMeasuresStep implements ComputationStep { public List getEvaluatedConditions() { return evaluatedConditions; } + + public boolean isIgnoredConditions() { + return ignoredConditions; + } + + public QualityGateDetailsDataBuilder setIgnoredConditions(boolean ignoredConditions) { + this.ignoredConditions = ignoredConditions; + return this; + } } private enum EvaluatedConditionToCondition implements Function { diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SmallChangesetQualityGateSpecialCase.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SmallChangesetQualityGateSpecialCase.java index 6f4a1224cd8..fc0a3af4851 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SmallChangesetQualityGateSpecialCase.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SmallChangesetQualityGateSpecialCase.java @@ -27,15 +27,16 @@ import org.sonar.server.computation.task.projectanalysis.measure.Measure; import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository; import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository; import org.sonar.server.computation.task.projectanalysis.qualitygate.EvaluationResult; +import org.sonar.server.computation.task.projectanalysis.step.QualityGateMeasuresStep.MetricEvaluationResult; import static java.util.Arrays.asList; public class SmallChangesetQualityGateSpecialCase { /** - * Some metrics will only produce warnings (never errors) on very small change sets. + * Some metrics will be ignored on very small change sets. */ - private static final Collection METRICS_THAT_CAN_ONLY_PRODUCE_WARNINGS_ON_SMALL_CHANGESETS = asList( + private static final Collection METRICS_TO_IGNORE_ON_SMALL_CHANGESETS = asList( CoreMetrics.NEW_COVERAGE_KEY, CoreMetrics.NEW_LINE_COVERAGE_KEY, CoreMetrics.NEW_BRANCH_COVERAGE_KEY, @@ -53,24 +54,15 @@ public class SmallChangesetQualityGateSpecialCase { this.metricRepository = metricRepository; } - QualityGateMeasuresStep.MetricEvaluationResult applyIfNeeded(Component project, @Nullable QualityGateMeasuresStep.MetricEvaluationResult metricEvaluationResult) { - if (metricEvaluationResult == null) { - return metricEvaluationResult; - } - if (metricEvaluationResult.evaluationResult.getLevel() == Measure.Level.OK) { - return metricEvaluationResult; - } - if (!METRICS_THAT_CAN_ONLY_PRODUCE_WARNINGS_ON_SMALL_CHANGESETS.contains(metricEvaluationResult.condition.getMetric().getKey())) { - return metricEvaluationResult; - } - if (!isSmallChangeset(project)) { - return metricEvaluationResult; - } - return calculateModifiedResult(metricEvaluationResult); + public boolean appliesTo(Component project, @Nullable MetricEvaluationResult metricEvaluationResult) { + return metricEvaluationResult != null + && metricEvaluationResult.evaluationResult.getLevel() != Measure.Level.OK + && METRICS_TO_IGNORE_ON_SMALL_CHANGESETS.contains(metricEvaluationResult.condition.getMetric().getKey()) + && isSmallChangeset(project); } - private QualityGateMeasuresStep.MetricEvaluationResult calculateModifiedResult(@Nullable QualityGateMeasuresStep.MetricEvaluationResult metricEvaluationResult) { - return new QualityGateMeasuresStep.MetricEvaluationResult( + MetricEvaluationResult apply(MetricEvaluationResult metricEvaluationResult) { + return new MetricEvaluationResult( new EvaluationResult(Measure.Level.OK, metricEvaluationResult.evaluationResult.getValue()), metricEvaluationResult.condition); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/ProjectStatusAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/ProjectStatusAction.java index d61184b3d9a..37b98e830a6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/ProjectStatusAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/ProjectStatusAction.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; @@ -87,7 +88,10 @@ public class ProjectStatusAction implements QualityGatesWsAction { "", QG_STATUSES_ONE_LINE, ProjectStatusWsResponse.Status.NONE)) .setResponseExample(getClass().getResource("project_status-example.json")) .setSince("5.3") - .setHandler(this); + .setHandler(this) + .setChangelog( + new Change("6.4", "The field 'ignoredConditions' is added to the response") + ); action.createParam(PARAM_ANALYSIS_ID) .setDescription("Analysis id") diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java index b45804f50e7..6c6a9508142 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/QualityGateDetailsFormatter.java @@ -55,12 +55,22 @@ public class QualityGateDetailsFormatter { ProjectStatusWsResponse.Status qualityGateStatus = measureLevelToQualityGateStatus(json.get("level").getAsString()); projectStatusBuilder.setStatus(qualityGateStatus); + formatIgnoredConditions(json); formatConditions(json.getAsJsonArray("conditions")); formatPeriods(); return projectStatusBuilder.build(); } + private void formatIgnoredConditions(JsonObject json) { + JsonElement ignoredConditions = json.get("ignoredConditions"); + if (ignoredConditions != null) { + projectStatusBuilder.setIgnoredConditions(ignoredConditions.getAsBoolean()); + } else { + projectStatusBuilder.setIgnoredConditions(false); + } + } + private void formatPeriods() { if (!optionalSnapshot.isPresent()) { return; diff --git a/server/sonar-server/src/main/resources/org/sonar/server/qualitygate/ws/project_status-example.json b/server/sonar-server/src/main/resources/org/sonar/server/qualitygate/ws/project_status-example.json index 31308d8f45c..946f9c4fdb3 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/qualitygate/ws/project_status-example.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/qualitygate/ws/project_status-example.json @@ -1,6 +1,7 @@ { "projectStatus": { "status": "ERROR", + "ignoredConditions": false, "conditions": [ { "status": "ERROR", diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/measure/qualitygatedetails/QualityGateDetailsDataTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/measure/qualitygatedetails/QualityGateDetailsDataTest.java index 46a97eb845e..ac2730c9d48 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/measure/qualitygatedetails/QualityGateDetailsDataTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/measure/qualitygatedetails/QualityGateDetailsDataTest.java @@ -31,17 +31,17 @@ import org.sonar.test.JsonAssert; public class QualityGateDetailsDataTest { @Test(expected = NullPointerException.class) public void constructor_throws_NPE_if_Level_arg_is_null() { - new QualityGateDetailsData(null, Collections.emptyList()); + new QualityGateDetailsData(null, Collections.emptyList(), false); } @Test(expected = NullPointerException.class) public void constructor_throws_NPE_if_Iterable_arg_is_null() { - new QualityGateDetailsData(Measure.Level.OK, null); + new QualityGateDetailsData(Measure.Level.OK, null, false); } @Test public void verify_json_when_there_is_no_condition() { - String actualJson = new QualityGateDetailsData(Measure.Level.OK, Collections.emptyList()).toJson(); + String actualJson = new QualityGateDetailsData(Measure.Level.OK, Collections.emptyList(), false).toJson(); JsonAssert.assertJson(actualJson).isSimilarTo("{" + "\"level\":\"OK\"," + @@ -57,7 +57,7 @@ public class QualityGateDetailsDataTest { new EvaluatedCondition(condition, Measure.Level.OK, value), new EvaluatedCondition(condition, Measure.Level.WARN, value), new EvaluatedCondition(condition, Measure.Level.ERROR, value)); - String actualJson = new QualityGateDetailsData(Measure.Level.OK, evaluatedConditions).toJson(); + String actualJson = new QualityGateDetailsData(Measure.Level.OK, evaluatedConditions, false).toJson(); JsonAssert.assertJson(actualJson).isSimilarTo("{" + "\"level\":\"OK\"," + @@ -92,4 +92,14 @@ public class QualityGateDetailsDataTest { "]" + "}"); } + + @Test + public void verify_json_for_small_leak() { + String actualJson = new QualityGateDetailsData(Measure.Level.OK, Collections.emptyList(), false).toJson(); + JsonAssert.assertJson(actualJson).isSimilarTo("{\"ignoredConditions\": false}"); + + String actualJson2 = new QualityGateDetailsData(Measure.Level.OK, Collections.emptyList(), true).toJson(); + JsonAssert.assertJson(actualJson2).isSimilarTo("{\"ignoredConditions\": true}"); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStepTest.java index 1b6860e5d1f..92fdda5700e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStepTest.java @@ -161,7 +161,7 @@ public class QualityGateMeasuresStepTest { .hasQualityGateLevel(OK) .hasQualityGateText(""); assertThat(getQGDetailsMeasure()) - .hasValue(new QualityGateDetailsData(OK, Collections.emptyList()).toJson()); + .hasValue(new QualityGateDetailsData(OK, Collections.emptyList(), false).toJson()); QualityGateStatusHolderAssertions.assertThat(qualityGateStatusHolder) .hasStatus(QualityGateStatus.OK) @@ -189,7 +189,7 @@ public class QualityGateMeasuresStepTest { .hasQualityGateLevel(OK) .hasQualityGateText(dumbResultTextAnswer(equals2Condition, OK, rawValue)); assertThat(getQGDetailsMeasure().get()) - .hasValue(new QualityGateDetailsData(OK, of(new EvaluatedCondition(equals2Condition, OK, rawValue))).toJson()); + .hasValue(new QualityGateDetailsData(OK, of(new EvaluatedCondition(equals2Condition, OK, rawValue)), false).toJson()); QualityGateStatusHolderAssertions.assertThat(qualityGateStatusHolder) .hasStatus(QualityGateStatus.OK) @@ -226,7 +226,7 @@ public class QualityGateMeasuresStepTest { assertThat(getQGDetailsMeasure()) .hasValue(new QualityGateDetailsData(ERROR, of( new EvaluatedCondition(equals1ErrorCondition, ERROR, rawValue), - new EvaluatedCondition(equals1WarningCondition, WARN, rawValue))).toJson()); + new EvaluatedCondition(equals1WarningCondition, WARN, rawValue)), false).toJson()); QualityGateStatusHolderAssertions.assertThat(qualityGateStatusHolder) .hasStatus(QualityGateStatus.ERROR) @@ -264,7 +264,7 @@ public class QualityGateMeasuresStepTest { assertThat(getQGDetailsMeasure()) .hasValue(new QualityGateDetailsData(WARN, of( new EvaluatedCondition(equals2Condition, OK, rawValue), - new EvaluatedCondition(equals1WarningCondition, WARN, rawValue))).toJson()); + new EvaluatedCondition(equals1WarningCondition, WARN, rawValue)), false).toJson()); QualityGateStatusHolderAssertions.assertThat(qualityGateStatusHolder) .hasStatus(QualityGateStatus.WARN) diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SmallChangesetQualityGateSpecialCaseTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SmallChangesetQualityGateSpecialCaseTest.java index 0f41d0bf7cf..482ef242a6f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SmallChangesetQualityGateSpecialCaseTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SmallChangesetQualityGateSpecialCaseTest.java @@ -61,9 +61,9 @@ public class SmallChangesetQualityGateSpecialCaseTest { Component project = generateNewRootProject(); measureRepository.addRawMeasure(PROJECT_REF, CoreMetrics.NEW_LINES_KEY, newMeasureBuilder().setVariation(19).create(1000)); - QualityGateMeasuresStep.MetricEvaluationResult result = underTest.applyIfNeeded(project, metricEvaluationResult); + boolean result = underTest.appliesTo(project, metricEvaluationResult); - assertThat(result.evaluationResult.getLevel()).isSameAs(OK); + assertThat(result).isTrue(); } @Test @@ -72,9 +72,9 @@ public class SmallChangesetQualityGateSpecialCaseTest { Component project = generateNewRootProject(); measureRepository.addRawMeasure(PROJECT_REF, CoreMetrics.NEW_LINES_KEY, newMeasureBuilder().setVariation(19).create(1000)); - QualityGateMeasuresStep.MetricEvaluationResult result = underTest.applyIfNeeded(project, metricEvaluationResult); + boolean result = underTest.appliesTo(project, metricEvaluationResult); - assertThat(result.evaluationResult.getLevel()).isSameAs(OK); + assertThat(result).isTrue(); } @Test @@ -83,9 +83,9 @@ public class SmallChangesetQualityGateSpecialCaseTest { Component project = generateNewRootProject(); measureRepository.addRawMeasure(PROJECT_REF, CoreMetrics.NEW_LINES_KEY, newMeasureBuilder().setVariation(20).create(1000)); - QualityGateMeasuresStep.MetricEvaluationResult result = underTest.applyIfNeeded(project, metricEvaluationResult); + boolean result = underTest.appliesTo(project, metricEvaluationResult); - assertThat(result.evaluationResult.getLevel()).isSameAs(ERROR); + assertThat(result).isFalse(); } @Test @@ -94,9 +94,9 @@ public class SmallChangesetQualityGateSpecialCaseTest { Component project = generateNewRootProject(); measureRepository.addRawMeasure(PROJECT_REF, CoreMetrics.NEW_LINES_KEY, newMeasureBuilder().setVariation(19).create(1000)); - QualityGateMeasuresStep.MetricEvaluationResult result = underTest.applyIfNeeded(project, metricEvaluationResult); + boolean result = underTest.appliesTo(project, metricEvaluationResult); - assertThat(result.evaluationResult.getLevel()).isSameAs(ERROR); + assertThat(result).isFalse(); } @Test @@ -105,9 +105,9 @@ public class SmallChangesetQualityGateSpecialCaseTest { Component project = generateNewRootProject(); measureRepository.addRawMeasure(PROJECT_REF, CoreMetrics.NEW_LINES_KEY, newMeasureBuilder().setVariation(19).create(1000)); - QualityGateMeasuresStep.MetricEvaluationResult result = underTest.applyIfNeeded(project, metricEvaluationResult); + boolean result = underTest.appliesTo(project, metricEvaluationResult); - assertThat(result.evaluationResult.getLevel()).isSameAs(OK); + assertThat(result).isFalse(); } @Test @@ -115,17 +115,31 @@ public class SmallChangesetQualityGateSpecialCaseTest { QualityGateMeasuresStep.MetricEvaluationResult metricEvaluationResult = generateEvaluationResult(NEW_COVERAGE_KEY, ERROR); Component project = generateNewRootProject(); - QualityGateMeasuresStep.MetricEvaluationResult result = underTest.applyIfNeeded(project, metricEvaluationResult); + boolean result = underTest.appliesTo(project, metricEvaluationResult); - assertThat(result.evaluationResult.getLevel()).isSameAs(ERROR); + assertThat(result).isFalse(); } @Test public void should_silently_ignore_null_values() throws Exception { - QualityGateMeasuresStep.MetricEvaluationResult result = underTest.applyIfNeeded(mock(Component.class), null); + boolean result = underTest.appliesTo(mock(Component.class), null); - assertThat(result).isNull(); + assertThat(result).isFalse(); + } + + @Test + public void apply() throws Exception { + Comparable value = mock(Comparable.class); + Condition condition = mock(Condition.class); + QualityGateMeasuresStep.MetricEvaluationResult original = new QualityGateMeasuresStep.MetricEvaluationResult( + new EvaluationResult(Measure.Level.ERROR, value), condition); + + QualityGateMeasuresStep.MetricEvaluationResult modified = underTest.apply(original); + + assertThat(modified.evaluationResult.getLevel()).isSameAs(OK); + assertThat(modified.evaluationResult.getValue()).isSameAs(value); + assertThat(modified.condition).isSameAs(condition); } private Component generateNewRootProject() { diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/ProjectStatusActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/ProjectStatusActionTest.java index 13305eb54db..8e96d3fd2d5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/ProjectStatusActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/ProjectStatusActionTest.java @@ -26,6 +26,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.server.ws.Change; +import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; import org.sonar.db.DbClient; @@ -47,6 +49,7 @@ import org.sonarqube.ws.WsQualityGates.ProjectStatusWsResponse.Status; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; import static org.sonar.db.component.SnapshotTesting.newAnalysis; import static org.sonar.db.measure.MeasureTesting.newMeasureDto; import static org.sonar.db.metric.MetricTesting.newMetricDto; @@ -77,6 +80,15 @@ public class ProjectStatusActionTest { ws = new WsActionTester(new ProjectStatusAction(dbClient, TestComponentFinder.from(db), userSession)); } + @Test + public void definition() throws Exception { + WebService.Action def = ws.getDef(); + assertThat(def.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("analysisId", "projectKey", "projectId"); + assertThat(def.changelog()).extracting(Change::getVersion, Change::getDescription).containsExactly( + tuple("6.4", "The field 'ignoredConditions' is added to the response") + ); + } + @Test public void json_example() throws IOException { ComponentDto project = db.components().insertPrivateProject(db.organizations().insert()); diff --git a/sonar-ws/src/main/protobuf/ws-qualitygates.proto b/sonar-ws/src/main/protobuf/ws-qualitygates.proto index 92d2be56d33..8444dbac729 100644 --- a/sonar-ws/src/main/protobuf/ws-qualitygates.proto +++ b/sonar-ws/src/main/protobuf/ws-qualitygates.proto @@ -32,6 +32,7 @@ message ProjectStatusWsResponse { optional Status status = 1; repeated Condition conditions = 2; repeated Period periods = 3; + optional bool ignoredConditions = 4; } message Condition { diff --git a/tests/projects/qualitygate/small-changesets/v2-1020-lines/src/sample/Sample.xoo.scm b/tests/projects/qualitygate/small-changesets/v2-1020-lines/src/sample/Sample.xoo.scm index 07c227857e1..98fd9a53dda 100644 --- a/tests/projects/qualitygate/small-changesets/v2-1020-lines/src/sample/Sample.xoo.scm +++ b/tests/projects/qualitygate/small-changesets/v2-1020-lines/src/sample/Sample.xoo.scm @@ -1017,4 +1017,4 @@ 2,user2,2014-04-01 2,user2,2014-04-01 2,user2,2014-04-01 -3,user3,2014-04-02 \ No newline at end of file +3,user3,2014-04-03 \ No newline at end of file diff --git a/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateForSmallChangesetsTest.java b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateForSmallChangesetsTest.java index 715b80cab80..ca4c99e47dc 100644 --- a/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateForSmallChangesetsTest.java +++ b/tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateForSmallChangesetsTest.java @@ -21,17 +21,29 @@ package org.sonarqube.tests.qualityGate; import com.sonar.orchestrator.Orchestrator; import com.sonar.orchestrator.build.SonarScanner; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.Properties; +import org.apache.commons.io.FileUtils; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.sonarqube.tests.Category6Suite; import org.sonarqube.tests.Tester; +import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.Organizations; +import org.sonarqube.ws.WsCe; import org.sonarqube.ws.WsProjects.CreateWsResponse.Project; import org.sonarqube.ws.WsQualityGates; import org.sonarqube.ws.WsQualityGates.CreateWsResponse; import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.WsResponse; import org.sonarqube.ws.client.qualitygate.CreateConditionRequest; +import org.sonarqube.ws.client.qualitygate.ProjectStatusWsRequest; +import org.sonarqube.ws.client.qualitygate.UpdateConditionRequest; import static org.assertj.core.api.Assertions.assertThat; import static util.ItUtils.getMeasure; @@ -64,6 +76,7 @@ public class QualityGateForSmallChangesetsTest { String password = "password1"; WsUsers.CreateWsResponse.User user = tester.users().generateAdministrator(organization, u -> u.setPassword(password)); + // no leak => use usual behaviour SonarScanner analysis = SonarScanner .create(projectDir("qualitygate/small-changesets/v1-1000-lines")) .setProperty("sonar.projectKey", project.getKey()) @@ -76,7 +89,9 @@ public class QualityGateForSmallChangesetsTest { .setDebugLogs(true); orchestrator.executeBuild(analysis); assertThat(getMeasure(orchestrator, project.getKey(), "alert_status").getValue()).isEqualTo("OK"); + assertIgnoredConditions(project, "qualitygate/small-changesets/v1-1000-lines", false); + // small leak => ignore coverage warning or error SonarScanner analysis2 = SonarScanner .create(projectDir("qualitygate/small-changesets/v2-1019-lines")) .setProperty("sonar.projectKey", project.getKey()) @@ -89,7 +104,40 @@ public class QualityGateForSmallChangesetsTest { .setDebugLogs(true); orchestrator.executeBuild(analysis2); assertThat(getMeasure(orchestrator, project.getKey(), "alert_status").getValue()).isEqualTo("OK"); + assertIgnoredConditions(project, "qualitygate/small-changesets/v2-1019-lines", true); + // small leak => if coverage is OK anyways, we do not have to ignore anything + tester.wsClient().qualityGates().updateCondition(UpdateConditionRequest.builder() + .setConditionId(condition.getId()) + .setMetricKey("new_coverage") + .setOperator("LT") + .setWarning("10") + .setError("20") + .setPeriod(1) + .build()); + SonarScanner analysis3 = SonarScanner + .create(projectDir("qualitygate/small-changesets/v2-1019-lines")) + .setProperty("sonar.projectKey", project.getKey()) + .setProperty("sonar.organization", organization.getKey()) + .setProperty("sonar.login", user.getLogin()) + .setProperty("sonar.password", password) + .setProperty("sonar.scm.provider", "xoo") + .setProperty("sonar.scm.disabled", "false") + .setProperty("sonar.projectDate", "2014-04-02") + .setDebugLogs(true); + orchestrator.executeBuild(analysis3); + assertThat(getMeasure(orchestrator, project.getKey(), "alert_status").getValue()).isEqualTo("OK"); + assertIgnoredConditions(project, "qualitygate/small-changesets/v2-1019-lines", false); + + // big leak => use usual behaviour + tester.wsClient().qualityGates().updateCondition(UpdateConditionRequest.builder() + .setConditionId(condition.getId()) + .setMetricKey("new_coverage") + .setOperator("LT") + .setWarning(null) + .setError("70") + .setPeriod(1) + .build()); SonarScanner analysis4 = SonarScanner .create(projectDir("qualitygate/small-changesets/v2-1020-lines")) .setProperty("sonar.projectKey", project.getKey()) @@ -98,9 +146,40 @@ public class QualityGateForSmallChangesetsTest { .setProperty("sonar.password", password) .setProperty("sonar.scm.provider", "xoo") .setProperty("sonar.scm.disabled", "false") - .setProperty("sonar.projectDate", "2014-04-02") + .setProperty("sonar.projectDate", "2014-04-03") .setDebugLogs(true); orchestrator.executeBuild(analysis4); assertThat(getMeasure(orchestrator, project.getKey(), "alert_status").getValue()).isEqualTo("ERROR"); + assertIgnoredConditions(project, "qualitygate/small-changesets/v2-1020-lines", false); + } + + private void assertIgnoredConditions(Project project, String projectDir, boolean expected) throws IOException { + String analysisId = getAnalysisId(getTaskIdInLocalReport(projectDir(projectDir))); + boolean ignoredConditions = tester.wsClient().qualityGates() + .projectStatus(new ProjectStatusWsRequest().setAnalysisId(analysisId)) + .getProjectStatus() + .getIgnoredConditions(); + assertThat(ignoredConditions).isEqualTo(expected); + } + + private String getAnalysisId(String taskId) throws IOException { + WsResponse activity = tester.wsClient() + .wsConnector() + .call(new GetRequest("api/ce/task") + .setParam("id", taskId) + .setMediaType(MediaTypes.PROTOBUF)); + WsCe.TaskResponse activityWsResponse = WsCe.TaskResponse.parseFrom(activity.contentStream()); + return activityWsResponse.getTask().getAnalysisId(); + } + + private String getTaskIdInLocalReport(File projectDirectory) throws IOException { + File metadata = new File(projectDirectory, ".sonar/report-task.txt"); + assertThat(metadata).exists().isFile(); + // verify properties + Properties props = new Properties(); + props.load(new StringReader(FileUtils.readFileToString(metadata, StandardCharsets.UTF_8))); + assertThat(props.getProperty("ceTaskId")).isNotEmpty(); + + return props.getProperty("ceTaskId"); } } -- 2.39.5