package org.sonar.server.qualitygate; | package org.sonar.server.qualitygate; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.stream.IntStream; | |||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.measures.Metric; | import org.sonar.api.measures.Metric; | ||||
import org.sonar.db.qualitygate.QualityGateConditionDto; | import org.sonar.db.qualitygate.QualityGateConditionDto; | ||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertFalse; | |||||
import static org.junit.Assert.assertTrue; | |||||
import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS; | |||||
import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES; | import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES; | ||||
import static org.sonar.api.measures.CoreMetrics.FUNCTION_COMPLEXITY; | |||||
import static org.sonar.api.measures.CoreMetrics.LINE_COVERAGE; | import static org.sonar.api.measures.CoreMetrics.LINE_COVERAGE; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE; | import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY; | import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING; | |||||
import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING; | |||||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; | import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING; | |||||
import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS; | |||||
import static org.sonar.server.qualitygate.QualityGateCaycChecker.BEST_VALUE_REQUIREMENTS; | |||||
import static org.sonar.server.qualitygate.QualityGateCaycChecker.CAYC_METRICS; | import static org.sonar.server.qualitygate.QualityGateCaycChecker.CAYC_METRICS; | ||||
import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT; | import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT; | ||||
import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT; | import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT; | ||||
QualityGateCaycChecker underTest = new QualityGateCaycChecker(db.getDbClient()); | QualityGateCaycChecker underTest = new QualityGateCaycChecker(db.getDbClient()); | ||||
@Test | @Test | ||||
public void checkCaycCompliant() { | |||||
public void checkCaycCompliant_when_contains_all_and_only_complicant_conditions_should_return_compliant() { | |||||
String qualityGateUuid = "abcd"; | String qualityGateUuid = "abcd"; | ||||
CAYC_METRICS.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | CAYC_METRICS.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | ||||
assertEquals(COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)); | assertEquals(COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)); | ||||
} | } | ||||
@Test | @Test | ||||
public void check_Cayc_non_compliant_with_extra_conditions() { | |||||
public void checkCaycCompliant_when_extra_conditions_should_return_over_compliant() { | |||||
String qualityGateUuid = "abcd"; | String qualityGateUuid = "abcd"; | ||||
CAYC_METRICS.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | CAYC_METRICS.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | ||||
// extra conditions outside of CAYC requirements | // extra conditions outside of CAYC requirements | ||||
List.of(LINE_COVERAGE, DUPLICATED_LINES).forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | |||||
List.of(LINE_COVERAGE, DUPLICATED_LINES).forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, | |||||
metric.getBestValue())); | |||||
assertEquals(OVER_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)); | assertEquals(OVER_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)); | ||||
} | } | ||||
@Test | @Test | ||||
public void check_Cayc_NonCompliant_with_lesser_threshold_value() { | |||||
public void checkCaycCompliant_when_conditions_have_lesser_threshold_value_should_return_non_compliant() { | |||||
var metrics = CAYC_METRICS.stream().map(this::insertMetric).toList(); | var metrics = CAYC_METRICS.stream().map(this::insertMetric).toList(); | ||||
IntStream.range(0, 4).forEach(idx -> { | |||||
String qualityGateUuid = "abcd" + idx; | |||||
for (int i = 0; i < metrics.size(); i++) { | |||||
var metric = metrics.get(i); | |||||
insertCondition(metric, qualityGateUuid, idx == i ? metric.getWorstValue() : metric.getBestValue()); | |||||
String qualityGateUuid = "abcd"; | |||||
for (var metric : metrics) { | |||||
if (BEST_VALUE_REQUIREMENTS.keySet().contains(metric.getKey())) { | |||||
insertCondition(metric, qualityGateUuid, metric.getBestValue() - 1); | |||||
} else { | |||||
insertCondition(metric, qualityGateUuid, metric.getBestValue()); | |||||
} | } | ||||
assertEquals(NON_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)); | |||||
}); | |||||
} | |||||
assertEquals(NON_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)); | |||||
} | |||||
@Test | |||||
public void isCaycCondition_when_check_compliant_condition_should_return_true() { | |||||
CAYC_METRICS.stream().map(this::toMetricDto) | |||||
.forEach(metricDto -> assertTrue(underTest.isCaycCondition(metricDto))); | |||||
} | |||||
@Test | |||||
public void isCaycCondition_when_check_non_compliant_condition_should_return_false() { | |||||
List.of(BLOCKER_VIOLATIONS, FUNCTION_COMPLEXITY) | |||||
.stream().map(this::toMetricDto) | |||||
.forEach(metricDto -> assertFalse(underTest.isCaycCondition(metricDto))); | |||||
} | } | ||||
@Test | @Test | ||||
public void check_Cayc_NonCompliant_with_missing_metric() { | |||||
public void checkCaycCompliant_when_missing_compliant_condition_should_return_non_compliant() { | |||||
String qualityGateUuid = "abcd"; | String qualityGateUuid = "abcd"; | ||||
List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED, NEW_DUPLICATED_LINES_DENSITY) | |||||
List.of(NEW_VIOLATIONS, NEW_SECURITY_HOTSPOTS_REVIEWED) | |||||
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | .forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | ||||
assertEquals(NON_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)); | assertEquals(NON_COMPLIANT, underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)); | ||||
} | } | ||||
@Test | @Test | ||||
public void existency_requirements_check_only_existency() { | public void existency_requirements_check_only_existency() { | ||||
String qualityGateUuid = "abcd"; | String qualityGateUuid = "abcd"; | ||||
List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED, NEW_SECURITY_RATING) | |||||
List.of(NEW_VIOLATIONS, NEW_SECURITY_HOTSPOTS_REVIEWED) | |||||
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | .forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue())); | ||||
List.of(NEW_COVERAGE, NEW_DUPLICATED_LINES_DENSITY) | List.of(NEW_COVERAGE, NEW_DUPLICATED_LINES_DENSITY) | ||||
.forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getWorstValue())); | .forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getWorstValue())); | ||||
.setValueType(metric.getType().name()) | .setValueType(metric.getType().name()) | ||||
.setHidden(false) | .setHidden(false) | ||||
.setBestValue(metric.getBestValue()) | .setBestValue(metric.getBestValue()) | ||||
.setBestValue(metric.getWorstValue()) | |||||
.setWorstValue(metric.getWorstValue()) | |||||
.setDirection(metric.getDirection())); | .setDirection(metric.getDirection())); | ||||
} | } | ||||
private MetricDto toMetricDto(Metric metric) { | |||||
return new MetricDto() | |||||
.setKey(metric.key()) | |||||
.setValueType(metric.getType().name()) | |||||
.setBestValue(metric.getBestValue()) | |||||
.setWorstValue(metric.getWorstValue()) | |||||
.setDirection(metric.getDirection()); | |||||
} | |||||
} | } |
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.junit.runner.RunWith; | import org.junit.runner.RunWith; | ||||
import org.sonar.api.measures.CoreMetrics; | |||||
import org.sonar.api.measures.Metric; | import org.sonar.api.measures.Metric; | ||||
import org.sonar.api.server.ws.WebService; | import org.sonar.api.server.ws.WebService; | ||||
import org.sonar.core.util.UuidFactoryFast; | import org.sonar.core.util.UuidFactoryFast; | ||||
private final DbClient dbClient = db.getDbClient(); | private final DbClient dbClient = db.getDbClient(); | ||||
private final DbSession dbSession = db.getSession(); | private final DbSession dbSession = db.getSession(); | ||||
private final CreateAction underTest = new CreateAction(dbClient, userSession, new QualityGateUpdater(dbClient, UuidFactoryFast.getInstance()), | |||||
private final CreateAction underTest = new CreateAction(dbClient, userSession, new QualityGateUpdater(dbClient, | |||||
UuidFactoryFast.getInstance()), | |||||
new QualityGateConditionsUpdater(dbClient)); | new QualityGateConditionsUpdater(dbClient)); | ||||
private final WsActionTester ws = new WsActionTester(underTest); | private final WsActionTester ws = new WsActionTester(underTest); | ||||
var conditions = getConditions(dbSession, qualityGateDto); | var conditions = getConditions(dbSession, qualityGateDto); | ||||
CAYC_METRICS.stream() | CAYC_METRICS.stream() | ||||
.map(m -> dbClient.metricDao().selectByKey(dbSession, m.getKey())) | |||||
.map(metric -> dbClient.metricDao().selectByKey(dbSession, metric.getKey())) | |||||
.forEach(metricDto -> assertThat(conditions) | .forEach(metricDto -> assertThat(conditions) | ||||
.anyMatch(c -> metricDto.getUuid().equals(c.getMetricUuid()) && c.getErrorThreshold().equals(String.valueOf(getDefaultCaycValue(metricDto))))); | .anyMatch(c -> metricDto.getUuid().equals(c.getMetricUuid()) && c.getErrorThreshold().equals(String.valueOf(getDefaultCaycValue(metricDto))))); | ||||
} | } | ||||
@DataProvider | @DataProvider | ||||
public static Object[][] nullOrEmpty() { | public static Object[][] nullOrEmpty() { | ||||
return new Object[][] { | |||||
return new Object[][]{ | |||||
{null}, | {null}, | ||||
{""}, | {""}, | ||||
{" "} | {" "} |
package org.sonar.server.qualitygate; | package org.sonar.server.qualitygate; | ||||
import java.io.Serializable; | import java.io.Serializable; | ||||
import java.util.List; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.function.Function; | import java.util.function.Function; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import java.util.stream.Stream; | |||||
import org.sonar.api.measures.Metric; | import org.sonar.api.measures.Metric; | ||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import org.sonar.db.DbSession; | import org.sonar.db.DbSession; | ||||
import org.sonar.db.metric.MetricDto; | import org.sonar.db.metric.MetricDto; | ||||
import org.sonar.db.qualitygate.QualityGateConditionDto; | import org.sonar.db.qualitygate.QualityGateConditionDto; | ||||
import static java.util.stream.Collectors.toUnmodifiableMap; | |||||
import static java.util.stream.Collectors.toMap; | |||||
import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE; | import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY; | import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY; | import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY; | import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING; | |||||
import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING; | |||||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; | import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED; | ||||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING; | |||||
import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS; | |||||
import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT; | import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT; | ||||
import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT; | import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT; | ||||
import static org.sonar.server.qualitygate.QualityGateCaycStatus.OVER_COMPLIANT; | import static org.sonar.server.qualitygate.QualityGateCaycStatus.OVER_COMPLIANT; | ||||
public class QualityGateCaycChecker { | public class QualityGateCaycChecker { | ||||
public static final List<Metric<? extends Serializable>> CAYC_METRICS = List.of( | |||||
NEW_MAINTAINABILITY_RATING, | |||||
NEW_RELIABILITY_RATING, | |||||
static final Map<String, Double> BEST_VALUE_REQUIREMENTS = Stream.of( | |||||
NEW_VIOLATIONS, | |||||
NEW_SECURITY_HOTSPOTS_REVIEWED | |||||
).collect(toMap(Metric::getKey, Metric::getBestValue)); | |||||
static final Set<String> EXISTENCY_REQUIREMENTS = Set.of( | |||||
NEW_DUPLICATED_LINES_DENSITY_KEY, | |||||
NEW_COVERAGE_KEY | |||||
); | |||||
public static final Set<Metric<? extends Serializable>> CAYC_METRICS = Set.of( | |||||
NEW_VIOLATIONS, | |||||
NEW_SECURITY_HOTSPOTS_REVIEWED, | NEW_SECURITY_HOTSPOTS_REVIEWED, | ||||
NEW_SECURITY_RATING, | |||||
NEW_DUPLICATED_LINES_DENSITY, | NEW_DUPLICATED_LINES_DENSITY, | ||||
NEW_COVERAGE | NEW_COVERAGE | ||||
); | ); | ||||
private static final Set<String> EXISTENCY_REQUIREMENTS = Set.of( | |||||
NEW_DUPLICATED_LINES_DENSITY_KEY, | |||||
NEW_COVERAGE_KEY | |||||
); | |||||
private static final Map<String, Double> BEST_VALUE_REQUIREMENTS = CAYC_METRICS.stream() | |||||
.filter(metric -> !EXISTENCY_REQUIREMENTS.contains(metric.getKey())) | |||||
.collect(toUnmodifiableMap(Metric::getKey, Metric::getBestValue)); | |||||
private final DbClient dbClient; | private final DbClient dbClient; | ||||
public QualityGateCaycChecker(DbClient dbClient) { | public QualityGateCaycChecker(DbClient dbClient) { | ||||
.orElse(NON_COMPLIANT); | .orElse(NON_COMPLIANT); | ||||
} | } | ||||
public boolean isCaycCondition(MetricDto metric) { | |||||
return CAYC_METRICS.stream().map(Metric::getKey).anyMatch(metric.getKey()::equals); | |||||
} | |||||
private static boolean checkMetricCaycCompliant(QualityGateConditionDto condition, MetricDto metric) { | private static boolean checkMetricCaycCompliant(QualityGateConditionDto condition, MetricDto metric) { | ||||
if (EXISTENCY_REQUIREMENTS.contains(metric.getKey())) { | if (EXISTENCY_REQUIREMENTS.contains(metric.getKey())) { | ||||
return true; | return true; |
} | } | ||||
private void addCaycConditions(DbSession dbSession, QualityGateDto newQualityGate) { | private void addCaycConditions(DbSession dbSession, QualityGateDto newQualityGate) { | ||||
CAYC_METRICS.forEach(m -> | |||||
qualityGateConditionsUpdater.createCondition(dbSession, newQualityGate, m.getKey(), OPERATORS_BY_DIRECTION.get(m.getDirection()).getDbValue(), | |||||
String.valueOf(getDefaultCaycValue(m))) | |||||
CAYC_METRICS.forEach(metric -> | |||||
qualityGateConditionsUpdater.createCondition(dbSession, newQualityGate, metric.getKey(), OPERATORS_BY_DIRECTION.get(metric.getDirection()).getDbValue(), | |||||
String.valueOf(getDefaultCaycValue(metric))) | |||||
); | ); | ||||
} | } | ||||
return dbClient.metricDao().selectByUuids(dbSession, metricUuids).stream().filter(MetricDto::isEnabled).collect(Collectors.toMap(MetricDto::getUuid, Function.identity())); | return dbClient.metricDao().selectByUuids(dbSession, metricUuids).stream().filter(MetricDto::isEnabled).collect(Collectors.toMap(MetricDto::getUuid, Function.identity())); | ||||
} | } | ||||
private ShowWsResponse buildResponse(DbSession dbSession, QualityGateDto qualityGate, QualityGateDto defaultQualityGate, Collection<QualityGateConditionDto> conditions, | |||||
Map<String, MetricDto> metricsByUuid, QualityGateCaycStatus caycStatus) { | |||||
private ShowWsResponse buildResponse(DbSession dbSession, QualityGateDto qualityGate, QualityGateDto defaultQualityGate, | |||||
Collection<QualityGateConditionDto> conditions, Map<String, MetricDto> metricsByUuid, QualityGateCaycStatus caycStatus) { | |||||
return ShowWsResponse.newBuilder() | return ShowWsResponse.newBuilder() | ||||
.setName(qualityGate.getName()) | .setName(qualityGate.getName()) | ||||
.setIsBuiltIn(qualityGate.isBuiltIn()) | .setIsBuiltIn(qualityGate.isBuiltIn()) | ||||
.build(); | .build(); | ||||
} | } | ||||
private static Function<QualityGateConditionDto, ShowWsResponse.Condition> toWsCondition(Map<String, MetricDto> metricsByUuid) { | |||||
private Function<QualityGateConditionDto, ShowWsResponse.Condition> toWsCondition(Map<String, MetricDto> metricsByUuid) { | |||||
return condition -> { | return condition -> { | ||||
String metricUuid = condition.getMetricUuid(); | String metricUuid = condition.getMetricUuid(); | ||||
MetricDto metric = metricsByUuid.get(metricUuid); | MetricDto metric = metricsByUuid.get(metricUuid); | ||||
ShowWsResponse.Condition.Builder builder = ShowWsResponse.Condition.newBuilder() | ShowWsResponse.Condition.Builder builder = ShowWsResponse.Condition.newBuilder() | ||||
.setId(condition.getUuid()) | .setId(condition.getUuid()) | ||||
.setMetric(metric.getKey()) | .setMetric(metric.getKey()) | ||||
.setIsCaycCondition(qualityGateCaycChecker.isCaycCondition(metric)) | |||||
.setOp(condition.getOperator()); | .setOp(condition.getOperator()); | ||||
ofNullable(condition.getErrorThreshold()).ifPresent(builder::setError); | ofNullable(condition.getErrorThreshold()).ifPresent(builder::setError); | ||||
return builder.build(); | return builder.build(); |
optional string caycStatus = 6; | optional string caycStatus = 6; | ||||
message Condition { | message Condition { | ||||
optional string id = 1; | |||||
optional string metric = 2; | |||||
optional string op = 4; | |||||
required string id = 1; | |||||
required string metric = 2; | |||||
required string op = 4; | |||||
optional string error = 6; | optional string error = 6; | ||||
required bool isCaycCondition = 7; | |||||
} | } | ||||
} | } | ||||