]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6620 step to set QualityGate on measures and create QG measures
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Mon, 8 Jun 2015 14:56:33 +0000 (16:56 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 19 Jun 2015 13:42:25 +0000 (15:42 +0200)
18 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedCondition.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsData.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/measure/qualitygatedetails/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/Condition.java
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/ConditionEvaluator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResult.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverter.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityGateMeasuresStep.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/EvaluatedConditionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/measure/qualitygatedetails/QualityGateDetailsDataTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/ConditionEvaluatorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultAssert.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/EvaluationResultTextConverterTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateMeasuresStepTest.java [new file with mode: 0644]

index ef1775c72931bd3d7686c68fac4358b5b22be013..53fbde2c698d0a407ad90e0ab770667f0c72800f 100644 (file)
@@ -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 (file)
index 0000000..b97e661
--- /dev/null
@@ -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 (file)
index 0000000..c728677
--- /dev/null
@@ -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 (file)
index 0000000..a6c0581
--- /dev/null
@@ -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;
index 826edf08a0ffbbd891ceb47eef9d451577f77ff2..74f8d90670b7939dc21491336f590a37a191f4bc 100644 (file)
@@ -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 (file)
index 0000000..2bf2c46
--- /dev/null
@@ -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 (file)
index 0000000..a6ccaec
--- /dev/null
@@ -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 (file)
index 0000000..3afe462
--- /dev/null
@@ -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 (file)
index 0000000..64cbf21
--- /dev/null
@@ -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);
+  }
+}
index 4c74a56c8723a9fcf230edae01b83a8802588612..081ae51c5e50ac9283720a9a7a2c8cf1beffa424 100644 (file)
@@ -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 (file)
index 0000000..0b98ab3
--- /dev/null
@@ -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 (file)
index 0000000..e12c394
--- /dev/null
@@ -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 (file)
index 0000000..3975f77
--- /dev/null
@@ -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 (file)
index 0000000..c8aa67f
--- /dev/null
@@ -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 (file)
index 0000000..2c55c23
--- /dev/null
@@ -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 (file)
index 0000000..8fed949
--- /dev/null
@@ -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 (file)
index 0000000..6282330
--- /dev/null
@@ -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 (file)
index 0000000..ad1e98c
--- /dev/null
@@ -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);
+  }
+
+}