aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>2024-10-28 11:33:05 +0100
committersonartech <sonartech@sonarsource.com>2024-11-05 20:03:01 +0000
commit61c6e94bf9f7d2826de0bc8e4d5a2a1cadc237f0 (patch)
treebf1a7b3c9a1228860db5b99b1f820350da7c0bae
parent969c7f691192a9f3af6220ef85de973b303ed473 (diff)
downloadsonarqube-61c6e94bf9f7d2826de0bc8e4d5a2a1cadc237f0.tar.gz
sonarqube-61c6e94bf9f7d2826de0bc8e4d5a2a1cadc237f0.zip
SONAR-23299 add fallback condition to live quality gate evaluation
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/metric/StandardToMQRMetrics.java (renamed from server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/StandardToMQRMetrics.java)2
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java4
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java28
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java12
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java17
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFallbackManager.java40
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/metric/StandardToMQRMetricsTest.java (renamed from server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/StandardToMQRMetricsTest.java)4
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java13
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java40
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateEvaluatorImplTest.java56
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFallbackManagerTest.java57
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModeChecker.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java3
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java2
15 files changed, 238 insertions, 44 deletions
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/StandardToMQRMetrics.java b/server/sonar-server-common/src/main/java/org/sonar/server/metric/StandardToMQRMetrics.java
index e50a91cf9cb..a08ec970281 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/StandardToMQRMetrics.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/metric/StandardToMQRMetrics.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.qualitygate.ws;
+package org.sonar.server.metric;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java
index 916e79626d9..4e8e2fd3338 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java
@@ -50,7 +50,7 @@ class ConditionEvaluator {
static EvaluatedCondition evaluate(Condition condition, QualityGateEvaluator.Measures measures) {
Optional<QualityGateEvaluator.Measure> measure = measures.get(condition.getMetricKey());
if (measure.isEmpty()) {
- return new EvaluatedCondition(condition, EvaluationStatus.OK, null);
+ return new EvaluatedCondition(condition, EvaluationStatus.OK, null, true);
}
Optional<Comparable> value = getMeasureValue(condition, measure.get());
@@ -105,7 +105,7 @@ class ConditionEvaluator {
return measure.getValue().isPresent() ? getNumericValue(measure.getType(), measure.getValue().getAsDouble()) : null;
}
- checkArgument(ValueType.LEVEL.equals(measure.getType()), "Condition is not allowed for type %s" , measure.getType());
+ checkArgument(ValueType.LEVEL.equals(measure.getType()), "Condition is not allowed for type %s", measure.getType());
return measure.getStringValue().orElse(null);
}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java
index db183632ec8..930e124096d 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java
@@ -29,20 +29,41 @@ import static java.util.Objects.requireNonNull;
@Immutable
public class EvaluatedCondition {
private final Condition condition;
+ private Condition originalCondition;
private final EvaluationStatus status;
@Nullable
private final String value;
+ private final boolean missingMeasure;
public EvaluatedCondition(Condition condition, EvaluationStatus status, @Nullable String value) {
+ this(condition, status, value, false);
+ }
+
+ public EvaluatedCondition(Condition condition, EvaluationStatus status, @Nullable String value, boolean measureMissing) {
+ this(condition, condition, status, value, measureMissing);
+ }
+
+ public EvaluatedCondition(Condition condition, Condition originalCondition, EvaluationStatus status, @Nullable String value, boolean measureMissing) {
this.condition = requireNonNull(condition, "condition can't be null");
+ this.originalCondition = originalCondition;
this.status = requireNonNull(status, "status can't be null");
this.value = value;
+ this.missingMeasure = measureMissing;
}
public Condition getCondition() {
return condition;
}
+ public Condition getOriginalCondition() {
+ return originalCondition;
+ }
+
+ public EvaluatedCondition setOriginalCondition(Condition originalCondition) {
+ this.originalCondition = originalCondition;
+ return this;
+ }
+
public EvaluationStatus getStatus() {
return status;
}
@@ -74,11 +95,16 @@ public class EvaluatedCondition {
public String toString() {
return "EvaluatedCondition{" +
"condition=" + condition +
+ ", originalCondition=" + originalCondition +
", status=" + status +
", value=" + (value == null ? null : ('\'' + value + '\'')) +
'}';
}
+ public boolean isMissingMeasure() {
+ return missingMeasure;
+ }
+
/**
* Quality gate condition evaluation status.
*/
@@ -86,7 +112,9 @@ public class EvaluatedCondition {
/**
* No measure found or measure had no value. The condition has not been evaluated and therefor ignored in
* the computation of the Quality Gate status.
+ * @deprecated since 10.8, the value is never used in the code
*/
+ @Deprecated(since = "10.8")
NO_VALUE,
/**
* Condition evaluated as OK, error thresholds hasn't been reached.
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java
index 82d702223cf..eb83e0a7d06 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java
@@ -152,14 +152,20 @@ public class EvaluatedQualityGate {
Set<Condition> conditions = qualityGate.getConditions();
Set<Condition> conditionsNotEvaluated = conditions.stream()
- .filter(c -> !evaluatedConditions.containsKey(c))
+ .filter(c -> !evaluatedConditions.containsKey(c) && !hasOriginalCondition(evaluatedConditions.values(), c))
.collect(Collectors.toSet());
checkArgument(conditionsNotEvaluated.isEmpty(), "Evaluation missing for the following conditions: %s", conditionsNotEvaluated);
- Set<Condition> unknownConditions = evaluatedConditions.keySet().stream()
- .filter(c -> !conditions.contains(c))
+ Set<Condition> unknownConditions = evaluatedConditions.entrySet().stream()
+ .filter(c -> !conditions.contains(c.getValue().getCondition()) && !conditions.contains(c.getValue().getOriginalCondition()))
+ .map(Map.Entry::getKey)
.collect(Collectors.toSet());
+
checkArgument(unknownConditions.isEmpty(), "Evaluation provided for unknown conditions: %s", unknownConditions);
}
+
+ private static boolean hasOriginalCondition(Collection<EvaluatedCondition> evaluatedConditions, Condition c) {
+ return evaluatedConditions.stream().anyMatch(ec -> ec.getOriginalCondition().equals(c));
+ }
}
}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java
index ed13f8c86dd..b9239d9419b 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java
@@ -45,6 +45,12 @@ public class QualityGateEvaluatorImpl implements QualityGateEvaluator {
CoreMetrics.NEW_DUPLICATED_LINES_KEY,
CoreMetrics.NEW_BLOCKS_DUPLICATED_KEY);
+ private final QualityGateFallbackManager qualityGateFallbackManager;
+
+ public QualityGateEvaluatorImpl(QualityGateFallbackManager qualityGateFallbackManager) {
+ this.qualityGateFallbackManager = qualityGateFallbackManager;
+ }
+
@Override
public EvaluatedQualityGate evaluate(QualityGate gate, Measures measures, Configuration configuration) {
EvaluatedQualityGate.Builder result = EvaluatedQualityGate.newBuilder()
@@ -57,8 +63,15 @@ public class QualityGateEvaluatorImpl implements QualityGateEvaluator {
String metricKey = condition.getMetricKey();
EvaluatedCondition evaluation = ConditionEvaluator.evaluate(condition, measures);
+ if (evaluation.isMissingMeasure()) {
+ evaluation = qualityGateFallbackManager.getFallbackCondition(evaluation.getCondition())
+ .map(c -> ConditionEvaluator.evaluate(c, measures).setOriginalCondition(condition))
+ .orElse(evaluation);
+ }
+
if (isSmallChangeset && evaluation.getStatus() != EvaluationStatus.OK && METRICS_TO_IGNORE_ON_SMALL_CHANGESETS.contains(metricKey)) {
- result.addEvaluatedCondition(new EvaluatedCondition(evaluation.getCondition(), EvaluationStatus.OK, evaluation.getValue().orElse(null)));
+ result.addEvaluatedCondition(new EvaluatedCondition(evaluation.getCondition(), evaluation.getOriginalCondition(),
+ EvaluationStatus.OK, evaluation.getValue().orElse(null), evaluation.isMissingMeasure()));
result.setIgnoredConditionsOnSmallChangeset(true);
} else {
result.addEvaluatedCondition(evaluation);
@@ -76,6 +89,8 @@ public class QualityGateEvaluatorImpl implements QualityGateEvaluator {
metricKeys.add(CoreMetrics.NEW_LINES_KEY);
for (Condition condition : gate.getConditions()) {
metricKeys.add(condition.getMetricKey());
+ qualityGateFallbackManager.getFallbackCondition(condition)
+ .ifPresent(c -> metricKeys.add(c.getMetricKey()));
}
return metricKeys;
}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFallbackManager.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFallbackManager.java
new file mode 100644
index 00000000000..d81165121c8
--- /dev/null
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFallbackManager.java
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualitygate;
+
+import java.util.Optional;
+import org.sonar.server.metric.StandardToMQRMetrics;
+
+/**
+ * This class is used to manage the fallback of the quality gate conditions, in case one of the measure has not been computed yet.
+ * This is to ensure continuity of the quality gate evaluation in the scenario of introduction of new measures, but the project has not been reanalyzed yet.
+ * The live quality gate evaluation will fallback to an equivalent metric until the project is reanalyzed
+ */
+public class QualityGateFallbackManager {
+
+ public Optional<Condition> getFallbackCondition(Condition condition) {
+ Optional<String> equivalentMetric = StandardToMQRMetrics.getEquivalentMetric(condition.getMetricKey());
+ if (StandardToMQRMetrics.isMQRMetric(condition.getMetricKey()) && equivalentMetric.isPresent()) {
+ return Optional.of(new Condition(equivalentMetric.get(), condition.getOperator(), condition.getErrorThreshold()));
+ } else {
+ return Optional.empty();
+ }
+ }
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/StandardToMQRMetricsTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/metric/StandardToMQRMetricsTest.java
index c779311c589..66d98b9dbbb 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/StandardToMQRMetricsTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/metric/StandardToMQRMetricsTest.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.qualitygate.ws;
+package org.sonar.server.metric;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -25,7 +25,6 @@ import org.sonar.api.measures.CoreMetrics;
import org.sonar.core.metric.SoftwareQualitiesMetrics;
class StandardToMQRMetricsTest {
-
@Test
void isStandardMetric_shouldReturnExpectedResult() {
Assertions.assertThat(StandardToMQRMetrics.isStandardMetric(CoreMetrics.RELIABILITY_RATING_KEY)).isTrue();
@@ -41,5 +40,4 @@ class StandardToMQRMetricsTest {
Assertions.assertThat(StandardToMQRMetrics.getEquivalentMetric(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY)).hasValue(CoreMetrics.RELIABILITY_RATING_KEY);
Assertions.assertThat(StandardToMQRMetrics.getEquivalentMetric(CoreMetrics.RELIABILITY_RATING_KEY)).hasValue(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY);
}
-
}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java
index 33402ef0969..9215eb11886 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java
@@ -64,17 +64,20 @@ public class EvaluatedConditionTest {
@Test
public void override_toString() {
- assertThat(underTest).hasToString("EvaluatedCondition{condition=" +
- "Condition{metricKey='metricKey', operator=GREATER_THAN, errorThreshold='2'}, " +
- "status=ERROR, value='value'}");
+ assertThat(underTest).hasToString("EvaluatedCondition{condition=Condition{metricKey='metricKey', " +
+ "operator=GREATER_THAN, errorThreshold='2'}, " +
+ "originalCondition=Condition{metricKey='metricKey', " +
+ "operator=GREATER_THAN, errorThreshold='2'}, status=ERROR, value='value'}");
}
@Test
public void toString_does_not_quote_null_value() {
EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, ERROR, null);
- assertThat(underTest).hasToString("EvaluatedCondition{condition=" +
- "Condition{metricKey='metricKey', operator=GREATER_THAN, errorThreshold='2'}, " +
+ assertThat(underTest).hasToString("EvaluatedCondition{condition=Condition{metricKey='metricKey', " +
+ "operator=GREATER_THAN, errorThreshold='2'}, " +
+ "originalCondition=Condition{metricKey='metricKey', " +
+ "operator=GREATER_THAN, errorThreshold='2'}, " +
"status=ERROR, value=null}");
}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java
index 2b0ce22d34d..f076e163f53 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java
@@ -24,7 +24,8 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import org.apache.commons.lang3.RandomStringUtils;
-import org.junit.Test;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric.Level;
@@ -34,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.sonar.server.qualitygate.EvaluatedQualityGate.newBuilder;
-public class EvaluatedQualityGateTest {
+class EvaluatedQualityGateTest {
private static final String QUALITY_GATE_ID = "qg_id";
private static final String QUALITY_GATE_NAME = "qg_name";
private static final QualityGate NO_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, emptySet());
@@ -55,7 +56,7 @@ public class EvaluatedQualityGateTest {
private EvaluatedQualityGate.Builder builder = newBuilder();
@Test
- public void build_fails_with_NPE_if_status_not_set() {
+ void build_fails_with_NPE_if_status_not_set() {
builder.setQualityGate(NO_CONDITION_QUALITY_GATE);
assertThatThrownBy(() -> builder.build())
@@ -64,21 +65,21 @@ public class EvaluatedQualityGateTest {
}
@Test
- public void addCondition_fails_with_NPE_if_condition_is_null() {
+ void addCondition_fails_with_NPE_if_condition_is_null() {
assertThatThrownBy(() -> builder.addEvaluatedCondition(null, EvaluatedCondition.EvaluationStatus.ERROR, "a_value"))
.isInstanceOf(NullPointerException.class)
.hasMessage("condition can't be null");
}
@Test
- public void addCondition_fails_with_NPE_if_status_is_null() {
+ void addCondition_fails_with_NPE_if_status_is_null() {
assertThatThrownBy(() -> builder.addEvaluatedCondition(new Condition("metric_key", Condition.Operator.LESS_THAN, "2"), null, "a_value"))
.isInstanceOf(NullPointerException.class)
.hasMessage("status can't be null");
}
@Test
- public void addCondition_accepts_null_value() {
+ void addCondition_accepts_null_value() {
builder.addEvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null);
assertThat(builder.getEvaluatedConditions())
@@ -86,12 +87,12 @@ public class EvaluatedQualityGateTest {
}
@Test
- public void getEvaluatedConditions_returns_empty_with_no_condition_added_to_builder() {
+ void getEvaluatedConditions_returns_empty_with_no_condition_added_to_builder() {
assertThat(builder.getEvaluatedConditions()).isEmpty();
}
@Test
- public void build_fails_with_IAE_if_condition_added_and_no_on_QualityGate() {
+ void build_fails_with_IAE_if_condition_added_and_no_on_QualityGate() {
builder.setQualityGate(NO_CONDITION_QUALITY_GATE)
.setStatus(randomStatus)
.addEvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue);
@@ -102,7 +103,7 @@ public class EvaluatedQualityGateTest {
}
@Test
- public void build_fails_with_IAE_if_condition_is_missing_for_one_defined_in_QualityGate() {
+ void build_fails_with_IAE_if_condition_is_missing_for_one_defined_in_QualityGate() {
builder.setQualityGate(ONE_CONDITION_QUALITY_GATE)
.setStatus(randomStatus);
@@ -112,7 +113,16 @@ public class EvaluatedQualityGateTest {
}
@Test
- public void getEvaluatedConditions_is_sorted() {
+ void build_whenConditionIsAFallback_shouldNotThrowException() {
+ builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(randomStatus)
+ .addEvaluatedCondition(new EvaluatedCondition(CONDITION_2, CONDITION_1, randomEvaluationStatus, randomValue, false));
+
+ Assertions.assertThat(builder.build()
+ .getEvaluatedConditions()).hasSize(1);
+ }
+
+ @Test
+ void getEvaluatedConditions_is_sorted() {
EvaluatedQualityGate underTest = builder
.setQualityGate(ALL_CONDITIONS_QUALITY_GATE)
.setStatus(randomStatus)
@@ -128,7 +138,7 @@ public class EvaluatedQualityGateTest {
}
@Test
- public void verify_getters() {
+ void verify_getters() {
EvaluatedQualityGate underTest = builder
.setQualityGate(ONE_CONDITION_QUALITY_GATE)
.setStatus(randomStatus)
@@ -142,7 +152,7 @@ public class EvaluatedQualityGateTest {
}
@Test
- public void verify_getters_when_no_condition() {
+ void verify_getters_when_no_condition() {
EvaluatedQualityGate underTest = builder
.setQualityGate(NO_CONDITION_QUALITY_GATE)
.setStatus(randomStatus)
@@ -154,7 +164,7 @@ public class EvaluatedQualityGateTest {
}
@Test
- public void verify_getters_when_multiple_conditions() {
+ void verify_getters_when_multiple_conditions() {
QualityGate qualityGate = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2));
EvaluatedQualityGate underTest = builder
.setQualityGate(qualityGate)
@@ -171,7 +181,7 @@ public class EvaluatedQualityGateTest {
}
@Test
- public void equals_is_based_on_all_fields() {
+ void equals_is_based_on_all_fields() {
EvaluatedQualityGate.Builder builder = this.builder
.setQualityGate(ONE_CONDITION_QUALITY_GATE)
.setStatus(Level.ERROR)
@@ -193,7 +203,7 @@ public class EvaluatedQualityGateTest {
}
@Test
- public void hashcode_is_based_on_all_fields() {
+ void hashcode_is_based_on_all_fields() {
EvaluatedQualityGate.Builder builder = this.builder
.setQualityGate(ONE_CONDITION_QUALITY_GATE)
.setStatus(Level.ERROR)
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateEvaluatorImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateEvaluatorImplTest.java
index 31f22433cf3..07af74eed75 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateEvaluatorImplTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateEvaluatorImplTest.java
@@ -25,7 +25,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Configuration;
import org.sonar.api.config.internal.ConfigurationBridge;
@@ -34,25 +34,28 @@ import org.sonar.api.measures.Metric;
import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_LINES_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY;
-public class QualityGateEvaluatorImplTest {
+class QualityGateEvaluatorImplTest {
private final MapSettings settings = new MapSettings();
private final Configuration configuration = new ConfigurationBridge(settings);
- private final QualityGateEvaluator underTest = new QualityGateEvaluatorImpl();
+ QualityGateFallbackManager qualityGateFallbackManager = mock(QualityGateFallbackManager.class);
+ private final QualityGateEvaluator underTest = new QualityGateEvaluatorImpl(qualityGateFallbackManager);
@Test
- public void getMetricKeys_includes_by_default_new_lines() {
+ void getMetricKeys_includes_by_default_new_lines() {
QualityGate gate = mock(QualityGate.class);
assertThat(underTest.getMetricKeys(gate)).containsExactly(NEW_LINES_KEY);
}
@Test
- public void getMetricKeys_includes_metrics_from_qgate() {
+ void getMetricKeys_includes_metrics_from_qgate() {
Set<String> metricKeys = ImmutableSet.of("foo", "bar", "baz");
Set<Condition> conditions = metricKeys.stream().map(key -> {
Condition condition = mock(Condition.class);
@@ -66,7 +69,40 @@ public class QualityGateEvaluatorImplTest {
}
@Test
- public void evaluated_conditions_are_sorted() {
+ void getMetricKeys_shouldIncludeFallbackConditionsMetricKeys() {
+ Set<String> metricKeys = ImmutableSet.of("foo", "bar", "baz");
+ when(qualityGateFallbackManager.getFallbackCondition(any())).thenReturn(Optional.of(new Condition("fallback", Condition.Operator.GREATER_THAN, "0")));
+ Set<Condition> conditions = metricKeys.stream().map(key -> {
+ Condition condition = mock(Condition.class);
+ when(condition.getMetricKey()).thenReturn(key);
+ return condition;
+ }).collect(Collectors.toSet());
+
+ QualityGate gate = mock(QualityGate.class);
+ when(gate.getConditions()).thenReturn(conditions);
+ assertThat(underTest.getMetricKeys(gate)).containsAll(metricKeys);
+ assertThat(underTest.getMetricKeys(gate)).contains("fallback");
+ }
+
+ @Test
+ void evaluate_whenConditionHasNoDataAndHasFallBack_shouldEvaluateFallbackInstead() {
+ when(qualityGateFallbackManager.getFallbackCondition(any())).thenReturn(Optional.of(new Condition("fallback", Condition.Operator.GREATER_THAN, "0")));
+ Condition condition = mock(Condition.class);
+ when(condition.getMetricKey()).thenReturn("foo");
+
+ QualityGate gate = mock(QualityGate.class);
+ when(gate.getConditions()).thenReturn(Set.of(condition));
+ QualityGateEvaluator.Measures measures = mock(QualityGateEvaluator.Measures.class);
+ when(measures.get("foo")).thenReturn(Optional.empty());
+ when(measures.get("fallback")).thenReturn(Optional.of(new FakeMeasure(1)));
+
+ assertThat(underTest.evaluate(gate, measures, configuration).getEvaluatedConditions())
+ .extracting(x -> x.getCondition().getMetricKey(), x -> x.getOriginalCondition().getMetricKey())
+ .containsExactly(tuple("fallback", "foo"));
+ }
+
+ @Test
+ void evaluated_conditions_are_sorted() {
Set<String> metricKeys = ImmutableSet.of("foo", "bar", NEW_MAINTAINABILITY_RATING_KEY);
Set<Condition> conditions = metricKeys.stream().map(key -> {
Condition condition = mock(Condition.class);
@@ -79,11 +115,11 @@ public class QualityGateEvaluatorImplTest {
QualityGateEvaluator.Measures measures = mock(QualityGateEvaluator.Measures.class);
assertThat(underTest.evaluate(gate, measures, configuration).getEvaluatedConditions()).extracting(x -> x.getCondition().getMetricKey())
- .containsExactly(NEW_MAINTAINABILITY_RATING_KEY, "bar", "foo");
+ .containsExactly(NEW_MAINTAINABILITY_RATING_KEY, "bar", "foo");
}
@Test
- public void evaluate_is_OK_for_empty_qgate() {
+ void evaluate_is_OK_for_empty_qgate() {
QualityGate gate = mock(QualityGate.class);
QualityGateEvaluator.Measures measures = mock(QualityGateEvaluator.Measures.class);
EvaluatedQualityGate evaluatedQualityGate = underTest.evaluate(gate, measures, configuration);
@@ -91,7 +127,7 @@ public class QualityGateEvaluatorImplTest {
}
@Test
- public void evaluate_is_ERROR() {
+ void evaluate_is_ERROR() {
Condition condition = new Condition(NEW_MAINTAINABILITY_RATING_KEY, Condition.Operator.GREATER_THAN, "0");
QualityGate gate = mock(QualityGate.class);
@@ -102,7 +138,7 @@ public class QualityGateEvaluatorImplTest {
}
@Test
- public void evaluate_for_small_changes() {
+ void evaluate_for_small_changes() {
Condition condition = new Condition(NEW_DUPLICATED_LINES_KEY, Condition.Operator.GREATER_THAN, "0");
Map<String, QualityGateEvaluator.Measure> notSmallChange = new HashMap<>();
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFallbackManagerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFallbackManagerTest.java
new file mode 100644
index 00000000000..2552761fff3
--- /dev/null
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFallbackManagerTest.java
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualitygate;
+
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+class QualityGateFallbackManagerTest {
+
+ QualityGateFallbackManager underTest = new QualityGateFallbackManager();
+
+ @Test
+ void getFallbackCondition_whenConditionIsSoftwareQualityMetricWithEquivalence_shouldReturnExpectedCondition() {
+ Condition condition = new Condition(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY, Condition.Operator.GREATER_THAN, "3");
+ Optional<Condition> fallbackCondition = underTest.getFallbackCondition(condition);
+ assertThat(fallbackCondition).isPresent()
+ .get()
+ .extracting(Condition::getMetricKey, Condition::getOperator, Condition::getErrorThreshold)
+ .containsExactly(CoreMetrics.SECURITY_RATING_KEY, Condition.Operator.GREATER_THAN, "3");
+ }
+
+ @Test
+ void getFallbackCondition_whenConditionIsStandardMetricWithEquivalence_shouldNotReturnCondition() {
+ Condition condition = new Condition(CoreMetrics.SECURITY_RATING_KEY, Condition.Operator.GREATER_THAN, "3");
+ Optional<Condition> fallbackCondition = underTest.getFallbackCondition(condition);
+ assertThat(fallbackCondition).isEmpty();
+ }
+
+ @Test
+ void getFallbackCondition_whenConditionIsAnyMetricWithoutEquivalence_shouldNotReturnCondition() {
+ Condition condition = new Condition(CoreMetrics.LINES_KEY, Condition.Operator.GREATER_THAN, "3");
+ Optional<Condition> fallbackCondition = underTest.getFallbackCondition(condition);
+ assertThat(fallbackCondition).isEmpty();
+ }
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java
index 41490914c5b..42c23ed78ed 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java
@@ -39,7 +39,7 @@ import org.sonar.db.qualitygate.QualityGateConditionDto;
import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.measure.Rating;
-import org.sonar.server.qualitygate.ws.StandardToMQRMetrics;
+import org.sonar.server.metric.StandardToMQRMetrics;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.Double.parseDouble;
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModeChecker.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModeChecker.java
index e664db04910..413aabad2db 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModeChecker.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModeChecker.java
@@ -23,7 +23,7 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.sonar.db.metric.MetricDto;
-import org.sonar.server.qualitygate.ws.StandardToMQRMetrics;
+import org.sonar.server.metric.StandardToMQRMetrics;
public class QualityGateModeChecker {
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java
index 53b6a93cc38..24c4eb83a21 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java
@@ -30,6 +30,7 @@ public class QualityGateModule extends Module {
QualityGateModeChecker.class,
QualityGateConditionsUpdater.class,
QualityGateFinder.class,
- QualityGateEvaluatorImpl.class);
+ QualityGateEvaluatorImpl.class,
+ QualityGateFallbackManager.class);
}
}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java
index 1c785a45dd1..4579de0b936 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java
@@ -29,6 +29,6 @@ public class QualityGateModuleTest {
public void verify_count_of_added_components() {
ListContainer container = new ListContainer();
new QualityGateModule().configure(container);
- assertThat(container.getAddedObjects()).hasSize(6);
+ assertThat(container.getAddedObjects()).hasSize(7);
}
}