From dae42b839197f656db1eff35c7703739e2f3dcd1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Mon, 8 Jun 2015 16:56:33 +0200 Subject: [PATCH] SONAR-6620 step to set QualityGate on measures and create QG measures --- .../container/ComputeEngineContainerImpl.java | 2 + .../EvaluatedCondition.java | 52 ++++ .../QualityGateDetailsData.java | 75 ++++++ .../qualitygatedetails/package-info.java | 24 ++ .../computation/qualitygate/Condition.java | 6 +- .../qualitygate/ConditionEvaluator.java | 193 +++++++++++++ .../qualitygate/EvaluationResult.java | 57 ++++ .../EvaluationResultTextConverter.java | 27 ++ .../EvaluationResultTextConverterImpl.java | 104 +++++++ .../computation/step/ComputationSteps.java | 5 +- .../step/QualityGateMeasuresStep.java | 165 ++++++++++++ .../EvaluatedConditionTest.java | 72 +++++ .../QualityGateDetailsDataTest.java | 96 +++++++ .../qualitygate/ConditionEvaluatorTest.java | 248 +++++++++++++++++ .../qualitygate/EvaluationResultAssert.java | 56 ++++ .../qualitygate/EvaluationResultTest.java | 48 ++++ .../EvaluationResultTextConverterTest.java | 183 +++++++++++++ .../step/QualityGateMeasuresStepTest.java | 254 ++++++++++++++++++ 18 files changed, 1664 insertions(+), 3 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedCondition.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsData.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/package-info.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/ConditionEvaluator.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResult.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverter.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityGateMeasuresStep.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedConditionTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsDataTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/ConditionEvaluatorTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultAssert.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateMeasuresStepTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java index ef1775c7293..53fbde2c698 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java @@ -53,6 +53,7 @@ import org.sonar.server.computation.language.LanguageRepositoryImpl; import org.sonar.server.computation.measure.MeasureRepositoryImpl; import org.sonar.server.computation.metric.MetricRepositoryImpl; import org.sonar.server.computation.period.PeriodsHolderImpl; +import org.sonar.server.computation.qualitygate.EvaluationResultTextConverterImpl; import org.sonar.server.computation.qualitygate.QualityGateHolderImpl; import org.sonar.server.computation.qualitygate.QualityGateServiceImpl; import org.sonar.server.computation.step.ComputationStep; @@ -144,6 +145,7 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co DbIdsRepository.class, QualityGateServiceImpl.class, + EvaluationResultTextConverterImpl.class, // issues ScmAccountCacheLoader.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedCondition.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedCondition.java new file mode 100644 index 00000000000..b97e6613328 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedCondition.java @@ -0,0 +1,52 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.measure.qualitygatedetails; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.qualitygate.Condition; + +import static java.util.Objects.requireNonNull; + +@Immutable +public final class EvaluatedCondition { + private final Condition condition; + private final Measure.Level level; + private final String actualValue; + + public EvaluatedCondition(Condition condition, Measure.Level level, @Nullable Object actualValue) { + this.condition = requireNonNull(condition); + this.level = requireNonNull(level); + this.actualValue = actualValue == null ? "" : actualValue.toString(); + } + + public Condition getCondition() { + return condition; + } + + public Measure.Level getLevel() { + return level; + } + + public String getActualValue() { + return actualValue; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsData.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsData.java new file mode 100644 index 00000000000..c728677e00c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsData.java @@ -0,0 +1,75 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.measure.qualitygatedetails; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.util.List; +import javax.annotation.concurrent.Immutable; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.qualitygate.Condition; + +import static com.google.common.collect.FluentIterable.from; +import static java.util.Objects.requireNonNull; + +@Immutable +public class QualityGateDetailsData { + private static final String FIELD_LEVEL = "level"; + + private final Measure.Level level; + private final List conditions; + + public QualityGateDetailsData(Measure.Level level, Iterable conditions) { + this.level = requireNonNull(level); + this.conditions = from(conditions).toList(); + } + + public String toJson() { + JsonObject details = new JsonObject(); + details.addProperty(FIELD_LEVEL, level.toString()); + JsonArray conditionResults = new JsonArray(); + for (EvaluatedCondition condition : this.conditions) { + conditionResults.add(toJson(condition)); + } + details.add("conditions", conditionResults); + return details.toString(); + } + + private JsonObject toJson(EvaluatedCondition evaluatedCondition) { + Condition condition = evaluatedCondition.getCondition(); + + JsonObject result = new JsonObject(); + result.addProperty("metric", condition.getMetric().getKey()); + result.addProperty("op", condition.getOperator().getDbValue()); + if (condition.getPeriod() != null) { + result.addProperty("period", condition.getPeriod()); + } + if (condition.getWarningThreshold() != null) { + result.addProperty("warning", condition.getWarningThreshold()); + } + if (condition.getErrorThreshold() != null) { + result.addProperty("error", condition.getErrorThreshold()); + } + result.addProperty("actual", evaluatedCondition.getActualValue()); + result.addProperty(FIELD_LEVEL, level.toString()); + return result; + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/package-info.java new file mode 100644 index 00000000000..a6c058128f9 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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. + */ + +@ParametersAreNonnullByDefault +package org.sonar.server.computation.measure.qualitygatedetails; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/Condition.java b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/Condition.java index 826edf08a0f..74f8d90670b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/Condition.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/Condition.java @@ -31,7 +31,7 @@ import static java.util.Objects.requireNonNull; @Immutable public class Condition { - enum Operator { + public enum Operator { EQUALS("EQ"), NOT_EQUALS("NE"), GREATER_THAN("GT"), LESS_THAN("LT"); private final String dbValue; @@ -39,6 +39,10 @@ public class Condition { Operator(String dbValue) { this.dbValue = dbValue; } + + public String getDbValue() { + return dbValue; + } } private final Metric metric; diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/ConditionEvaluator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/ConditionEvaluator.java new file mode 100644 index 00000000000..2bf2c464718 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/ConditionEvaluator.java @@ -0,0 +1,193 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.qualitygate; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import javax.annotation.CheckForNull; +import org.apache.commons.lang.StringUtils; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureVariations; +import org.sonar.server.computation.metric.Metric; + +import static com.google.common.base.Optional.of; +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +public final class ConditionEvaluator { + + private static final Optional NO_PERIOD_VALUE = Optional.absent(); + + /** + * Evaluates the condition for the specified measure + */ + public EvaluationResult evaluate(Condition condition, Measure measure) { + checkArgument(condition.getMetric().getType() != Metric.MetricType.DATA, "Conditions on MetricType DATA are not supported"); + + Comparable measureComparable = parseMeasure(condition, measure); + if (measureComparable == null) { + return new EvaluationResult(Measure.Level.OK, null); + } + + return evaluateCondition(condition, measureComparable, Measure.Level.ERROR) + .or(evaluateCondition(condition, measureComparable, Measure.Level.WARN)) + .or(new EvaluationResult(Measure.Level.OK, measureComparable)); + } + + private Optional evaluateCondition(Condition condition, Comparable measureComparable, Measure.Level alertLevel) { + String conditionValue = getValueToEval(condition, alertLevel); + if (StringUtils.isEmpty(conditionValue)) { + return Optional.absent(); + } + + try { + Comparable conditionComparable = parseConditionValue(condition.getMetric(), conditionValue); + if (doesReachThresholds(measureComparable, conditionComparable, condition)) { + return of(new EvaluationResult(alertLevel, measureComparable)); + } + return Optional.absent(); + } catch (NumberFormatException badValueFormat) { + throw new IllegalArgumentException(String.format( + "Quality Gate: Unable to parse value '%s' to compare against %s", + conditionValue, condition.getMetric().getName())); + } + } + + private static String getValueToEval(Condition condition, Measure.Level alertLevel) { + if (alertLevel.equals(Measure.Level.ERROR)) { + return condition.getErrorThreshold(); + } else if (alertLevel.equals(Measure.Level.WARN)) { + return condition.getWarningThreshold(); + } else { + throw new IllegalStateException(alertLevel.toString()); + } + } + + private static boolean doesReachThresholds(Comparable measureValue, Comparable criteriaValue, Condition condition) { + int comparison = measureValue.compareTo(criteriaValue); + switch (condition.getOperator()) { + case EQUALS: + return comparison == 0; + case NOT_EQUALS: + return comparison != 0; + case GREATER_THAN: + return comparison == 1; + case LESS_THAN: + return comparison == -1; + default: + throw new IllegalArgumentException(String.format("Unsupported operator '%s'", condition.getOperator())); + } + } + + private static Comparable parseConditionValue(Metric metric, String value) { + switch (metric.getType().getValueType()) { + case BOOLEAN: + return Integer.parseInt(value) == 1; + case INT: + return parseInteger(value); + case LONG: + return Long.parseLong(value); + case DOUBLE: + return Double.parseDouble(value); + case STRING: + case LEVEL: + return value; + default: + throw new IllegalArgumentException(String.format("Unsupported value type %s. Can not convert condition value", metric.getType().getValueType())); + } + } + + private static Comparable parseInteger(String value) { + return value.contains(".") ? Integer.parseInt(value.substring(0, value.indexOf('.'))) : Integer.parseInt(value); + } + + @CheckForNull + private static Comparable parseMeasure(Condition condition, Measure measure) { + if (condition.getPeriod() != null) { + return parseMeasureFromVariation(condition, measure); + } + + switch (measure.getValueType()) { + case BOOLEAN: + return measure.getBooleanValue(); + case INT: + return measure.getIntValue(); + case LONG: + return measure.getLongValue(); + case DOUBLE: + return measure.getDoubleValue(); + case STRING: + return measure.getStringValue(); + case LEVEL: + return measure.getLevelValue().name(); + case NO_VALUE: + return null; + default: + throw new IllegalArgumentException( + String.format("Unsupported measure ValueType %s. Can not parse measure to a Comparable", measure.getValueType())); + } + } + + @CheckForNull + private static Comparable parseMeasureFromVariation(Condition condition, Measure measure) { + Optional periodValue = getPeriodValue(measure, condition.getPeriod()); + if (periodValue.isPresent()) { + switch (condition.getMetric().getType().getValueType()) { + case BOOLEAN: + return periodValue.get().intValue() == 1; + case INT: + return periodValue.get().intValue(); + case LONG: + return periodValue.get().longValue(); + case DOUBLE: + return periodValue.get(); + case NO_VALUE: + case STRING: + case LEVEL: + default: + throw new IllegalArgumentException("Period conditions are not supported for metric type " + condition.getMetric().getType()); + } + } + return null; + } + + private static Optional getPeriodValue(Measure measure, int period) { + if (!measure.hasVariations()) { + return Optional.absent(); + } + + MeasureVariations variations = measure.getVariations(); + switch (period) { + case 1: + return variations.hasVariation1() ? of(variations.getVariation1()) : NO_PERIOD_VALUE; + case 2: + return variations.hasVariation2() ? of(variations.getVariation2()) : NO_PERIOD_VALUE; + case 3: + return variations.hasVariation3() ? of(variations.getVariation3()) : NO_PERIOD_VALUE; + case 4: + return variations.hasVariation4() ? of(variations.getVariation4()) : NO_PERIOD_VALUE; + case 5: + return variations.hasVariation5() ? of(variations.getVariation5()) : NO_PERIOD_VALUE; + default: + throw new IllegalStateException("Following index period is not allowed : " + Double.toString(period)); + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResult.java b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResult.java new file mode 100644 index 00000000000..a6ccaecba74 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResult.java @@ -0,0 +1,57 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.qualitygate; + +import com.google.common.base.Objects; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.server.computation.measure.Measure; + +import static java.util.Objects.requireNonNull; + +@Immutable +public final class EvaluationResult { + private final Measure.Level level; + @CheckForNull + private final Comparable value; + + public EvaluationResult(Measure.Level level, @Nullable Comparable value) { + this.level = requireNonNull(level); + this.value = value; + } + + public Measure.Level getLevel() { + return level; + } + + @CheckForNull + public Comparable getValue() { + return value; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("level", level) + .add("value", value) + .toString(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverter.java b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverter.java new file mode 100644 index 00000000000..3afe4625b3f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverter.java @@ -0,0 +1,27 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.qualitygate; + +import javax.annotation.CheckForNull; + +public interface EvaluationResultTextConverter { + @CheckForNull + String asText(Condition condition, EvaluationResult evaluationResult); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterImpl.java new file mode 100644 index 00000000000..64cbf2186eb --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterImpl.java @@ -0,0 +1,104 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.qualitygate; + +import com.google.common.collect.ImmutableMap; +import java.util.Locale; +import java.util.Map; +import javax.annotation.CheckForNull; +import org.sonar.api.i18n.I18n; +import org.sonar.api.utils.DateUtils; +import org.sonar.api.utils.Duration; +import org.sonar.api.utils.Durations; +import org.sonar.core.timemachine.Periods; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.period.Period; +import org.sonar.server.computation.period.PeriodsHolder; + +import static java.util.Objects.requireNonNull; + +public final class EvaluationResultTextConverterImpl implements EvaluationResultTextConverter { + private static final String VARIATION_METRIC_PREFIX = "new_"; + private static final String VARIATION = "variation"; + private static final Map OPERATOR_LABELS = ImmutableMap.of( + Condition.Operator.EQUALS, "=", + Condition.Operator.NOT_EQUALS, "!=", + Condition.Operator.GREATER_THAN, ">", + Condition.Operator.LESS_THAN, "<"); + + private final I18n i18n; + private final Durations durations; + private final Periods periods; + private final PeriodsHolder periodsHolder; + + public EvaluationResultTextConverterImpl(I18n i18n, Durations durations, Periods periods, PeriodsHolder periodsHolder) { + this.i18n = i18n; + this.durations = durations; + this.periods = periods; + this.periodsHolder = periodsHolder; + } + + @Override + @CheckForNull + public String asText(Condition condition, EvaluationResult evaluationResult) { + requireNonNull(condition); + if (evaluationResult.getLevel() == Measure.Level.OK) { + return null; + } + return getAlertLabel(condition, evaluationResult.getLevel()); + } + + private String getAlertLabel(Condition condition, Measure.Level level) { + Integer alertPeriod = condition.getPeriod(); + String metric = i18n.message(Locale.ENGLISH, "metric." + condition.getMetric().getKey() + ".name", condition.getMetric().getName()); + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(metric); + + if (alertPeriod != null && !condition.getMetric().getKey().startsWith(VARIATION_METRIC_PREFIX)) { + String variation = i18n.message(Locale.ENGLISH, VARIATION, VARIATION).toLowerCase(); + stringBuilder.append(" ").append(variation); + } + + stringBuilder + .append(" ").append(OPERATOR_LABELS.get(condition.getOperator())).append(" ") + .append(alertValue(condition, level)); + + if (alertPeriod != null) { + Period period = periodsHolder.getPeriod(alertPeriod); + stringBuilder.append(" ").append(periods.label(period.getMode(), period.getModeParameter(), DateUtils.longToDate(period.getSnapshotDate()))); + } + + return stringBuilder.toString(); + } + + private String alertValue(Condition condition, Measure.Level level) { + String value = (level == Measure.Level.ERROR ? condition.getErrorThreshold() : condition.getWarningThreshold()); + if (condition.getMetric().getType() == Metric.MetricType.WORK_DUR) { + return formatDuration(value); + } + return value; + } + + private String formatDuration(String value) { + return durations.format(Locale.ENGLISH, Duration.create(Long.parseLong(value)), Durations.DurationFormat.SHORT); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java index 4c74a56c872..081ae51c5e5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java @@ -45,16 +45,17 @@ public class ComputationSteps { ValidateProjectStep.class, FeedDebtModelStep.class, - FeedPeriodsStep.class, - // Read report ParseReportStep.class, + // load project related stuffs QualityGateLoadingStep.class, + FeedPeriodsStep.class, // data computation QualityProfileEventsStep.class, QualityGateEventsStep.class, + QualityGateMeasuresStep.class, FillMeasuresWithVariationsStep.class, // Persist data diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityGateMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityGateMeasuresStep.java new file mode 100644 index 00000000000..0b98ab3360d --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityGateMeasuresStep.java @@ -0,0 +1,165 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.step; + +import com.google.common.base.Optional; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor; +import org.sonar.server.computation.component.TreeRootHolder; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.measure.QualityGateStatus; +import org.sonar.server.computation.measure.qualitygatedetails.EvaluatedCondition; +import org.sonar.server.computation.measure.qualitygatedetails.QualityGateDetailsData; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricRepository; +import org.sonar.server.computation.qualitygate.Condition; +import org.sonar.server.computation.qualitygate.ConditionEvaluator; +import org.sonar.server.computation.qualitygate.EvaluationResult; +import org.sonar.server.computation.qualitygate.EvaluationResultTextConverter; +import org.sonar.server.computation.qualitygate.QualityGateHolder; + +import static org.sonar.server.computation.component.Component.Type.PROJECT; +import static org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor.Order.PRE_ORDER; + +/** + * This step: + *
    + *
  • updates the QualityGateStatus of all the project's measures for the metrics of the conditions of the current + * QualityGate (retrieved from {@link QualityGateHolder})
  • + *
  • computes the measures on the project for metrics {@link CoreMetrics#QUALITY_GATE_DETAILS_KEY} and + * {@link CoreMetrics#ALERT_STATUS_KEY}
  • + *
+ */ +public class QualityGateMeasuresStep implements ComputationStep { + + private final TreeRootHolder treeRootHolder; + private final QualityGateHolder qualityGateHolder; + private final MeasureRepository measureRepository; + private final MetricRepository metricRepository; + private final EvaluationResultTextConverter evaluationResultTextConverter; + + public QualityGateMeasuresStep(TreeRootHolder treeRootHolder, QualityGateHolder qualityGateHolder, + MeasureRepository measureRepository, MetricRepository metricRepository, + EvaluationResultTextConverter evaluationResultTextConverter) { + this.treeRootHolder = treeRootHolder; + this.qualityGateHolder = qualityGateHolder; + this.evaluationResultTextConverter = evaluationResultTextConverter; + this.measureRepository = measureRepository; + this.metricRepository = metricRepository; + } + + @Override + public void execute() { + new DepthTraversalTypeAwareVisitor(PROJECT, PRE_ORDER) { + @Override + public void visitProject(Component project) { + executeForProject(project); + } + }.visit(treeRootHolder.getRoot()); + } + + private void executeForProject(Component project) { + QualityGateDetailsDataBuilder builder = new QualityGateDetailsDataBuilder(); + + updateMeasures(project, qualityGateHolder.getQualityGate().getConditions(), builder); + + addProjectMeasure(project, builder); + } + + private void updateMeasures(Component project, Set conditions, QualityGateDetailsDataBuilder builder) { + for (Condition condition : conditions) { + Optional measure = measureRepository.getRawMeasure(project, condition.getMetric()); + if (!measure.isPresent()) { + continue; + } + + EvaluationResult evaluationResult = new ConditionEvaluator().evaluate(condition, measure.get()); + + String text = evaluationResultTextConverter.asText(condition, evaluationResult); + builder.addLabel(text); + + Measure updatedMeasure = Measure.updatedMeasureBuilder(measure.get()) + .setQualityGateStatus(new QualityGateStatus(evaluationResult.getLevel(), text)) + .create(); + measureRepository.update(project, condition.getMetric(), updatedMeasure); + + builder.addEvaluatedCondition(condition, evaluationResult); + } + } + + private void addProjectMeasure(Component project, QualityGateDetailsDataBuilder builder) { + Measure globalMeasure = Measure.newMeasureBuilder() + .setQualityGateStatus(new QualityGateStatus(builder.getGlobalLevel(), StringUtils.join(builder.getLabels(), ", "))) + .create(builder.getGlobalLevel()); + Metric metric = metricRepository.getByKey(CoreMetrics.ALERT_STATUS_KEY); + measureRepository.add(project, metric, globalMeasure); + + String detailMeasureValue = new QualityGateDetailsData(builder.getGlobalLevel(), builder.getEvaluatedConditions()).toJson(); + Measure detailsMeasure = Measure.newMeasureBuilder().create(detailMeasureValue); + Metric qgDetailsMetric = metricRepository.getByKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY); + measureRepository.add(project, qgDetailsMetric, detailsMeasure); + } + + @Override + public String getDescription() { + return "Computes Quality Gate measures"; + } + + private static final class QualityGateDetailsDataBuilder { + private Measure.Level globalLevel = Measure.Level.OK; + private List labels = new ArrayList<>(); + private List evaluatedConditions = new ArrayList<>(); + + public Measure.Level getGlobalLevel() { + return globalLevel; + } + + public void addLabel(@Nullable String label) { + if (StringUtils.isNotBlank(label)) { + labels.add(label); + } + } + + public List getLabels() { + return labels; + } + + public void addEvaluatedCondition(Condition condition, EvaluationResult evaluationResult) { + if (Measure.Level.WARN == evaluationResult.getLevel() && this.globalLevel != Measure.Level.ERROR) { + globalLevel = Measure.Level.WARN; + + } else if (Measure.Level.ERROR == evaluationResult.getLevel()) { + globalLevel = Measure.Level.ERROR; + } + evaluatedConditions.add(new EvaluatedCondition(condition, evaluationResult.getLevel(), evaluationResult.getValue())); + } + + public List getEvaluatedConditions() { + return evaluatedConditions; + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedConditionTest.java new file mode 100644 index 00000000000..e12c394db5c --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedConditionTest.java @@ -0,0 +1,72 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.measure.qualitygatedetails; + +import org.junit.Test; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.qualitygate.Condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class EvaluatedConditionTest { + + private static final Condition SOME_CONDITION = new Condition(mock(Metric.class), Condition.Operator.EQUALS.getDbValue(), "1", null, null); + private static final Measure.Level SOME_LEVEL = Measure.Level.OK; + private static final String SOME_VALUE = "some value"; + + @Test(expected = NullPointerException.class) + public void constructor_throws_NPE_if_Condition_arg_is_null() { + new EvaluatedCondition(null, SOME_LEVEL, SOME_VALUE); + } + + @Test(expected = NullPointerException.class) + public void constructor_throws_NPE_if_Level_arg_is_null() { + new EvaluatedCondition(SOME_CONDITION, null, SOME_VALUE); + } + + @Test + public void getCondition_returns_object_passed_in_constructor() { + assertThat(new EvaluatedCondition(SOME_CONDITION, SOME_LEVEL, SOME_VALUE).getCondition()).isSameAs(SOME_CONDITION); + } + + @Test + public void getLevel_returns_object_passed_in_constructor() { + assertThat(new EvaluatedCondition(SOME_CONDITION, SOME_LEVEL, SOME_VALUE).getLevel()).isSameAs(SOME_LEVEL); + } + + @Test + public void getValue_returns_empty_string_if_null_was_passed_in_constructor() { + assertThat(new EvaluatedCondition(SOME_CONDITION, SOME_LEVEL, null).getActualValue()).isEmpty(); + } + + @Test + public void getValue_returns_toString_of_Object_passed_in_constructor() { + assertThat(new EvaluatedCondition(SOME_CONDITION, SOME_LEVEL, new A()).getActualValue()).isEqualTo("A string"); + } + + private static class A { + @Override + public String toString() { + return "A string"; + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsDataTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsDataTest.java new file mode 100644 index 00000000000..3975f77ed10 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsDataTest.java @@ -0,0 +1,96 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.measure.qualitygatedetails; + +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import org.junit.Test; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricImpl; +import org.sonar.server.computation.qualitygate.Condition; +import org.sonar.test.JsonAssert; + +public class QualityGateDetailsDataTest { + @Test(expected = NullPointerException.class) + public void constructor_throws_NPE_if_Level_arg_is_null() { + new QualityGateDetailsData(null, Collections.emptyList()); + } + + @Test(expected = NullPointerException.class) + public void constructor_throws_NPE_if_Iterable_arg_is_null() { + new QualityGateDetailsData(Measure.Level.OK, null); + } + + @Test + public void verify_json_when_there_is_no_condition() { + String actualJson = new QualityGateDetailsData(Measure.Level.OK, Collections.emptyList()).toJson(); + + JsonAssert.assertJson(actualJson).isSimilarTo("{" + + "\"level\":\"OK\"," + + "\"conditions\":[]" + + "}"); + } + + @Test + public void verify_json_for_each_type_of_condition() { + String value = "actualValue"; + Condition condition = new Condition(new MetricImpl(1, "key1", "name1", Metric.MetricType.STRING), Condition.Operator.GREATER_THAN.getDbValue(), "errorTh", "warnTh", 10); + ImmutableList evaluatedConditions = ImmutableList.of( + new EvaluatedCondition(condition, Measure.Level.OK, value), + new EvaluatedCondition(condition, Measure.Level.WARN, value), + new EvaluatedCondition(condition, Measure.Level.ERROR, value) + ); + String actualJson = new QualityGateDetailsData(Measure.Level.OK, evaluatedConditions).toJson(); + + JsonAssert.assertJson(actualJson).isSimilarTo("{" + + "\"level\":\"OK\"," + + "\"conditions\":[" + + " {" + + " \"metric\":\"key1\"," + + " \"op\":\"GT\"," + + " \"period\":10," + + " \"warning\":\"warnTh\"," + + " \"error\":\"errorTh\"," + + " \"actual\":\"actualValue\"," + + " \"level\":\"OK\"" + + " }," + + " {" + + " \"metric\":\"key1\"," + + " \"op\":\"GT\"," + + " \"period\":10," + + " \"warning\":\"warnTh\"," + + " \"error\":\"errorTh\"," + + " \"actual\":\"actualValue\"," + + " \"level\":\"OK\"" + + " }," + + " {" + + " \"metric\":\"key1\"," + + " \"op\":\"GT\"," + + " \"period\":10," + + " \"warning\":\"warnTh\"," + + " \"error\":\"errorTh\"," + + " \"actual\":\"actualValue\"," + + " \"level\":\"OK\"" + + " }" + + "]" + + "}"); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/ConditionEvaluatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/ConditionEvaluatorTest.java new file mode 100644 index 00000000000..c8aa67fac9c --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/ConditionEvaluatorTest.java @@ -0,0 +1,248 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.qualitygate; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricImpl; + +import static org.junit.Assert.fail; +import static org.sonar.server.computation.measure.Measure.Level.ERROR; +import static org.sonar.server.computation.measure.Measure.Level.OK; +import static org.sonar.server.computation.qualitygate.Condition.Operator.EQUALS; +import static org.sonar.server.computation.qualitygate.Condition.Operator.GREATER_THAN; +import static org.sonar.server.computation.qualitygate.Condition.Operator.LESS_THAN; +import static org.sonar.server.computation.qualitygate.Condition.Operator.NOT_EQUALS; +import static org.sonar.server.computation.qualitygate.EvaluationResultAssert.assertThat; + +public class ConditionEvaluatorTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private ConditionEvaluator underTest = new ConditionEvaluator(); + + @Test + public void testInputNumbers() { + try { + Metric metric = createMetric(Metric.MetricType.FLOAT); + Measure measure = Measure.newMeasureBuilder().create(10.2d, null); + underTest.evaluate(createErrorCondition(metric, LESS_THAN, "20"), measure); + } catch (NumberFormatException ex) { + fail(); + } + + try { + Metric metric = createMetric(Metric.MetricType.INT); + Measure measure = Measure.newMeasureBuilder().create(5, null); + underTest.evaluate(createErrorCondition(metric, LESS_THAN, "20.1"), measure); + } catch (NumberFormatException ex) { + fail(); + } + + try { + Metric metric = createMetric(Metric.MetricType.PERCENT); + Measure measure = Measure.newMeasureBuilder().create(10.2d, null); + underTest.evaluate(createErrorCondition(metric, LESS_THAN, "20.1"), measure); + } catch (NumberFormatException ex) { + fail(); + } + } + + private static Condition createErrorCondition(Metric metric, Condition.Operator operator, String errorThreshold) { + return new Condition(metric, operator.getDbValue(), errorThreshold, null, null); + } + + private static MetricImpl createMetric(Metric.MetricType metricType) { + return new MetricImpl(1, "key", "name", metricType); + } + + @Test + public void testEquals_for_double() { + Metric metric = createMetric(Metric.MetricType.FLOAT); + Measure measure = Measure.newMeasureBuilder().create(10.2d, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.2"), measure)).hasLevel(ERROR).hasValue(10.2d); + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.1"), measure)).hasLevel(OK).hasValue(10.2d); + } + + @Test + public void testEquals_for_String() { + Metric metric = createMetric(Metric.MetricType.STRING); + Measure measure = Measure.newMeasureBuilder().create("TEST"); + + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "TEST"), measure)).hasLevel(ERROR).hasValue("TEST"); + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "TEST2"), measure)).hasLevel(OK).hasValue("TEST"); + } + + @Test + public void testNotEquals_for_double() { + Metric metric = createMetric(Metric.MetricType.FLOAT); + Measure measure = Measure.newMeasureBuilder().create(10.2d, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, "10.2"), measure)).hasLevel(OK).hasValue(10.2d); + assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, "10.1"), measure)).hasLevel(ERROR).hasValue(10.2d); + } + + @Test + public void testNotEquals() { + Metric metric = createMetric(Metric.MetricType.STRING); + Measure measure = Measure.newMeasureBuilder().create("TEST"); + + assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, "TEST"), measure)).hasLevel(OK).hasValue("TEST"); + assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, "TEST2"), measure)).hasLevel(ERROR).hasValue("TEST"); + } + + @Test + public void testGreater() { + Metric metric = createMetric(Metric.MetricType.FLOAT); + Measure measure = Measure.newMeasureBuilder().create(10.2d, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, GREATER_THAN, "10.1"), measure)).hasLevel(ERROR).hasValue(10.2d); + assertThat(underTest.evaluate(createErrorCondition(metric, GREATER_THAN, "10.2"), measure)).hasLevel(OK).hasValue(10.2d); + assertThat(underTest.evaluate(createErrorCondition(metric, GREATER_THAN, "10.3"), measure)).hasLevel(OK).hasValue(10.2d); + } + + @Test + public void testSmaller() { + Metric metric = createMetric(Metric.MetricType.FLOAT); + Measure measure = Measure.newMeasureBuilder().create(10.2d, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, LESS_THAN, "10.1"), measure)).hasLevel(OK).hasValue(10.2d); + assertThat(underTest.evaluate(createErrorCondition(metric, LESS_THAN, "10.2"), measure)).hasLevel(OK).hasValue(10.2d); + assertThat(underTest.evaluate(createErrorCondition(metric, LESS_THAN, "10.3"), measure)).hasLevel(ERROR).hasValue(10.2d); + } + + @Test + public void testEquals_Percent() { + Metric metric = createMetric(Metric.MetricType.PERCENT); + Measure measure = Measure.newMeasureBuilder().create(10.2d, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.2"), measure)).hasLevel(ERROR).hasValue(10.2d); + } + + @Test + public void testEquals_Float() { + Metric metric = createMetric(Metric.MetricType.PERCENT); + Measure measure = Measure.newMeasureBuilder().create(10.2d, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.2"), measure)).hasLevel(ERROR).hasValue(10.2d); + } + + @Test + public void testEquals_Int() { + Metric metric = createMetric(Metric.MetricType.INT); + Measure measure = Measure.newMeasureBuilder().create(10, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10"), measure)).hasLevel(ERROR).hasValue(10); + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.2"), measure)).hasLevel(ERROR).hasValue(10); + } + + @Test + public void testEquals_Level() { + Metric metric = createMetric(Metric.MetricType.LEVEL); + Measure measure = Measure.newMeasureBuilder().create(ERROR); + + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, ERROR.name()), measure)).hasLevel(ERROR).hasValue(ERROR.name()); + + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, OK.name()), measure)).hasLevel(OK).hasValue(ERROR.name()); + } + + @Test + public void testNotEquals_Level() { + Metric metric = createMetric(Metric.MetricType.LEVEL); + Measure measure = Measure.newMeasureBuilder().create(ERROR); + + assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, OK.name()), measure)).hasLevel(ERROR).hasValue(ERROR.name()); + } + + @Test + public void testEquals_BOOL() { + Metric metric = createMetric(Metric.MetricType.BOOL); + Measure measure = Measure.newMeasureBuilder().create(false, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "1"), measure)).hasLevel(OK).hasValue(false); + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "0"), measure)).hasLevel(ERROR).hasValue(false); + } + + @Test + public void testNotEquals_BOOL() { + Metric metric = createMetric(Metric.MetricType.BOOL); + Measure measure = Measure.newMeasureBuilder().create(false, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, "1"), measure)).hasLevel(ERROR).hasValue(false); + assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, "0"), measure)).hasLevel(OK).hasValue(false); + } + + @Test + public void getLevel_throws_IEA_if_error_threshold_is_not_parsable_boolean() { + Metric metric = createMetric(Metric.MetricType.BOOL); + Measure measure = Measure.newMeasureBuilder().create(false, null); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Quality Gate: Unable to parse value 'polop' to compare against name"); + + underTest.evaluate(createErrorCondition(metric, EQUALS, "polop"), measure); + } + + @Test + public void testEquals_work_duration() { + Metric metric = createMetric(Metric.MetricType.WORK_DUR); + Measure measure = Measure.newMeasureBuilder().create(60l, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "60"), measure)).hasLevel(ERROR); + } + + @Test + public void getLevel_throws_IEA_if_error_threshold_is_not_parsable_long() { + Metric metric = createMetric(Metric.MetricType.WORK_DUR); + Measure measure = Measure.newMeasureBuilder().create(60l, null); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Quality Gate: Unable to parse value 'polop' to compare against name"); + + underTest.evaluate(createErrorCondition(metric, EQUALS, "polop"), measure); + } + + @Test + public void testErrorAndWarningLevel() { + Metric metric = createMetric(Metric.MetricType.FLOAT); + Measure measure = Measure.newMeasureBuilder().create(10.2d, null); + + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.2"), measure)).hasLevel(ERROR); + assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.1"), measure)).hasLevel(OK); + + assertThat(underTest.evaluate(new Condition(metric, EQUALS.getDbValue(), "10.3", "10.2", null), measure)).hasLevel(Measure.Level.WARN); + } + + @Test + public void testUnsupportedType() { + Metric metric = createMetric(Metric.MetricType.DATA); + Measure measure = Measure.newMeasureBuilder().create("3.14159265358"); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Conditions on MetricType DATA are not supported"); + + underTest.evaluate(createErrorCondition(metric, EQUALS, "1.60217657"), measure); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultAssert.java b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultAssert.java new file mode 100644 index 00000000000..2c55c2305f0 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultAssert.java @@ -0,0 +1,56 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.qualitygate; + +import java.util.Objects; +import org.assertj.core.api.AbstractAssert; +import org.sonar.server.computation.measure.Measure; + +class EvaluationResultAssert extends AbstractAssert { + + protected EvaluationResultAssert(EvaluationResult actual) { + super(actual, EvaluationResultAssert.class); + } + + public static EvaluationResultAssert assertThat(EvaluationResult actual) { + return new EvaluationResultAssert(actual); + } + + public EvaluationResultAssert hasLevel(Measure.Level expected) { + isNotNull(); + + // check condition + if (actual.getLevel() != expected) { + failWithMessage("Expected Level to be <%s> but was <%s>", expected, actual.getLevel()); + } + + return this; + } + + public EvaluationResultAssert hasValue(Comparable expected) { + isNotNull(); + + if (!Objects.equals(actual.getValue(), expected)) { + failWithMessage("Expected Value to be <%s> but was <%s>", expected, actual.getValue()); + } + + return this; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTest.java new file mode 100644 index 00000000000..8fed949783c --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTest.java @@ -0,0 +1,48 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.qualitygate; + +import org.junit.Test; +import org.sonar.server.computation.measure.Measure; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EvaluationResultTest { + @Test(expected = NullPointerException.class) + public void constructor_throws_NPE_if_Level_arg_is_null() { + new EvaluationResult(null, 11); + } + + @Test + public void verify_getters() { + String value = "toto"; + Measure.Level level = Measure.Level.OK; + + EvaluationResult evaluationResult = new EvaluationResult(level, value); + assertThat(evaluationResult.getLevel()).isEqualTo(level); + assertThat(evaluationResult.getValue()).isEqualTo(value); + } + + @Test + public void toString_is_defined() { + assertThat(new EvaluationResult(Measure.Level.OK, "toto").toString()) + .isEqualTo("EvaluationResult{level=OK, value=toto}"); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterTest.java new file mode 100644 index 00000000000..62823305f59 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterTest.java @@ -0,0 +1,183 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.qualitygate; + +import com.google.common.collect.ImmutableList; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sonar.api.i18n.I18n; +import org.sonar.api.utils.Durations; +import org.sonar.core.timemachine.Periods; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricImpl; +import org.sonar.server.computation.period.Period; +import org.sonar.server.computation.period.PeriodsHolderRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.computation.measure.Measure.Level.ERROR; +import static org.sonar.server.computation.measure.Measure.Level.WARN; + +@RunWith(DataProviderRunner.class) +public class EvaluationResultTextConverterTest { + private static final Metric INT_METRIC = new MetricImpl(1, "key", "int_metric_name", Metric.MetricType.INT); + private static final Metric SOME_VARIATION_METRIC = new MetricImpl(2, "new_variation_of_trololo", "variation_of_trololo_name", Metric.MetricType.INT); + private static final Condition EQ_10_CONDITION = new Condition(INT_METRIC, Condition.Operator.EQUALS.getDbValue(), "10", null, null); + private static final EvaluationResult OK_EVALUATION_RESULT = new EvaluationResult(Measure.Level.OK, null); + private static final String ERROR_THRESHOLD = "error_threshold"; + private static final String WARNING_THRESHOLD = "warning_threshold"; + private static final String SOME_MODE = "mode"; + private static final long SOME_SNAPSHOT_ID = 1l; + + @Rule + public PeriodsHolderRule periodsHolder = new PeriodsHolderRule(); + + private I18n i18n = mock(I18n.class); + private Durations durations = mock(Durations.class); + private Periods periods = mock(Periods.class); + private EvaluationResultTextConverter underTest = new EvaluationResultTextConverterImpl(i18n, durations, periods, periodsHolder); + + @Test(expected = NullPointerException.class) + public void evaluate_throws_NPE_if_Condition_arg_is_null() { + underTest.asText(null, OK_EVALUATION_RESULT); + } + + @Test(expected = NullPointerException.class) + public void evaluate_throws_NPE_if_EvaluationResult_arg_is_null() { + underTest.asText(EQ_10_CONDITION, null); + } + + @Test + public void evaluate_returns_null_if_EvaluationResult_has_level_OK() { + assertThat(underTest.asText(EQ_10_CONDITION, OK_EVALUATION_RESULT)).isNull(); + } + + @DataProvider + public static Object[][] all_operators_for_error_warning_levels() { + List res = new ArrayList<>(); + for (Condition.Operator operator : Condition.Operator.values()) { + for (Measure.Level level : ImmutableList.of(ERROR, WARN)) { + res.add(new Object[] {operator, level}); + } + } + return res.toArray(new Object[res.size()][2]); + } + + @Test + @UseDataProvider("all_operators_for_error_warning_levels") + public void evaluate_returns_msg_of_metric_plus_operator_plus_threshold_for_level_argument(Condition.Operator operator, Measure.Level level) { + String metricMsg = "int_metric_msg"; + + when(i18n.message(Locale.ENGLISH, "metric." + INT_METRIC.getKey() + ".name", INT_METRIC.getName())) + .thenReturn(metricMsg); + + Condition condition = new Condition(INT_METRIC, operator.getDbValue(), ERROR_THRESHOLD, WARNING_THRESHOLD, null); + + assertThat(underTest.asText(condition, new EvaluationResult(level, null))) + .isEqualTo(metricMsg + " " + toSign(operator) + " " + getThreshold(level)); + } + + private String getThreshold(Measure.Level level) { + return level == ERROR ? ERROR_THRESHOLD : WARNING_THRESHOLD; + } + + @Test + @UseDataProvider("all_operators_for_error_warning_levels") + public void evaluate_does_not_add_variation_if_metric_starts_with_variation_prefix_but_period_is_null(Condition.Operator operator, Measure.Level level) { + String metricMsg = "trololo_metric_msg"; + + when(i18n.message(Locale.ENGLISH, "metric." + SOME_VARIATION_METRIC.getKey() + ".name", SOME_VARIATION_METRIC.getName())) + .thenReturn(metricMsg); + + Condition condition = new Condition(SOME_VARIATION_METRIC, operator.getDbValue(), ERROR_THRESHOLD, WARNING_THRESHOLD, null); + + assertThat(underTest.asText(condition, new EvaluationResult(level, null))) + .isEqualTo(metricMsg + " " + toSign(operator) + " " + getThreshold(level)); + } + + @Test + @UseDataProvider("all_operators_for_error_warning_levels") + public void evaluate_adds_only_period_if_metric_starts_with_new_prefix(Condition.Operator operator, Measure.Level level) { + String metricMsg = "trololo_metric_msg"; + int periodIndex = 1; + String periodLabel = "periodLabel"; + + when(i18n.message(Locale.ENGLISH, "metric." + SOME_VARIATION_METRIC.getKey() + ".name", SOME_VARIATION_METRIC.getName())) + .thenReturn(metricMsg); + + Date date = new Date(); + Period period = new Period(periodIndex, SOME_MODE, null, date.getTime(), SOME_SNAPSHOT_ID); + periodsHolder.setPeriods(period); + when(periods.label(period.getMode(), period.getModeParameter(), date)).thenReturn(periodLabel); + + Condition condition = new Condition(SOME_VARIATION_METRIC, operator.getDbValue(), ERROR_THRESHOLD, WARNING_THRESHOLD, periodIndex); + + assertThat(underTest.asText(condition, new EvaluationResult(level, null))) + .isEqualTo(metricMsg + " " + toSign(operator) + " " + (getThreshold(level)) + " " + periodLabel); + } + + @Test + @UseDataProvider("all_operators_for_error_warning_levels") + public void evaluate_adds_variation_and_period_if_metric_does_not_starts_with_variation_prefix(Condition.Operator operator, Measure.Level level) { + String metricMsg = "trololo_metric_msg"; + String variationMsg = "_variation_"; + int periodIndex = 1; + String periodLabel = "periodLabel"; + + when(i18n.message(Locale.ENGLISH, "metric." + INT_METRIC.getKey() + ".name", INT_METRIC.getName())) + .thenReturn(metricMsg); + when(i18n.message(Locale.ENGLISH, "variation", "variation")).thenReturn(variationMsg); + + Date date = new Date(); + Period period = new Period(periodIndex, SOME_MODE, null, date.getTime(), SOME_SNAPSHOT_ID); + periodsHolder.setPeriods(period); + when(periods.label(period.getMode(), period.getModeParameter(), date)).thenReturn(periodLabel); + + Condition condition = new Condition(INT_METRIC, operator.getDbValue(), ERROR_THRESHOLD, WARNING_THRESHOLD, periodIndex); + + assertThat(underTest.asText(condition, new EvaluationResult(level, null))) + .isEqualTo(metricMsg + " " + variationMsg + " " + toSign(operator) + " " + (getThreshold(level)) + " " + periodLabel); + } + + private static String toSign(Condition.Operator operator) { + switch (operator) { + case EQUALS: + return "="; + case NOT_EQUALS: + return "!="; + case GREATER_THAN: + return ">"; + case LESS_THAN: + return "<"; + default: + throw new IllegalArgumentException("Unsupported operator"); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateMeasuresStepTest.java new file mode 100644 index 00000000000..ad1e98c4923 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateMeasuresStepTest.java @@ -0,0 +1,254 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.computation.step; + +import com.google.common.base.Optional; +import java.util.Collections; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.server.computation.batch.TreeRootHolderRule; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.DumbComponent; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.measure.qualitygatedetails.EvaluatedCondition; +import org.sonar.server.computation.measure.qualitygatedetails.QualityGateDetailsData; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricImpl; +import org.sonar.server.computation.metric.MetricRepository; +import org.sonar.server.computation.qualitygate.Condition; +import org.sonar.server.computation.qualitygate.EvaluationResult; +import org.sonar.server.computation.qualitygate.EvaluationResultTextConverter; +import org.sonar.server.computation.qualitygate.MutableQualityGateHolderRule; +import org.sonar.server.computation.qualitygate.QualityGate; + +import static com.google.common.collect.ImmutableList.of; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.server.computation.measure.Measure.Level.ERROR; +import static org.sonar.server.computation.measure.Measure.Level.OK; +import static org.sonar.server.computation.measure.Measure.Level.WARN; +import static org.sonar.server.computation.measure.MeasureAssert.assertThat; + +public class QualityGateMeasuresStepTest { + private static final MetricImpl INT_METRIC_1 = createIntMetric(1); + private static final MetricImpl INT_METRIC_2 = createIntMetric(2); + + private static final DumbComponent PROJECT_COMPONENT = DumbComponent.builder(Component.Type.PROJECT, 1).build(); + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + @Rule + public MutableQualityGateHolderRule qualityGateHolder = new MutableQualityGateHolderRule(); + + private static final Metric ALERT_STATUS_METRIC = mock(Metric.class); + private static final Metric QUALITY_GATE_DETAILS_METRIC = mock(Metric.class); + + private ArgumentCaptor alertStatusMeasureCaptor = ArgumentCaptor.forClass(Measure.class); + private ArgumentCaptor qgDetailsMeasureCaptor = ArgumentCaptor.forClass(Measure.class); + + private MeasureRepository measureRepository = mock(MeasureRepository.class); + private MetricRepository metricRepository = mock(MetricRepository.class); + private EvaluationResultTextConverter resultTextConverter = mock(EvaluationResultTextConverter.class); + private QualityGateMeasuresStep underTest = new QualityGateMeasuresStep(treeRootHolder, qualityGateHolder, measureRepository, metricRepository, resultTextConverter); + + @Before + public void setUp() throws Exception { + treeRootHolder.setRoot(PROJECT_COMPONENT); + + when(metricRepository.getByKey(CoreMetrics.ALERT_STATUS_KEY)).thenReturn(ALERT_STATUS_METRIC); + when(metricRepository.getByKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY)).thenReturn(QUALITY_GATE_DETAILS_METRIC); + + // mock response of asText to any argument to return the result of dumbResultTextAnswer method + when(resultTextConverter.asText(any(Condition.class), any(EvaluationResult.class))).thenAnswer(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + Condition condition = (Condition) invocation.getArguments()[0]; + EvaluationResult evaluationResult = (EvaluationResult) invocation.getArguments()[1]; + return dumbResultTextAnswer(condition, evaluationResult.getLevel(), evaluationResult.getValue()); + } + }); + } + + private static String dumbResultTextAnswer(Condition condition, Measure.Level level, Object value) { + return condition.toString() + level + value; + } + + @Test + public void no_measure_if_tree_has_no_project() { + DumbComponent notAProjectComponent = DumbComponent.builder(Component.Type.MODULE, 1).build(); + + treeRootHolder.setRoot(notAProjectComponent); + + underTest.execute(); + + verifyNoMoreInteractions(measureRepository); + } + + @Test + public void new_measures_are_created_even_if_there_is_no_rawMeasure_for_metric_of_condition() { + Condition equals2Condition = createEqualsCondition(INT_METRIC_1, "2", null); + qualityGateHolder.setQualityGate(new QualityGate("name", of(equals2Condition))); + when(measureRepository.getRawMeasure(PROJECT_COMPONENT, INT_METRIC_1)).thenReturn(Optional.absent()); + + underTest.execute(); + + verify(measureRepository).getRawMeasure(PROJECT_COMPONENT, INT_METRIC_1); + verify(measureRepository).add(same(PROJECT_COMPONENT), same(ALERT_STATUS_METRIC), alertStatusMeasureCaptor.capture()); + verify(measureRepository).add(same(PROJECT_COMPONENT), same(QUALITY_GATE_DETAILS_METRIC), qgDetailsMeasureCaptor.capture()); + verifyNoMoreInteractions(measureRepository); + + assertThat(alertStatusMeasureCaptor.getValue()) + .hasQualityGateLevel(OK) + .hasQualityGateText(""); + assertThat(qgDetailsMeasureCaptor.getValue()) + .hasValue(new QualityGateDetailsData(OK, Collections.emptyList()).toJson()); + } + + @Test + public void rawMeasure_is_updated_if_present_and_new_measures_are_created_if_project_has_measure_for_metric_of_condition() { + int rawValue = 1; + Condition equals2Condition = createEqualsCondition(INT_METRIC_1, "2", null); + Measure rawMeasure = Measure.newMeasureBuilder().create(rawValue, null); + + qualityGateHolder.setQualityGate(new QualityGate("name", of(equals2Condition))); + when(measureRepository.getRawMeasure(PROJECT_COMPONENT, INT_METRIC_1)).thenReturn(Optional.of(rawMeasure)); + + underTest.execute(); + + ArgumentCaptor equals2ConditionMeasureCaptor = ArgumentCaptor.forClass(Measure.class); + + verify(measureRepository).getRawMeasure(PROJECT_COMPONENT, INT_METRIC_1); + verify(measureRepository).update(same(PROJECT_COMPONENT), same(INT_METRIC_1), equals2ConditionMeasureCaptor.capture()); + verify(measureRepository).add(same(PROJECT_COMPONENT), same(ALERT_STATUS_METRIC), alertStatusMeasureCaptor.capture()); + verify(measureRepository).add(same(PROJECT_COMPONENT), same(QUALITY_GATE_DETAILS_METRIC), qgDetailsMeasureCaptor.capture()); + verifyNoMoreInteractions(measureRepository); + + assertThat(equals2ConditionMeasureCaptor.getValue()) + .hasQualityGateLevel(OK) + .hasQualityGateText(dumbResultTextAnswer(equals2Condition, OK, rawValue)); + assertThat(alertStatusMeasureCaptor.getValue()) + .hasQualityGateLevel(OK) + .hasQualityGateText(dumbResultTextAnswer(equals2Condition, OK, rawValue)); + assertThat(qgDetailsMeasureCaptor.getValue()) + .hasValue(new QualityGateDetailsData(OK, of(new EvaluatedCondition(equals2Condition, OK, rawValue))).toJson()); + } + + @Test + public void new_measures_have_ERROR_level_if_at_least_one_updated_measure_has_ERROR_level() { + int rawValue = 1; + Condition equals1ErrorCondition = createEqualsCondition(INT_METRIC_1, "1", null); + Condition equals1WarningCondition = createEqualsCondition(INT_METRIC_2, null, "1"); + Measure rawMeasure = Measure.newMeasureBuilder().create(rawValue, null); + + qualityGateHolder.setQualityGate(new QualityGate("name", of(equals1ErrorCondition, equals1WarningCondition))); + when(measureRepository.getRawMeasure(PROJECT_COMPONENT, INT_METRIC_1)).thenReturn(Optional.of(rawMeasure)); + when(measureRepository.getRawMeasure(PROJECT_COMPONENT, INT_METRIC_2)).thenReturn(Optional.of(rawMeasure)); + + underTest.execute(); + + ArgumentCaptor equals1ErrorConditionMeasureCaptor = ArgumentCaptor.forClass(Measure.class); + ArgumentCaptor equals1WarningConditionMeasureCaptor = ArgumentCaptor.forClass(Measure.class); + + verify(measureRepository).getRawMeasure(PROJECT_COMPONENT, INT_METRIC_1); + verify(measureRepository).getRawMeasure(PROJECT_COMPONENT, INT_METRIC_2); + verify(measureRepository).update(same(PROJECT_COMPONENT), same(INT_METRIC_1), equals1ErrorConditionMeasureCaptor.capture()); + verify(measureRepository).update(same(PROJECT_COMPONENT), same(INT_METRIC_2), equals1WarningConditionMeasureCaptor.capture()); + verify(measureRepository).add(same(PROJECT_COMPONENT), same(ALERT_STATUS_METRIC), alertStatusMeasureCaptor.capture()); + verify(measureRepository).add(same(PROJECT_COMPONENT), same(QUALITY_GATE_DETAILS_METRIC), qgDetailsMeasureCaptor.capture()); + verifyNoMoreInteractions(measureRepository); + + assertThat(equals1ErrorConditionMeasureCaptor.getValue()) + .hasQualityGateLevel(ERROR) + .hasQualityGateText(dumbResultTextAnswer(equals1ErrorCondition, ERROR, rawValue)); + assertThat(equals1WarningConditionMeasureCaptor.getValue()) + .hasQualityGateLevel(WARN) + .hasQualityGateText(dumbResultTextAnswer(equals1WarningCondition, WARN, rawValue)); + assertThat(alertStatusMeasureCaptor.getValue()) + .hasQualityGateLevel(ERROR) + .hasQualityGateText(dumbResultTextAnswer(equals1ErrorCondition, ERROR, rawValue) + ", " + + dumbResultTextAnswer(equals1WarningCondition, WARN, rawValue)); + assertThat(qgDetailsMeasureCaptor.getValue()) + .hasValue(new QualityGateDetailsData(ERROR, of( + new EvaluatedCondition(equals1ErrorCondition, ERROR, rawValue), + new EvaluatedCondition(equals1WarningCondition, WARN, rawValue) + )).toJson()); + } + + @Test + public void new_measures_have_WARNING_level_if_no_updated_measure_has_ERROR_level() { + int rawValue = 1; + Condition equals2Condition = createEqualsCondition(INT_METRIC_1, "2", null); + Condition equals1WarningCondition = createEqualsCondition(INT_METRIC_2, null, "1"); + Measure rawMeasure = Measure.newMeasureBuilder().create(rawValue, null); + + qualityGateHolder.setQualityGate(new QualityGate("name", of(equals2Condition, equals1WarningCondition))); + when(measureRepository.getRawMeasure(PROJECT_COMPONENT, INT_METRIC_1)).thenReturn(Optional.of(rawMeasure)); + when(measureRepository.getRawMeasure(PROJECT_COMPONENT, INT_METRIC_2)).thenReturn(Optional.of(rawMeasure)); + + underTest.execute(); + + ArgumentCaptor equals2ConditionMeasureCaptor = ArgumentCaptor.forClass(Measure.class); + ArgumentCaptor equals1WarningConditionMeasureCaptor = ArgumentCaptor.forClass(Measure.class); + + verify(measureRepository).getRawMeasure(PROJECT_COMPONENT, INT_METRIC_1); + verify(measureRepository).getRawMeasure(PROJECT_COMPONENT, INT_METRIC_2); + verify(measureRepository).update(same(PROJECT_COMPONENT), same(INT_METRIC_1), equals2ConditionMeasureCaptor.capture()); + verify(measureRepository).update(same(PROJECT_COMPONENT), same(INT_METRIC_2), equals1WarningConditionMeasureCaptor.capture()); + verify(measureRepository).add(same(PROJECT_COMPONENT), same(ALERT_STATUS_METRIC), alertStatusMeasureCaptor.capture()); + verify(measureRepository).add(same(PROJECT_COMPONENT), same(QUALITY_GATE_DETAILS_METRIC), qgDetailsMeasureCaptor.capture()); + verifyNoMoreInteractions(measureRepository); + + assertThat(equals2ConditionMeasureCaptor.getValue()) + .hasQualityGateLevel(OK) + .hasQualityGateText(dumbResultTextAnswer(equals2Condition, OK, rawValue)); + assertThat(equals1WarningConditionMeasureCaptor.getValue()) + .hasQualityGateLevel(WARN) + .hasQualityGateText(dumbResultTextAnswer(equals1WarningCondition, WARN, rawValue)); + assertThat(alertStatusMeasureCaptor.getValue()) + .hasQualityGateLevel(WARN) + .hasQualityGateText(dumbResultTextAnswer(equals2Condition, OK, rawValue) + ", " + + dumbResultTextAnswer(equals1WarningCondition, WARN, rawValue)); + assertThat(qgDetailsMeasureCaptor.getValue()) + .hasValue(new QualityGateDetailsData(WARN, of( + new EvaluatedCondition(equals2Condition, OK, rawValue), + new EvaluatedCondition(equals1WarningCondition, WARN, rawValue) + )).toJson()); + } + + private static Condition createEqualsCondition(Metric metric, @Nullable String errorThreshold, @Nullable String warningThreshold) { + return new Condition(metric, Condition.Operator.EQUALS.getDbValue(), errorThreshold, warningThreshold, null); + } + + private static MetricImpl createIntMetric(int index) { + return new MetricImpl(index, "metricKey" + index, "metricName" + index, Metric.MetricType.INT); + } + +} -- 2.39.5