From: Sébastien Lesaint Date: Fri, 17 Nov 2017 10:17:06 +0000 (+0100) Subject: SONAR-10085 add QualityGate model to be shared accross server X-Git-Tag: 7.0-RC1~299 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=ef1259e286f8778e13a2f1ba3f7b073558bd3ad3;p=sonarqube.git SONAR-10085 add QualityGate model to be shared accross server --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java new file mode 100644 index 00000000000..5fa0a0161d7 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java @@ -0,0 +1,123 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate; + +import java.util.Objects; +import java.util.Optional; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static java.util.Objects.requireNonNull; + +@Immutable +public class Condition { + + private final String metricKey; + private final Operator operator; + @CheckForNull + private final String warningThreshold; + @CheckForNull + private final String errorThreshold; + private final boolean onLeakPeriod; + + public Condition(String metricKey, Operator operator, + @Nullable String errorThreshold, @Nullable String warningThreshold, + boolean onLeakPeriod) { + this.metricKey = requireNonNull(metricKey, "metricKey can't be null"); + this.operator = requireNonNull(operator, "operator can't be null"); + this.onLeakPeriod = onLeakPeriod; + this.errorThreshold = errorThreshold; + this.warningThreshold = warningThreshold; + } + + public String getMetricKey() { + return metricKey; + } + + public boolean isOnLeakPeriod() { + return onLeakPeriod; + } + + public Operator getOperator() { + return operator; + } + + public Optional getWarningThreshold() { + return Optional.ofNullable(warningThreshold); + } + + public Optional getErrorThreshold() { + return Optional.ofNullable(errorThreshold); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Condition condition = (Condition) o; + return onLeakPeriod == condition.onLeakPeriod && + Objects.equals(metricKey, condition.metricKey) && + operator == condition.operator && + Objects.equals(warningThreshold, condition.warningThreshold) && + Objects.equals(errorThreshold, condition.errorThreshold); + } + + @Override + public int hashCode() { + return Objects.hash(metricKey, operator, warningThreshold, errorThreshold, onLeakPeriod); + } + + @Override + public String toString() { + return "Condition{" + + "metricKey='" + metricKey + '\'' + + ", operator=" + operator + + ", warningThreshold=" + toString(warningThreshold) + + ", errorThreshold=" + toString(errorThreshold) + + ", onLeakPeriod=" + onLeakPeriod + + '}'; + } + + private static String toString(@Nullable String errorThreshold) { + if (errorThreshold == null) { + return null; + } + return '\'' + errorThreshold + '\''; + } + + public enum Operator { + EQUALS("EQ"), NOT_EQUALS("NE"), GREATER_THAN("GT"), LESS_THAN("LT"); + + private final String dbValue; + + Operator(String dbValue) { + this.dbValue = dbValue; + } + + public String getDbValue() { + return dbValue; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java new file mode 100644 index 00000000000..71e65c33399 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate; + +import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static java.util.Objects.requireNonNull; + +@Immutable +public class EvaluatedCondition { + private final Condition condition; + private final EvaluationStatus status; + private final String value; + + public EvaluatedCondition(Condition condition, EvaluationStatus status, @Nullable String value) { + this.condition = requireNonNull(condition, "condition can't be null"); + this.status = requireNonNull(status, "status can't be null"); + this.value = value; + } + + public Condition getCondition() { + return condition; + } + + public EvaluationStatus getStatus() { + return status; + } + + public Optional getValue() { + return Optional.ofNullable(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EvaluatedCondition that = (EvaluatedCondition) o; + return Objects.equals(condition, that.condition) && + status == that.status && + Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(condition, status, value); + } + + @Override + public String toString() { + return "EvaluatedCondition{" + + "condition=" + condition + + ", status=" + status + + ", value=" + (value == null ? null : '\'' + value + '\'') + + '}'; + } + + /** + * Quality gate condition evaluation status. + */ + public enum EvaluationStatus { + /** + * No measure found or measure had no value. The condition has not been evaluated and therefor ignored in + * the computation of the Quality Gate status. + */ + NO_VALUE, + /** + * Condition evaluated as OK, neither error nor warning thresholds have been reached. + */ + OK, + /** + * Condition evaluated as WARN, only warning thresholds has been reached. + */ + WARN, + /** + * Condition evaluated as ERROR, error thresholds has been reached (and most likely warning thresholds too). + */ + ERROR + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java new file mode 100644 index 00000000000..f594de77f78 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java @@ -0,0 +1,159 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate; + +import com.google.common.collect.ImmutableSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +@Immutable +public class EvaluatedQualityGate { + private final QualityGate qualityGate; + private final Status status; + private final Set evaluatedConditions; + + private EvaluatedQualityGate(QualityGate qualityGate, Status status, Set evaluatedConditions) { + this.qualityGate = qualityGate; + this.status = status; + this.evaluatedConditions = evaluatedConditions; + } + + public QualityGate getQualityGate() { + return qualityGate; + } + + public Status getStatus() { + return status; + } + + public Set getEvaluatedConditions() { + return evaluatedConditions; + } + + public static Builder newBuilder() { + return new Builder(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EvaluatedQualityGate that = (EvaluatedQualityGate) o; + return Objects.equals(qualityGate, that.qualityGate) && + status == that.status && + Objects.equals(evaluatedConditions, that.evaluatedConditions); + } + + @Override + public int hashCode() { + return Objects.hash(qualityGate, status, evaluatedConditions); + } + + @Override + public String toString() { + return "EvaluatedQualityGate{" + + "qualityGate=" + qualityGate + + ", status=" + status + + ", evaluatedConditions=" + evaluatedConditions + + '}'; + } + + public static final class Builder { + private QualityGate qualityGate; + private Status status; + private final Map evaluatedConditions = new HashMap<>(); + + private Builder() { + // use static factory method + } + + public Builder setQualityGate(QualityGate qualityGate) { + this.qualityGate = checkQualityGate(qualityGate); + return this; + } + + public Builder setStatus(Status status) { + this.status = checkStatus(status); + return this; + } + + public Builder addCondition(Condition condition, EvaluationStatus status, @Nullable String value) { + evaluatedConditions.put(condition, new EvaluatedCondition(condition, status, value)); + return this; + } + + public Set getEvaluatedConditions() { + return ImmutableSet.copyOf(evaluatedConditions.values()); + } + + public EvaluatedQualityGate build() { + checkQualityGate(this.qualityGate); + checkStatus(this.status); + + return new EvaluatedQualityGate( + this.qualityGate, + this.status, + checkEvaluatedConditions(qualityGate, evaluatedConditions)); + } + + private static Set checkEvaluatedConditions(QualityGate qualityGate, Map evaluatedConditions) { + Set conditions = qualityGate.getConditions(); + + Set conditionsNotEvaluated = conditions.stream() + .filter(c -> !evaluatedConditions.containsKey(c)) + .collect(Collectors.toSet()); + checkArgument(conditionsNotEvaluated.isEmpty(), "Evaluation missing for the following conditions: %s", conditionsNotEvaluated); + + Set unknownConditions = evaluatedConditions.keySet().stream() + .filter(c -> !conditions.contains(c)) + .collect(Collectors.toSet()); + checkArgument(unknownConditions.isEmpty(), "Evaluation provided for unknown conditions: %s", unknownConditions); + + return ImmutableSet.copyOf(evaluatedConditions.values()); + } + + private static QualityGate checkQualityGate(@Nullable QualityGate qualityGate) { + return requireNonNull(qualityGate, "qualityGate can't be null"); + } + + private static Status checkStatus(@Nullable Status status) { + return requireNonNull(status, "status can't be null"); + } + } + + public enum Status { + OK, + WARN, + ERROR + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java new file mode 100644 index 00000000000..fe482fa440a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate; + +import java.util.Objects; +import java.util.Set; +import javax.annotation.concurrent.Immutable; + +import static java.util.Objects.requireNonNull; +import static org.sonar.core.util.stream.MoreCollectors.toSet; + +@Immutable +public class QualityGate { + private final String id; + private final String name; + private final Set conditions; + + public QualityGate(String id, String name, Set conditions) { + this.id = requireNonNull(id, "id can't be null"); + this.name = requireNonNull(name, "name can't be null"); + this.conditions = requireNonNull(conditions, "conditions can't be null") + .stream() + .map(c -> requireNonNull(c, "condition can't be null")) + .collect(toSet(conditions.size())); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public Set getConditions() { + return conditions; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QualityGate that = (QualityGate) o; + return Objects.equals(id, that.id) && + Objects.equals(name, that.name) && + Objects.equals(conditions, that.conditions); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, conditions); + } + + @Override + public String toString() { + return "QualityGate{" + + "id=" + id + + ", name='" + name + '\'' + + ", conditions=" + conditions + + '}'; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java new file mode 100644 index 00000000000..578871d5a4f --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java @@ -0,0 +1,128 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate; + +import java.util.Arrays; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ConditionTest { + private static final String METRIC_KEY = "metric_key"; + private static final Condition.Operator OPERATOR = Condition.Operator.EQUALS; + private static final String ERROR_THRESHOLD = "2"; + private static final String WARN_THRESHOLD = "4"; + private static final boolean ON_LEAK_PERIOD = true; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private Condition underTest = new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD); + + @Test + public void constructor_throws_NPE_if_metricKey_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("metricKey can't be null"); + + new Condition(null, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD); + } + + @Test + public void constructor_throws_NPE_if_operator_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("operator can't be null"); + + new Condition(METRIC_KEY, null, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD); + } + + @Test + public void errorThreshold_can_be_null() { + Condition underTest = new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD); + + assertThat(underTest.getErrorThreshold()).isEmpty(); + } + + @Test + public void warnThreshold_can_be_null() { + Condition underTest = new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD); + + assertThat(underTest.getWarningThreshold()).isEmpty(); + } + + @Test + public void verify_getters() { + assertThat(underTest.getMetricKey()).isEqualTo(METRIC_KEY); + assertThat(underTest.getOperator()).isEqualTo(OPERATOR); + assertThat(underTest.getErrorThreshold()).contains(ERROR_THRESHOLD); + assertThat(underTest.getWarningThreshold()).contains(WARN_THRESHOLD); + assertThat(underTest.isOnLeakPeriod()).isEqualTo(ON_LEAK_PERIOD); + } + + @Test + public void toString_is_override() { + assertThat(underTest.toString()) + .isEqualTo("Condition{metricKey='metric_key', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=true}"); + } + + @Test + public void toString_does_not_quote_nulls() { + Condition withNulls = new Condition("metric_key", Condition.Operator.LESS_THAN, null, null, false); + assertThat(withNulls.toString()) + .isEqualTo("Condition{metricKey='metric_key', operator=LESS_THAN, warningThreshold=null, errorThreshold=null, onLeakPeriod=false}"); + } + + @Test + public void equals_is_based_on_all_fields() { + assertThat(underTest).isEqualTo(underTest); + assertThat(underTest).isNotEqualTo(null); + assertThat(underTest).isNotEqualTo(new Object()); + assertThat(underTest).isEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD)); + assertThat(underTest).isNotEqualTo(new Condition("other_metric_key", OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD)); + Arrays.stream(Condition.Operator.values()) + .filter(s -> !OPERATOR.equals(s)) + .forEach(otherOperator -> assertThat(underTest) + .isNotEqualTo(new Condition(METRIC_KEY, otherOperator, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD))); + assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD)); + assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, "other_error_threshold", WARN_THRESHOLD, ON_LEAK_PERIOD)); + assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD)); + assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, "other_warn_threshold", ON_LEAK_PERIOD)); + assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, !ON_LEAK_PERIOD)); + } + + @Test + public void hashcode_is_based_on_all_fields() { + assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(null); + assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode()); + assertThat(underTest.hashCode()).isEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new Condition("other_metric_key", OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode()); + Arrays.stream(Condition.Operator.values()) + .filter(s -> !OPERATOR.equals(s)) + .forEach(otherOperator -> assertThat(underTest.hashCode()) + .isNotEqualTo(new Condition(METRIC_KEY, otherOperator, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode())); + assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, "other_error_threshold", WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, "other_warn_threshold", ON_LEAK_PERIOD).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, !ON_LEAK_PERIOD).hashCode()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java new file mode 100644 index 00000000000..4213dea784e --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.server.qualitygate.Condition.Operator.EQUALS; +import static org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus.OK; +import static org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus.WARN; + +public class EvaluatedConditionTest { + private static final Condition CONDITION_1 = new Condition("metricKey", EQUALS, "2", "4", false); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, "value"); + + @Test + public void constructor_throws_NPE_if_condition_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("condition can't be null"); + + new EvaluatedCondition(null, WARN, "value"); + } + + @Test + public void constructor_throws_NPE_if_EvaluationStatus_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("status can't be null"); + + new EvaluatedCondition(CONDITION_1, null, "value"); + } + + @Test + public void constructor_accepts_null_value() { + EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, null); + + assertThat(underTest.getValue()).isEmpty(); + } + + @Test + public void verify_getters() { + EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, "value"); + + assertThat(underTest.getCondition()).isEqualTo(CONDITION_1); + assertThat(underTest.getStatus()).isEqualTo(WARN); + assertThat(underTest.getValue()).contains("value"); + } + + @Test + public void override_toString() { + assertThat(underTest.toString()).isEqualTo("EvaluatedCondition{condition=" + + "Condition{metricKey='metricKey', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=false}, " + + "status=WARN, value='value'}"); + } + + @Test + public void toString_does_not_quote_null_value() { + EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, null); + + assertThat(underTest.toString()).isEqualTo("EvaluatedCondition{condition=" + + "Condition{metricKey='metricKey', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=false}, " + + "status=WARN, value=null}"); + } + + @Test + public void equals_is_based_on_all_fields() { + assertThat(underTest).isEqualTo(underTest); + assertThat(underTest).isEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "value")); + assertThat(underTest).isNotEqualTo(null); + assertThat(underTest).isNotEqualTo(new Object()); + assertThat(underTest).isNotEqualTo(new EvaluatedCondition(new Condition("other_metric", EQUALS, "a", "b", true), WARN, "value")); + assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, OK, "value")); + assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, null)); + assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "other_value")); + } + + @Test + public void hashcode_is_based_on_all_fields() { + assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode()); + assertThat(underTest.hashCode()).isEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "value").hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(null); + assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(new Condition("other_metric", EQUALS, "a", "b", true), WARN, "value").hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, OK, "value").hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, null).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "other_value").hashCode()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java new file mode 100644 index 00000000000..acea86369f6 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java @@ -0,0 +1,225 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate; + +import com.google.common.collect.ImmutableSet; +import java.util.Random; +import org.apache.commons.lang.RandomStringUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.server.qualitygate.EvaluatedQualityGate.newBuilder; +import static org.sonar.server.qualitygate.EvaluatedQualityGate.Status.OK; +import static org.sonar.server.qualitygate.EvaluatedQualityGate.Status.WARN; + +public class EvaluatedQualityGateTest { + private static final String QUALITY_GATE_ID = "qg_id"; + private static final String QUALITY_GATE_NAME = "qg_name"; + private static final QualityGate NO_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, emptySet()); + private static final Condition CONDITION_1 = new Condition("metric_key", Condition.Operator.LESS_THAN, "2", "4", true); + private static final Condition CONDITION_2 = new Condition("metric_key_2", Condition.Operator.GREATER_THAN, "6", "12", false); + private static final QualityGate ONE_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, singleton(CONDITION_1)); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private final Random random = new Random(); + private final EvaluatedQualityGate.Status randomStatus = EvaluatedQualityGate.Status.values()[random.nextInt(EvaluatedQualityGate.Status.values().length)]; + private final EvaluatedCondition.EvaluationStatus randomEvaluationStatus = EvaluatedCondition.EvaluationStatus.values()[random + .nextInt(EvaluatedCondition.EvaluationStatus.values().length)]; + private final String randomValue = random.nextBoolean() ? null : RandomStringUtils.randomAlphanumeric(3); + + private EvaluatedQualityGate.Builder builder = newBuilder(); + + @Test + public void setQualityGate_fails_with_NPE_if_argument_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("qualityGate can't be null"); + + builder.setQualityGate(null); + } + + @Test + public void setStatus_fails_with_NPE_if_argument_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("status can't be null"); + + builder.setStatus(null); + } + + @Test + public void build_fails_with_NPE_if_qualityGate_not_set() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("qualityGate can't be null"); + + builder.build(); + } + + @Test + public void build_fails_with_NPE_if_status_not_set() { + builder.setQualityGate(NO_CONDITION_QUALITY_GATE); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("status can't be null"); + + builder.build(); + } + + @Test + public void addCondition_fails_with_NPE_if_condition_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("condition can't be null"); + + builder.addCondition(null, EvaluatedCondition.EvaluationStatus.WARN, "a_value"); + } + + @Test + public void addCondition_fails_with_NPE_if_status_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("status can't be null"); + + builder.addCondition(new Condition("metric_key", Condition.Operator.LESS_THAN, "2", "4", true), null, "a_value"); + } + + @Test + public void addCondition_accepts_null_value() { + builder.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null); + + assertThat(builder.getEvaluatedConditions()) + .containsOnly(new EvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null)); + } + + @Test + public void getEvaluatedConditions_returns_empty_with_no_condition_added_to_builder() { + assertThat(builder.getEvaluatedConditions()).isEmpty(); + } + + @Test + public void build_fails_with_IAE_if_condition_added_and_no_on_QualityGate() { + builder.setQualityGate(NO_CONDITION_QUALITY_GATE) + .setStatus(randomStatus) + .addCondition(CONDITION_1, randomEvaluationStatus, randomValue); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Evaluation provided for unknown conditions: [" + CONDITION_1 + "]"); + + builder.build(); + } + + @Test + public void build_fails_with_IAE_if_condition_is_missing_for_one_defined_in_QualityGate() { + builder.setQualityGate(ONE_CONDITION_QUALITY_GATE) + .setStatus(randomStatus); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Evaluation missing for the following conditions: [" + CONDITION_1 + "]"); + + builder.build(); + } + + @Test + public void verify_getters() { + EvaluatedQualityGate underTest = builder + .setQualityGate(ONE_CONDITION_QUALITY_GATE) + .setStatus(randomStatus) + .addCondition(CONDITION_1, randomEvaluationStatus, randomValue) + .build(); + + assertThat(underTest.getQualityGate()).isEqualTo(ONE_CONDITION_QUALITY_GATE); + assertThat(underTest.getStatus()).isEqualTo(randomStatus); + assertThat(underTest.getEvaluatedConditions()) + .containsOnly(new EvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue)); + } + + @Test + public void verify_getters_when_no_condition() { + EvaluatedQualityGate underTest = builder + .setQualityGate(NO_CONDITION_QUALITY_GATE) + .setStatus(randomStatus) + .build(); + + assertThat(underTest.getQualityGate()).isEqualTo(NO_CONDITION_QUALITY_GATE); + assertThat(underTest.getStatus()).isEqualTo(randomStatus); + assertThat(underTest.getEvaluatedConditions()).isEmpty(); + } + + @Test + public void verify_getters_when_multiple_conditions() { + QualityGate qualityGate = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2)); + EvaluatedQualityGate underTest = builder + .setQualityGate(qualityGate) + .setStatus(randomStatus) + .addCondition(CONDITION_1, randomEvaluationStatus, randomValue) + .addCondition(CONDITION_2, EvaluatedCondition.EvaluationStatus.WARN, "bad") + .build(); + + assertThat(underTest.getQualityGate()).isEqualTo(qualityGate); + assertThat(underTest.getStatus()).isEqualTo(randomStatus); + assertThat(underTest.getEvaluatedConditions()).containsOnly( + new EvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue), + new EvaluatedCondition(CONDITION_2, EvaluatedCondition.EvaluationStatus.WARN, "bad")); + } + + @Test + public void equals_is_based_on_all_fields() { + EvaluatedQualityGate.Builder builder = this.builder + .setQualityGate(ONE_CONDITION_QUALITY_GATE) + .setStatus(WARN) + .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.WARN, "foo"); + + EvaluatedQualityGate underTest = builder.build(); + assertThat(underTest).isEqualTo(builder.build()); + assertThat(underTest).isNotSameAs(builder.build()); + assertThat(underTest).isNotEqualTo(null); + assertThat(underTest).isNotEqualTo(new Object()); + assertThat(underTest).isNotEqualTo(builder.setQualityGate(new QualityGate("other_id", QUALITY_GATE_NAME, singleton(CONDITION_1))).build()); + assertThat(underTest).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(OK).build()); + assertThat(underTest).isNotEqualTo(newBuilder() + .setQualityGate(ONE_CONDITION_QUALITY_GATE) + .setStatus(WARN) + .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo") + .build()); + } + + @Test + public void hashcode_is_based_on_all_fields() { + EvaluatedQualityGate.Builder builder = this.builder + .setQualityGate(ONE_CONDITION_QUALITY_GATE) + .setStatus(WARN) + .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.WARN, "foo"); + + EvaluatedQualityGate underTest = builder.build(); + assertThat(underTest.hashCode()).isEqualTo(builder.build().hashCode()); + assertThat(underTest.hashCode()).isNotSameAs(builder.build().hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(null); + assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(new QualityGate("other_id", QUALITY_GATE_NAME, singleton(CONDITION_1))).build().hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(OK).build().hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(newBuilder() + .setQualityGate(ONE_CONDITION_QUALITY_GATE) + .setStatus(WARN) + .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo") + .build().hashCode()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java new file mode 100644 index 00000000000..820265327ed --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java @@ -0,0 +1,135 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualitygate; + +import com.google.common.collect.ImmutableSet; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static java.util.Collections.emptySet; +import static org.assertj.core.api.Assertions.assertThat; + +public class QualityGateTest { + private static final String QUALIGATE_ID = "qg_id"; + private static final String QUALIGATE_NAME = "qg_name"; + private static final Condition CONDITION_1 = new Condition("m1", Condition.Operator.EQUALS, "1", "2", false); + private static final Condition CONDITION_2 = new Condition("m2", Condition.Operator.LESS_THAN, "2", "4", true); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private QualityGate underTest = new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2)); + + @Test + public void constructor_fails_with_NPE_if_id_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("id can't be null"); + + new QualityGate(null, "name", emptySet()); + } + + @Test + public void constructor_fails_with_NPE_if_name_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("name can't be null"); + + new QualityGate("id", null, emptySet()); + } + + @Test + public void constructor_fails_with_NPE_if_conditions_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("conditions can't be null"); + + new QualityGate("id", "name", null); + } + + @Test + public void constructor_fails_with_NPE_if_conditions_contains_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("condition can't be null"); + Random random = new Random(); + Set conditions = Stream.of( + IntStream.range(0, random.nextInt(5)) + .mapToObj(i -> new Condition("m_before_" + i, Condition.Operator.EQUALS, null, null, false)), + Stream.of((Condition) null), + IntStream.range(0, random.nextInt(5)) + .mapToObj(i -> new Condition("m_after_" + i, Condition.Operator.EQUALS, null, null, false))) + .flatMap(s -> s) + .collect(Collectors.toSet()); + + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("condition can't be null"); + + new QualityGate("id", "name", conditions); + } + + @Test + public void verify_getters() { + assertThat(underTest.getId()).isEqualTo(QUALIGATE_ID); + assertThat(underTest.getName()).isEqualTo(QUALIGATE_NAME); + assertThat(underTest.getConditions()).containsOnly(CONDITION_1, CONDITION_2); + } + + @Test + public void toString_is_override() { + QualityGate underTest = new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2)); + + assertThat(underTest.toString()).isEqualTo("QualityGate{id=qg_id, name='qg_name', conditions=[" + + "Condition{metricKey='m2', operator=LESS_THAN, warningThreshold='4', errorThreshold='2', onLeakPeriod=true}" + + "]}"); + } + + @Test + public void equals_is_based_on_all_fields() { + assertThat(underTest).isEqualTo(underTest); + assertThat(underTest).isEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1))); + assertThat(underTest).isNotEqualTo(null); + assertThat(underTest).isNotEqualTo(new Object()); + assertThat(underTest).isNotEqualTo(new QualityGate("other_id", QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1))); + assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, "other_name", ImmutableSet.of(CONDITION_2, CONDITION_1))); + assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, emptySet())); + assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1))); + assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2))); + assertThat(underTest).isNotEqualTo( + new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2, new Condition("new", Condition.Operator.GREATER_THAN, "a", "b", false)))); + } + + @Test + public void hashcode_is_based_on_all_fields() { + assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode()); + assertThat(underTest.hashCode()).isEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(null); + assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate("other_id", QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, "other_name", ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, emptySet()).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1)).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2)).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo( + new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2, new Condition("new", Condition.Operator.GREATER_THAN, "a", "b", false))).hashCode()); + } +}