]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7488 support PostProjectAnalysisTask in report CE task
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 1 Apr 2016 16:33:08 +0000 (18:33 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Wed, 6 Apr 2016 13:42:53 +0000 (15:42 +0200)
29 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java
server/sonar-server/src/main/java/org/sonar/server/computation/posttask/CeTaskImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/posttask/ConditionImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/posttask/ConditionToCondition.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/posttask/PostProjectAnalysisTasksExecutor.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/posttask/ProjectImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/posttask/QualityGateImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/posttask/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/ConditionStatus.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/MutableQualityGateStatusHolder.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGate.java
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGateServiceImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGateStatus.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGateStatusHolder.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGateStatusHolderImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/QualityGateMeasuresStep.java
server/sonar-server/src/test/java/org/sonar/server/computation/posttask/CeTaskImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/posttask/ConditionImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/posttask/ConditionToConditionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/posttask/PostProjectAnalysisTasksExecutorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/posttask/ProjectImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/posttask/QualityGateImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/ConditionStatusTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/MutableQualityGateStatusHolderRule.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/QualityGateHolderImplTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/QualityGateServiceImplTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/QualityGateStatusHolderImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateLoadingStepTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateMeasuresStepTest.java

index 99a8e0a3078d07822b04469ac7137058194180a6..e78c44c8e79169e85d567f665be86861b2e1b5a3 100644 (file)
@@ -77,9 +77,11 @@ import org.sonar.server.computation.measure.MeasureRepositoryImpl;
 import org.sonar.server.computation.measure.MeasureToMeasureDto;
 import org.sonar.server.computation.metric.MetricModule;
 import org.sonar.server.computation.period.PeriodsHolderImpl;
+import org.sonar.server.computation.posttask.PostProjectAnalysisTasksExecutor;
 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.qualitygate.QualityGateStatusHolderImpl;
 import org.sonar.server.computation.qualitymodel.NewQualityModelMeasuresVisitor;
 import org.sonar.server.computation.qualitymodel.QualityModelMeasuresVisitor;
 import org.sonar.server.computation.qualitymodel.RatingSettings;
@@ -124,6 +126,7 @@ public final class ReportComputeEngineContainerPopulator implements ContainerPop
    */
   private static List componentClasses() {
     return Arrays.asList(
+      PostProjectAnalysisTasksExecutor.class,
       ComputationStepExecutor.class,
 
       // File System
@@ -138,6 +141,7 @@ public final class ReportComputeEngineContainerPopulator implements ContainerPop
       TreeRootHolderImpl.class,
       PeriodsHolderImpl.class,
       QualityGateHolderImpl.class,
+      QualityGateStatusHolderImpl.class,
       RatingSettings.class,
       ActiveRulesHolderImpl.class,
       MeasureComputersHolderImpl.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/CeTaskImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/CeTaskImpl.java
new file mode 100644 (file)
index 0000000..c95e52b
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ce.posttask.CeTask;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+class CeTaskImpl implements CeTask {
+  private final String id;
+  private final Status status;
+
+  CeTaskImpl(String id, Status status) {
+    this.id = requireNonNull(id, "uuid can not be null");
+    this.status = requireNonNull(status, "status can not be null");
+  }
+
+  @Override
+  public String getId() {
+    return id;
+  }
+
+  @Override
+  public Status getStatus() {
+    return status;
+  }
+
+  @Override
+  public String toString() {
+    return "CeTaskImpl{" +
+      "id='" + id + '\'' +
+      ", status=" + status +
+      '}';
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/ConditionImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/ConditionImpl.java
new file mode 100644 (file)
index 0000000..8cd92a9
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ce.posttask.QualityGate;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.api.ce.posttask.QualityGate.EvaluationStatus.NO_VALUE;
+
+@Immutable
+class ConditionImpl implements QualityGate.Condition {
+  private final QualityGate.EvaluationStatus status;
+  private final String metricKey;
+  private final QualityGate.Operator operator;
+  @CheckForNull
+  private final String errorThreshold;
+  @CheckForNull
+  private final String warningThreshold;
+  private final boolean onLeakPeriod;
+  @CheckForNull
+  private final String value;
+
+  private ConditionImpl(Builder builder) {
+    requireNonNull(builder.status, "status can not be null");
+    requireNonNull(builder.metricKey, "metricKey can not be null");
+    requireNonNull(builder.operator, "operator can not be null");
+    verifyThresholds(builder);
+    verifyValue(builder);
+
+    this.status = builder.status;
+    this.metricKey = builder.metricKey;
+    this.operator = builder.operator;
+    this.errorThreshold = builder.errorThreshold;
+    this.warningThreshold = builder.warningThreshold;
+    this.onLeakPeriod = builder.onLeakPeriod;
+    this.value = builder.value;
+  }
+
+  private static void verifyThresholds(Builder builder) {
+    checkArgument(
+      builder.errorThreshold != null || builder.warningThreshold != null,
+      "At least one of errorThreshold and warningThreshold must be non null");
+  }
+
+  private static void verifyValue(Builder builder) {
+    if (builder.status == NO_VALUE) {
+      checkArgument(builder.value == null, "value must be null when status is %s", NO_VALUE);
+    } else {
+      checkArgument(builder.value != null, "value can not be null when status is not %s", NO_VALUE);
+    }
+  }
+
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private String metricKey;
+    private QualityGate.Operator operator;
+    @CheckForNull
+    private String errorThreshold;
+    @CheckForNull
+    private String warningThreshold;
+    private boolean onLeakPeriod;
+    @CheckForNull
+    private String value;
+    private QualityGate.EvaluationStatus status;
+
+    private Builder() {
+      // enforce use of static method
+    }
+
+    public Builder setMetricKey(String metricKey) {
+      this.metricKey = metricKey;
+      return this;
+    }
+
+    public Builder setOperator(QualityGate.Operator operator) {
+      this.operator = operator;
+      return this;
+    }
+
+    public Builder setErrorThreshold(String errorThreshold) {
+      this.errorThreshold = errorThreshold;
+      return this;
+    }
+
+    public Builder setWarningThreshold(String warningThreshold) {
+      this.warningThreshold = warningThreshold;
+      return this;
+    }
+
+    public Builder setOnLeakPeriod(boolean onLeakPeriod) {
+      this.onLeakPeriod = onLeakPeriod;
+      return this;
+    }
+
+    public Builder setValue(String value) {
+      this.value = value;
+      return this;
+    }
+
+    public Builder setStatus(QualityGate.EvaluationStatus status) {
+      this.status = status;
+      return this;
+    }
+
+    public ConditionImpl build() {
+      return new ConditionImpl(this);
+    }
+  }
+
+  @Override
+  public QualityGate.EvaluationStatus getStatus() {
+    return status;
+  }
+
+  @Override
+  public String getMetricKey() {
+    return metricKey;
+  }
+
+  @Override
+  public QualityGate.Operator getOperator() {
+    return operator;
+  }
+
+  @Override
+  public String getErrorThreshold() {
+    return errorThreshold;
+  }
+
+  @Override
+  public String getWarningThreshold() {
+    return warningThreshold;
+  }
+
+  @Override
+  public boolean isOnLeakPeriod() {
+    return onLeakPeriod;
+  }
+
+  @Override
+  public String getValue() {
+    checkState(status != NO_VALUE, "There is no value when status is " + NO_VALUE);
+
+    return value;
+  }
+
+  @Override
+  public String toString() {
+    return "ConditionImpl{" +
+      "status=" + status +
+      ", metricKey='" + metricKey + '\'' +
+      ", operator=" + operator +
+      ", errorThreshold='" + errorThreshold + '\'' +
+      ", warningThreshold='" + warningThreshold + '\'' +
+      ", onLeakPeriod=" + onLeakPeriod +
+      ", value='" + value + '\'' +
+      '}';
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/ConditionToCondition.java b/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/ConditionToCondition.java
new file mode 100644 (file)
index 0000000..55df858
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import com.google.common.base.Function;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.sonar.api.ce.posttask.QualityGate;
+import org.sonar.server.computation.qualitygate.Condition;
+import org.sonar.server.computation.qualitygate.ConditionStatus;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+
+/**
+ * Converts a {@link Condition} from the Compute Engine internal API to a {@link org.sonar.api.ce.posttask.QualityGate.Condition}
+ * of the public API.
+ */
+class ConditionToCondition implements Function<Condition, QualityGate.Condition> {
+  private final ConditionImpl.Builder builder = ConditionImpl.newBuilder();
+  private final Map<Condition, ConditionStatus> statusPerConditions;
+
+  public ConditionToCondition(Map<Condition, ConditionStatus> statusPerConditions) {
+    this.statusPerConditions = statusPerConditions;
+  }
+
+  @Override
+  @Nonnull
+  public QualityGate.Condition apply(@Nonnull Condition input) {
+    String metricKey = input.getMetric().getKey();
+    ConditionStatus conditionStatus = statusPerConditions.get(input);
+    checkState(conditionStatus != null, "Missing ConditionStatus for condition on metric key %s", metricKey);
+    return builder
+      .setStatus(convert(conditionStatus.getStatus()))
+      .setMetricKey(metricKey)
+      .setOperator(convert(input.getOperator()))
+      .setErrorThreshold(input.getErrorThreshold())
+      .setWarningThreshold(input.getWarningThreshold())
+      .setOnLeakPeriod(input.getPeriod() != null)
+      .setValue(conditionStatus.getValue())
+      .build();
+  }
+
+  private static QualityGate.EvaluationStatus convert(ConditionStatus.EvaluationStatus status) {
+    switch (status) {
+      case NO_VALUE:
+        return QualityGate.EvaluationStatus.NO_VALUE;
+      case OK:
+        return QualityGate.EvaluationStatus.OK;
+      case WARN:
+        return QualityGate.EvaluationStatus.WARN;
+      case ERROR:
+        return QualityGate.EvaluationStatus.ERROR;
+      default:
+        throw new IllegalArgumentException(format(
+          "Unsupported value '%s' of ConditionStatus.EvaluationStatus can not be converted to QualityGate.EvaluationStatus",
+          status));
+    }
+  }
+
+  private static QualityGate.Operator convert(Condition.Operator operator) {
+    switch (operator) {
+      case EQUALS:
+        return QualityGate.Operator.EQUALS;
+      case NOT_EQUALS:
+        return QualityGate.Operator.NOT_EQUALS;
+      case GREATER_THAN:
+        return QualityGate.Operator.GREATER_THAN;
+      case LESS_THAN:
+        return QualityGate.Operator.LESS_THAN;
+      default:
+        throw new IllegalArgumentException(format(
+          "Unsupported value '%s' of Condition.Operation can not be converted to QualityGate.Operator",
+          operator));
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/PostProjectAnalysisTasksExecutor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/PostProjectAnalysisTasksExecutor.java
new file mode 100644 (file)
index 0000000..d89b571
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import com.google.common.base.Optional;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.ce.posttask.CeTask;
+import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
+import org.sonar.api.ce.posttask.Project;
+import org.sonar.api.ce.posttask.QualityGate;
+import org.sonar.server.computation.analysis.AnalysisMetadataHolder;
+import org.sonar.server.computation.qualitygate.Condition;
+import org.sonar.server.computation.qualitygate.ConditionStatus;
+import org.sonar.server.computation.qualitygate.QualityGateHolder;
+import org.sonar.server.computation.qualitygate.QualityGateStatus;
+import org.sonar.server.computation.qualitygate.QualityGateStatusHolder;
+import org.sonar.server.computation.step.ComputationStepExecutor;
+
+import static com.google.common.collect.FluentIterable.from;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.api.ce.posttask.CeTask.Status.FAILED;
+import static org.sonar.api.ce.posttask.CeTask.Status.SUCCESS;
+
+/**
+ * Responsible for calling {@link PostProjectAnalysisTask} implementations (if any).
+ */
+public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor.Listener {
+  private static final PostProjectAnalysisTask[] NO_POST_PROJECT_ANALYSIS_TASKS = new PostProjectAnalysisTask[0];
+
+  private final org.sonar.ce.queue.CeTask ceTask;
+  private final AnalysisMetadataHolder analysisMetadataHolder;
+  private final QualityGateHolder qualityGateHolder;
+  private final QualityGateStatusHolder qualityGateStatusHolder;
+  private final PostProjectAnalysisTask[] postProjectAnalysisTasks;
+
+  /**
+   * Constructor used by Pico when there is no {@link PostProjectAnalysisTask} in the container.
+   */
+  public PostProjectAnalysisTasksExecutor(org.sonar.ce.queue.CeTask ceTask,
+    AnalysisMetadataHolder analysisMetadataHolder,
+    QualityGateHolder qualityGateHolder, QualityGateStatusHolder qualityGateStatusHolder) {
+    this(ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, null);
+  }
+
+  public PostProjectAnalysisTasksExecutor(org.sonar.ce.queue.CeTask ceTask,
+    AnalysisMetadataHolder analysisMetadataHolder,
+    QualityGateHolder qualityGateHolder, QualityGateStatusHolder qualityGateStatusHolder,
+    @Nullable PostProjectAnalysisTask[] postProjectAnalysisTasks) {
+    this.analysisMetadataHolder = analysisMetadataHolder;
+    this.qualityGateHolder = qualityGateHolder;
+    this.qualityGateStatusHolder = qualityGateStatusHolder;
+    this.ceTask = ceTask;
+    this.postProjectAnalysisTasks = postProjectAnalysisTasks == null ? NO_POST_PROJECT_ANALYSIS_TASKS : postProjectAnalysisTasks;
+  }
+
+  @Override
+  public void finished(boolean allStepsExecuted) {
+    if (postProjectAnalysisTasks.length == 0) {
+      return;
+    }
+
+    ProjectAnalysis projectAnalysis = createProjectAnalysis(allStepsExecuted ? SUCCESS : FAILED);
+    for (PostProjectAnalysisTask postProjectAnalysisTask : postProjectAnalysisTasks) {
+      postProjectAnalysisTask.finished(projectAnalysis);
+    }
+  }
+
+  private ProjectAnalysis createProjectAnalysis(CeTask.Status status) {
+    return new ProjectAnalysis(
+      new CeTaskImpl(this.ceTask.getUuid(), status),
+      createProject(this.ceTask),
+      getAnalysisDate(),
+      status == SUCCESS ? createQualityGate(this.qualityGateHolder) : null);
+  }
+
+  private static Project createProject(org.sonar.ce.queue.CeTask ceTask) {
+    return new ProjectImpl(
+      ceTask.getComponentUuid(),
+      ceTask.getComponentKey(),
+      ceTask.getComponentName());
+  }
+
+  private Date getAnalysisDate() {
+    return new Date(this.analysisMetadataHolder.getAnalysisDate());
+  }
+
+  @CheckForNull
+  private QualityGateImpl createQualityGate(QualityGateHolder qualityGateHolder) {
+    Optional<org.sonar.server.computation.qualitygate.QualityGate> qualityGateOptional = qualityGateHolder.getQualityGate();
+    if (qualityGateOptional.isPresent()) {
+      org.sonar.server.computation.qualitygate.QualityGate qualityGate = qualityGateOptional.get();
+
+      return new QualityGateImpl(
+        String.valueOf(qualityGate.getId()),
+        qualityGate.getName(),
+        convert(qualityGateStatusHolder.getStatus()),
+        convert(qualityGate.getConditions(), qualityGateStatusHolder.getStatusPerConditions()));
+    }
+    return null;
+  }
+
+  private static QualityGate.Status convert(QualityGateStatus status) {
+    switch (status) {
+      case OK:
+        return QualityGate.Status.OK;
+      case WARN:
+        return QualityGate.Status.WARN;
+      case ERROR:
+        return QualityGate.Status.ERROR;
+      default:
+        throw new IllegalArgumentException(format(
+          "Unsupported value '%s' of QualityGateStatus can not be converted to QualityGate.Status",
+          status));
+    }
+  }
+
+  private static Collection<QualityGate.Condition> convert(Set<Condition> conditions, Map<Condition, ConditionStatus> statusPerConditions) {
+    return from(conditions)
+      .transform(new ConditionToCondition(statusPerConditions))
+      .toList();
+  }
+
+  private static class ProjectAnalysis implements PostProjectAnalysisTask.ProjectAnalysis {
+    private final CeTask ceTask;
+    private final Project project;
+    private final Date date;
+    @CheckForNull
+    private final QualityGate qualityGate;
+
+    private ProjectAnalysis(CeTask ceTask, Project project, Date date, @Nullable QualityGate qualityGate) {
+      this.ceTask = requireNonNull(ceTask, "ceTask can not be null");
+      this.project = requireNonNull(project, "project can not be null");
+      this.date = requireNonNull(date, "date can not be null");
+      this.qualityGate = qualityGate;
+    }
+
+    @Override
+    public CeTask getCeTask() {
+      return ceTask;
+    }
+
+    @Override
+    public Project getProject() {
+      return project;
+    }
+
+    @Override
+    @CheckForNull
+    public QualityGate getQualityGate() {
+      return qualityGate;
+    }
+
+    @Override
+    public Date getDate() {
+      return date;
+    }
+
+    @Override
+    public String toString() {
+      return "ProjectAnalysis{" +
+        "ceTask=" + ceTask +
+        ", project=" + project +
+        ", date=" + date +
+        ", qualityGate=" + qualityGate +
+        '}';
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/ProjectImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/ProjectImpl.java
new file mode 100644 (file)
index 0000000..128a665
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ce.posttask.Project;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+class ProjectImpl implements Project {
+  private final String uuid;
+  private final String key;
+  private final String name;
+
+  ProjectImpl(String uuid, String key, String name) {
+    this.uuid = requireNonNull(uuid, "uuid can not be null");
+    this.key = requireNonNull(key, "key can not be null");
+    this.name = requireNonNull(name, "name can not be null");
+  }
+
+  @Override
+  public String getUuid() {
+    return uuid;
+  }
+
+  @Override
+  public String getKey() {
+    return key;
+  }
+
+  @Override
+  public String getName() {
+    return name;
+  }
+
+  @Override
+  public String toString() {
+    return "ProjectImpl{" +
+      "uuid='" + uuid + '\'' +
+      ", key='" + key + '\'' +
+      ", name='" + name + '\'' +
+      '}';
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/QualityGateImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/QualityGateImpl.java
new file mode 100644 (file)
index 0000000..376327f
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ce.posttask.QualityGate;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+class QualityGateImpl implements QualityGate {
+  private final String id;
+  private final String name;
+  private final Status status;
+  private final Collection<Condition> conditions;
+
+  public QualityGateImpl(String id, String name, Status status, Collection<Condition> conditions) {
+    this.id = requireNonNull(id, "id can not be null");
+    this.name = requireNonNull(name, "name can not be null");
+    this.status = requireNonNull(status, "status can not be null");
+    this.conditions = ImmutableList.copyOf(requireNonNull(conditions, "conditions can not be null"));
+  }
+
+  @Override
+  public String getId() {
+    return id;
+  }
+
+  @Override
+  public String getName() {
+    return name;
+  }
+
+  @Override
+  public Status getStatus() {
+    return status;
+  }
+
+  @Override
+  public Collection<Condition> getConditions() {
+    return conditions;
+  }
+
+  @Override
+  public String toString() {
+    return "QualityGateImpl{" +
+      "id='" + id + '\'' +
+      ", name='" + name + '\'' +
+      ", status=" + status +
+      ", conditions=" + conditions +
+      '}';
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/posttask/package-info.java
new file mode 100644 (file)
index 0000000..a01c923
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.computation.posttask;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/ConditionStatus.java b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/ConditionStatus.java
new file mode 100644 (file)
index 0000000..8e41bb5
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.qualitygate;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class ConditionStatus {
+  public static final ConditionStatus NO_VALUE_STATUS = new ConditionStatus(EvaluationStatus.NO_VALUE, null);
+
+  private final EvaluationStatus status;
+  @CheckForNull
+  private final String value;
+
+  private ConditionStatus(EvaluationStatus status, @Nullable String value) {
+    this.status = requireNonNull(status, "status can not be null");
+    this.value = value;
+  }
+
+  public static ConditionStatus create(EvaluationStatus status, String value) {
+    requireNonNull(status, "status can not be null");
+    checkArgument(status != EvaluationStatus.NO_VALUE, "EvaluationStatus 'NO_VALUE' can not be used with this method, use constant ConditionStatus.NO_VALUE_STATUS instead.");
+    requireNonNull(value, "value can not be null");
+    return new ConditionStatus(status, value);
+  }
+
+  public EvaluationStatus getStatus() {
+    return status;
+  }
+
+  /**
+   * @return {@code null} when {@link #getStatus()} is {@link EvaluationStatus#NO_VALUE}, otherwise non {@code null}
+   */
+  @CheckForNull
+  public String getValue() {
+    return value;
+  }
+
+  @Override
+  public String toString() {
+    return "ConditionStatus{" +
+      "status=" + status +
+      ", value='" + value + '\'' +
+      '}';
+  }
+
+  public enum EvaluationStatus {
+    NO_VALUE, OK, WARN, ERROR
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/MutableQualityGateStatusHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/MutableQualityGateStatusHolder.java
new file mode 100644 (file)
index 0000000..3e0067c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.qualitygate;
+
+import java.util.Map;
+
+public interface MutableQualityGateStatusHolder extends QualityGateStatusHolder {
+  /**
+   * Sets the status of the quality gate and its conditions in the holder.
+   *
+   * @throws NullPointerException if either {@code globalStatus} or {@code statusPerCondition} is {@code null}
+   * @throws IllegalArgumentException if {@code statusPerCondition} is empty
+   * @throws IllegalStateException if the status has already been set in the holder
+   */
+  void setStatus(QualityGateStatus globalStatus, Map<Condition, ConditionStatus> statusPerCondition);
+}
index 565c388a10b5e2ef4f2f872bbe896f94a2a064cd..fe4dcfad1114409813dbc9c1d2a99a002fc52644 100644 (file)
@@ -28,14 +28,20 @@ import static com.google.common.collect.FluentIterable.from;
 
 @Immutable
 public class QualityGate {
+  private final long id;
   private final String name;
   private final Set<Condition> conditions;
 
-  public QualityGate(String name, Iterable<Condition> conditions) {
+  public QualityGate(long id, String name, Iterable<Condition> conditions) {
+    this.id = id;
     this.name = Objects.requireNonNull(name);
     this.conditions = from(conditions).filter(notNull()).toSet();
   }
 
+  public long getId() {
+    return id;
+  }
+
   public String getName() {
     return name;
   }
index e419e9bc1b6b5d87f9549a2b9ba6540d1a4814de..5d6e715606323401c864ee6a83e48725af63a397 100644 (file)
@@ -61,7 +61,7 @@ public class QualityGateServiceImpl implements QualityGateService {
   private QualityGate toQualityGate(QualityGateDto qualityGateDto) {
     Iterable<Condition> conditions = from(conditionDao.selectForQualityGate(qualityGateDto.getId())).transform(conditionDtoToBean);
 
-    return new QualityGate(qualityGateDto.getName(), conditions);
+    return new QualityGate(qualityGateDto.getId(), qualityGateDto.getName(), conditions);
   }
 
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGateStatus.java b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGateStatus.java
new file mode 100644 (file)
index 0000000..7517e95
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.qualitygate;
+
+public enum QualityGateStatus {
+  OK, WARN, ERROR
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGateStatusHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGateStatusHolder.java
new file mode 100644 (file)
index 0000000..cf212aa
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.qualitygate;
+
+import java.util.Map;
+
+public interface QualityGateStatusHolder {
+
+  /**
+   * The global status of the quality gate (if there is a quality gate on the project).
+   *
+   * @throws IllegalStateException if status has not yet been set in the holder
+   * @see QualityGateHolder#getQualityGate()
+   */
+  QualityGateStatus getStatus();
+
+  /**
+   * The status per condition of the quality gate (if there is a quality gate on the project).
+   *
+   * @throws IllegalStateException if status has not yet been set in the holder
+   * @see QualityGateHolder#getQualityGate()
+   */
+  Map<Condition, ConditionStatus> getStatusPerConditions();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGateStatusHolderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/qualitygate/QualityGateStatusHolderImpl.java
new file mode 100644 (file)
index 0000000..92a89dd
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.qualitygate;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+public class QualityGateStatusHolderImpl implements MutableQualityGateStatusHolder {
+  @CheckForNull
+  private QualityGateStatus status;
+  @CheckForNull
+  private Map<Condition, ConditionStatus> statusPerCondition;
+
+  @Override
+  public QualityGateStatus getStatus() {
+    checkInitialized();
+
+    return status;
+  }
+
+  @Override
+  public Map<Condition, ConditionStatus> getStatusPerConditions() {
+    checkInitialized();
+
+    return statusPerCondition;
+  }
+
+  private void checkInitialized() {
+    checkState(status != null, "Quality gate status has not been set yet");
+  }
+
+  @Override
+  public void setStatus(QualityGateStatus globalStatus, Map<Condition, ConditionStatus> statusPerCondition) {
+    checkState(status == null, "Quality gate status has already been set in the holder");
+    requireNonNull(globalStatus, "global status can not be null");
+    requireNonNull(statusPerCondition, "status per condition can not be null");
+
+    this.status = globalStatus;
+    this.statusPerCondition = ImmutableMap.copyOf(statusPerCondition);
+  }
+}
index 4fb734d7eb61f8d333f9ffe88d3d5e74418f2821..affc20eec8a59e85095ab9dff4bc758e58a2c768 100644 (file)
  */
 package org.sonar.server.computation.step;
 
+import com.google.common.base.Function;
 import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.measures.CoreMetrics;
@@ -40,12 +44,18 @@ 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.ConditionStatus;
 import org.sonar.server.computation.qualitygate.EvaluationResult;
 import org.sonar.server.computation.qualitygate.EvaluationResultTextConverter;
+import org.sonar.server.computation.qualitygate.MutableQualityGateStatusHolder;
 import org.sonar.server.computation.qualitygate.QualityGate;
 import org.sonar.server.computation.qualitygate.QualityGateHolder;
 
+import static com.google.common.collect.FluentIterable.from;
+import static java.lang.String.format;
 import static org.sonar.server.computation.component.ComponentVisitor.Order.PRE_ORDER;
+import static org.sonar.server.computation.qualitygate.ConditionStatus.NO_VALUE_STATUS;
+import static org.sonar.server.computation.qualitygate.ConditionStatus.create;
 
 /**
  * This step:
@@ -62,15 +72,18 @@ public class QualityGateMeasuresStep implements ComputationStep {
 
   private final TreeRootHolder treeRootHolder;
   private final QualityGateHolder qualityGateHolder;
+  private final MutableQualityGateStatusHolder qualityGateStatusHolder;
   private final MeasureRepository measureRepository;
   private final MetricRepository metricRepository;
   private final EvaluationResultTextConverter evaluationResultTextConverter;
 
-  public QualityGateMeasuresStep(TreeRootHolder treeRootHolder, QualityGateHolder qualityGateHolder,
+  public QualityGateMeasuresStep(TreeRootHolder treeRootHolder,
+    QualityGateHolder qualityGateHolder, MutableQualityGateStatusHolder qualityGateStatusHolder,
     MeasureRepository measureRepository, MetricRepository metricRepository,
     EvaluationResultTextConverter evaluationResultTextConverter) {
     this.treeRootHolder = treeRootHolder;
     this.qualityGateHolder = qualityGateHolder;
+    this.qualityGateStatusHolder = qualityGateStatusHolder;
     this.evaluationResultTextConverter = evaluationResultTextConverter;
     this.measureRepository = measureRepository;
     this.metricRepository = metricRepository;
@@ -88,16 +101,68 @@ public class QualityGateMeasuresStep implements ComputationStep {
   }
 
   private void executeForProject(Component project) {
-    QualityGateDetailsDataBuilder builder = new QualityGateDetailsDataBuilder();
-
     Optional<QualityGate> qualityGate = qualityGateHolder.getQualityGate();
     if (qualityGate.isPresent()) {
+      QualityGateDetailsDataBuilder builder = new QualityGateDetailsDataBuilder();
       updateMeasures(project, qualityGate.get().getConditions(), builder);
 
       addProjectMeasure(project, builder);
+
+      updateQualityGateStatusHolder(qualityGate.get(), builder);
     }
   }
 
+  private void updateQualityGateStatusHolder(QualityGate qualityGate, QualityGateDetailsDataBuilder builder) {
+    qualityGateStatusHolder.setStatus(convert(builder.getGlobalLevel()), createStatusPerCondition(qualityGate.getConditions(), builder.getEvaluatedConditions()));
+  }
+
+  private static ConditionStatus.EvaluationStatus toEvaluationStatus(Measure.Level globalLevel) {
+    switch (globalLevel) {
+      case OK:
+        return ConditionStatus.EvaluationStatus.OK;
+      case WARN:
+        return ConditionStatus.EvaluationStatus.WARN;
+      case ERROR:
+        return ConditionStatus.EvaluationStatus.ERROR;
+      default:
+        throw new IllegalArgumentException(format(
+          "Unsupported value '%s' of Measure.Level can not be converted to EvaluationStatus",
+          globalLevel));
+    }
+  }
+
+  private static org.sonar.server.computation.qualitygate.QualityGateStatus convert(Measure.Level globalLevel) {
+    switch (globalLevel) {
+      case OK:
+        return org.sonar.server.computation.qualitygate.QualityGateStatus.OK;
+      case WARN:
+        return org.sonar.server.computation.qualitygate.QualityGateStatus.WARN;
+      case ERROR:
+        return org.sonar.server.computation.qualitygate.QualityGateStatus.ERROR;
+      default:
+        throw new IllegalArgumentException(format(
+          "Unsupported value '%s' of Measure.Level can not be converted to QualityGateStatus",
+          globalLevel));
+    }
+  }
+
+  private static Map<Condition, ConditionStatus> createStatusPerCondition(Iterable<Condition> conditions, Iterable<EvaluatedCondition> evaluatedConditions) {
+    Map<Condition, EvaluatedCondition> evaluatedConditionPerCondition = from(evaluatedConditions)
+      .uniqueIndex(EvaluatedConditionToCondition.INSTANCE);
+
+    ImmutableMap.Builder<Condition, ConditionStatus> builder = ImmutableMap.builder();
+    for (Condition condition : conditions) {
+      EvaluatedCondition evaluatedCondition = evaluatedConditionPerCondition.get(condition);
+
+      if (evaluatedCondition == null) {
+        builder.put(condition, NO_VALUE_STATUS);
+      } else {
+        builder.put(condition, create(toEvaluationStatus(evaluatedCondition.getLevel()), evaluatedCondition.getActualValue()));
+      }
+    }
+    return builder.build();
+  }
+
   private void updateMeasures(Component project, Set<Condition> conditions, QualityGateDetailsDataBuilder builder) {
     for (Condition condition : conditions) {
       Optional<Measure> measure = measureRepository.getRawMeasure(project, condition.getMetric());
@@ -170,4 +235,14 @@ public class QualityGateMeasuresStep implements ComputationStep {
       return evaluatedConditions;
     }
   }
+
+  private enum EvaluatedConditionToCondition implements Function<EvaluatedCondition, Condition> {
+    INSTANCE;
+
+    @Override
+    @Nonnull
+    public Condition apply(@Nonnull EvaluatedCondition input) {
+      return input.getCondition();
+    }
+  }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/CeTaskImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/CeTaskImplTest.java
new file mode 100644 (file)
index 0000000..785441d
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.ce.posttask.CeTask;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CeTaskImplTest {
+  private static final String SOME_ID = "some id";
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void constructor_throws_NPE_if_id_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("id can not be null");
+
+    new CeTaskImpl(null, CeTask.Status.SUCCESS);
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_status_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can not be null");
+
+    new CeTaskImpl(SOME_ID, null);
+  }
+
+  @Test
+  public void verify_getters() {
+    CeTaskImpl underTest = new CeTaskImpl(SOME_ID, CeTask.Status.FAILED);
+
+    assertThat(underTest.getId()).isEqualTo(SOME_ID);
+    assertThat(underTest.getStatus()).isEqualTo(CeTask.Status.FAILED);
+  }
+
+  @Test
+  public void verify_toString() {
+    assertThat(new CeTaskImpl(SOME_ID, CeTask.Status.SUCCESS).toString()).isEqualTo("CeTaskImpl{id='some id', status=SUCCESS}");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/ConditionImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/ConditionImplTest.java
new file mode 100644 (file)
index 0000000..577fa19
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.ce.posttask.QualityGate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class ConditionImplTest {
+  private static final String METRIC_KEY = "metricKey";
+  private static final String ERROR_THRESHOLD = "error threshold";
+  private static final String WARN_THRESHOLD = "warn threshold";
+  private static final String VALUE = "value";
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private ConditionImpl.Builder builder = ConditionImpl.newBuilder()
+    .setStatus(QualityGate.EvaluationStatus.OK)
+    .setMetricKey(METRIC_KEY)
+    .setOperator(QualityGate.Operator.GREATER_THAN)
+    .setErrorThreshold(ERROR_THRESHOLD)
+    .setWarningThreshold(WARN_THRESHOLD)
+    .setOnLeakPeriod(true)
+    .setValue(VALUE);
+
+  @Test
+  public void build_throws_NPE_if_status_is_null() {
+    builder.setStatus(null);
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can not be null");
+
+    builder.build();
+  }
+
+  @Test
+  public void build_throws_NPE_if_metricKey_is_null() {
+    builder.setMetricKey(null);
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("metricKey can not be null");
+
+    builder.build();
+  }
+
+  @Test
+  public void build_throws_NPE_if_operator_is_null() {
+    builder.setOperator(null);
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("operator can not be null");
+
+    builder.build();
+  }
+
+  @Test
+  public void build_throws_IAE_if_both_thresholds_are_null() {
+    builder.setWarningThreshold(null)
+      .setErrorThreshold(null);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("At least one of errorThreshold and warningThreshold must be non null");
+
+    builder.build();
+  }
+
+  @Test
+  public void getValue_throws_ISE_when_condition_type_is_NO_VALUE() {
+    builder.setStatus(QualityGate.EvaluationStatus.NO_VALUE).setValue(null);
+    ConditionImpl condition = builder.build();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("There is no value when status is NO_VALUE");
+
+    condition.getValue();
+  }
+
+  @DataProvider
+  public static Object[][] allStatusesButNO_VALUE() {
+    Object[][] res = new Object[QualityGate.EvaluationStatus.values().length - 1][1];
+    int i = 0;
+    for (QualityGate.EvaluationStatus status : QualityGate.EvaluationStatus.values()) {
+      if (status != QualityGate.EvaluationStatus.NO_VALUE) {
+        res[i][0] = status;
+        i++;
+      }
+    }
+    return res;
+  }
+
+  @Test
+  @UseDataProvider("allStatusesButNO_VALUE")
+  public void build_throws_IAE_if_value_is_null_but_status_is_not_NO_VALUE(QualityGate.EvaluationStatus status) {
+    builder.setStatus(status)
+      .setValue(null);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("value can not be null when status is not NO_VALUE");
+
+    builder.build();
+  }
+
+  @Test
+  public void build_throws_IAE_if_value_is_not_null_but_status_is_NO_VALUE() {
+    builder.setStatus(QualityGate.EvaluationStatus.NO_VALUE);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("value must be null when status is NO_VALUE");
+
+    builder.build();
+  }
+
+  @Test
+  public void toString_ConditionImpl_of_type_different_from_NO_VALUE() {
+    assertThat(builder.build().toString())
+      .isEqualTo(
+        "ConditionImpl{status=OK, metricKey='metricKey', operator=GREATER_THAN, errorThreshold='error threshold', warningThreshold='warn threshold', onLeakPeriod=true, value='value'}");
+  }
+
+  @Test
+  public void toString_ConditionImpl_of_type_NO_VALUE() {
+    builder.setStatus(QualityGate.EvaluationStatus.NO_VALUE)
+      .setValue(null);
+
+    assertThat(builder.build().toString())
+      .isEqualTo(
+          "ConditionImpl{status=NO_VALUE, metricKey='metricKey', operator=GREATER_THAN, errorThreshold='error threshold', warningThreshold='warn threshold', onLeakPeriod=true, value='null'}");
+  }
+
+  @Test
+  public void verify_getters() {
+    ConditionImpl underTest = builder.build();
+
+    assertThat(underTest.getStatus()).isEqualTo(QualityGate.EvaluationStatus.OK);
+    assertThat(underTest.getMetricKey()).isEqualTo(METRIC_KEY);
+    assertThat(underTest.getOperator()).isEqualTo(QualityGate.Operator.GREATER_THAN);
+    assertThat(underTest.getErrorThreshold()).isEqualTo(ERROR_THRESHOLD);
+    assertThat(underTest.getWarningThreshold()).isEqualTo(WARN_THRESHOLD);
+    assertThat(underTest.isOnLeakPeriod()).isEqualTo(true);
+    assertThat(underTest.getValue()).isEqualTo(VALUE);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/ConditionToConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/ConditionToConditionTest.java
new file mode 100644 (file)
index 0000000..0cc1565
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Collections;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.ce.posttask.QualityGate;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.qualitygate.Condition;
+import org.sonar.server.computation.qualitygate.ConditionStatus;
+
+import static com.google.common.collect.ImmutableMap.of;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(DataProviderRunner.class)
+public class ConditionToConditionTest {
+  private static final String METRIC_KEY = "metricKey";
+  private static final String ERROR_THRESHOLD = "error threshold";
+  private static final String WARN_THRESHOLD = "warn threshold";
+  private static final Map<Condition, ConditionStatus> NO_STATUS_PER_CONDITIONS = Collections.emptyMap();
+  private static final String SOME_VALUE = "some value";
+  private static final ConditionStatus SOME_CONDITION_STATUS = ConditionStatus.create(ConditionStatus.EvaluationStatus.OK, SOME_VALUE);
+  private static final Condition SOME_CONDITION = new Condition(newMetric(METRIC_KEY), Condition.Operator.EQUALS.getDbValue(), ERROR_THRESHOLD, WARN_THRESHOLD, 1);
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void apply_throws_NPE_if_Condition_argument_is_null() {
+    ConditionToCondition underTest = new ConditionToCondition(NO_STATUS_PER_CONDITIONS);
+
+    expectedException.expect(NullPointerException.class);
+
+    underTest.apply(null);
+  }
+
+  @Test
+  public void apply_throws_ISE_if_there_is_no_ConditionStatus_for_Condition_argument() {
+    ConditionToCondition underTest = new ConditionToCondition(NO_STATUS_PER_CONDITIONS);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Missing ConditionStatus for condition on metric key " + METRIC_KEY);
+
+    underTest.apply(SOME_CONDITION);
+  }
+
+  @Test
+  @UseDataProvider("allEvaluationStatuses")
+  public void apply_converts_all_values_of_status(ConditionStatus.EvaluationStatus status) {
+    ConditionToCondition underTest = new ConditionToCondition(of(
+      SOME_CONDITION,
+      status == ConditionStatus.EvaluationStatus.NO_VALUE ? ConditionStatus.NO_VALUE_STATUS : ConditionStatus.create(status, SOME_VALUE)));
+
+    assertThat(underTest.apply(SOME_CONDITION).getStatus().name()).isEqualTo(status.name());
+  }
+
+  @Test
+  public void apply_converts_key_from_metric() {
+    ConditionToCondition underTest = new ConditionToCondition(of(SOME_CONDITION, SOME_CONDITION_STATUS));
+
+    assertThat(underTest.apply(SOME_CONDITION).getMetricKey()).isEqualTo(METRIC_KEY);
+  }
+
+  @Test
+  public void apply_copies_thresholds() {
+    ConditionToCondition underTest = new ConditionToCondition(of(SOME_CONDITION, SOME_CONDITION_STATUS));
+
+    assertThat(underTest.apply(SOME_CONDITION).getErrorThreshold()).isEqualTo(ERROR_THRESHOLD);
+    assertThat(underTest.apply(SOME_CONDITION).getWarningThreshold()).isEqualTo(WARN_THRESHOLD);
+  }
+
+  @Test
+  @UseDataProvider("allOperatorValues")
+  public void apply_converts_all_values_of_operator(Condition.Operator operator) {
+    Condition condition = new Condition(newMetric(METRIC_KEY), operator.getDbValue(), ERROR_THRESHOLD, WARN_THRESHOLD, 1);
+    ConditionToCondition underTest = new ConditionToCondition(of(condition, SOME_CONDITION_STATUS));
+
+    assertThat(underTest.apply(condition).getOperator().name()).isEqualTo(operator.name());
+  }
+
+  @Test
+  public void apply_sets_onLeakPeriod_flag_when_Condition_has_non_null_Period() {
+    Condition noPeriodCondition = new Condition(newMetric(METRIC_KEY), Condition.Operator.NOT_EQUALS.getDbValue(), ERROR_THRESHOLD, WARN_THRESHOLD, null);
+    ConditionToCondition underTest = new ConditionToCondition(of(
+      SOME_CONDITION, SOME_CONDITION_STATUS,
+      noPeriodCondition, SOME_CONDITION_STATUS));
+
+    assertThat(underTest.apply(SOME_CONDITION).isOnLeakPeriod()).isTrue();
+    assertThat(underTest.apply(noPeriodCondition).isOnLeakPeriod()).isFalse();
+  }
+
+  @Test
+  public void apply_copies_value() {
+    Condition otherCondition = new Condition(newMetric(METRIC_KEY), Condition.Operator.NOT_EQUALS.getDbValue(), ERROR_THRESHOLD, WARN_THRESHOLD, null);
+    ConditionToCondition underTest = new ConditionToCondition(of(
+        SOME_CONDITION, SOME_CONDITION_STATUS,
+        otherCondition, ConditionStatus.NO_VALUE_STATUS
+        ));
+
+    assertThat(underTest.apply(SOME_CONDITION).getValue()).isEqualTo(SOME_VALUE);
+
+    QualityGate.Condition res = underTest.apply(otherCondition);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("There is no value when status is NO_VALUE");
+
+    res.getValue();
+  }
+
+  @DataProvider
+  public static Object[][] allEvaluationStatuses() {
+    Object[][] res = new Object[ConditionStatus.EvaluationStatus.values().length][1];
+    int i = 0;
+    for (ConditionStatus.EvaluationStatus status : ConditionStatus.EvaluationStatus.values()) {
+      res[i][0] = status;
+      i++;
+    }
+    return res;
+  }
+
+  @DataProvider
+  public static Object[][] allOperatorValues() {
+    Object[][] res = new Object[Condition.Operator.values().length][1];
+    int i = 0;
+    for (Condition.Operator operator : Condition.Operator.values()) {
+      res[i][0] = operator;
+      i++;
+    }
+    return res;
+  }
+
+  private static Metric newMetric(String metricKey) {
+    Metric metric = mock(Metric.class);
+    when(metric.getKey()).thenReturn(metricKey);
+    return metric;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/PostProjectAnalysisTasksExecutorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/PostProjectAnalysisTasksExecutorTest.java
new file mode 100644 (file)
index 0000000..6064273
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import com.google.common.collect.ImmutableMap;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Date;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
+import org.sonar.api.ce.posttask.Project;
+import org.sonar.ce.queue.CeTask;
+import org.sonar.server.computation.analysis.AnalysisMetadataHolderRule;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.qualitygate.Condition;
+import org.sonar.server.computation.qualitygate.ConditionStatus;
+import org.sonar.server.computation.qualitygate.MutableQualityGateHolderRule;
+import org.sonar.server.computation.qualitygate.MutableQualityGateStatusHolderRule;
+import org.sonar.server.computation.qualitygate.QualityGate;
+import org.sonar.server.computation.qualitygate.QualityGateStatus;
+
+import static com.google.common.collect.ImmutableList.of;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(DataProviderRunner.class)
+public class PostProjectAnalysisTasksExecutorTest {
+  private static final long QUALITY_GATE_ID = 98451;
+  private static final String QUALITY_GATE_NAME = "qualityGate name";
+  private static final Condition CONDITION_1 = createCondition("metric key 1");
+  private static final Condition CONDITION_2 = createCondition("metric key 2");
+
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
+    .setAnalysisDate(8465132498L);
+  @Rule
+  public MutableQualityGateHolderRule qualityGateHolder = new MutableQualityGateHolderRule();
+  @Rule
+  public MutableQualityGateStatusHolderRule qualityGateStatusHolder = new MutableQualityGateStatusHolderRule();
+
+  private ArgumentCaptor<PostProjectAnalysisTask.ProjectAnalysis> projectAnalysisArgumentCaptor = ArgumentCaptor.forClass(PostProjectAnalysisTask.ProjectAnalysis.class);
+  private CeTask ceTask = new CeTask.Builder()
+    .setType("type")
+    .setUuid("uuid")
+    .setComponentKey("component key")
+    .setComponentName("component name")
+    .setComponentUuid("component uuid")
+    .build();
+  private PostProjectAnalysisTask postProjectAnalysisTask = mock(PostProjectAnalysisTask.class);
+  private PostProjectAnalysisTasksExecutor underTest = new PostProjectAnalysisTasksExecutor(
+    ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder,
+    new PostProjectAnalysisTask[] {postProjectAnalysisTask});
+
+  @Before
+  public void setUp() throws Exception {
+    qualityGateHolder.setQualityGate(new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, of(CONDITION_1, CONDITION_2)));
+    qualityGateStatusHolder.setStatus(QualityGateStatus.OK, ImmutableMap.of(
+      CONDITION_1, ConditionStatus.create(ConditionStatus.EvaluationStatus.OK, "value"),
+      CONDITION_2, ConditionStatus.NO_VALUE_STATUS));
+  }
+
+  @Test
+  @UseDataProvider("booleanValues")
+  public void does_not_fail_when_there_is_no_PostProjectAnalysisTasksExecutor(boolean allStepsExecuted) {
+    new PostProjectAnalysisTasksExecutor(ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder)
+      .finished(allStepsExecuted);
+  }
+
+  @Test
+  @UseDataProvider("booleanValues")
+  public void finished_calls_all_PostProjectAnalysisTask_in_order_of_the_array_and_passes_the_same_object_to_all(boolean allStepsExecuted) {
+    PostProjectAnalysisTask postProjectAnalysisTask1 = mock(PostProjectAnalysisTask.class);
+    PostProjectAnalysisTask postProjectAnalysisTask2 = mock(PostProjectAnalysisTask.class);
+    InOrder inOrder = inOrder(postProjectAnalysisTask1, postProjectAnalysisTask2);
+
+    new PostProjectAnalysisTasksExecutor(
+      ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder,
+      new PostProjectAnalysisTask[] {postProjectAnalysisTask1, postProjectAnalysisTask2})
+        .finished(allStepsExecuted);
+
+    inOrder.verify(postProjectAnalysisTask1).finished(projectAnalysisArgumentCaptor.capture());
+    inOrder.verify(postProjectAnalysisTask2).finished(projectAnalysisArgumentCaptor.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    List<PostProjectAnalysisTask.ProjectAnalysis> allValues = projectAnalysisArgumentCaptor.getAllValues();
+    assertThat(allValues).hasSize(2);
+    assertThat(allValues.get(0)).isSameAs(allValues.get(1));
+  }
+
+  @Test
+  @UseDataProvider("booleanValues")
+  public void CeTask_status_depends_on_finished_method_argument_is_true_or_false(boolean allStepsExecuted) {
+    underTest.finished(allStepsExecuted);
+
+    verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
+
+    assertThat(projectAnalysisArgumentCaptor.getValue().getCeTask().getStatus())
+      .isEqualTo(
+        allStepsExecuted ? org.sonar.api.ce.posttask.CeTask.Status.SUCCESS : org.sonar.api.ce.posttask.CeTask.Status.FAILED);
+  }
+
+  @Test
+  public void ceTask_uuid_is_UUID_of_CeTask() {
+    underTest.finished(true);
+
+    verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
+
+    assertThat(projectAnalysisArgumentCaptor.getValue().getCeTask().getId())
+      .isEqualTo(ceTask.getUuid());
+  }
+
+  @Test
+  public void project_uuid_key_and_name_come_from_CeTask() {
+    underTest.finished(true);
+
+    verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
+
+    Project project = projectAnalysisArgumentCaptor.getValue().getProject();
+    assertThat(project.getUuid()).isEqualTo(ceTask.getComponentUuid());
+    assertThat(project.getKey()).isEqualTo(ceTask.getComponentKey());
+    assertThat(project.getName()).isEqualTo(ceTask.getComponentName());
+  }
+
+  @Test
+  public void analysisDate_comes_from_AnalysisMetadataHolder() {
+    underTest.finished(true);
+
+    verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
+
+    assertThat(projectAnalysisArgumentCaptor.getValue().getDate())
+      .isEqualTo(new Date(analysisMetadataHolder.getAnalysisDate()));
+  }
+
+  @Test
+  public void qualityGate_is_null_when_finished_method_argument_is_false() {
+    underTest.finished(false);
+
+    verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
+
+    assertThat(projectAnalysisArgumentCaptor.getValue().getQualityGate()).isNull();
+  }
+
+  @Test
+  public void qualityGate_is_populated_when_finished_method_argument_is_true() {
+    underTest.finished(true);
+
+    verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
+
+    org.sonar.api.ce.posttask.QualityGate qualityGate = projectAnalysisArgumentCaptor.getValue().getQualityGate();
+    assertThat(qualityGate.getStatus()).isEqualTo(org.sonar.api.ce.posttask.QualityGate.Status.OK);
+    assertThat(qualityGate.getId()).isEqualTo(String.valueOf(QUALITY_GATE_ID));
+    assertThat(qualityGate.getName()).isEqualTo(QUALITY_GATE_NAME);
+    assertThat(qualityGate.getConditions()).hasSize(2);
+  }
+
+  @DataProvider
+  public static Object[][] booleanValues() {
+    return new Object[][] {
+      {true},
+      {false}
+    };
+  }
+
+  private static Condition createCondition(String metricKey) {
+    Metric metric = mock(Metric.class);
+    when(metric.getKey()).thenReturn(metricKey);
+    return new Condition(metric, Condition.Operator.EQUALS.getDbValue(), "error threshold", "warn threshold", null);
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/ProjectImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/ProjectImplTest.java
new file mode 100644 (file)
index 0000000..716627a
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProjectImplTest {
+  private static final String SOME_UUID = "some uuid";
+  private static final String SOME_KEY = "some key";
+  private static final String SOME_NAME = "some name";
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void constructor_throws_NPE_if_uuid_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("uuid can not be null");
+
+    new ProjectImpl(null, SOME_KEY, SOME_NAME);
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_key_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("key can not be null");
+
+    new ProjectImpl(SOME_UUID, null, SOME_NAME);
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_name_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("name can not be null");
+
+    new ProjectImpl(SOME_UUID, SOME_KEY, null);
+  }
+
+  @Test
+  public void verify_getters() {
+    ProjectImpl underTest = new ProjectImpl(SOME_UUID, SOME_KEY, SOME_NAME);
+
+    assertThat(underTest.getUuid()).isEqualTo(SOME_UUID);
+    assertThat(underTest.getKey()).isEqualTo(SOME_KEY);
+    assertThat(underTest.getName()).isEqualTo(SOME_NAME);
+  }
+
+  @Test
+  public void verify_toString() {
+    assertThat(new ProjectImpl(SOME_UUID, SOME_KEY, SOME_NAME).toString())
+      .isEqualTo("ProjectImpl{uuid='some uuid', key='some key', name='some name'}");
+
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/QualityGateImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/posttask/QualityGateImplTest.java
new file mode 100644 (file)
index 0000000..e8c8bda
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.posttask;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.ce.posttask.QualityGate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class QualityGateImplTest {
+  private static final String SOME_ID = "some id";
+  private static final String SOME_NAME = "some name";
+  private static final QualityGate.Status SOME_STATUS = QualityGate.Status.OK;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private QualityGate.Condition condition = mock(QualityGate.Condition.class);
+  private QualityGateImpl underTest = new QualityGateImpl(SOME_ID, SOME_NAME, SOME_STATUS, ImmutableList.of(condition));
+
+  @Test
+  public void constructor_throws_NPE_if_id_argument_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("id can not be null");
+
+    new QualityGateImpl(null, SOME_NAME, SOME_STATUS, Collections.<QualityGate.Condition>emptyList());
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_name_argument_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("name can not be null");
+
+    new QualityGateImpl(SOME_ID, null, SOME_STATUS, Collections.<QualityGate.Condition>emptyList());
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_status_argument_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can not be null");
+
+    new QualityGateImpl(SOME_ID, SOME_NAME, null, Collections.<QualityGate.Condition>emptyList());
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_conditions_argument_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("conditions can not be null");
+
+    new QualityGateImpl(SOME_ID, SOME_NAME, SOME_STATUS, null);
+  }
+
+  @Test
+  public void verify_getters() {
+    List<QualityGate.Condition> conditions = ImmutableList.of(condition);
+
+    QualityGateImpl underTest = new QualityGateImpl(SOME_ID, SOME_NAME, SOME_STATUS, conditions);
+
+    assertThat(underTest.getId()).isEqualTo(SOME_ID);
+    assertThat(underTest.getName()).isEqualTo(SOME_NAME);
+    assertThat(underTest.getStatus()).isEqualTo(SOME_STATUS);
+    assertThat(underTest.getConditions()).isEqualTo(conditions);
+  }
+
+  @Test
+  public void verify_toString() {
+    when(condition.toString()).thenReturn("{Condition}");
+
+    assertThat(underTest.toString())
+      .isEqualTo("QualityGateImpl{id='some id', name='some name', status=OK, conditions=[{Condition}]}");
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/ConditionStatusTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/ConditionStatusTest.java
new file mode 100644 (file)
index 0000000..630f654
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.qualitygate;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.computation.qualitygate.ConditionStatus.EvaluationStatus.NO_VALUE;
+import static org.sonar.server.computation.qualitygate.ConditionStatus.EvaluationStatus.OK;
+import static org.sonar.server.computation.qualitygate.ConditionStatus.EvaluationStatus.values;
+
+@RunWith(DataProviderRunner.class)
+public class ConditionStatusTest {
+  private static final String SOME_VALUE = "value";
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void create_throws_NPE_if_status_argument_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can not be null");
+
+    ConditionStatus.create(null, SOME_VALUE);
+  }
+
+  @Test
+  public void create_throws_IAE_if_status_argument_is_NO_VALUE() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("EvaluationStatus 'NO_VALUE' can not be used with this method, use constant ConditionStatus.NO_VALUE_STATUS instead.");
+
+    ConditionStatus.create(NO_VALUE, SOME_VALUE);
+  }
+
+  @Test
+  @UseDataProvider("allStatusesButNO_VALUE")
+  public void create_throws_NPE_if_value_is_null_and_status_argument_is_not_NO_VALUE(ConditionStatus.EvaluationStatus status) {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("value can not be null");
+
+    ConditionStatus.create(status, null);
+  }
+
+  @Test
+  public void verify_getters() {
+    ConditionStatus underTest = ConditionStatus.create(OK, SOME_VALUE);
+
+    assertThat(underTest.getStatus()).isEqualTo(OK);
+    assertThat(underTest.getValue()).isEqualTo(SOME_VALUE);
+  }
+
+  @Test
+  public void verify_toString() {
+    assertThat(ConditionStatus.create(OK, SOME_VALUE).toString()).isEqualTo("ConditionStatus{status=OK, value='value'}");
+    assertThat(ConditionStatus.NO_VALUE_STATUS.toString()).isEqualTo("ConditionStatus{status=NO_VALUE, value='null'}");
+  }
+
+  @Test
+  public void constant_NO_VALUE_STATUS_has_status_NO_VALUE_and_null_value() {
+    assertThat(ConditionStatus.NO_VALUE_STATUS.getStatus()).isEqualTo(NO_VALUE);
+    assertThat(ConditionStatus.NO_VALUE_STATUS.getValue()).isNull();
+  }
+
+  @DataProvider
+  public static Object[][] allStatusesButNO_VALUE() {
+    Object[][] res = new Object[values().length - 1][1];
+    int i = 0;
+    for (ConditionStatus.EvaluationStatus status : values()) {
+      if (status != NO_VALUE) {
+        res[i][0] = status;
+        i++;
+      }
+    }
+    return res;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/MutableQualityGateStatusHolderRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/MutableQualityGateStatusHolderRule.java
new file mode 100644 (file)
index 0000000..4153897
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.qualitygate;
+
+import java.util.Map;
+import org.junit.rules.ExternalResource;
+
+public class MutableQualityGateStatusHolderRule extends ExternalResource implements MutableQualityGateStatusHolder {
+  private MutableQualityGateStatusHolder delegate = new QualityGateStatusHolderImpl();
+
+  @Override
+  public void setStatus(QualityGateStatus globalStatus, Map<Condition, ConditionStatus> statusPerCondition) {
+    delegate.setStatus(globalStatus, statusPerCondition);
+  }
+
+  @Override
+  public QualityGateStatus getStatus() {
+    return delegate.getStatus();
+  }
+
+  @Override
+  public Map<Condition, ConditionStatus> getStatusPerConditions() {
+    return delegate.getStatusPerConditions();
+  }
+
+  @Override
+  protected void after() {
+    reset();
+  }
+
+  public void reset() {
+    this.delegate = new QualityGateStatusHolderImpl();
+  }
+}
index ee861e7935a495b727b02e914b1e49a4c223cfb1..11ba2a4460e669836f2b3d1da0dd10fd73e33e5d 100644 (file)
@@ -27,7 +27,7 @@ import static org.assertj.guava.api.Assertions.assertThat;
 
 public class QualityGateHolderImplTest {
 
-  public static final QualityGate QUALITY_GATE = new QualityGate("name", Collections.<Condition>emptyList());
+  public static final QualityGate QUALITY_GATE = new QualityGate(4612, "name", Collections.<Condition>emptyList());
 
   @Test(expected = IllegalStateException.class)
   public void getQualityGate_throws_ISE_if_QualityGate_not_set() {
index da6af29e9a1a3500751366d95772bf9eae3caeeb..31df844a5eb6a6208415df5ec98b13a668159b52 100644 (file)
@@ -64,6 +64,7 @@ public class QualityGateServiceImplTest {
     Optional<QualityGate> res = underTest.findById(SOME_ID);
 
     assertThat(res).isPresent();
+    assertThat(res.get().getId()).isEqualTo(SOME_ID);
     assertThat(res.get().getName()).isEqualTo(SOME_NAME);
     assertThat(res.get().getConditions()).isEmpty();
   }
@@ -79,6 +80,7 @@ public class QualityGateServiceImplTest {
     Optional<QualityGate> res = underTest.findById(SOME_ID);
 
     assertThat(res).isPresent();
+    assertThat(res.get().getId()).isEqualTo(SOME_ID);
     assertThat(res.get().getName()).isEqualTo(SOME_NAME);
     assertThat(res.get().getConditions()).containsOnly(
         new Condition(METRIC_1, CONDITION_1.getOperator(), CONDITION_1.getErrorThreshold(), CONDITION_1.getWarningThreshold(), CONDITION_1.getPeriod()),
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/QualityGateStatusHolderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/qualitygate/QualityGateStatusHolderImplTest.java
new file mode 100644 (file)
index 0000000..f894864
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.computation.qualitygate;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Collections;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+@RunWith(DataProviderRunner.class)
+public class QualityGateStatusHolderImplTest {
+  private static final Map<Condition, ConditionStatus> SOME_STATUS_PER_CONDITION = Collections.singletonMap(
+    mock(Condition.class), ConditionStatus.create(ConditionStatus.EvaluationStatus.OK, "val"));
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private QualityGateStatusHolderImpl underTest = new QualityGateStatusHolderImpl();
+
+  @Test
+  public void setStatus_throws_NPE_if_globalStatus_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("global status can not be null");
+
+    underTest.setStatus(null, SOME_STATUS_PER_CONDITION);
+  }
+
+  @Test
+  public void setStatus_throws_NPE_if_statusPerCondition_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status per condition can not be null");
+
+    underTest.setStatus(QualityGateStatus.OK, null);
+  }
+
+  @Test
+  public void setStatus_throws_ISE_if_called_twice() {
+    underTest.setStatus(QualityGateStatus.OK, SOME_STATUS_PER_CONDITION);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Quality gate status has already been set in the holder");
+
+    underTest.setStatus(null, null);
+  }
+
+  @Test
+  public void getStatus_throws_ISE_if_setStatus_not_called_yet() {
+    expectQGNotSetYetISE();
+
+    underTest.getStatus();
+  }
+
+  @Test
+  @UseDataProvider("qualityGateStatusValue")
+  public void getStatus_returns_status_argument_from_setStatus(QualityGateStatus status) {
+    underTest.setStatus(status, SOME_STATUS_PER_CONDITION);
+
+    assertThat(underTest.getStatus()).isEqualTo(status);
+  }
+
+  @Test
+  public void getStatusPerConditions_throws_ISE_if_setStatus_not_called_yet() {
+    expectQGNotSetYetISE();
+
+    underTest.getStatusPerConditions();
+  }
+
+  @Test
+  public void getStatusPerConditions_returns_statusPerCondition_argument_from_setStatus() {
+    underTest.setStatus(QualityGateStatus.ERROR, SOME_STATUS_PER_CONDITION);
+
+    assertThat(underTest.getStatusPerConditions()).isEqualTo(SOME_STATUS_PER_CONDITION);
+    // a copy is made to be immutable
+    assertThat(underTest.getStatusPerConditions()).isNotSameAs(SOME_STATUS_PER_CONDITION);
+  }
+
+  private void expectQGNotSetYetISE() {
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Quality gate status has not been set yet");
+  }
+
+  @DataProvider
+  public static Object[][] qualityGateStatusValue() {
+    Object[][] res = new Object[QualityGateStatus.values().length][1];
+    int i = 0;
+    for (QualityGateStatus status : QualityGateStatus.values()) {
+      res[i][0] = status;
+      i++;
+    }
+    return res;
+  }
+}
index 1a5ebf7c81522fe75d458835da10a630cfb845db..024c56109ea1db6c71ed38de9de755fc471c0ddb 100644 (file)
@@ -101,7 +101,7 @@ public class QualityGateLoadingStepTest {
 
   @Test
   public void execute_sets_QualityGate_if_it_can_be_found_by_service() {
-    QualityGate qualityGate = new QualityGate("name", Collections.<Condition>emptyList());
+    QualityGate qualityGate = new QualityGate(465, "name", Collections.<Condition>emptyList());
 
     treeRootHolder.setRoot(PROJECT_ALONE);
     when(settingsRepository.getSettings(PROJECT_ALONE)).thenReturn(new Settings().setProperty("sonar.qualitygate", 10));
index de11b92aea9eec520e14fe96dd865866674518d2..20cb102fc26001e02db5e7f04962e4222bbe0e67 100644 (file)
@@ -21,10 +21,14 @@ package org.sonar.server.computation.step;
 
 import com.google.common.base.Optional;
 import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
 import javax.annotation.Nullable;
+import org.assertj.core.api.AbstractAssert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.mockito.ArgumentCaptor;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -40,10 +44,14 @@ 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.ConditionStatus;
 import org.sonar.server.computation.qualitygate.EvaluationResult;
 import org.sonar.server.computation.qualitygate.EvaluationResultTextConverter;
+import org.sonar.server.computation.qualitygate.MutableQualityGateStatusHolderRule;
 import org.sonar.server.computation.qualitygate.QualityGate;
 import org.sonar.server.computation.qualitygate.QualityGateHolderRule;
+import org.sonar.server.computation.qualitygate.QualityGateStatus;
+import org.sonar.server.computation.qualitygate.QualityGateStatusHolder;
 
 import static com.google.common.collect.ImmutableList.of;
 import static org.mockito.Matchers.any;
@@ -62,11 +70,17 @@ public class QualityGateMeasuresStepTest {
   private static final MetricImpl INT_METRIC_2 = createIntMetric(2);
 
   private static final ReportComponent PROJECT_COMPONENT = ReportComponent.builder(Component.Type.PROJECT, 1).build();
+  private static final long SOME_QG_ID = 7521551;
+  private static final String SOME_QG_NAME = "name";
 
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
   @Rule
   public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
   @Rule
   public QualityGateHolderRule qualityGateHolder = new QualityGateHolderRule();
+  @Rule
+  public MutableQualityGateStatusHolderRule qualityGateStatusHolder = new MutableQualityGateStatusHolderRule();
 
   private static final Metric ALERT_STATUS_METRIC = mock(Metric.class);
   private static final Metric QUALITY_GATE_DETAILS_METRIC = mock(Metric.class);
@@ -77,7 +91,8 @@ public class QualityGateMeasuresStepTest {
   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);
+  private QualityGateMeasuresStep underTest = new QualityGateMeasuresStep(treeRootHolder, qualityGateHolder, qualityGateStatusHolder, measureRepository, metricRepository,
+    resultTextConverter);
 
   @Before
   public void setUp() {
@@ -121,10 +136,22 @@ public class QualityGateMeasuresStepTest {
     verifyNoMoreInteractions(measureRepository);
   }
 
+  @Test
+  public void mutableQualityGateStatusHolder_is_not_populated_if_there_is_no_qualitygate() {
+    qualityGateHolder.setQualityGate(null);
+
+    underTest.execute();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Quality gate status has not been set yet");
+
+    qualityGateStatusHolder.getStatus();
+  }
+
   @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)));
+    qualityGateHolder.setQualityGate(new QualityGate(SOME_QG_ID, SOME_QG_NAME, of(equals2Condition)));
     when(measureRepository.getRawMeasure(PROJECT_COMPONENT, INT_METRIC_1)).thenReturn(Optional.<Measure>absent());
 
     underTest.execute();
@@ -139,6 +166,11 @@ public class QualityGateMeasuresStepTest {
       .hasQualityGateText("");
     assertThat(qgDetailsMeasureCaptor.getValue())
       .hasValue(new QualityGateDetailsData(OK, Collections.<EvaluatedCondition>emptyList()).toJson());
+
+    QualityGateStatusHolderAssertions.assertThat(qualityGateStatusHolder)
+      .hasStatus(QualityGateStatus.OK)
+      .hasConditionCount(1)
+      .hasCondition(equals2Condition, ConditionStatus.EvaluationStatus.NO_VALUE, null);
   }
 
   @Test
@@ -147,7 +179,7 @@ public class QualityGateMeasuresStepTest {
     Condition equals2Condition = createEqualsCondition(INT_METRIC_1, "2", null);
     Measure rawMeasure = Measure.newMeasureBuilder().create(rawValue, null);
 
-    qualityGateHolder.setQualityGate(new QualityGate("name", of(equals2Condition)));
+    qualityGateHolder.setQualityGate(new QualityGate(SOME_QG_ID, SOME_QG_NAME, of(equals2Condition)));
     when(measureRepository.getRawMeasure(PROJECT_COMPONENT, INT_METRIC_1)).thenReturn(Optional.of(rawMeasure));
 
     underTest.execute();
@@ -168,6 +200,11 @@ public class QualityGateMeasuresStepTest {
       .hasQualityGateText(dumbResultTextAnswer(equals2Condition, OK, rawValue));
     assertThat(qgDetailsMeasureCaptor.getValue())
       .hasValue(new QualityGateDetailsData(OK, of(new EvaluatedCondition(equals2Condition, OK, rawValue))).toJson());
+
+    QualityGateStatusHolderAssertions.assertThat(qualityGateStatusHolder)
+      .hasStatus(QualityGateStatus.OK)
+      .hasConditionCount(1)
+      .hasCondition(equals2Condition, ConditionStatus.EvaluationStatus.OK, String.valueOf(rawValue));
   }
 
   @Test
@@ -177,7 +214,7 @@ public class QualityGateMeasuresStepTest {
     Condition equals1WarningCondition = createEqualsCondition(INT_METRIC_2, null, "1");
     Measure rawMeasure = Measure.newMeasureBuilder().create(rawValue, null);
 
-    qualityGateHolder.setQualityGate(new QualityGate("name", of(equals1ErrorCondition, equals1WarningCondition)));
+    qualityGateHolder.setQualityGate(new QualityGate(SOME_QG_ID, SOME_QG_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));
 
@@ -195,20 +232,25 @@ public class QualityGateMeasuresStepTest {
     verifyNoMoreInteractions(measureRepository);
 
     assertThat(equals1ErrorConditionMeasureCaptor.getValue())
-        .hasQualityGateLevel(ERROR)
-        .hasQualityGateText(dumbResultTextAnswer(equals1ErrorCondition, ERROR, rawValue));
+      .hasQualityGateLevel(ERROR)
+      .hasQualityGateText(dumbResultTextAnswer(equals1ErrorCondition, ERROR, rawValue));
     assertThat(equals1WarningConditionMeasureCaptor.getValue())
-        .hasQualityGateLevel(WARN)
-        .hasQualityGateText(dumbResultTextAnswer(equals1WarningCondition, WARN, rawValue));
+      .hasQualityGateLevel(WARN)
+      .hasQualityGateText(dumbResultTextAnswer(equals1WarningCondition, WARN, rawValue));
     assertThat(alertStatusMeasureCaptor.getValue())
-        .hasQualityGateLevel(ERROR)
-        .hasQualityGateText(dumbResultTextAnswer(equals1ErrorCondition, ERROR, rawValue) + ", "
-            + dumbResultTextAnswer(equals1WarningCondition, WARN, rawValue));
+      .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());
+      .hasValue(new QualityGateDetailsData(ERROR, of(
+        new EvaluatedCondition(equals1ErrorCondition, ERROR, rawValue),
+        new EvaluatedCondition(equals1WarningCondition, WARN, rawValue))).toJson());
+
+    QualityGateStatusHolderAssertions.assertThat(qualityGateStatusHolder)
+      .hasStatus(QualityGateStatus.ERROR)
+      .hasConditionCount(2)
+      .hasCondition(equals1ErrorCondition, ConditionStatus.EvaluationStatus.ERROR, String.valueOf(rawValue))
+      .hasCondition(equals1WarningCondition, ConditionStatus.EvaluationStatus.WARN, String.valueOf(rawValue));
   }
 
   @Test
@@ -218,7 +260,7 @@ public class QualityGateMeasuresStepTest {
     Condition equals1WarningCondition = createEqualsCondition(INT_METRIC_2, null, "1");
     Measure rawMeasure = Measure.newMeasureBuilder().create(rawValue, null);
 
-    qualityGateHolder.setQualityGate(new QualityGate("name", of(equals2Condition, equals1WarningCondition)));
+    qualityGateHolder.setQualityGate(new QualityGate(SOME_QG_ID, SOME_QG_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));
 
@@ -236,20 +278,25 @@ public class QualityGateMeasuresStepTest {
     verifyNoMoreInteractions(measureRepository);
 
     assertThat(equals2ConditionMeasureCaptor.getValue())
-        .hasQualityGateLevel(OK)
-        .hasQualityGateText(dumbResultTextAnswer(equals2Condition, OK, rawValue));
+      .hasQualityGateLevel(OK)
+      .hasQualityGateText(dumbResultTextAnswer(equals2Condition, OK, rawValue));
     assertThat(equals1WarningConditionMeasureCaptor.getValue())
-        .hasQualityGateLevel(WARN)
-        .hasQualityGateText(dumbResultTextAnswer(equals1WarningCondition, WARN, rawValue));
+      .hasQualityGateLevel(WARN)
+      .hasQualityGateText(dumbResultTextAnswer(equals1WarningCondition, WARN, rawValue));
     assertThat(alertStatusMeasureCaptor.getValue())
-        .hasQualityGateLevel(WARN)
-        .hasQualityGateText(dumbResultTextAnswer(equals2Condition, OK, rawValue) + ", "
-            + dumbResultTextAnswer(equals1WarningCondition, WARN, rawValue));
+      .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());
+      .hasValue(new QualityGateDetailsData(WARN, of(
+        new EvaluatedCondition(equals2Condition, OK, rawValue),
+        new EvaluatedCondition(equals1WarningCondition, WARN, rawValue))).toJson());
+
+    QualityGateStatusHolderAssertions.assertThat(qualityGateStatusHolder)
+      .hasStatus(QualityGateStatus.WARN)
+      .hasConditionCount(2)
+      .hasCondition(equals2Condition, ConditionStatus.EvaluationStatus.OK, String.valueOf(rawValue))
+      .hasCondition(equals1WarningCondition, ConditionStatus.EvaluationStatus.WARN, String.valueOf(rawValue));
   }
 
   private static Condition createEqualsCondition(Metric metric, @Nullable String errorThreshold, @Nullable String warningThreshold) {
@@ -260,4 +307,62 @@ public class QualityGateMeasuresStepTest {
     return new MetricImpl(index, "metricKey" + index, "metricName" + index, Metric.MetricType.INT);
   }
 
+  private static class QualityGateStatusHolderAssertions extends AbstractAssert<QualityGateStatusHolderAssertions, QualityGateStatusHolder> {
+
+    private QualityGateStatusHolderAssertions(QualityGateStatusHolder actual) {
+      super(actual, QualityGateStatusHolderAssertions.class);
+    }
+
+    public static QualityGateStatusHolderAssertions assertThat(QualityGateStatusHolder holder) {
+      return new QualityGateStatusHolderAssertions(holder);
+    }
+
+    public QualityGateStatusHolderAssertions hasStatus(QualityGateStatus status) {
+      if (actual.getStatus() != status) {
+        failWithMessage(
+          "Expected QualityGateStatusHolder to have global status <%s> but was <%s>",
+          status, actual.getStatus());
+      }
+
+      return this;
+    }
+
+    public QualityGateStatusHolderAssertions hasConditionCount(int count) {
+      int conditionCount = actual.getStatusPerConditions().size();
+      if (conditionCount != count) {
+        failWithMessage(
+          "Expected QualityGateStatusHolder to have <%s> conditions but it has <%s>",
+          count, conditionCount);
+      }
+
+      return this;
+    }
+
+    public QualityGateStatusHolderAssertions hasCondition(Condition condition, ConditionStatus.EvaluationStatus evaluationStatus, @Nullable String expectedValue) {
+      for (Map.Entry<Condition, ConditionStatus> entry : actual.getStatusPerConditions().entrySet()) {
+        if (entry.getKey() == condition) {
+          ConditionStatus.EvaluationStatus actualStatus = entry.getValue().getStatus();
+          if (actualStatus != evaluationStatus) {
+            failWithMessage(
+              "Expected Status of condition <%s> in QualityGateStatusHolder to be <%s> but it was <%s>",
+              condition, evaluationStatus, actualStatus);
+          }
+          String actualValue = entry.getValue().getValue();
+          if (!Objects.equals(expectedValue, actualValue)) {
+            failWithMessage(
+              "Expected Value of condition <%s> in QualityGateStatusHolder to be <%s> but it was <%s>",
+              condition, expectedValue, actualValue);
+          }
+          return this;
+        }
+      }
+
+      failWithMessage(
+        "Expected QualityGateStatusHolder to have an entry for <%s> but none was found",
+        condition);
+
+      return this;
+    }
+  }
+
 }