aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-server-common/src
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2018-07-02 17:44:49 +0200
committersonartech <sonartech@sonarsource.com>2018-07-09 10:39:32 +0200
commit0dae853007134cfe1343f8fbe6b2046e19b217df (patch)
tree3c84fb8a57a0fde0d1c22af06c64d4e3649b3ca4 /server/sonar-server-common/src
parent4182b00648ddf1157c271d431c938b53b811a76b (diff)
downloadsonarqube-0dae853007134cfe1343f8fbe6b2046e19b217df.tar.gz
sonarqube-0dae853007134cfe1343f8fbe6b2046e19b217df.zip
move shared Quality Gate classes to server-common
Diffstat (limited to 'server/sonar-server-common/src')
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java181
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluator.java60
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java133
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java89
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionEvaluatorTest.java219
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java105
6 files changed, 787 insertions, 0 deletions
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
new file mode 100644
index 00000000000..f154d3414fd
--- /dev/null
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java
@@ -0,0 +1,181 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.EnumSet;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import org.sonar.api.measures.Metric.ValueType;
+import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus;
+
+import static java.util.Optional.of;
+import static org.sonar.api.measures.Metric.ValueType.BOOL;
+import static org.sonar.api.measures.Metric.ValueType.FLOAT;
+import static org.sonar.api.measures.Metric.ValueType.INT;
+import static org.sonar.api.measures.Metric.ValueType.MILLISEC;
+import static org.sonar.api.measures.Metric.ValueType.PERCENT;
+import static org.sonar.api.measures.Metric.ValueType.RATING;
+import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
+
+class ConditionEvaluator {
+
+ private static final Set<ValueType> NUMERICAL_TYPES = EnumSet.of(BOOL, INT, RATING, FLOAT, MILLISEC, PERCENT, WORK_DUR);
+
+ private ConditionEvaluator() {
+ // prevent instantiation
+ }
+
+ /**
+ * Evaluates the condition for the specified measure
+ */
+ static EvaluatedCondition evaluate(Condition condition, QualityGateEvaluator.Measures measures) {
+ Optional<QualityGateEvaluator.Measure> measure = measures.get(condition.getMetricKey());
+ if (!measure.isPresent()) {
+ return new EvaluatedCondition(condition, EvaluationStatus.OK, null);
+ }
+
+ Optional<Comparable> value = getMeasureValue(condition, measure.get());
+ if (!value.isPresent()) {
+ return new EvaluatedCondition(condition, EvaluationStatus.OK, null);
+ }
+
+ ValueType type = measure.get().getType();
+ return evaluateCondition(condition, type, value.get(), true)
+ .orElseGet(() -> evaluateCondition(condition, type, value.get(), false)
+ .orElseGet(() -> new EvaluatedCondition(condition, EvaluationStatus.OK, value.get().toString())));
+ }
+
+ /**
+ * Evaluates the error or warning condition. Returns empty if threshold or measure value is not defined.
+ */
+ private static Optional<EvaluatedCondition> evaluateCondition(Condition condition, ValueType type, Comparable value, boolean error) {
+ Optional<Comparable> threshold = getThreshold(condition, type, error);
+ if (!threshold.isPresent()) {
+ return Optional.empty();
+ }
+
+ if (reachThreshold(value, threshold.get(), condition)) {
+ EvaluationStatus status = error ? EvaluationStatus.ERROR : EvaluationStatus.WARN;
+ return of(new EvaluatedCondition(condition, status, value.toString()));
+ }
+ return Optional.empty();
+ }
+
+ private static Optional<Comparable> getThreshold(Condition condition, ValueType valueType, boolean error) {
+ Optional<String> valString = error ? condition.getErrorThreshold() : condition.getWarningThreshold();
+ return valString.map(s -> {
+ try {
+ switch (valueType) {
+ case BOOL:
+ return parseInteger(s) == 1;
+ case INT:
+ case RATING:
+ return parseInteger(s);
+ case MILLISEC:
+ case WORK_DUR:
+ return Long.parseLong(s);
+ case FLOAT:
+ case PERCENT:
+ return Double.parseDouble(s);
+ case STRING:
+ case LEVEL:
+ return s;
+ default:
+ throw new IllegalArgumentException(String.format("Unsupported value type %s. Cannot convert condition value", valueType));
+ }
+ } catch (NumberFormatException badValueFormat) {
+ throw new IllegalArgumentException(String.format(
+ "Quality Gate: unable to parse threshold '%s' to compare against %s", s, condition.getMetricKey()));
+ }
+ });
+ }
+
+ private static Optional<Comparable> getMeasureValue(Condition condition, QualityGateEvaluator.Measure measure) {
+ if (condition.isOnLeakPeriod()) {
+ return Optional.ofNullable(getLeakValue(measure));
+ }
+
+ return Optional.ofNullable(getValue(measure));
+ }
+
+ @CheckForNull
+ private static Comparable getValue(QualityGateEvaluator.Measure measure) {
+ if (NUMERICAL_TYPES.contains(measure.getType())) {
+ return measure.getValue().isPresent() ? getNumericValue(measure.getType(), measure.getValue().getAsDouble()) : null;
+ }
+
+ switch (measure.getType()) {
+ case LEVEL:
+ case STRING:
+ case DISTRIB:
+ return measure.getStringValue().orElse(null);
+ default:
+ throw new IllegalArgumentException("Condition on leak period is not allowed for type " + measure.getType());
+ }
+ }
+
+ @CheckForNull
+ private static Comparable getLeakValue(QualityGateEvaluator.Measure measure) {
+ if (NUMERICAL_TYPES.contains(measure.getType())) {
+ return measure.getLeakValue().isPresent() ? getNumericValue(measure.getType(), measure.getLeakValue().getAsDouble()) : null;
+ }
+
+ throw new IllegalArgumentException("Condition on leak period is not allowed for type " + measure.getType());
+ }
+
+ private static Comparable getNumericValue(ValueType type, double value) {
+ switch (type) {
+ case BOOL:
+ return Double.compare(value, 1.0) == 1;
+ case INT:
+ case RATING:
+ return (int) value;
+ case FLOAT:
+ case PERCENT:
+ return value;
+ case MILLISEC:
+ case WORK_DUR:
+ return (long) value;
+ default:
+ throw new IllegalArgumentException("Condition on numeric value is not allowed for type " + type);
+ }
+ }
+
+ private static int parseInteger(String value) {
+ return value.contains(".") ? Integer.parseInt(value.substring(0, value.indexOf('.'))) : Integer.parseInt(value);
+ }
+
+ private static boolean reachThreshold(Comparable measureValue, Comparable threshold, Condition condition) {
+ int comparison = measureValue.compareTo(threshold);
+ switch (condition.getOperator()) {
+ case EQUALS:
+ return comparison == 0;
+ case NOT_EQUALS:
+ return comparison != 0;
+ case GREATER_THAN:
+ return comparison > 0;
+ case LESS_THAN:
+ return comparison < 0;
+ default:
+ throw new IllegalArgumentException(String.format("Unsupported operator '%s'", condition.getOperator()));
+ }
+ }
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluator.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluator.java
new file mode 100644
index 00000000000..3ecf03d08dd
--- /dev/null
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluator.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 java.util.Set;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.server.ServerSide;
+
+@ComputeEngineSide
+@ServerSide
+public interface QualityGateEvaluator {
+
+ /**
+ * @param measures must provide the measures related to the metrics
+ * defined by {@link #getMetricKeys(QualityGate)}
+ */
+ EvaluatedQualityGate evaluate(QualityGate gate, Measures measures);
+
+ /**
+ * Keys of the metrics involved in the computation of gate status.
+ * It may include metrics that are not part of conditions,
+ * for instance "new_lines" for the circuit-breaker on
+ * small changesets.
+ */
+ Set<String> getMetricKeys(QualityGate gate);
+
+ interface Measures {
+ Optional<Measure> get(String metricKey);
+ }
+
+ interface Measure {
+ Metric.ValueType getType();
+
+ OptionalDouble getValue();
+
+ Optional<String> getStringValue();
+
+ OptionalDouble getLeakValue();
+ }
+}
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
new file mode 100644
index 00000000000..f0ce9449320
--- /dev/null
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java
@@ -0,0 +1,133 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.ImmutableSet;
+import com.google.common.collect.Multimap;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric.Level;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus;
+
+import static java.util.Objects.requireNonNull;
+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;
+ /**
+ * Some metrics will be ignored on very small change sets.
+ */
+ private 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,
+ CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY,
+ CoreMetrics.NEW_DUPLICATED_LINES_KEY,
+ CoreMetrics.NEW_BLOCKS_DUPLICATED_KEY);
+
+ @Override
+ public EvaluatedQualityGate evaluate(QualityGate gate, Measures measures) {
+ EvaluatedQualityGate.Builder result = EvaluatedQualityGate.newBuilder()
+ .setQualityGate(gate);
+
+ boolean isSmallChangeset = isSmallChangeset(measures);
+ Multimap<String, Condition> conditionsPerMetric = gate.getConditions().stream()
+ .collect(MoreCollectors.index(Condition::getMetricKey, Function.identity()));
+
+ for (Map.Entry<String, Collection<Condition>> entry : conditionsPerMetric.asMap().entrySet()) {
+ String metricKey = entry.getKey();
+ Collection<Condition> conditionsOnSameMetric = entry.getValue();
+
+ EvaluatedCondition evaluation = evaluateConditionsOnMetric(conditionsOnSameMetric, measures);
+
+ if (isSmallChangeset && evaluation.getStatus() != EvaluationStatus.OK && METRICS_TO_IGNORE_ON_SMALL_CHANGESETS.contains(metricKey)) {
+ result.addCondition(new EvaluatedCondition(evaluation.getCondition(), EvaluationStatus.OK, evaluation.getValue().orElse(null)));
+ result.setIgnoredConditionsOnSmallChangeset(true);
+ } else {
+ result.addCondition(evaluation);
+ }
+ }
+
+ result.setStatus(overallStatusOf(result.getEvaluatedConditions()));
+
+ return result.build();
+ }
+
+ @Override
+ public Set<String> getMetricKeys(QualityGate gate) {
+ Set<String> metricKeys = new HashSet<>();
+ metricKeys.add(CoreMetrics.NEW_LINES_KEY);
+ for (Condition condition : gate.getConditions()) {
+ metricKeys.add(condition.getMetricKey());
+ }
+ return metricKeys;
+ }
+
+ private static boolean isSmallChangeset(Measures measures) {
+ Optional<Measure> newLines = measures.get(CoreMetrics.NEW_LINES_KEY);
+ return newLines.isPresent() &&
+ newLines.get().getLeakValue().isPresent() &&
+ newLines.get().getLeakValue().getAsDouble() < MAXIMUM_NEW_LINES_FOR_SMALL_CHANGESETS;
+ }
+
+ private static EvaluatedCondition evaluateConditionsOnMetric(Collection<Condition> conditionsOnSameMetric, Measures measures) {
+ EvaluatedCondition leakEvaluation = null;
+ EvaluatedCondition absoluteEvaluation = null;
+ for (Condition condition : conditionsOnSameMetric) {
+ if (condition.isOnLeakPeriod()) {
+ leakEvaluation = ConditionEvaluator.evaluate(condition, measures);
+ } else {
+ absoluteEvaluation = ConditionEvaluator.evaluate(condition, measures);
+ }
+ }
+
+ if (leakEvaluation == null) {
+ return requireNonNull(absoluteEvaluation, "Evaluation of absolute value can't be null on conditions " + conditionsOnSameMetric);
+ }
+ if (absoluteEvaluation == null) {
+ return requireNonNull(leakEvaluation, "Evaluation of leak value can't be null on conditions " + conditionsOnSameMetric);
+ }
+ // both conditions are present. Take the worse one. In case of equality, take
+ // the one on the leak period
+ if (absoluteEvaluation.getStatus().compareTo(leakEvaluation.getStatus()) > 0) {
+ return absoluteEvaluation;
+ }
+ return leakEvaluation;
+ }
+
+ private static Level overallStatusOf(Set<EvaluatedCondition> conditions) {
+ Set<EvaluationStatus> statuses = conditions.stream().map(EvaluatedCondition::getStatus).collect(toEnumSet(EvaluationStatus.class));
+ if (statuses.contains(EvaluationStatus.ERROR)) {
+ return Level.ERROR;
+ }
+ if (statuses.contains(EvaluationStatus.WARN)) {
+ return Level.WARN;
+ }
+ return Level.OK;
+ }
+
+}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java
new file mode 100644
index 00000000000..e0ba6bc326a
--- /dev/null
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.qualitygate.QGateWithOrgDto;
+import org.sonar.db.qualitygate.QualityGateDto;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Optional.ofNullable;
+
+public class QualityGateFinder {
+
+ public static final String SONAR_QUALITYGATE_PROPERTY = "sonar.qualitygate";
+
+ private final DbClient dbClient;
+
+ public QualityGateFinder(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ /**
+ * Return effective quality gate of a project.
+ *
+ * It will first try to get the quality gate explicitly defined on a project, if none it will try to return default quality gate of the organization
+ */
+ public Optional<QualityGateData> getQualityGate(DbSession dbSession, OrganizationDto organization, ComponentDto component) {
+ Optional<QualityGateData> res = dbClient.projectQgateAssociationDao().selectQGateIdByComponentId(dbSession, component.getId())
+ .map(qualityGateId -> dbClient.qualityGateDao().selectById(dbSession, qualityGateId))
+ .map(qualityGateDto -> new QualityGateData(qualityGateDto, false));
+ if (res.isPresent()) {
+ return res;
+ }
+ return ofNullable(dbClient.qualityGateDao().selectByOrganizationAndUuid(dbSession, organization, organization.getDefaultQualityGateUuid()))
+ .map(qualityGateDto -> new QualityGateData(qualityGateDto, true));
+ }
+
+ public QualityGateDto getDefault(DbSession dbSession, OrganizationDto organization) {
+ QGateWithOrgDto qgate = dbClient.qualityGateDao().selectByOrganizationAndUuid(dbSession, organization, organization.getDefaultQualityGateUuid());
+ checkState(qgate != null, "Default quality gate [%s] is missing on organization [%s]", organization.getDefaultQualityGateUuid(), organization.getUuid());
+ return qgate;
+ }
+
+ public QualityGateDto getBuiltInQualityGate(DbSession dbSession) {
+ QualityGateDto builtIn = dbClient.qualityGateDao().selectBuiltIn(dbSession);
+ checkState(builtIn != null, "Builtin quality gate is missing.");
+ return builtIn;
+ }
+
+ public static class QualityGateData {
+ private final QualityGateDto qualityGate;
+ private final boolean isDefault;
+
+ private QualityGateData(QualityGateDto qualityGate, boolean isDefault) {
+ this.qualityGate = qualityGate;
+ this.isDefault = isDefault;
+ }
+
+ public QualityGateDto getQualityGate() {
+ return qualityGate;
+ }
+
+ public boolean isDefault() {
+ return isDefault;
+ }
+ }
+
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionEvaluatorTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionEvaluatorTest.java
new file mode 100644
index 00000000000..8f73faf337c
--- /dev/null
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionEvaluatorTest.java
@@ -0,0 +1,219 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.qualitygate.ConditionEvaluatorTest.FakeMeasure.newFakeMeasureOnLeak;
+
+public class ConditionEvaluatorTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void EQUALS_double() {
+ test(new FakeMeasure(10.1d), Condition.Operator.EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.1");
+ test(new FakeMeasure(10.2d), Condition.Operator.EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.ERROR, "10.2");
+ test(new FakeMeasure(10.3d), Condition.Operator.EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.3");
+ }
+
+ @Test
+ public void NOT_EQUALS_double() {
+ test(new FakeMeasure(10.1d), Condition.Operator.NOT_EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.ERROR, "10.1");
+ test(new FakeMeasure(10.2d), Condition.Operator.NOT_EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.2");
+ test(new FakeMeasure(10.3d), Condition.Operator.NOT_EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.ERROR, "10.3");
+ }
+
+ @Test
+ public void GREATER_THAN_double() {
+ test(new FakeMeasure(10.1d), Condition.Operator.GREATER_THAN, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.1");
+ test(new FakeMeasure(10.2d), Condition.Operator.GREATER_THAN, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.2");
+ test(new FakeMeasure(10.3d), Condition.Operator.GREATER_THAN, "10.2", EvaluatedCondition.EvaluationStatus.ERROR, "10.3");
+ }
+
+ @Test
+ public void LESS_THAN_double() {
+ test(new FakeMeasure(10.1d), Condition.Operator.LESS_THAN, "10.2", EvaluatedCondition.EvaluationStatus.ERROR, "10.1");
+ test(new FakeMeasure(10.2d), Condition.Operator.LESS_THAN, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.2");
+ test(new FakeMeasure(10.3d), Condition.Operator.LESS_THAN, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.3");
+ }
+
+ @Test
+ public void EQUALS_int() {
+ test(new FakeMeasure(10), Condition.Operator.EQUALS, "9", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.EQUALS, "10", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ test(new FakeMeasure(10), Condition.Operator.EQUALS, "11", EvaluatedCondition.EvaluationStatus.OK, "10");
+
+ // badly stored thresholds are truncated
+ test(new FakeMeasure(10), Condition.Operator.EQUALS, "10.4", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ test(new FakeMeasure(10), Condition.Operator.EQUALS, "10.9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ test(new FakeMeasure(11), Condition.Operator.EQUALS, "10.9", EvaluatedCondition.EvaluationStatus.OK, "11");
+ }
+
+ @Test
+ public void NOT_EQUALS_int() {
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "11", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+
+ // badly stored thresholds are truncated
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "10.4", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "10.9", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "9.9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ }
+
+ @Test
+ public void GREATER_THAN_int() {
+ test(new FakeMeasure(10), Condition.Operator.GREATER_THAN, "9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ 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");
+ }
+
+ @Test
+ public void LESS_THAN_int() {
+ test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "9", EvaluatedCondition.EvaluationStatus.OK, "10");
+ 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");
+ 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(newFakeMeasureOnLeak(10), Condition.Operator.LESS_THAN, "11", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ }
+
+ @Test
+ public void no_value_present() {
+ test(new FakeMeasure((Integer) null), Condition.Operator.LESS_THAN, "9", EvaluatedCondition.EvaluationStatus.OK, null);
+ test(null, Condition.Operator.LESS_THAN, "9", EvaluatedCondition.EvaluationStatus.OK, null);
+ }
+
+ @Test
+ public void empty_warning_condition() {
+ test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "9", null, EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "9", "", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(3), Condition.Operator.LESS_THAN, "9", "", EvaluatedCondition.EvaluationStatus.ERROR, "3");
+ }
+
+ private void test(@Nullable QualityGateEvaluator.Measure measure, Condition.Operator operator, String errorThreshold, EvaluatedCondition.EvaluationStatus expectedStatus,
+ @Nullable String expectedValue) {
+ test(measure, operator, errorThreshold, null, expectedStatus, expectedValue);
+ }
+
+ private void test(@Nullable QualityGateEvaluator.Measure measure, Condition.Operator operator, String errorThreshold, @Nullable String warningThreshold,
+ EvaluatedCondition.EvaluationStatus expectedStatus, @Nullable String expectedValue) {
+ Condition condition = new Condition("foo", operator, errorThreshold, warningThreshold, false);
+
+ EvaluatedCondition result = ConditionEvaluator.evaluate(condition, new FakeMeasures(measure));
+
+ assertThat(result.getStatus()).isEqualTo(expectedStatus);
+ if (expectedValue == null) {
+ assertThat(result.getValue()).isNotPresent();
+ } else {
+ assertThat(result.getValue()).hasValue(expectedValue);
+ }
+ }
+
+ private void testOnLeak(QualityGateEvaluator.Measure measure, Condition.Operator operator, String errorThreshold, EvaluatedCondition.EvaluationStatus expectedStatus,
+ @Nullable String expectedValue) {
+ Condition condition = new Condition("foo", operator, errorThreshold, null, true);
+
+ EvaluatedCondition result = ConditionEvaluator.evaluate(condition, new FakeMeasures(measure));
+
+ assertThat(result.getStatus()).isEqualTo(expectedStatus);
+ if (expectedValue == null) {
+ assertThat(result.getValue()).isNotPresent();
+ } else {
+ assertThat(result.getValue()).hasValue(expectedValue);
+ }
+ }
+
+ private static class FakeMeasures implements QualityGateEvaluator.Measures {
+ private final QualityGateEvaluator.Measure measure;
+
+ FakeMeasures(@Nullable QualityGateEvaluator.Measure measure) {
+ this.measure = measure;
+ }
+
+ @Override
+ public Optional<QualityGateEvaluator.Measure> get(String metricKey) {
+ return Optional.ofNullable(measure);
+ }
+ }
+
+ static class FakeMeasure implements QualityGateEvaluator.Measure {
+ private Double leakValue;
+ private Double value;
+ private Metric.ValueType valueType;
+
+ private FakeMeasure() {
+
+ }
+
+ 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 getLeakValue() {
+ return leakValue == null ? OptionalDouble.empty() : OptionalDouble.of(leakValue);
+ }
+ }
+
+}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java
new file mode 100644
index 00000000000..8d5905aedc0
--- /dev/null
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java
@@ -0,0 +1,105 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.qualitygate.QualityGateDto;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class QualityGateFinderTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+
+ private DbSession dbSession = db.getSession();
+
+ private QualityGateFinder underTest = new QualityGateFinder(db.getDbClient());
+
+ @Test
+ public void return_default_quality_gate_for_project() {
+ ComponentDto project = db.components().insertPrivateProject();
+ QualityGateDto dbQualityGate = db.qualityGates().createDefaultQualityGate(db.getDefaultOrganization(), qg -> qg.setName("Sonar way"));
+
+ Optional<QualityGateFinder.QualityGateData> result = underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project);
+
+ assertThat(result).isPresent();
+ assertThat(result.get().getQualityGate().getId()).isEqualTo(dbQualityGate.getId());
+ assertThat(result.get().isDefault()).isTrue();
+ }
+
+ @Test
+ public void return_project_quality_gate_over_default() {
+ ComponentDto project = db.components().insertPrivateProject();
+ db.qualityGates().createDefaultQualityGate(db.getDefaultOrganization(),qg -> qg.setName("Sonar way"));
+ QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization(), qg -> qg.setName("My team QG"));
+ db.qualityGates().associateProjectToQualityGate(project, dbQualityGate);
+
+ Optional<QualityGateFinder.QualityGateData> result = underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project);
+
+ assertThat(result).isPresent();
+ assertThat(result.get().getQualityGate().getId()).isEqualTo(dbQualityGate.getId());
+ assertThat(result.get().isDefault()).isFalse();
+ }
+
+ @Test
+ public void fail_when_default_qgate_defined_does_not_exists() {
+ ComponentDto project = db.components().insertPrivateProject();
+ QualityGateDto dbQualityGate = db.qualityGates().createDefaultQualityGate(db.getDefaultOrganization(), qg -> qg.setName("Sonar way"));
+ db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
+ db.commit();
+
+ assertThat(underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project)).isEmpty();
+ }
+
+ @Test
+ public void fail_when_project_qgate_defined_does_not_exists() {
+ ComponentDto project = db.components().insertPrivateProject();
+ QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization(), qg -> qg.setName("My team QG"));
+ db.qualityGates().associateProjectToQualityGate(project, dbQualityGate);
+ db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
+
+ assertThat(underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project)).isEmpty();
+ }
+
+ @Test
+ public void fail_when_default_quality_gate_does_not_exists() {
+ QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization(), qg -> qg.setName("My team QG"));
+ db.qualityGates().setDefaultQualityGate(db.getDefaultOrganization(), dbQualityGate);
+ db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage(format("Default quality gate [%s] is missing on organization [%s]", dbQualityGate.getUuid(), db.getDefaultOrganization().getUuid()));
+
+ underTest.getDefault(dbSession, db.getDefaultOrganization());
+ }
+
+}