]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10485 Provide a way to always enforce coverage and duplication Quality Gate...
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 1 Apr 2021 20:57:52 +0000 (15:57 -0500)
committersonartech <sonartech@sonarsource.com>
Thu, 8 Apr 2021 20:03:55 +0000 (20:03 +0000)
17 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/SmallChangesetQualityGateSpecialCase.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/QualityGateMeasuresStepTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SmallChangesetQualityGateSpecialCaseTest.java
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluator.java
server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionEvaluatorTest.java
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/FakeMeasure.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateEvaluatorImplTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveMeasureComputerImpl.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputer.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/LiveMeasureComputerImplTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/live/LiveQualityGateComputerImplTest.java
sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java
sonar-core/src/main/resources/org/sonar/l10n/core.properties
sonar-core/src/test/java/org/sonar/core/config/CorePropertyDefinitionsTest.java
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java

index cdd014ddf481672b52fbf69e5e4f00a491bc72f7..bd001b071723f18b3c7eec56226314c0f0a88c36 100644 (file)
  */
 package org.sonar.ce.task.projectanalysis.step;
 
-import java.util.Collection;
 import javax.annotation.Nullable;
+import org.sonar.api.CoreProperties;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository;
 import org.sonar.ce.task.projectanalysis.measure.Measure;
 import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
 import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
 import org.sonar.ce.task.projectanalysis.qualitygate.EvaluationResult;
 import org.sonar.ce.task.projectanalysis.step.QualityGateMeasuresStep.MetricEvaluationResult;
 
-import static java.util.Arrays.asList;
+import static org.sonar.server.qualitygate.QualityGateEvaluatorImpl.MAXIMUM_NEW_LINES_FOR_SMALL_CHANGESETS;
+import static org.sonar.server.qualitygate.QualityGateEvaluatorImpl.METRICS_TO_IGNORE_ON_SMALL_CHANGESETS;
 
 public class SmallChangesetQualityGateSpecialCase {
-
-  /**
-   * Some metrics will be ignored on very small change sets.
-   */
-  private static final Collection<String> METRICS_TO_IGNORE_ON_SMALL_CHANGESETS = asList(
-    CoreMetrics.NEW_COVERAGE_KEY,
-    CoreMetrics.NEW_LINE_COVERAGE_KEY,
-    CoreMetrics.NEW_BRANCH_COVERAGE_KEY,
-    CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY,
-    CoreMetrics.NEW_DUPLICATED_LINES_KEY,
-    CoreMetrics.NEW_BLOCKS_DUPLICATED_KEY);
-  private static final int MAXIMUM_NEW_LINES_FOR_SMALL_CHANGESETS = 19;
-
   private final MeasureRepository measureRepository;
   private final MetricRepository metricRepository;
+  private final ConfigurationRepository config;
 
-  public SmallChangesetQualityGateSpecialCase(MeasureRepository measureRepository, MetricRepository metricRepository) {
+  public SmallChangesetQualityGateSpecialCase(MeasureRepository measureRepository, MetricRepository metricRepository, ConfigurationRepository config) {
     this.measureRepository = measureRepository;
     this.metricRepository = metricRepository;
+    this.config = config;
   }
 
   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())
+      && config.getConfiguration().getBoolean(CoreProperties.QUALITY_GATE_IGNORE_SMALL_CHANGES).orElse(true)
       && isSmallChangeset(project);
   }
 
@@ -67,7 +59,7 @@ public class SmallChangesetQualityGateSpecialCase {
 
   private boolean isSmallChangeset(Component project) {
     return measureRepository.getRawMeasure(project, metricRepository.getByKey(CoreMetrics.NEW_LINES_KEY))
-      .map(newLines -> newLines.hasVariation() && newLines.getVariation() <= MAXIMUM_NEW_LINES_FOR_SMALL_CHANGESETS)
+      .map(newLines -> newLines.hasVariation() && newLines.getVariation() < MAXIMUM_NEW_LINES_FOR_SMALL_CHANGESETS)
       .orElse(false);
   }
 }
index 559264afc618e0d3601b827155c5c7ef5a7cd2f1..db36984463356f9b612b764fe7e30da0d9cfe77d 100644 (file)
@@ -31,9 +31,12 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
+import org.sonar.api.config.internal.ConfigurationBridge;
+import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.ce.task.projectanalysis.component.Component;
 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TestSettingsRepository;
 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
 import org.sonar.ce.task.projectanalysis.measure.Measure;
 import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
@@ -86,10 +89,11 @@ public class QualityGateMeasuresStepTest {
   public MetricRepositoryRule metricRepository = new MetricRepositoryRule();
   @Rule
   public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
-
   private EvaluationResultTextConverter resultTextConverter = mock(EvaluationResultTextConverter.class);
+  private MapSettings mapSettings = new MapSettings();
+  private TestSettingsRepository settings = new TestSettingsRepository(new ConfigurationBridge(mapSettings));
   private QualityGateMeasuresStep underTest = new QualityGateMeasuresStep(treeRootHolder, qualityGateHolder, qualityGateStatusHolder, measureRepository, metricRepository,
-    resultTextConverter, new SmallChangesetQualityGateSpecialCase(measureRepository, metricRepository));
+    resultTextConverter, new SmallChangesetQualityGateSpecialCase(measureRepository, metricRepository, settings));
 
   @Before
   public void setUp() {
index 09da1b863daef39b3c584b78355a31aada140a6d..c18ab0b5a19f19c024d791a89850cd74c1d2cf67 100644 (file)
@@ -21,8 +21,12 @@ package org.sonar.ce.task.projectanalysis.step;
 
 import org.junit.Rule;
 import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.internal.ConfigurationBridge;
+import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.TestSettingsRepository;
 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
 import org.sonar.ce.task.projectanalysis.measure.Measure;
 import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
@@ -52,10 +56,14 @@ public class SmallChangesetQualityGateSpecialCaseTest {
     .add(CoreMetrics.NEW_BUGS);
   @Rule
   public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
-  private final SmallChangesetQualityGateSpecialCase underTest = new SmallChangesetQualityGateSpecialCase(measureRepository, metricRepository);
+
+  private final MapSettings mapSettings = new MapSettings();
+  private final TestSettingsRepository settings = new TestSettingsRepository(new ConfigurationBridge(mapSettings));
+  private final SmallChangesetQualityGateSpecialCase underTest = new SmallChangesetQualityGateSpecialCase(measureRepository, metricRepository, settings);
 
   @Test
   public void ignore_errors_about_new_coverage_for_small_changesets() {
+    mapSettings.setProperty(CoreProperties.QUALITY_GATE_IGNORE_SMALL_CHANGES, true);
     QualityGateMeasuresStep.MetricEvaluationResult metricEvaluationResult = generateEvaluationResult(NEW_COVERAGE_KEY, ERROR);
     Component project = generateNewRootProject();
     measureRepository.addRawMeasure(PROJECT_REF, CoreMetrics.NEW_LINES_KEY, newMeasureBuilder().setVariation(19).create(1000));
@@ -65,6 +73,18 @@ public class SmallChangesetQualityGateSpecialCaseTest {
     assertThat(result).isTrue();
   }
 
+  @Test
+  public void dont_ignore_errors_about_new_coverage_for_small_changesets_if_disabled() {
+    mapSettings.setProperty(CoreProperties.QUALITY_GATE_IGNORE_SMALL_CHANGES, false);
+    QualityGateMeasuresStep.MetricEvaluationResult metricEvaluationResult = generateEvaluationResult(NEW_COVERAGE_KEY, ERROR);
+    Component project = generateNewRootProject();
+    measureRepository.addRawMeasure(PROJECT_REF, CoreMetrics.NEW_LINES_KEY, newMeasureBuilder().setVariation(19).create(1000));
+
+    boolean result = underTest.appliesTo(project, metricEvaluationResult);
+
+    assertThat(result).isFalse();
+  }
+
   @Test
   public void should_not_change_for_bigger_changesets() {
     QualityGateMeasuresStep.MetricEvaluationResult metricEvaluationResult = generateEvaluationResult(NEW_COVERAGE_KEY, ERROR);
index 9c691051033991b55893ab4223760f9a99b52f8b..64adffd21081021360c913b1ba363dc9deb5cc8d 100644 (file)
@@ -23,6 +23,7 @@ import java.util.Optional;
 import java.util.OptionalDouble;
 import java.util.Set;
 import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.config.Configuration;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.server.ServerSide;
 
@@ -34,7 +35,7 @@ public interface QualityGateEvaluator {
    * @param measures must provide the measures related to the metrics
    *                 defined by {@link #getMetricKeys(QualityGate)}
    */
-  EvaluatedQualityGate evaluate(QualityGate gate, Measures measures);
+  EvaluatedQualityGate evaluate(QualityGate gate, Measures measures, Configuration configuration);
 
   /**
    * Keys of the metrics involved in the computation of gate status.
index 043c50723ea5e0df9d01e734bd13427b1ed66615..e1bc6c93992e8f4bf6f0cd5ed07be8dbd6c85da8 100644 (file)
@@ -23,6 +23,8 @@ import com.google.common.collect.ImmutableSet;
 import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Configuration;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Metric.Level;
 import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus;
@@ -31,11 +33,11 @@ import static org.sonar.core.util.stream.MoreCollectors.toEnumSet;
 
 public class QualityGateEvaluatorImpl implements QualityGateEvaluator {
 
-  private static final int MAXIMUM_NEW_LINES_FOR_SMALL_CHANGESETS = 20;
+  public static final int MAXIMUM_NEW_LINES_FOR_SMALL_CHANGESETS = 20;
   /**
    * Some metrics will be ignored on very small change sets.
    */
-  private static final Set<String> METRICS_TO_IGNORE_ON_SMALL_CHANGESETS = ImmutableSet.of(
+  public static final Set<String> METRICS_TO_IGNORE_ON_SMALL_CHANGESETS = ImmutableSet.of(
     CoreMetrics.NEW_COVERAGE_KEY,
     CoreMetrics.NEW_LINE_COVERAGE_KEY,
     CoreMetrics.NEW_BRANCH_COVERAGE_KEY,
@@ -44,11 +46,13 @@ public class QualityGateEvaluatorImpl implements QualityGateEvaluator {
     CoreMetrics.NEW_BLOCKS_DUPLICATED_KEY);
 
   @Override
-  public EvaluatedQualityGate evaluate(QualityGate gate, Measures measures) {
+  public EvaluatedQualityGate evaluate(QualityGate gate, Measures measures, Configuration configuration) {
     EvaluatedQualityGate.Builder result = EvaluatedQualityGate.newBuilder()
       .setQualityGate(gate);
 
-    boolean isSmallChangeset = isSmallChangeset(measures);
+    boolean ignoreSmallChanges = configuration.getBoolean(CoreProperties.QUALITY_GATE_IGNORE_SMALL_CHANGES).orElse(true);
+    boolean isSmallChangeset = ignoreSmallChanges && isSmallChangeset(measures);
+
     gate.getConditions().forEach(condition -> {
       String metricKey = condition.getMetricKey();
       EvaluatedCondition evaluation = ConditionEvaluator.evaluate(condition, measures);
index 7309ad688a2fcdf11567a2f3d8c2d515a60866ec..d377e9d658c286d416f4d3ea3167331b2aff3fdc 100644 (file)
@@ -23,7 +23,6 @@ import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import java.util.Optional;
-import java.util.OptionalDouble;
 import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
@@ -36,7 +35,7 @@ import static org.sonar.api.measures.Metric.ValueType.BOOL;
 import static org.sonar.api.measures.Metric.ValueType.DATA;
 import static org.sonar.api.measures.Metric.ValueType.DISTRIB;
 import static org.sonar.api.measures.Metric.ValueType.STRING;
-import static org.sonar.server.qualitygate.ConditionEvaluatorTest.FakeMeasure.newFakeMeasureOnLeak;
+import static org.sonar.server.qualitygate.FakeMeasure.newMeasureOnLeak;
 
 @RunWith(DataProviderRunner.class)
 public class ConditionEvaluatorTest {
@@ -63,9 +62,9 @@ public class ConditionEvaluatorTest {
     test(new FakeMeasure(10), Condition.Operator.GREATER_THAN, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
     test(new FakeMeasure(10), Condition.Operator.GREATER_THAN, "11", EvaluatedCondition.EvaluationStatus.OK, "10");
 
-    testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.GREATER_THAN, "9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
-    testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.GREATER_THAN, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
-    testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.GREATER_THAN, "11", EvaluatedCondition.EvaluationStatus.OK, "10");
+    testOnLeak(newMeasureOnLeak(10), Condition.Operator.GREATER_THAN, "9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+    testOnLeak(newMeasureOnLeak(10), Condition.Operator.GREATER_THAN, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
+    testOnLeak(newMeasureOnLeak(10), Condition.Operator.GREATER_THAN, "11", EvaluatedCondition.EvaluationStatus.OK, "10");
   }
 
   @Test
@@ -74,9 +73,9 @@ public class ConditionEvaluatorTest {
     test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
     test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "11", EvaluatedCondition.EvaluationStatus.ERROR, "10");
 
-    testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.LESS_THAN, "9", EvaluatedCondition.EvaluationStatus.OK, "10");
-    testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.LESS_THAN, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
-    testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.LESS_THAN, "11", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+    testOnLeak(newMeasureOnLeak(10), Condition.Operator.LESS_THAN, "9", EvaluatedCondition.EvaluationStatus.OK, "10");
+    testOnLeak(newMeasureOnLeak(10), Condition.Operator.LESS_THAN, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
+    testOnLeak(newMeasureOnLeak(10), Condition.Operator.LESS_THAN, "11", EvaluatedCondition.EvaluationStatus.ERROR, "10");
   }
 
   @Test
@@ -153,56 +152,4 @@ public class ConditionEvaluatorTest {
       return Optional.ofNullable(measure);
     }
   }
-
-  static class FakeMeasure implements QualityGateEvaluator.Measure {
-    private Double leakValue;
-    private Double value;
-    private Metric.ValueType valueType;
-
-    private FakeMeasure() {
-
-    }
-
-    FakeMeasure(Metric.ValueType valueType) {
-      this.valueType = valueType;
-    }
-
-    FakeMeasure(@Nullable Double value) {
-      this.value = value;
-      this.valueType = Metric.ValueType.FLOAT;
-    }
-
-    FakeMeasure(@Nullable Integer value) {
-      this.value = value == null ? null : value.doubleValue();
-      this.valueType = Metric.ValueType.INT;
-    }
-
-    static FakeMeasure newFakeMeasureOnLeak(@Nullable Integer value) {
-      FakeMeasure that = new FakeMeasure();
-      that.leakValue = value == null ? null : value.doubleValue();
-      that.valueType = Metric.ValueType.INT;
-      return that;
-    }
-
-    @Override
-    public Metric.ValueType getType() {
-      return valueType;
-    }
-
-    @Override
-    public OptionalDouble getValue() {
-      return value == null ? OptionalDouble.empty() : OptionalDouble.of(value);
-    }
-
-    @Override
-    public Optional<String> getStringValue() {
-      return Optional.empty();
-    }
-
-    @Override
-    public OptionalDouble getNewMetricValue() {
-      return leakValue == null ? OptionalDouble.empty() : OptionalDouble.of(leakValue);
-    }
-  }
-
 }
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/FakeMeasure.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/FakeMeasure.java
new file mode 100644 (file)
index 0000000..27ceff3
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 java.util.OptionalDouble;
+import javax.annotation.Nullable;
+import org.sonar.api.measures.Metric;
+
+public class FakeMeasure  implements QualityGateEvaluator.Measure {
+  private Double leakValue;
+  private Double value;
+  private Metric.ValueType valueType;
+
+  private FakeMeasure() {
+    // nothing to do
+  }
+
+  public FakeMeasure(Metric.ValueType valueType) {
+    this.valueType = valueType;
+  }
+
+  public FakeMeasure(@Nullable Double value) {
+    this.value = value;
+    this.valueType = Metric.ValueType.FLOAT;
+  }
+
+  public FakeMeasure(@Nullable Integer value) {
+    this.value = value == null ? null : value.doubleValue();
+    this.valueType = Metric.ValueType.INT;
+  }
+
+  public static FakeMeasure newMeasureOnLeak(@Nullable Integer value) {
+    FakeMeasure measure = new FakeMeasure();
+    measure.leakValue = value == null ? null : value.doubleValue();
+    measure.valueType = Metric.ValueType.INT;
+    return measure;
+  }
+
+  @Override
+  public Metric.ValueType getType() {
+    return valueType;
+  }
+
+  @Override
+  public OptionalDouble getValue() {
+    return value == null ? OptionalDouble.empty() : OptionalDouble.of(value);
+  }
+
+  @Override
+  public Optional<String> getStringValue() {
+    return Optional.empty();
+  }
+
+  @Override
+  public OptionalDouble getNewMetricValue() {
+    return leakValue == null ? OptionalDouble.empty() : OptionalDouble.of(leakValue);
+  }
+}
index 5b8d2dc9e542efba85805c950e26eb2393235ddc..849fcb103c20b86f217991790da673664f324a72 100644 (file)
 package org.sonar.server.qualitygate;
 
 import com.google.common.collect.ImmutableSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.junit.Test;
-import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.ConfigurationBridge;
+import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.measures.Metric;
 
+import static java.util.Collections.singleton;
 import static org.assertj.core.api.Assertions.assertThat;
 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;
+import static org.sonar.server.qualitygate.FakeMeasure.newMeasureOnLeak;
 
 public class QualityGateEvaluatorImplTest {
-
+  private final MapSettings settings = new MapSettings();
+  private final Configuration configuration = new ConfigurationBridge(settings);
   private final QualityGateEvaluator underTest = new QualityGateEvaluatorImpl();
 
   @Test
@@ -57,7 +68,7 @@ public class QualityGateEvaluatorImplTest {
 
   @Test
   public void evaluated_conditions_are_sorted() {
-    Set<String> metricKeys = ImmutableSet.of("foo", "bar", CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY);
+    Set<String> metricKeys = ImmutableSet.of("foo", "bar", NEW_MAINTAINABILITY_RATING_KEY);
     Set<Condition> conditions = metricKeys.stream().map(key -> {
       Condition condition = mock(Condition.class);
       when(condition.getMetricKey()).thenReturn(key);
@@ -68,15 +79,51 @@ public class QualityGateEvaluatorImplTest {
     when(gate.getConditions()).thenReturn(conditions);
     QualityGateEvaluator.Measures measures = mock(QualityGateEvaluator.Measures.class);
 
-    assertThat(underTest.evaluate(gate, measures).getEvaluatedConditions()).extracting(x -> x.getCondition().getMetricKey())
-    .containsExactly(CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, "bar", "foo");
+    assertThat(underTest.evaluate(gate, measures, configuration).getEvaluatedConditions()).extracting(x -> x.getCondition().getMetricKey())
+    .containsExactly(NEW_MAINTAINABILITY_RATING_KEY, "bar", "foo");
   }
 
   @Test
   public 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);
+    EvaluatedQualityGate evaluatedQualityGate = underTest.evaluate(gate, measures, configuration);
     assertThat(evaluatedQualityGate.getStatus()).isEqualTo(Metric.Level.OK);
   }
+
+  @Test
+  public void evaluate_is_ERROR() {
+    Condition condition = new Condition(NEW_MAINTAINABILITY_RATING_KEY, Condition.Operator.GREATER_THAN, "0");
+
+    QualityGate gate = mock(QualityGate.class);
+    when(gate.getConditions()).thenReturn(singleton(condition));
+    QualityGateEvaluator.Measures measures = key -> Optional.of(newMeasureOnLeak(1));
+
+    assertThat(underTest.evaluate(gate, measures, configuration).getStatus()).isEqualTo(Metric.Level.ERROR);
+  }
+
+  @Test
+  public 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<>();
+    notSmallChange.put(NEW_DUPLICATED_LINES_KEY, newMeasureOnLeak(1));
+    notSmallChange.put(NEW_LINES_KEY, newMeasureOnLeak(1000));
+
+    Map<String, QualityGateEvaluator.Measure> smallChange = new HashMap<>();
+    smallChange.put(NEW_DUPLICATED_LINES_KEY, newMeasureOnLeak(1));
+    smallChange.put(NEW_LINES_KEY, newMeasureOnLeak(10));
+
+    QualityGate gate = mock(QualityGate.class);
+    when(gate.getConditions()).thenReturn(singleton(condition));
+    QualityGateEvaluator.Measures notSmallChangeMeasures = key -> Optional.ofNullable(notSmallChange.get(key));
+    QualityGateEvaluator.Measures smallChangeMeasures = key -> Optional.ofNullable(smallChange.get(key));
+
+    settings.setProperty(CoreProperties.QUALITY_GATE_IGNORE_SMALL_CHANGES, true);
+    assertThat(underTest.evaluate(gate, notSmallChangeMeasures, configuration).getStatus()).isEqualTo(Metric.Level.ERROR);
+    assertThat(underTest.evaluate(gate, smallChangeMeasures, configuration).getStatus()).isEqualTo(Metric.Level.OK);
+
+    settings.setProperty(CoreProperties.QUALITY_GATE_IGNORE_SMALL_CHANGES, false);
+    assertThat(underTest.evaluate(gate, smallChangeMeasures, configuration).getStatus()).isEqualTo(Metric.Level.ERROR);
+  }
 }
index 7ce30889a98f4acf545057bb87caaae7c7a8a342..b575ec0c436281b4ad8e14b3ace8200b0628f005 100644 (file)
@@ -134,7 +134,7 @@ public class LiveMeasureComputerImpl implements LiveMeasureComputer {
       }
     });
 
-    EvaluatedQualityGate evaluatedQualityGate = qGateComputer.refreshGateStatus(branchComponent, qualityGate, matrix);
+    EvaluatedQualityGate evaluatedQualityGate = qGateComputer.refreshGateStatus(branchComponent, qualityGate, matrix, config);
 
     // persist the measures that have been created or updated
     matrix.getChanged().sorted(LiveMeasureComparator.INSTANCE)
index 439e360059748332cb64bbccf57fed8ece127b73..dfe41e26c852b1cf55cc9fc91f93d5eadc70bd95 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.measure.live;
 
 import java.util.Set;
+import org.sonar.api.config.Configuration;
 import org.sonar.api.server.ServerSide;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.BranchDto;
@@ -33,7 +34,7 @@ public interface LiveQualityGateComputer {
 
   QualityGate loadQualityGate(DbSession dbSession, ProjectDto project, BranchDto branch);
 
-  EvaluatedQualityGate refreshGateStatus(ComponentDto project, QualityGate gate, MeasureMatrix measureMatrix);
+  EvaluatedQualityGate refreshGateStatus(ComponentDto project, QualityGate gate, MeasureMatrix measureMatrix, Configuration configuration);
 
   Set<String> getMetricsRelatedTo(QualityGate gate);
 
index 6c3dd8d828c69424704ccbc21de08abb9c366513..b71cb42db60909699d3f2d125e4945b95003b9fe 100644 (file)
@@ -26,6 +26,7 @@ import java.util.Optional;
 import java.util.OptionalDouble;
 import java.util.Set;
 import java.util.stream.Stream;
+import org.sonar.api.config.Configuration;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Metric;
 import org.sonar.db.DbClient;
@@ -84,7 +85,7 @@ public class LiveQualityGateComputerImpl implements LiveQualityGateComputer {
   }
 
   @Override
-  public EvaluatedQualityGate refreshGateStatus(ComponentDto project, QualityGate gate, MeasureMatrix measureMatrix) {
+  public EvaluatedQualityGate refreshGateStatus(ComponentDto project, QualityGate gate, MeasureMatrix measureMatrix, Configuration configuration) {
     QualityGateEvaluator.Measures measures = metricKey -> {
       Optional<LiveMeasureDto> liveMeasureDto = measureMatrix.getMeasure(project, metricKey);
       if (!liveMeasureDto.isPresent()) {
@@ -94,7 +95,7 @@ public class LiveQualityGateComputerImpl implements LiveQualityGateComputer {
       return Optional.of(new LiveMeasure(liveMeasureDto.get(), metric));
     };
 
-    EvaluatedQualityGate evaluatedGate = evaluator.evaluate(gate, measures);
+    EvaluatedQualityGate evaluatedGate = evaluator.evaluate(gate, measures, configuration);
 
     measureMatrix.setValue(project, CoreMetrics.ALERT_STATUS_KEY, evaluatedGate.getStatus().name());
     measureMatrix.setValue(project, CoreMetrics.QUALITY_GATE_DETAILS_KEY, QualityGateConverter.toJson(evaluatedGate));
index 6911e926691387e6bbdebb32cbe5126b3c2904e9..4e027f4b95221127108e9e009b5b6cf15f3f1967 100644 (file)
@@ -34,6 +34,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
+import org.sonar.api.config.Configuration;
 import org.sonar.api.config.PropertyDefinitions;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.measures.CoreMetrics;
@@ -369,7 +370,7 @@ public class LiveMeasureComputerImplTest {
       .containsExactly(Optional.of(newQualityGate));
     verify(qGateComputer).loadQualityGate(any(DbSession.class), argThat(p -> p.getUuid().equals(projectDto.getUuid())), eq(branch));
     verify(qGateComputer).getMetricsRelatedTo(qualityGate);
-    verify(qGateComputer).refreshGateStatus(eq(project), same(qualityGate), any(MeasureMatrix.class));
+    verify(qGateComputer).refreshGateStatus(eq(project), same(qualityGate), any(MeasureMatrix.class), any(Configuration.class));
   }
 
   @Test
@@ -395,7 +396,7 @@ public class LiveMeasureComputerImplTest {
     when(qGateComputer.loadQualityGate(any(DbSession.class), any(ProjectDto.class), any(BranchDto.class)))
       .thenReturn(qualityGate);
     when(qGateComputer.getMetricsRelatedTo(qualityGate)).thenReturn(singleton(CoreMetrics.ALERT_STATUS_KEY));
-    when(qGateComputer.refreshGateStatus(eq(project), same(qualityGate), any(MeasureMatrix.class)))
+    when(qGateComputer.refreshGateStatus(eq(project), same(qualityGate), any(MeasureMatrix.class), any(Configuration.class)))
       .thenReturn(newQualityGate);
     MapSettings settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, CorePropertyDefinitions.all()));
     ProjectConfigurationLoader configurationLoader = new TestProjectConfigurationLoader(settings.asConfig());
index 59c0a71b6040fd55178ca9ccc401ee9580213c9a..a755eb31e7547730b4dcd709945cae4f5a98c294 100644 (file)
@@ -26,6 +26,9 @@ import java.util.stream.Collectors;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.ConfigurationBridge;
+import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.Metric;
 import org.sonar.db.DbTester;
@@ -61,6 +64,8 @@ public class LiveQualityGateComputerImplTest {
   @Rule
   public DbTester db = DbTester.create();
 
+  private final MapSettings settings = new MapSettings();
+  private final Configuration configuration = new ConfigurationBridge(settings);
   private final TestQualityGateEvaluator qualityGateEvaluator = new TestQualityGateEvaluator();
   private final LiveQualityGateComputerImpl underTest = new LiveQualityGateComputerImpl(db.getDbClient(), new QualityGateFinder(db.getDbClient()), qualityGateEvaluator);
 
@@ -124,7 +129,7 @@ public class LiveQualityGateComputerImplTest {
     QualityGate gate = new QualityGate("1", "foo", ImmutableSet.of(condition));
     MeasureMatrix matrix = new MeasureMatrix(singleton(project), asList(conditionMetric, statusMetric, detailsMetric), emptyList());
 
-    EvaluatedQualityGate result = underTest.refreshGateStatus(project, gate, matrix);
+    EvaluatedQualityGate result = underTest.refreshGateStatus(project, gate, matrix, configuration);
 
     QualityGateEvaluator.Measures measures = qualityGateEvaluator.getCalledMeasures();
     assertThat(measures.get(conditionMetric.getKey())).isEmpty();
@@ -155,7 +160,7 @@ public class LiveQualityGateComputerImplTest {
     MeasureMatrix matrix = new MeasureMatrix(singleton(project), asList(statusMetric, detailsMetric, numericMetric, numericNewMetric, stringMetric),
       asList(numericMeasure, numericNewMeasure, stringMeasure));
 
-    underTest.refreshGateStatus(project, gate, matrix);
+    underTest.refreshGateStatus(project, gate, matrix, configuration);
 
     QualityGateEvaluator.Measures measures = qualityGateEvaluator.getCalledMeasures();
 
@@ -179,7 +184,7 @@ public class LiveQualityGateComputerImplTest {
     private Measures measures;
 
     @Override
-    public EvaluatedQualityGate evaluate(QualityGate gate, Measures measures) {
+    public EvaluatedQualityGate evaluate(QualityGate gate, Measures measures, Configuration configuration) {
       checkState(this.measures == null);
       this.measures = measures;
       EvaluatedQualityGate.Builder builder = EvaluatedQualityGate.newBuilder().setQualityGate(gate).setStatus(Metric.Level.OK);
index 8fb44a8b270b21280d74adef6cd2d6b04a9d87c1..77e216a3fd01098673b3fa7b4d0ec9574e7cfc14 100644 (file)
@@ -159,6 +159,17 @@ public class CorePropertyDefinitions {
         .type(PropertyType.USER_LOGIN)
         .build(),
 
+      // QUALITY GATE
+      PropertyDefinition.builder(CoreProperties.QUALITY_GATE_IGNORE_SMALL_CHANGES)
+        .name("Ignore duplication and coverage on small changes")
+        .description("Quality Gate conditions about duplications in new code and coverage on new code are ignored until the number of new lines is at least 20.")
+        .category(CoreProperties.CATEGORY_GENERAL)
+        .subCategory(CoreProperties.SUBCATEGORY_QUALITY_GATE)
+        .onQualifiers(Qualifiers.PROJECT)
+        .type(BOOLEAN)
+        .defaultValue(Boolean.toString(true))
+        .build(),
+
       // CPD
       PropertyDefinition.builder(CoreProperties.CPD_CROSS_PROJECT)
         .defaultValue(Boolean.toString(false))
index 26ae1fdc0af57e0e098dced1f2731c91c65a3317..82d61c9092fbd46ba231f6313bd05747e0c440e2 100644 (file)
@@ -1195,6 +1195,7 @@ property.category.general.localization=Localization
 property.category.general.databaseCleaner=Database Cleaner
 property.category.general.looknfeel=Look & Feel
 property.category.general.issues=Issues
+property.category.general.qualityGate=Quality Gate
 property.category.general.subProjects=Sub-projects
 property.category.almintegration=ALM Integrations
 property.category.almintegration.github=GitHub Authentication
@@ -2891,7 +2892,7 @@ overview.quality_gate_code_clean=Your code is clean!
 overview.quality_gate_all_conditions_passed=All conditions passed.
 overview.you_should_define_quality_gate=You should define a quality gate on this project.
 overview.quality_gate.ignored_conditions=Some Quality Gate conditions on New Code were ignored because of the small number of New Lines
-overview.quality_gate.ignored_conditions.tooltip=At the start of a new code period, if very few lines have been added or modified, it might be difficult to reach the desired level of code coverage or duplications. To prevent Quality Gate failure when there's little that can be done about it, Quality Gate conditions about duplications in new code and coverage on new code are ignored until the number of new lines is at least 20.
+overview.quality_gate.ignored_conditions.tooltip=At the start of a new code period, if very few lines have been added or modified, it might be difficult to reach the desired level of code coverage or duplications. To prevent Quality Gate failure when there's little that can be done about it, Quality Gate conditions about duplications in new code and coverage on new code are ignored until the number of new lines is at least 20. An administrator can disable this in the general settings.
 overview.quality_gate.conditions_on_new_code=Only conditions on new code that are defined in the Quality Gate are checked. See the {link} associated to the project for details.
 overview.quality_profiles=Quality Profiles used
 overview.new_code_period_x=New Code: {0}
index b8eaf30a2bcfa8939a97e9d1fb14b86d5dd337df..0079520e18f9ec8642243146252ac29df3d2159e 100644 (file)
@@ -30,7 +30,7 @@ public class CorePropertyDefinitionsTest {
   @Test
   public void all() {
     List<PropertyDefinition> defs = CorePropertyDefinitions.all();
-    assertThat(defs).hasSize(55);
+    assertThat(defs).hasSize(56);
   }
 
   @Test
index 70a24ca47de39a99f29cf712f89300860424b44d..08dfa2c6f78fd953edd3eb948c2b473c135ba73e 100644 (file)
@@ -91,6 +91,11 @@ public interface CoreProperties {
    */
   String SUBCATEGORY_ISSUES = "issues";
 
+  /**
+   * @since 8.9
+   */
+  String SUBCATEGORY_QUALITY_GATE = "qualityGate";
+
   /**
    * @since 4.0
    */
@@ -403,4 +408,9 @@ public interface CoreProperties {
    * @since 7.6
    */
   String MODULE_LEVEL_ARCHIVED_SETTINGS = "sonar.subproject.settings.archived";
+
+  /**
+   * since 8.9
+   */
+  String QUALITY_GATE_IGNORE_SMALL_CHANGES = "sonar.qualitygate.ignoreSmallChanges";
 }