diff options
author | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2018-07-02 17:44:49 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2018-07-09 10:39:32 +0200 |
commit | 0dae853007134cfe1343f8fbe6b2046e19b217df (patch) | |
tree | 3c84fb8a57a0fde0d1c22af06c64d4e3649b3ca4 /server/sonar-server-common/src | |
parent | 4182b00648ddf1157c271d431c938b53b811a76b (diff) | |
download | sonarqube-0dae853007134cfe1343f8fbe6b2046e19b217df.tar.gz sonarqube-0dae853007134cfe1343f8fbe6b2046e19b217df.zip |
move shared Quality Gate classes to server-common
Diffstat (limited to 'server/sonar-server-common/src')
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()); + } + +} |