aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2015-06-08 16:56:33 +0200
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2015-06-19 15:42:25 +0200
commitdae42b839197f656db1eff35c7703739e2f3dcd1 (patch)
tree067b407f6bf8c1c1fbda299113bb718e1a570abd
parent9f24ecb3d66d0640dde65e42c347bf377d161639 (diff)
downloadsonarqube-dae42b839197f656db1eff35c7703739e2f3dcd1.tar.gz
sonarqube-dae42b839197f656db1eff35c7703739e2f3dcd1.zip
SONAR-6620 step to set QualityGate on measures and create QG measures
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedCondition.java52
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsData.java75
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/package-info.java24
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/Condition.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/ConditionEvaluator.java193
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResult.java57
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverter.java27
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterImpl.java104
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityGateMeasuresStep.java165
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedConditionTest.java72
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsDataTest.java96
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/ConditionEvaluatorTest.java248
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultAssert.java56
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTest.java48
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterTest.java183
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateMeasuresStepTest.java254
18 files changed, 1664 insertions, 3 deletions
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<EvaluatedCondition> conditions;
+
+ public QualityGateDetailsData(Measure.Level level, Iterable<EvaluatedCondition> 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<Double> 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<EvaluationResult> 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<Integer> 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<Double> 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<Double> 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<Condition.Operator, String> 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:
+ * <ul>
+ * <li>updates the QualityGateStatus of all the project's measures for the metrics of the conditions of the current
+ * QualityGate (retrieved from {@link QualityGateHolder})</li>
+ * <li>computes the measures on the project for metrics {@link CoreMetrics#QUALITY_GATE_DETAILS_KEY} and
+ * {@link CoreMetrics#ALERT_STATUS_KEY}</li>
+ * </ul>
+ */
+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<Condition> conditions, QualityGateDetailsDataBuilder builder) {
+ for (Condition condition : conditions) {
+ Optional<Measure> 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<String> labels = new ArrayList<>();
+ private List<EvaluatedCondition> evaluatedConditions = new ArrayList<>();
+
+ public Measure.Level getGlobalLevel() {
+ return globalLevel;
+ }
+
+ public void addLabel(@Nullable String label) {
+ if (StringUtils.isNotBlank(label)) {
+ labels.add(label);
+ }
+ }
+
+ public List<String> 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<EvaluatedCondition> 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.<EvaluatedCondition>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.<EvaluatedCondition>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<EvaluatedCondition> 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<EvaluationResultAssert, EvaluationResult> {
+
+ 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<Object[]> 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<Measure> alertStatusMeasureCaptor = ArgumentCaptor.forClass(Measure.class);
+ private ArgumentCaptor<Measure> 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<String>() {
+ @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.<Measure>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.<EvaluatedCondition>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<Measure> 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<Measure> equals1ErrorConditionMeasureCaptor = ArgumentCaptor.forClass(Measure.class);
+ ArgumentCaptor<Measure> 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<Measure> equals2ConditionMeasureCaptor = ArgumentCaptor.forClass(Measure.class);
+ ArgumentCaptor<Measure> 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);
+ }
+
+}