]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9871 add ShortLivingBranchQualityGate
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 28 Sep 2017 15:15:48 +0000 (17:15 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 17 Oct 2017 13:13:58 +0000 (15:13 +0200)
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateService.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateServiceImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java
server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateServiceImplTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGateTest.java [new file with mode: 0644]

index 455e1d0ab2bf917bcfa90417e20f329dee88e291..2a457118b2fb315a787babec0420b9630003f935 100644 (file)
@@ -23,8 +23,6 @@ import com.google.common.base.Optional;
 
 public interface QualityGateService {
 
-  long SHORT_LIVING_BRANCHES_QUALITY_GATE = -1_963_456_987L;
-
   /**
    * Retrieve the {@link QualityGate} from the database with the specified id, it it exists.
    */
index beb7b521601ebc2413a8c080588e45da7c28604d..f01810b5eab37e18efe902b110296552ecc78f8d 100644 (file)
 package org.sonar.server.computation.task.projectanalysis.qualitygate;
 
 import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.Objects;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.qualitygate.QualityGateConditionDto;
 import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
+import org.sonar.server.qualitygate.ShortLivingBranchQualityGate;
 
-import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_GREATER_THAN;
+import static org.sonar.core.util.stream.MoreCollectors.toList;
 
 public class QualityGateServiceImpl implements QualityGateService {
 
@@ -45,7 +43,7 @@ public class QualityGateServiceImpl implements QualityGateService {
 
   @Override
   public Optional<QualityGate> findById(long id) {
-    if (id == SHORT_LIVING_BRANCHES_QUALITY_GATE) {
+    if (id == ShortLivingBranchQualityGate.ID) {
       return Optional.of(buildShortLivingBranchHardcodedQualityGate());
     }
     try (DbSession dbSession = dbClient.openSession(false)) {
@@ -65,19 +63,18 @@ public class QualityGateServiceImpl implements QualityGateService {
         .map(metric -> new Condition(metric, input.getOperator(), input.getErrorThreshold(), input.getWarningThreshold(), input.getPeriod() != null))
         .orElse(null))
       .filter(Objects::nonNull)
-      .collect(MoreCollectors.toList(dtos.size()));
+      .collect(toList(dtos.size()));
 
     return new QualityGate(qualityGateDto.getId(), qualityGateDto.getName(), conditions);
   }
 
   private QualityGate buildShortLivingBranchHardcodedQualityGate() {
     return new QualityGate(
-      SHORT_LIVING_BRANCHES_QUALITY_GATE,
-      "Hardcoded short living branch quality gate",
-      ImmutableList.of(
-        new Condition(metricRepository.getByKey(CoreMetrics.NEW_BUGS_KEY), OPERATOR_GREATER_THAN, "0", null, true),
-        new Condition(metricRepository.getByKey(CoreMetrics.NEW_VULNERABILITIES_KEY), OPERATOR_GREATER_THAN, "0", null, true),
-        new Condition(metricRepository.getByKey(CoreMetrics.NEW_CODE_SMELLS_KEY), OPERATOR_GREATER_THAN, "0", null, true)));
+      ShortLivingBranchQualityGate.ID,
+      ShortLivingBranchQualityGate.NAME,
+      ShortLivingBranchQualityGate.CONDITIONS.stream()
+        .map(c -> new Condition(metricRepository.getByKey(c.getMetricKey()), c.getOperator(), c.getErrorThreshold(), c.getWarnThreshold(), c.isOnLeak()))
+        .collect(toList(ShortLivingBranchQualityGate.CONDITIONS.size())));
   }
 
 }
index f71bbd528e406b0b54bd67b85b7841be3ec6f94b..68abc8c8b62d46ea85b5f6c08821a7d216339d2a 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.server.computation.task.projectanalysis.qualitygate.MutableQual
 import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGate;
 import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateService;
 import org.sonar.server.computation.task.step.ComputationStep;
+import org.sonar.server.qualitygate.ShortLivingBranchQualityGate;
 
 import static org.apache.commons.lang.StringUtils.isBlank;
 
@@ -57,7 +58,7 @@ public class LoadQualityGateStep implements ComputationStep {
   @Override
   public void execute() {
     if (analysisMetadataHolder.isShortLivingBranch()) {
-      Optional<QualityGate> qualityGate = qualityGateService.findById(QualityGateService.SHORT_LIVING_BRANCHES_QUALITY_GATE);
+      Optional<QualityGate> qualityGate = qualityGateService.findById(ShortLivingBranchQualityGate.ID);
       if (qualityGate.isPresent()) {
         qualityGateHolder.setQualityGate(qualityGate.get());
       } else {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java
new file mode 100644 (file)
index 0000000..3c864df
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 com.google.common.collect.ImmutableList;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import org.sonar.api.measures.CoreMetrics;
+
+import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_GREATER_THAN;
+
+/**
+ * Offers constants describing the Hardcoded Quality Gate for short living branches.
+ */
+public final class ShortLivingBranchQualityGate {
+  public static final long ID = -1_963_456_987L;
+  public static final String NAME = "Hardcoded short living branch quality gate";
+  public static final List<Condition> CONDITIONS = ImmutableList.of(
+    new Condition(CoreMetrics.BUGS_KEY, OPERATOR_GREATER_THAN, "0", false),
+    new Condition(CoreMetrics.VULNERABILITIES_KEY, OPERATOR_GREATER_THAN, "0", false),
+    new Condition(CoreMetrics.CODE_SMELLS_KEY, OPERATOR_GREATER_THAN, "0", false));
+
+  private ShortLivingBranchQualityGate() {
+    // prevents instantiation
+  }
+
+  public static final class Condition {
+    private final String metricKey;
+    private final String operator;
+    private final String errorThreshold;
+    private final boolean onLeak;
+
+    public Condition(String metricKey, String operator, String errorThreshold, boolean onLeak) {
+      this.metricKey = metricKey;
+      this.operator = operator;
+      this.errorThreshold = errorThreshold;
+      this.onLeak = onLeak;
+    }
+
+    public String getMetricKey() {
+      return metricKey;
+    }
+
+    public String getOperator() {
+      return operator;
+    }
+
+    public String getErrorThreshold() {
+      return errorThreshold;
+    }
+
+    @CheckForNull
+    public String getWarnThreshold() {
+      return null;
+    }
+
+    public boolean isOnLeak() {
+      return onLeak;
+    }
+  }
+}
index 45c79379154584b254bcd0f8270434966f707752..8ca8e9504da4b3f44d18937b14bd83e842cd7568 100644 (file)
@@ -36,6 +36,7 @@ import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.server.computation.task.projectanalysis.metric.Metric;
 import org.sonar.server.computation.task.projectanalysis.metric.MetricImpl;
 import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
+import org.sonar.server.qualitygate.ShortLivingBranchQualityGate;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
@@ -125,22 +126,22 @@ public class QualityGateServiceImplTest {
 
   @Test
   public void findById_of_hardcoded_short_living_branch_returns_hardcoded_qp() {
-    MetricImpl newBugsMetric = mockMetricInRepository(CoreMetrics.NEW_BUGS_KEY);
-    MetricImpl newVulnerabilitiesMetric = mockMetricInRepository(CoreMetrics.NEW_VULNERABILITIES_KEY);
-    MetricImpl newCodeSmellsMetric = mockMetricInRepository(CoreMetrics.NEW_CODE_SMELLS_KEY);
+    MetricImpl bugsMetric = mockMetricInRepository(CoreMetrics.BUGS_KEY);
+    MetricImpl vulnerabilitiesMetric = mockMetricInRepository(CoreMetrics.VULNERABILITIES_KEY);
+    MetricImpl codeSmellsMetric = mockMetricInRepository(CoreMetrics.CODE_SMELLS_KEY);
 
-    Optional<QualityGate> res = underTest.findById(QualityGateService.SHORT_LIVING_BRANCHES_QUALITY_GATE);
+    Optional<QualityGate> res = underTest.findById(ShortLivingBranchQualityGate.ID);
 
     assertThat(res).isPresent();
     QualityGate qualityGate = res.get();
-    assertThat(qualityGate.getId()).isEqualTo(QualityGateService.SHORT_LIVING_BRANCHES_QUALITY_GATE);
+    assertThat(qualityGate.getId()).isEqualTo(ShortLivingBranchQualityGate.ID);
     assertThat(qualityGate.getName()).isEqualTo("Hardcoded short living branch quality gate");
     assertThat(qualityGate.getConditions())
       .extracting(Condition::getMetric, Condition::getOperator, Condition::getErrorThreshold, Condition::getWarningThreshold, Condition::hasPeriod)
       .containsOnly(
-        tuple(newBugsMetric, GREATER_THAN, "0", null, true),
-        tuple(newVulnerabilitiesMetric, GREATER_THAN, "0", null, true),
-        tuple(newCodeSmellsMetric, GREATER_THAN, "0", null, true));
+        tuple(bugsMetric, GREATER_THAN, "0", null, false),
+        tuple(vulnerabilitiesMetric, GREATER_THAN, "0", null, false),
+        tuple(codeSmellsMetric, GREATER_THAN, "0", null, false));
   }
 
   private MetricImpl mockMetricInRepository(String metricKey) {
index 4e361f3d4bcce96e8295ebbe029fccc176eb9b1c..6abfb9f5b44218e7faa8feb621cdf015ce629cca 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.server.computation.task.projectanalysis.component.Configuration
 import org.sonar.server.computation.task.projectanalysis.qualitygate.MutableQualityGateHolderRule;
 import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGate;
 import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateService;
+import org.sonar.server.qualitygate.ShortLivingBranchQualityGate;
 
 import static java.lang.String.format;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -62,7 +63,7 @@ public class LoadQualityGateStepTest {
   public void add_hardcoded_QG_on_short_living_branch() {
     when(analysisMetadataHolder.isShortLivingBranch()).thenReturn(true);
     QualityGate qualityGate = mock(QualityGate.class);
-    when(qualityGateService.findById(QualityGateService.SHORT_LIVING_BRANCHES_QUALITY_GATE)).thenReturn(Optional.of(qualityGate));
+    when(qualityGateService.findById(ShortLivingBranchQualityGate.ID)).thenReturn(Optional.of(qualityGate));
 
     underTest.execute();
 
index 345d5199f6ecf1c26b1fb30d8e4256aa0fcb37c0..c610f8e91e2623aee031d6c793685c141e7d23e3 100644 (file)
  */
 package org.sonar.server.computation.task.projectanalysis.webhook;
 
+import com.google.common.collect.ImmutableMap;
 import java.util.Collections;
 import java.util.Date;
+import java.util.Map;
 import java.util.Random;
 import java.util.function.Supplier;
 import javax.annotation.Nullable;
-import org.apache.commons.lang.RandomStringUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.sonar.api.ce.posttask.Branch;
 import org.sonar.api.ce.posttask.CeTask;
 import org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester;
 import org.sonar.api.ce.posttask.Project;
@@ -40,7 +42,6 @@ import org.sonar.server.webhook.WebHooks;
 import org.sonar.server.webhook.WebhookPayload;
 import org.sonar.server.webhook.WebhookPayloadFactory;
 
-import static java.util.Collections.emptySet;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.any;
@@ -49,13 +50,16 @@ import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newBranchBuilder;
 import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newCeTaskBuilder;
+import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newConditionBuilder;
 import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newProjectBuilder;
 import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newQualityGateBuilder;
 import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newScannerContextBuilder;
 
 public class WebhookPostTaskTest {
 
+  private final Random random = new Random();
   private final Configuration configuration = mock(Configuration.class);
   private final WebhookPayload webhookPayload = mock(WebhookPayload.class);
   private final WebhookPayloadFactory payloadFactory = mock(WebhookPayloadFactory.class);
@@ -70,14 +74,27 @@ public class WebhookPostTaskTest {
   }
 
   @Test
-  public void call_webhooks() {
+  public void call_webhooks_when_no_analysis_not_qualitygate() {
     callWebHooks(null, null);
+  }
+
+  @Test
+  public void call_webhooks_with_analysis_and_qualitygate() {
+    QualityGate.Condition condition = newConditionBuilder()
+      .setMetricKey(randomAlphanumeric(96))
+      .setOperator(QualityGate.Operator.values()[random.nextInt(QualityGate.Operator.values().length)])
+      .setErrorThreshold(randomAlphanumeric(22))
+      .setWarningThreshold(randomAlphanumeric(23))
+      .setOnLeakPeriod(random.nextBoolean())
+      .build(QualityGate.EvaluationStatus.OK, randomAlphanumeric(33));
     QualityGate qualityGate = newQualityGateBuilder()
-      .setName("empty")
-      .setStatus(QualityGate.Status.OK)
-      .setId("1")
+      .setId(randomAlphanumeric(23))
+      .setName(randomAlphanumeric(66))
+      .setStatus(QualityGate.Status.values()[random.nextInt(QualityGate.Status.values().length)])
+      .add(condition)
       .build();
-    callWebHooks(RandomStringUtils.randomAlphanumeric(40), qualityGate);
+
+    callWebHooks(randomAlphanumeric(40), qualityGate);
   }
 
   private void callWebHooks(@Nullable String analysisUUid, @Nullable QualityGate qualityGate) {
@@ -87,16 +104,26 @@ public class WebhookPostTaskTest {
       .setName(randomAlphanumeric(5))
       .build();
     CeTask ceTask = newCeTaskBuilder()
-      .setStatus(CeTask.Status.values()[new Random().nextInt(CeTask.Status.values().length)])
+      .setStatus(CeTask.Status.values()[random.nextInt(CeTask.Status.values().length)])
       .setId(randomAlphanumeric(6))
       .build();
     Date date = new Date();
+    Map<String, String> properties = ImmutableMap.of(randomAlphanumeric(17), randomAlphanumeric(18));
+    Branch branch = newBranchBuilder()
+      .setIsMain(random.nextBoolean())
+      .setType(Branch.Type.values()[random.nextInt(Branch.Type.values().length)])
+      .setName(randomAlphanumeric(29))
+      .build();
 
     PostProjectAnalysisTaskTester.of(underTest)
       .at(date)
       .withCeTask(ceTask)
       .withProject(project)
-      .withScannerContext(newScannerContextBuilder().build())
+      .withBranch(branch)
+      .withQualityGate(qualityGate)
+      .withScannerContext(newScannerContextBuilder()
+        .addProperties(properties)
+        .build())
       .withAnalysisUuid(analysisUUid)
       .withQualityGate(qualityGate)
       .execute();
@@ -114,17 +141,26 @@ public class WebhookPostTaskTest {
 
     org.sonar.server.webhook.QualityGate webQualityGate = null;
     if (qualityGate != null) {
-      org.sonar.server.webhook.QualityGate.Status status = org.sonar.server.webhook.QualityGate.Status.valueOf(qualityGate.getStatus().name());
-      webQualityGate = new org.sonar.server.webhook.QualityGate(qualityGate.getId(), qualityGate.getName(), status, emptySet());
+      QualityGate.Condition condition = qualityGate.getConditions().iterator().next();
+      webQualityGate = new org.sonar.server.webhook.QualityGate(qualityGate.getId(), qualityGate.getName(),
+        org.sonar.server.webhook.QualityGate.Status.valueOf(qualityGate.getStatus().name()),
+        Collections.singleton(new org.sonar.server.webhook.QualityGate.Condition(
+          org.sonar.server.webhook.QualityGate.EvaluationStatus.valueOf(condition.getStatus().name()),
+          condition.getMetricKey(),
+          org.sonar.server.webhook.QualityGate.Operator.valueOf(condition.getOperator().name()),
+          condition.getErrorThreshold(),
+          condition.getWarningThreshold(),
+          condition.isOnLeakPeriod(),
+          condition.getValue())));
     }
 
     verify(payloadFactory).create(new ProjectAnalysis(
-      new org.sonar.server.webhook.Project(project.getUuid(), project.getKey(), project.getName()), new org.sonar.server.webhook.CeTask(ceTask.getId(),
-        org.sonar.server.webhook.CeTask.Status.valueOf(ceTask.getStatus().name())),
+      new org.sonar.server.webhook.Project(project.getUuid(), project.getKey(), project.getName()),
+      new org.sonar.server.webhook.CeTask(ceTask.getId(), org.sonar.server.webhook.CeTask.Status.valueOf(ceTask.getStatus().name())),
       analysisUUid == null ? null : new Analysis(analysisUUid, date.getTime()),
-      null,
+      new org.sonar.server.webhook.Branch(branch.isMain(), branch.getName().get(), org.sonar.server.webhook.Branch.Type.valueOf(branch.getType().name())),
       webQualityGate,
       analysisUUid == null ? null : date.getTime(),
-      Collections.emptyMap()));
+      properties));
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGateTest.java
new file mode 100644 (file)
index 0000000..15bbb6d
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 org.junit.Test;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.server.qualitygate.ShortLivingBranchQualityGate.Condition;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class ShortLivingBranchQualityGateTest {
+  @Test
+  public void defines_3_conditions() {
+    assertThat(ShortLivingBranchQualityGate.CONDITIONS)
+      .extracting(Condition::getMetricKey, Condition::getOperator, Condition::getErrorThreshold, Condition::getWarnThreshold, Condition::isOnLeak)
+      .containsExactly(
+        tuple(CoreMetrics.BUGS_KEY, "GT", "0", null, false),
+        tuple(CoreMetrics.VULNERABILITIES_KEY, "GT", "0", null, false),
+        tuple(CoreMetrics.CODE_SMELLS_KEY, "GT", "0", null, false));
+  }
+
+}