--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.qualitygate;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+
+class ConditionUtils {
+
+ private ConditionUtils() {
+ // only static stuff
+ }
+
+ /**
+ * Get the matching alert level for the given measure
+ */
+ static Metric.Level getLevel(ResolvedCondition condition, Measure measure) {
+ if (evaluateCondition(condition, measure, Metric.Level.ERROR)) {
+ return Metric.Level.ERROR;
+ }
+ if (evaluateCondition(condition, measure, Metric.Level.WARN)) {
+ return Metric.Level.WARN;
+ }
+ return Metric.Level.OK;
+ }
+
+ private static boolean evaluateCondition(ResolvedCondition condition, Measure measure, Metric.Level alertLevel) {
+ String valueToEval = getValueToEval(condition, alertLevel);
+ if (StringUtils.isEmpty(valueToEval)) {
+ return false;
+ }
+
+ Comparable criteriaValue = getValueForComparison(condition.metric(), valueToEval);
+ Comparable measureValue = getMeasureValue(condition, measure);
+ if (measureValue != null) {
+ return doesReachThresholds(measureValue, criteriaValue, condition);
+ }
+ return false;
+ }
+
+ private static boolean doesReachThresholds(Comparable measureValue, Comparable criteriaValue, ResolvedCondition condition) {
+ int comparison = measureValue.compareTo(criteriaValue);
+ return !(isNotEquals(comparison, condition)
+ || isGreater(comparison, condition)
+ || isSmaller(comparison, condition)
+ || isEquals(comparison, condition));
+ }
+
+ private static boolean isNotEquals(int comparison, ResolvedCondition condition) {
+ return condition.operator().equals("NE") && comparison == 0;
+ }
+
+ private static boolean isGreater(int comparison, ResolvedCondition condition) {
+ return condition.operator().equals("GT") && comparison != 1;
+ }
+
+ private static boolean isSmaller(int comparison, ResolvedCondition condition) {
+ return condition.operator().equals("LT") && comparison != -1;
+ }
+
+ private static boolean isEquals(int comparison, ResolvedCondition condition) {
+ return condition.operator().equals("EQ") && comparison != 0;
+ }
+
+ private static String getValueToEval(ResolvedCondition condition, Metric.Level alertLevel) {
+ if (alertLevel.equals(Metric.Level.ERROR)) {
+ return condition.errorThreshold();
+ } else if (alertLevel.equals(Metric.Level.WARN)) {
+ return condition.warningThreshold();
+ } else {
+ throw new IllegalStateException(alertLevel.toString());
+ }
+ }
+
+ private static Comparable getValueForComparison(Metric metric, String value) {
+ if (isADouble(metric)) {
+ return Double.parseDouble(value);
+ }
+ if (isAInteger(metric)) {
+ return parseInteger(value);
+ }
+ if (isAString(metric)) {
+ return value;
+ }
+ if (isABoolean(metric)) {
+ return Integer.parseInt(value);
+ }
+ if (isAWorkDuration(metric)) {
+ return Long.parseLong(value);
+ }
+ throw new NotImplementedException(metric.getType().toString());
+ }
+
+ private static Comparable<Integer> parseInteger(String value) {
+ return value.contains(".") ? Integer.parseInt(value.substring(0, value.indexOf('.'))) : Integer.parseInt(value);
+ }
+
+ private static Comparable getMeasureValue(ResolvedCondition condition, Measure measure) {
+ Metric metric = condition.metric();
+ if (isADouble(metric)) {
+ return getValue(condition, measure);
+ }
+ if (isAInteger(metric)) {
+ return parseInteger(condition, measure);
+ }
+ if (isAWorkDuration(metric)) {
+ return parseLong(condition, measure);
+ }
+ if (condition.period() == null) {
+ return getMeasureValueForStringOrBoolean(metric, measure);
+ }
+ throw new NotImplementedException(metric.getType().toString());
+ }
+
+ private static Comparable getMeasureValueForStringOrBoolean(Metric metric, Measure measure) {
+ if (isAString(metric)) {
+ return measure.getData();
+ }
+ if (isABoolean(metric)) {
+ return measure.getValue().intValue();
+ }
+ throw new NotImplementedException(metric.getType().toString());
+ }
+
+ private static Comparable<Integer> parseInteger(ResolvedCondition condition, Measure measure) {
+ Double value = getValue(condition, measure);
+ return value != null ? value.intValue() : null;
+ }
+
+ private static Comparable<Long> parseLong(ResolvedCondition condition, Measure measure) {
+ Double value = getValue(condition, measure);
+ return value != null ? value.longValue() : null;
+ }
+
+ private static boolean isADouble(Metric metric) {
+ return metric.getType() == Metric.ValueType.FLOAT ||
+ metric.getType() == Metric.ValueType.PERCENT ||
+ metric.getType() == Metric.ValueType.RATING;
+ }
+
+ private static boolean isAInteger(Metric metric) {
+ return metric.getType() == Metric.ValueType.INT ||
+ metric.getType() == Metric.ValueType.MILLISEC;
+ }
+
+ private static boolean isAString(Metric metric) {
+ return metric.getType() == Metric.ValueType.STRING ||
+ metric.getType() == Metric.ValueType.LEVEL;
+ }
+
+ private static boolean isABoolean(Metric metric) {
+ return metric.getType() == Metric.ValueType.BOOL;
+ }
+
+ private static boolean isAWorkDuration(Metric metric) {
+ return metric.getType() == Metric.ValueType.WORK_DUR;
+ }
+
+ private static Double getValue(ResolvedCondition condition, Measure measure) {
+ if (condition.period() == null) {
+ return measure.getValue();
+ } else if (condition.period() == 1) {
+ return measure.getVariation1();
+ } else if (condition.period() == 2) {
+ return measure.getVariation2();
+ } else if (condition.period() == 3) {
+ return measure.getVariation3();
+ } else {
+ throw new IllegalStateException("Following index period is not allowed : " + Double.toString(condition.period()));
+ }
+ }
+}
*/
package org.sonar.batch.qualitygate;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.sonar.api.BatchComponent;
-import org.sonar.wsclient.qualitygate.QualityGateCondition;
import javax.annotation.Nullable;
private final String name;
- private final Collection<QualityGateCondition> conditions;
+ private final Collection<ResolvedCondition> conditions;
QualityGate(@Nullable String name) {
this.name = name;
this.conditions = Lists.newArrayList();
}
+ void add(ResolvedCondition condition) {
+ this.conditions.add(condition);
+ }
+
static QualityGate disabled() {
return new QualityGate(null);
}
return name;
}
- public Collection<QualityGateCondition> conditions() {
- return conditions;
+ public Collection<ResolvedCondition> conditions() {
+ return ImmutableList.copyOf(conditions);
}
public boolean isEnabled() {
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.config.Settings;
+import org.sonar.api.measures.MetricFinder;
import org.sonar.api.utils.MessageException;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.wsclient.base.HttpException;
import org.sonar.wsclient.qualitygate.QualityGateClient;
+import org.sonar.wsclient.qualitygate.QualityGateCondition;
import org.sonar.wsclient.qualitygate.QualityGateDetails;
import java.net.HttpURLConnection;
private static final String PROPERTY_QUALITY_GATE = "sonar.qualitygate";
- public QualityGate provide(Settings settings, ServerClient client) {
- return init(settings, client, LOG);
+ public QualityGate provide(Settings settings, ServerClient client, MetricFinder metricFinder) {
+ return init(settings, client, metricFinder, LOG);
}
@VisibleForTesting
- QualityGate init(Settings settings, ServerClient client, Logger logger) {
+ QualityGate init(Settings settings, ServerClient client, MetricFinder metricFinder, Logger logger) {
QualityGate result = QualityGate.disabled();
String qualityGateSetting = settings.getString(PROPERTY_QUALITY_GATE);
if (qualityGateSetting == null) {
logger.info("No quality gate is configured.");
} else {
- result = load(qualityGateSetting, client.wsClient().qualityGateClient());
+ result = load(qualityGateSetting, client.wsClient().qualityGateClient(), metricFinder);
}
logger.info("Loaded quality gate '{}'", result.name());
return result;
}
- private QualityGate load(String qualityGateSetting, QualityGateClient qualityGateClient) {
+ private QualityGate load(String qualityGateSetting, QualityGateClient qualityGateClient, MetricFinder metricFinder) {
QualityGateDetails definitionFromServer = null;
try {
definitionFromServer = fetch(qualityGateSetting, qualityGateClient);
QualityGate configuredGate = new QualityGate(definitionFromServer.name());
+ for (QualityGateCondition condition: definitionFromServer.conditions()) {
+ configuredGate.add(new ResolvedCondition(condition, metricFinder.findByKey(condition.metricKey())));
+ }
+
return configuredGate;
}
*/
package org.sonar.batch.qualitygate;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang.StringUtils;
import org.sonar.api.batch.*;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.i18n.I18n;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.Metric;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.Durations;
+import org.sonar.core.timemachine.Periods;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
public class QualityGateVerifier implements Decorator {
+ private static final String VARIATION_METRIC_PREFIX = "new_";
+ private static final String VARIATION = "variation";
+
private QualityGate qualityGate;
- public QualityGateVerifier(QualityGate qualityGate) {
+ private Snapshot snapshot;
+ private Periods periods;
+ private I18n i18n;
+ private Durations durations;
+
+ public QualityGateVerifier(QualityGate qualityGate, Snapshot snapshot, Periods periods, I18n i18n, Durations durations) {
this.qualityGate = qualityGate;
+ this.snapshot = snapshot;
+ this.periods = periods;
+ this.i18n = i18n;
+ this.durations = durations;
}
@DependedUpon
return DecoratorBarriers.END_OF_TIME_MACHINE;
}
+ @DependsUpon
+ public Collection<Metric> dependsUponMetrics() {
+ Set<Metric> metrics = Sets.newHashSet();
+ for (ResolvedCondition condition: qualityGate.conditions()) {
+ metrics.add(condition.metric());
+ }
+ return metrics;
+ }
+
@Override
public boolean shouldExecuteOnProject(Project project) {
return qualityGate.isEnabled();
private void checkProjectConditions(DecoratorContext context) {
Metric.Level globalLevel = Metric.Level.OK;
+ List<String> labels = Lists.newArrayList();
+
+ for (ResolvedCondition condition: qualityGate.conditions()) {
+ Measure measure = context.getMeasure(condition.metric());
+ if (measure != null) {
+ Metric.Level level = ConditionUtils.getLevel(condition, measure);
+
+ /*
+ * This should probably be done only after migration from alerts
+ */
+ //measure.setAlertStatus(level);
+ String text = getText(condition, level);
+ if (!StringUtils.isBlank(text)) {
+ //measure.setAlertText(text);
+ labels.add(text);
+ }
+
+ //context.saveMeasure(measure);
+
+ if (Metric.Level.WARN == level && globalLevel != Metric.Level.ERROR) {
+ globalLevel = Metric.Level.WARN;
+
+ } else if (Metric.Level.ERROR == level) {
+ globalLevel = Metric.Level.ERROR;
+ }
+ }
+ }
+
Measure globalMeasure = new Measure(CoreMetrics.QUALITY_GATE_STATUS, globalLevel);
globalMeasure.setAlertStatus(globalLevel);
- globalMeasure.setAlertText("");
+ globalMeasure.setAlertText(StringUtils.join(labels, ", "));
context.saveMeasure(globalMeasure);
}
+ private String getText(ResolvedCondition condition, Metric.Level level) {
+ if (level == Metric.Level.OK) {
+ return null;
+ }
+ return getAlertLabel(condition, level);
+ }
+
+ private String getAlertLabel(ResolvedCondition condition, Metric.Level level) {
+ Integer alertPeriod = condition.period();
+ String metric = i18n.message(Locale.ENGLISH, "metric." + condition.metricKey() + ".name", condition.metric().getName());
+
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(metric);
+
+ if (alertPeriod != null && !condition.metricKey().startsWith(VARIATION_METRIC_PREFIX)) {
+ String variation = i18n.message(Locale.ENGLISH, VARIATION, VARIATION).toLowerCase();
+ stringBuilder.append(" ").append(variation);
+ }
+
+ stringBuilder
+ .append(" ").append(condition.operator()).append(" ")
+ .append(alertValue(condition, level));
+
+ if (alertPeriod != null) {
+ stringBuilder.append(" ").append(periods.label(snapshot, alertPeriod));
+ }
+
+ return stringBuilder.toString();
+ }
+
+ private String alertValue(ResolvedCondition condition, Metric.Level level) {
+ String value = level.equals(Metric.Level.ERROR) ? condition.errorThreshold() : condition.warningThreshold();
+ if (condition.metric().getType().equals(Metric.ValueType.WORK_DUR)) {
+ return durations.format(Locale.ENGLISH, Duration.create(Long.parseLong(value)), Durations.DurationFormat.SHORT);
+ } else {
+ return value;
+ }
+ }
+
@Override
public String toString() {
return getClass().getSimpleName();
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.qualitygate;
+
+import org.sonar.api.measures.Metric;
+import org.sonar.wsclient.qualitygate.QualityGateCondition;
+
+public class ResolvedCondition implements QualityGateCondition {
+
+ private QualityGateCondition wrapped;
+
+ private Metric metric;
+
+ public ResolvedCondition(QualityGateCondition condition, Metric metric) {
+ this.wrapped = condition;
+ this.metric = metric;
+ }
+
+ public Metric metric() {
+ return metric;
+ }
+
+ @Override
+ public Long id() {
+ return wrapped.id();
+ }
+
+ @Override
+ public String metricKey() {
+ return wrapped.metricKey();
+ }
+
+ @Override
+ public String operator() {
+ return wrapped.operator();
+ }
+
+ @Override
+ public String warningThreshold() {
+ return wrapped.warningThreshold();
+ }
+
+ @Override
+ public String errorThreshold() {
+ return wrapped.errorThreshold();
+ }
+
+ @Override
+ public Integer period() {
+ return wrapped.period();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.batch.qualitygate;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.core.qualitygate.db.QualityGateConditionDto;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ConditionUtilsTest {
+
+ private Metric metric;
+ private Measure measure;
+ private ResolvedCondition condition;
+
+ @Before
+ public void setup() {
+ metric = new Metric.Builder("test-metric", "name", Metric.ValueType.FLOAT).create();
+ measure = new Measure();
+ measure.setMetric(metric);
+ condition = mock(ResolvedCondition.class);
+ when(condition.period()).thenReturn(null);
+ }
+
+ @Test
+ public void testInputNumbers() {
+ metric.setType(Metric.ValueType.FLOAT);
+ measure.setValue(10.2d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_LESS_THAN);
+ when(condition.metric()).thenReturn(metric);
+
+ try {
+ metric.setType(Metric.ValueType.FLOAT);
+ when(condition.errorThreshold()).thenReturn("20");
+ ConditionUtils.getLevel(condition, measure);
+ } catch (NumberFormatException ex) {
+ Assert.fail();
+ }
+
+ try {
+ metric.setType(Metric.ValueType.INT);
+ when(condition.errorThreshold()).thenReturn("20.1");
+ ConditionUtils.getLevel(condition, measure);
+ } catch (NumberFormatException ex) {
+ Assert.fail();
+ }
+
+ try {
+ metric.setType(Metric.ValueType.PERCENT);
+ when(condition.errorThreshold()).thenReturn("20.1");
+ ConditionUtils.getLevel(condition, measure);
+ } catch (NumberFormatException ex) {
+ Assert.fail();
+ }
+ }
+
+ @Test
+ public void testEquals() {
+
+ metric.setType(Metric.ValueType.FLOAT);
+ measure.setValue(10.2d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_EQUALS);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn("10.2");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("10.1");
+ Assert.assertEquals(Metric.Level.OK, ConditionUtils.getLevel(condition, measure));
+
+ metric.setType(Metric.ValueType.STRING);
+ measure.setData("TEST");
+ measure.setValue(null);
+
+ when(condition.errorThreshold()).thenReturn("TEST");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("TEST2");
+ Assert.assertEquals(Metric.Level.OK, ConditionUtils.getLevel(condition, measure));
+
+ }
+
+ @Test
+ public void testNotEquals() {
+
+ metric.setType(Metric.ValueType.FLOAT);
+ measure.setValue(10.2d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_NOT_EQUALS);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn("10.2");
+ Assert.assertEquals(Metric.Level.OK, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("10.1");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+
+ metric.setType(Metric.ValueType.STRING);
+ measure.setData("TEST");
+ measure.setValue(null);
+
+ when(condition.errorThreshold()).thenReturn("TEST");
+ Assert.assertEquals(Metric.Level.OK, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("TEST2");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+
+ }
+
+ @Test
+ public void testGreater() {
+ metric.setType(Metric.ValueType.FLOAT);
+ measure.setValue(10.2d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_GREATER_THAN);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn("10.1");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("10.3");
+ Assert.assertEquals(Metric.Level.OK, ConditionUtils.getLevel(condition, measure));
+ }
+
+ @Test
+ public void testSmaller() {
+ metric.setType(Metric.ValueType.FLOAT);
+ measure.setValue(10.2d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_LESS_THAN);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn("10.1");
+ Assert.assertEquals(Metric.Level.OK, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("10.3");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+ }
+
+ @Test
+ public void testPercent() {
+ metric.setType(Metric.ValueType.PERCENT);
+ measure.setValue(10.2d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_EQUALS);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn("10.2");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+ }
+
+ @Test
+ public void testFloat() {
+ metric.setType(Metric.ValueType.FLOAT);
+ measure.setValue(10.2d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_EQUALS);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn("10.2");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+ }
+
+ @Test
+ public void testInteger() {
+ metric.setType(Metric.ValueType.INT);
+ measure.setValue(10.2d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_EQUALS);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn("10");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("10.2");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+ }
+
+ @Test
+ public void testLevel() {
+ metric.setType(Metric.ValueType.LEVEL);
+ measure.setData(Metric.Level.ERROR.toString());
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_EQUALS);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn(Metric.Level.ERROR.toString());
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn(Metric.Level.OK.toString());
+ Assert.assertEquals(Metric.Level.OK, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_NOT_EQUALS);
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+ }
+
+ @Test
+ public void testBooleans() {
+ metric.setType(Metric.ValueType.BOOL);
+ measure.setValue(0d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_EQUALS);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn("1");
+ Assert.assertEquals(Metric.Level.OK, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("0");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_NOT_EQUALS);
+ when(condition.errorThreshold()).thenReturn("1");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("0");
+ Assert.assertEquals(Metric.Level.OK, ConditionUtils.getLevel(condition, measure));
+ }
+
+ @Test
+ public void test_work_duration() {
+ metric.setType(Metric.ValueType.WORK_DUR);
+ measure.setValue(60.0d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_EQUALS);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn("60");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+ }
+
+ @Test
+ public void testErrorAndWarningLevel() {
+ metric.setType(Metric.ValueType.FLOAT);
+ measure.setValue(10.2d);
+ when(condition.operator()).thenReturn(QualityGateConditionDto.OPERATOR_EQUALS);
+ when(condition.metric()).thenReturn(metric);
+
+ when(condition.errorThreshold()).thenReturn("10.2");
+ Assert.assertEquals(Metric.Level.ERROR, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("10.1");
+ Assert.assertEquals(Metric.Level.OK, ConditionUtils.getLevel(condition, measure));
+
+ when(condition.errorThreshold()).thenReturn("10.3");
+ when(condition.warningThreshold()).thenReturn("10.2");
+ Assert.assertEquals(Metric.Level.WARN, ConditionUtils.getLevel(condition, measure));
+ }
+}
*/
package org.sonar.batch.qualitygate;
+import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;
import org.sonar.api.config.Settings;
+import org.sonar.api.measures.MetricFinder;
import org.sonar.api.utils.MessageException;
import org.sonar.batch.bootstrap.ServerClient;
import org.sonar.wsclient.SonarClient;
import org.sonar.wsclient.base.HttpException;
import org.sonar.wsclient.qualitygate.QualityGateClient;
+import org.sonar.wsclient.qualitygate.QualityGateCondition;
import org.sonar.wsclient.qualitygate.QualityGateDetails;
import java.net.HttpURLConnection;
+import java.util.Collection;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@Mock
private ServerClient client;
+ @Mock
+ private MetricFinder metricFinder;
+
@Mock
private QualityGateClient qualityGateClient;
@Test
public void should_load_empty_quality_gate_from_default_settings() {
- assertThat(new QualityGateProvider().provide(settings, client).conditions()).isEmpty();
- assertThat(new QualityGateProvider().init(settings, client, logger).isEnabled()).isFalse();
+ assertThat(new QualityGateProvider().provide(settings, client, metricFinder).conditions()).isEmpty();
+ assertThat(new QualityGateProvider().init(settings, client, metricFinder, logger).isEnabled()).isFalse();
verify(logger).info("No quality gate is configured.");
}
QualityGateDetails qGate = mock(QualityGateDetails.class);
when(qualityGateClient.show(qGateName)).thenReturn(qGate);
when(qGate.name()).thenReturn(qGateName);
- QualityGate actualGate = new QualityGateProvider().init(settings, client, logger);
+ QualityGate actualGate = new QualityGateProvider().init(settings, client, metricFinder, logger);
assertThat(actualGate.name()).isEqualTo(qGateName);
assertThat(actualGate.isEnabled()).isTrue();
verify(logger).info("Loaded quality gate '{}'", qGateName);
QualityGateDetails qGate = mock(QualityGateDetails.class);
when(qualityGateClient.show(qGateId)).thenReturn(qGate);
when(qGate.name()).thenReturn(qGateName);
- assertThat(new QualityGateProvider().init(settings, client, logger).name()).isEqualTo(qGateName);
+ String metricKey1 = "metric1";
+ QualityGateCondition serverCondition1 = mock(QualityGateCondition.class);
+ when(serverCondition1.metricKey()).thenReturn(metricKey1);
+ String metricKey2 = "metric2";
+ QualityGateCondition serverCondition2 = mock(QualityGateCondition.class);
+ when(serverCondition2.metricKey()).thenReturn(metricKey2);
+ Collection<QualityGateCondition> conditions = ImmutableList.of(serverCondition1, serverCondition2);
+ when(qGate.conditions()).thenReturn(conditions);
+ assertThat(new QualityGateProvider().init(settings, client, metricFinder, logger).name()).isEqualTo(qGateName);
verify(logger).info("Loaded quality gate '{}'", qGateName);
+ verify(metricFinder).findByKey(metricKey1);
+ verify(metricFinder).findByKey(metricKey2);
}
@Test(expected = MessageException.class)
String qGateName = "Sonar way";
when(settings.getString("sonar.qualitygate")).thenReturn(qGateName);
when(qualityGateClient.show(qGateName)).thenThrow(new HttpException("http://server/api/qualitygates/show?name=Sonar%20way", HttpURLConnection.HTTP_NOT_FOUND));
- new QualityGateProvider().provide(settings, client);
+ new QualityGateProvider().provide(settings, client, metricFinder);
}
@Test(expected = HttpException.class)
String qGateName = "Sonar way";
when(settings.getString("sonar.qualitygate")).thenReturn(qGateName);
when(qualityGateClient.show(qGateName)).thenThrow(new HttpException("http://server/api/qualitygates/show?name=Sonar%20way", HttpURLConnection.HTTP_NOT_ACCEPTABLE));
- new QualityGateProvider().provide(settings, client);
+ new QualityGateProvider().provide(settings, client, metricFinder);
}
}
*/
package org.sonar.batch.qualitygate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.NotImplementedException;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentMatcher;
import org.sonar.api.batch.DecoratorBarriers;
import org.sonar.api.batch.DecoratorContext;
import org.sonar.api.database.model.Snapshot;
import org.sonar.api.i18n.I18n;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.File;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
+import org.sonar.api.test.IsMeasure;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.Durations;
+import org.sonar.core.qualitygate.db.QualityGateConditionDto;
import org.sonar.core.timemachine.Periods;
+import java.util.ArrayList;
import java.util.Locale;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class QualityGateVerifierTest {
Snapshot snapshot;
Periods periods;
I18n i18n;
+ Durations durations;
@Before
public void before() {
periods = mock(Periods.class);
i18n = mock(I18n.class);
when(i18n.message(any(Locale.class), eq("variation"), eq("variation"))).thenReturn("variation");
+ durations = mock(Durations.class);
measureClasses = new Measure(CoreMetrics.CLASSES, 20d);
measureCoverage = new Measure(CoreMetrics.COVERAGE, 35d);
snapshot = mock(Snapshot.class);
qualityGate = mock(QualityGate.class);
when(qualityGate.isEnabled()).thenReturn(true);
- verifier = new QualityGateVerifier(qualityGate);
+ verifier = new QualityGateVerifier(qualityGate, snapshot, periods, i18n, durations);
project = new Project("foo");
}
assertThat(verifier.dependsOnVariations()).isEqualTo(DecoratorBarriers.END_OF_TIME_MACHINE);
}
+ @Test
+ public void depends_upon_metrics() {
+ when(qualityGate.conditions()).thenReturn(ImmutableList.of(new ResolvedCondition(null, CoreMetrics.CLASSES)));
+ assertThat(verifier.dependsUponMetrics()).containsOnly(CoreMetrics.CLASSES);
+ }
+
+ @Test
+ public void ok_when_no_alerts() {
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "20"),
+ mockCondition(CoreMetrics.COVERAGE, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "35.0"));
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.OK.toString())));
+ //verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
+ //verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
+ }
+
+ @Test
+ public void check_root_modules_only() {
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "20"),
+ mockCondition(CoreMetrics.COVERAGE, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "35.0"));
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(File.create("src/Foo.php"), context);
+
+ verify(context, never()).saveMeasure(any(Measure.class));
+ }
+
+ @Test
+ public void generate_warnings() {
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "100"),
+ mockCondition(CoreMetrics.COVERAGE, QualityGateConditionDto.OPERATOR_LESS_THAN, null, "95.0")); // generates warning because coverage 35% < 95%
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.WARN, null)));
+
+ //verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
+ //verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
+
+ }
+
+ @Test
+ public void globalStatusShouldBeErrorIfWarningsAndErrors() {
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_LESS_THAN, null, "100"), // generates warning because classes 20 < 100
+ mockCondition(CoreMetrics.COVERAGE, QualityGateConditionDto.OPERATOR_LESS_THAN, "50.0", "80.0")); // generates error because coverage 35% < 50%
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.ERROR, null)));
+
+ //verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
+ //verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.ERROR)));
+ }
+
+ @Test
+ public void globalLabelShouldAggregateAllLabels() {
+ when(i18n.message(any(Locale.class), eq("metric.classes.name"), anyString())).thenReturn("Classes");
+ when(i18n.message(any(Locale.class), eq("metric.coverage.name"), anyString())).thenReturn("Coverages");
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_LESS_THAN, null, "10000"), // there are 20 classes, error threshold is higher => alert
+ mockCondition(CoreMetrics.COVERAGE, QualityGateConditionDto.OPERATOR_LESS_THAN, "50.0", "80.0"));// coverage is 35%, warning threshold is higher => alert
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.ERROR, "Classes LT 10000, Coverages LT 50.0")));
+ }
+
+ @Test
+ public void alertLabelUsesL10nMetricName() {
+ Metric metric = new Metric.Builder("rating", "Rating", Metric.ValueType.INT).create();
+
+ // metric name is declared in l10n bundle
+ when(i18n.message(any(Locale.class), eq("metric.rating.name"), anyString())).thenReturn("THE RATING");
+
+ when(context.getMeasure(metric)).thenReturn(new Measure(metric, 4d));
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(mockCondition(metric, QualityGateConditionDto.OPERATOR_LESS_THAN, "10", null));
+ when(qualityGate.conditions()).thenReturn(conditions);
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.ERROR, "THE RATING LT 10")));
+ }
+
+ @Test
+ public void alertLabelUsesMetricNameIfMissingL10nBundle() {
+ // the third argument is Metric#getName()
+ when(i18n.message(any(Locale.class), eq("metric.classes.name"), eq("Classes"))).thenReturn("Classes");
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ // there are 20 classes, error threshold is higher => alert
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_LESS_THAN, "10000", null)
+ );
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.ERROR, "Classes LT 10000")));
+ }
+
+ @Test
+ public void shouldBeOkIfPeriodVariationIsEnough() {
+ measureClasses.setVariation1(0d);
+ measureCoverage.setVariation2(50d);
+ measureComplexity.setVariation3(2d);
+
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "10", 1), // ok because no variation
+ mockCondition(CoreMetrics.COVERAGE, QualityGateConditionDto.OPERATOR_LESS_THAN, null, "40.0", 2), // ok because coverage increases of 50%, which is more
+ // than 40%
+ mockCondition(CoreMetrics.COMPLEXITY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "5", 3) // ok because complexity increases of 2, which is less
+ // than 5
+ );
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.OK, null)));
+
+ //verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
+ //verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
+ //verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.OK)));
+ }
+
+ @Test
+ public void shouldGenerateWarningIfPeriodVariationIsNotEnough() {
+ measureClasses.setVariation1(40d);
+ measureCoverage.setVariation2(5d);
+ measureComplexity.setVariation3(70d);
+
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "30", 1), // generates warning because classes increases of 40,
+ // which is greater than 30
+ mockCondition(CoreMetrics.COVERAGE, QualityGateConditionDto.OPERATOR_LESS_THAN, null, "10.0", 2), // generates warning because coverage increases of 5%,
+ // which is smaller than 10%
+ mockCondition(CoreMetrics.COMPLEXITY, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "60", 3) // generates warning because complexity increases of
+ // 70, which is smaller than 60
+ );
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.WARN, null)));
+
+ //verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
+ //verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
+ //verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.WARN)));
+ }
+
+ @Test
+ public void shouldBeOkIfVariationIsNull() {
+ measureClasses.setVariation1(null);
+
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "10", 1));
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.OK, null)));
+ //verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
+ }
+
+ @Test
+ public void shouldVariationPeriodValueCouldBeUsedForRatingMetric() {
+ Metric ratingMetric = new Metric.Builder("key_rating_metric", "Rating metric", Metric.ValueType.RATING).create();
+ Measure measureRatingMetric = new Measure(ratingMetric, 150d);
+ measureRatingMetric.setVariation1(50d);
+ when(context.getMeasure(ratingMetric)).thenReturn(measureRatingMetric);
+
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(ratingMetric, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "100", 1)
+ );
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.OK, null)));
+ //verify(context).saveMeasure(argThat(hasLevel(measureRatingMetric, Metric.Level.OK)));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void shouldAllowOnlyVariationPeriodOneGlobalPeriods() {
+ measureClasses.setVariation4(40d);
+
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "30", 4)
+ );
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+ }
+
+ @Test(expected = NotImplementedException.class)
+ public void shouldNotAllowPeriodVariationAlertOnStringMetric() {
+ Measure measure = new Measure(CoreMetrics.SCM_AUTHORS_BY_LINE, 100d);
+ measure.setVariation1(50d);
+ when(context.getMeasure(CoreMetrics.SCM_AUTHORS_BY_LINE)).thenReturn(measure);
+
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.SCM_AUTHORS_BY_LINE, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "30", 1)
+ );
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+ }
+
+ @Test
+ public void shouldLabelAlertContainsPeriod() {
+ measureClasses.setVariation1(40d);
+
+ when(i18n.message(any(Locale.class), eq("metric.classes.name"), anyString())).thenReturn("Classes");
+ when(periods.label(snapshot, 1)).thenReturn("since someday");
+
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(CoreMetrics.CLASSES, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "30", 1) // generates warning because classes increases of 40,
+ // which is greater than 30
+ );
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.WARN, "Classes variation GT 30 since someday")));
+ }
+
+ @Test
+ public void shouldLabelAlertForNewMetricDoNotContainsVariationWord() {
+ Metric newMetric = new Metric.Builder("new_metric_key", "New Metric", Metric.ValueType.INT).create();
+ Measure measure = new Measure(newMetric, 15d);
+ measure.setVariation1(50d);
+ when(context.getMeasure(newMetric)).thenReturn(measure);
+ measureClasses.setVariation1(40d);
+
+ when(i18n.message(any(Locale.class), eq("metric.new_metric_key.name"), anyString())).thenReturn("New Measure");
+ when(periods.label(snapshot, 1)).thenReturn("since someday");
+
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(
+ mockCondition(newMetric, QualityGateConditionDto.OPERATOR_GREATER_THAN, null, "30", 1) // generates warning because classes increases of 40, which is
+ // greater than 30
+ );
+ when(qualityGate.conditions()).thenReturn(conditions);
+
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.WARN, "New Measure GT 30 since someday")));
+ }
+
+ @Test
+ public void alert_on_work_duration() {
+ Metric metric = new Metric.Builder("tech_debt", "Debt", Metric.ValueType.WORK_DUR).create();
+
+ // metric name is declared in l10n bundle
+ when(i18n.message(any(Locale.class), eq("metric.tech_debt.name"), anyString())).thenReturn("The Debt");
+ when(durations.format(any(Locale.class), eq(Duration.create(3600L)), eq(Durations.DurationFormat.SHORT))).thenReturn("1h");
+
+ when(context.getMeasure(metric)).thenReturn(new Measure(metric, 1800d));
+ ArrayList<ResolvedCondition> conditions = Lists.newArrayList(mockCondition(metric, QualityGateConditionDto.OPERATOR_LESS_THAN, "3600", null));
+ when(qualityGate.conditions()).thenReturn(conditions);
+ verifier.decorate(project, context);
+
+ verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.QUALITY_GATE_STATUS, Metric.Level.ERROR, "The Debt LT 1h")));
+ }
+
+ private ArgumentMatcher<Measure> matchesMetric(final Metric metric, final Metric.Level alertStatus, final String alertText) {
+ return new ArgumentMatcher<Measure>() {
+ @Override
+ public boolean matches(Object arg) {
+ boolean result = ((Measure) arg).getMetric().equals(metric) && ((Measure) arg).getAlertStatus() == alertStatus;
+ if (result && alertText != null) {
+ result = alertText.equals(((Measure) arg).getAlertText());
+ }
+ return result;
+ }
+ };
+ }
+
+ private ArgumentMatcher<Measure> hasLevel(final Measure measure, final Metric.Level alertStatus) {
+ return new ArgumentMatcher<Measure>() {
+ @Override
+ public boolean matches(Object arg) {
+ return arg == measure && ((Measure) arg).getAlertStatus().equals(alertStatus);
+ }
+ };
+ }
+
+ private ResolvedCondition mockCondition(Metric metric, String operator, String error, String warning) {
+ return mockCondition(metric, operator, error, warning, null);
+ }
+
+ private ResolvedCondition mockCondition(Metric metric, String operator, String error, String warning, Integer period) {
+ ResolvedCondition cond = mock(ResolvedCondition.class);
+ when(cond.metric()).thenReturn(metric);
+ when(cond.metricKey()).thenReturn(metric.getKey());
+ when(cond.operator()).thenReturn(operator);
+ when(cond.warningThreshold()).thenReturn(warning);
+ when(cond.errorThreshold()).thenReturn(error);
+ when(cond.period()).thenReturn(period);
+ return cond;
+ }
+
}