@@ -30,6 +30,9 @@ import static java.util.Objects.requireNonNull; | |||
public class QualityGate { | |||
public static final String BUILTIN_QUALITY_GATE_NAME = "Sonar way"; | |||
public static final String SONAR_WAY_LEGACY_QUALITY_GATE_NAME = "Sonar way (legacy)"; | |||
private final String id; | |||
private final String name; | |||
private final Set<Condition> conditions; |
@@ -19,12 +19,16 @@ | |||
*/ | |||
package org.sonar.server.qualitygate; | |||
import com.tngtech.java.junit.dataprovider.DataProvider; | |||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | |||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | |||
import java.util.ArrayList; | |||
import java.util.Date; | |||
import java.util.List; | |||
import java.util.Random; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.slf4j.event.Level; | |||
import org.sonar.api.testfixtures.log.LogTester; | |||
import org.sonar.api.utils.System2; | |||
@@ -54,16 +58,16 @@ import static org.sonar.api.measures.Metric.ValueType.PERCENT; | |||
import static org.sonar.db.metric.MetricTesting.newMetricDto; | |||
import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_GREATER_THAN; | |||
import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_LESS_THAN; | |||
import static org.sonar.server.qualitygate.QualityGate.BUILTIN_QUALITY_GATE_NAME; | |||
import static org.sonar.server.qualitygate.QualityGate.SONAR_WAY_LEGACY_QUALITY_GATE_NAME; | |||
@RunWith(DataProviderRunner.class) | |||
public class RegisterQualityGatesIT { | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
@Rule | |||
public LogTester logTester = new LogTester(); | |||
private static final String BUILT_IN_NAME = "Sonar way"; | |||
private final DbClient dbClient = db.getDbClient(); | |||
private final DbSession dbSession = db.getSession(); | |||
@@ -114,7 +118,6 @@ public class RegisterQualityGatesIT { | |||
underTest.start(); | |||
assertThat(db.countRowsOfTable("quality_gates")).isOne(); | |||
verifyCorrectBuiltInQualityGate(); | |||
assertThat( | |||
logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); | |||
@@ -131,7 +134,6 @@ public class RegisterQualityGatesIT { | |||
underTest.start(); | |||
assertThat(db.countRowsOfTable("quality_gates")).isOne(); | |||
verifyCorrectBuiltInQualityGate(); | |||
assertThat( | |||
logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); | |||
@@ -149,7 +151,6 @@ public class RegisterQualityGatesIT { | |||
underTest.start(); | |||
assertThat(db.countRowsOfTable("quality_gates")).isOne(); | |||
verifyCorrectBuiltInQualityGate(); | |||
assertThat( | |||
logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); | |||
@@ -169,7 +170,6 @@ public class RegisterQualityGatesIT { | |||
underTest.start(); | |||
assertThat(db.countRowsOfTable("quality_gates")).isOne(); | |||
verifyCorrectBuiltInQualityGate(); | |||
assertThat( | |||
logTester.logs(Level.INFO)).contains("Quality gate [Sonar way] has been set as built-in"); | |||
@@ -184,7 +184,6 @@ public class RegisterQualityGatesIT { | |||
underTest.start(); | |||
assertThat(db.countRowsOfTable("quality_gates")).isOne(); | |||
verifyCorrectBuiltInQualityGate(); | |||
// Log must not be present | |||
assertThat( | |||
@@ -208,9 +207,8 @@ public class RegisterQualityGatesIT { | |||
QualityGateDto oldQualityGate = qualityGateDao.selectByName(dbSession, qualityGateName); | |||
assertThat(oldQualityGate).isNotNull(); | |||
assertThat(oldQualityGate.isBuiltIn()).isFalse(); | |||
assertThat(db.select("select name as \"name\" from quality_gates where is_built_in is true")) | |||
.extracting(column -> column.get("name")) | |||
.containsExactly(BUILT_IN_NAME); | |||
var qualityGateDto = qualityGateDao.selectByName(dbSession, BUILTIN_QUALITY_GATE_NAME); | |||
assertThat(qualityGateDto).isNotNull(); | |||
assertThat( | |||
logTester.logs(Level.INFO)).contains("Built-in quality gate [Sonar way] has been created"); | |||
assertThat( | |||
@@ -237,6 +235,62 @@ public class RegisterQualityGatesIT { | |||
logTester.logs(Level.INFO)).contains("Built-in quality gate's conditions of [Sonar way] has been updated"); | |||
} | |||
@Test | |||
public void register_sonar_way_legacy_qg_if_not_exists_and_existing_instance() { | |||
insertMetrics(); | |||
QualityGateDto builtin = new QualityGateDto().setName(BUILTIN_QUALITY_GATE_NAME).setBuiltIn(true).setUuid(Uuids.createFast()); | |||
qualityGateDao.insert(dbSession, builtin); | |||
createBuiltInConditions(builtin); | |||
dbSession.commit(); | |||
underTest.start(); | |||
var qualityGateDto = qualityGateDao.selectByName(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME); | |||
assertThat(qualityGateDto).isNotNull(); | |||
verifyCorrectSonarWayLegacyQualityGate(); | |||
assertThat( | |||
logTester.logs(Level.INFO)).contains("Sonar way (legacy) quality gate has been created"); | |||
} | |||
@Test | |||
@UseDataProvider("data") | |||
public void do_not_register_sonar_way_legacy_qg(boolean isNewInstance, boolean hasSonarWayLegacyQG) { | |||
insertMetrics(); | |||
QualityGateDto builtin = new QualityGateDto().setName(BUILTIN_QUALITY_GATE_NAME).setBuiltIn(true).setUuid(Uuids.createFast()); | |||
qualityGateDao.insert(dbSession, builtin); | |||
if(!isNewInstance) { | |||
createBuiltInConditions(builtin); | |||
} | |||
if(hasSonarWayLegacyQG) { | |||
QualityGateDto sonarWayLegacy = new QualityGateDto().setName(SONAR_WAY_LEGACY_QUALITY_GATE_NAME).setBuiltIn(true).setUuid(Uuids.createFast()); | |||
qualityGateDao.insert(dbSession, sonarWayLegacy); | |||
} | |||
dbSession.commit(); | |||
underTest.start(); | |||
var qualityGateDto = qualityGateDao.selectByName(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME); | |||
if(hasSonarWayLegacyQG) { | |||
assertThat(qualityGateDto).isNotNull(); | |||
} else { | |||
assertThat(qualityGateDto).isNull(); | |||
} | |||
assertThat( | |||
logTester.logs(Level.INFO)).doesNotContain("Sonar way (legacy) quality gate has been created"); | |||
} | |||
@DataProvider | |||
public static Object[][] data() { | |||
return new Object[][] { | |||
{false, true}, | |||
{true, true}, | |||
{true, false} | |||
}; | |||
} | |||
private void insertMetrics() { | |||
dbClient.metricDao().insert(dbSession, | |||
newMetricDto().setKey(NEW_RELIABILITY_RATING_KEY).setValueType(INT.name()).setHidden(false).setDirection(0)); | |||
@@ -261,7 +315,7 @@ public class RegisterQualityGatesIT { | |||
MetricDto newDuplication = metricDao.selectByKey(dbSession, NEW_DUPLICATED_LINES_DENSITY_KEY); | |||
MetricDto newSecurityHotspots = metricDao.selectByKey(dbSession, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY); | |||
QualityGateDto qualityGateDto = qualityGateDao.selectByName(dbSession, BUILT_IN_NAME); | |||
QualityGateDto qualityGateDto = qualityGateDao.selectByName(dbSession, BUILTIN_QUALITY_GATE_NAME); | |||
assertThat(qualityGateDto).isNotNull(); | |||
assertThat(qualityGateDto.getCreatedAt()).isNotNull(); | |||
assertThat(qualityGateDto.isBuiltIn()).isTrue(); | |||
@@ -275,6 +329,29 @@ public class RegisterQualityGatesIT { | |||
tuple(newSecurityHotspots.getUuid(), OPERATOR_LESS_THAN, "100")); | |||
} | |||
private void verifyCorrectSonarWayLegacyQualityGate() { | |||
MetricDto newReliability = metricDao.selectByKey(dbSession, NEW_RELIABILITY_RATING_KEY); | |||
MetricDto newSecurity = metricDao.selectByKey(dbSession, NEW_SECURITY_RATING_KEY); | |||
MetricDto newMaintainability = metricDao.selectByKey(dbSession, NEW_MAINTAINABILITY_RATING_KEY); | |||
MetricDto newCoverage = metricDao.selectByKey(dbSession, NEW_COVERAGE_KEY); | |||
MetricDto newDuplication = metricDao.selectByKey(dbSession, NEW_DUPLICATED_LINES_DENSITY_KEY); | |||
MetricDto newSecurityHotspots = metricDao.selectByKey(dbSession, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY); | |||
QualityGateDto qualityGateDto = qualityGateDao.selectByName(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME); | |||
assertThat(qualityGateDto).isNotNull(); | |||
assertThat(qualityGateDto.getCreatedAt()).isNotNull(); | |||
assertThat(qualityGateDto.isBuiltIn()).isFalse(); | |||
assertThat(gateConditionDao.selectForQualityGate(dbSession, qualityGateDto.getUuid())) | |||
.extracting(QualityGateConditionDto::getMetricUuid, QualityGateConditionDto::getOperator, | |||
QualityGateConditionDto::getErrorThreshold) | |||
.containsExactlyInAnyOrder( | |||
tuple(newReliability.getUuid(), OPERATOR_GREATER_THAN, "1"), | |||
tuple(newSecurity.getUuid(), OPERATOR_GREATER_THAN, "1"), | |||
tuple(newMaintainability.getUuid(), OPERATOR_GREATER_THAN, "1"), | |||
tuple(newCoverage.getUuid(), OPERATOR_LESS_THAN, "80"), | |||
tuple(newDuplication.getUuid(), OPERATOR_GREATER_THAN, "3"), | |||
tuple(newSecurityHotspots.getUuid(), OPERATOR_LESS_THAN, "100")); | |||
} | |||
private List<QualityGateConditionDto> createBuiltInConditions(QualityGateDto qg) { | |||
List<QualityGateConditionDto> conditions = new ArrayList<>(); | |||
@@ -41,27 +41,42 @@ import org.sonar.db.qualitygate.QualityGateConditionDao; | |||
import org.sonar.db.qualitygate.QualityGateConditionDto; | |||
import org.sonar.db.qualitygate.QualityGateDao; | |||
import org.sonar.db.qualitygate.QualityGateDto; | |||
import org.sonar.server.measure.Rating; | |||
import static com.google.common.collect.ImmutableList.toImmutableList; | |||
import static java.util.Arrays.asList; | |||
import static java.util.stream.Collectors.toMap; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_COVERAGE_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING_KEY; | |||
import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY; | |||
import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_GREATER_THAN; | |||
import static org.sonar.db.qualitygate.QualityGateConditionDto.OPERATOR_LESS_THAN; | |||
import static org.sonar.server.qualitygate.QualityGate.BUILTIN_QUALITY_GATE_NAME; | |||
import static org.sonar.server.qualitygate.QualityGate.SONAR_WAY_LEGACY_QUALITY_GATE_NAME; | |||
public class RegisterQualityGates implements Startable { | |||
private static final Logger LOGGER = LoggerFactory.getLogger(RegisterQualityGates.class); | |||
private static final List<QualityGateCondition> QUALITY_GATE_CONDITIONS = asList( | |||
private static final String A_RATING = Integer.toString(Rating.A.getIndex()); | |||
private static final List<QualityGateCondition> BUILT_IN_QUALITY_GATE_CONDITIONS = asList( | |||
new QualityGateCondition().setMetricKey(NEW_VIOLATIONS_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold("0"), | |||
new QualityGateCondition().setMetricKey(NEW_COVERAGE_KEY).setOperator(OPERATOR_LESS_THAN).setErrorThreshold("80"), | |||
new QualityGateCondition().setMetricKey(NEW_DUPLICATED_LINES_DENSITY_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold("3"), | |||
new QualityGateCondition().setMetricKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY).setOperator(OPERATOR_LESS_THAN).setErrorThreshold("100")); | |||
private static final List<QualityGateCondition> SONAR_WAY_LEGACY_QUALITY_GATE_CONDITIONS = asList( | |||
new QualityGateCondition().setMetricKey(NEW_SECURITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold(A_RATING), | |||
new QualityGateCondition().setMetricKey(NEW_RELIABILITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold(A_RATING), | |||
new QualityGateCondition().setMetricKey(NEW_MAINTAINABILITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold(A_RATING), | |||
new QualityGateCondition().setMetricKey(NEW_COVERAGE_KEY).setOperator(OPERATOR_LESS_THAN).setErrorThreshold("80"), | |||
new QualityGateCondition().setMetricKey(NEW_DUPLICATED_LINES_DENSITY_KEY).setOperator(OPERATOR_GREATER_THAN).setErrorThreshold("3"), | |||
new QualityGateCondition().setMetricKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY).setOperator(OPERATOR_LESS_THAN).setErrorThreshold("100")); | |||
private final DbClient dbClient; | |||
private final QualityGateConditionsUpdater qualityGateConditionsUpdater; | |||
private final QualityGateDao qualityGateDao; | |||
@@ -86,9 +101,17 @@ public class RegisterQualityGates implements Startable { | |||
// Create builtinQualityGate if not present | |||
if (builtinQualityGate == null) { | |||
LOGGER.info("Built-in quality gate [{}] has been created", BUILTIN_QUALITY_GATE_NAME); | |||
builtinQualityGate = createQualityGate(dbSession, BUILTIN_QUALITY_GATE_NAME); | |||
builtinQualityGate = createQualityGate(dbSession, BUILTIN_QUALITY_GATE_NAME, true); | |||
} | |||
List<QualityGateCondition> builtInQualityGateConditions = getQualityGateConditions(dbSession, builtinQualityGate); | |||
// Create sonar way (legacy) only if it is not a new instance (a new instance has a Sonar way QG and no conditions) and if it is | |||
// not already present | |||
if (!builtInQualityGateConditions.isEmpty()) { | |||
createSonarWayLegacyQualityGateIfMissing(dbSession); | |||
} | |||
// Set builtinQualityGate if missing | |||
if (!builtinQualityGate.isBuiltIn()) { | |||
builtinQualityGate.setBuiltIn(true); | |||
@@ -96,7 +119,7 @@ public class RegisterQualityGates implements Startable { | |||
LOGGER.info("Quality gate [{}] has been set as built-in", BUILTIN_QUALITY_GATE_NAME); | |||
} | |||
updateQualityConditionsIfRequired(dbSession, builtinQualityGate); | |||
updateQualityConditionsIfRequired(dbSession, builtinQualityGate, builtInQualityGateConditions); | |||
qualityGateDao.ensureOneBuiltInQualityGate(dbSession, BUILTIN_QUALITY_GATE_NAME); | |||
@@ -104,8 +127,18 @@ public class RegisterQualityGates implements Startable { | |||
} | |||
} | |||
private void updateQualityConditionsIfRequired(DbSession dbSession, QualityGateDto builtinQualityGate) { | |||
List<QualityGateCondition> qualityGateConditions = getQualityGateConditions(dbSession, builtinQualityGate); | |||
private void createSonarWayLegacyQualityGateIfMissing(DbSession dbSession) { | |||
QualityGateDto sonarWayLegacyQualityGate = qualityGateDao.selectByName(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME); | |||
LOGGER.info("Sonar way legacy Gate: {} ", sonarWayLegacyQualityGate); | |||
if (sonarWayLegacyQualityGate == null) { | |||
sonarWayLegacyQualityGate = createQualityGate(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME, false); | |||
addConditionsToQualityGate(dbSession, sonarWayLegacyQualityGate, SONAR_WAY_LEGACY_QUALITY_GATE_CONDITIONS); | |||
LOGGER.info("Sonar way (legacy) quality gate has been created"); | |||
} | |||
} | |||
private void updateQualityConditionsIfRequired(DbSession dbSession, QualityGateDto builtinQualityGate, | |||
List<QualityGateCondition> qualityGateConditions) { | |||
List<QualityGateCondition> qgConditionsDeleted = removeExtraConditions(dbSession, builtinQualityGate, qualityGateConditions); | |||
qgConditionsDeleted.addAll(removeDuplicatedConditions(dbSession, builtinQualityGate, qualityGateConditions)); | |||
@@ -131,7 +164,7 @@ public class RegisterQualityGates implements Startable { | |||
// Find all conditions that are not present in QUALITY_GATE_CONDITIONS | |||
// Those conditions must be deleted | |||
List<QualityGateCondition> qgConditionsToBeDeleted = new ArrayList<>(qualityGateConditions); | |||
qgConditionsToBeDeleted.removeAll(QUALITY_GATE_CONDITIONS); | |||
qgConditionsToBeDeleted.removeAll(BUILT_IN_QUALITY_GATE_CONDITIONS); | |||
qgConditionsToBeDeleted | |||
.forEach(qgc -> qualityGateConditionDao.delete(qgc.toQualityGateDto(builtinQualityGate.getUuid()), dbSession)); | |||
return qgConditionsToBeDeleted; | |||
@@ -149,26 +182,31 @@ public class RegisterQualityGates implements Startable { | |||
return qgConditionsDuplicated; | |||
} | |||
private List<QualityGateCondition> addMissingConditions(DbSession dbSession, QualityGateDto builtinQualityGate, List<QualityGateCondition> qualityGateConditions) { | |||
private List<QualityGateCondition> addMissingConditions(DbSession dbSession, QualityGateDto builtinQualityGate, | |||
List<QualityGateCondition> qualityGateConditions) { | |||
// Find all conditions that are not present in qualityGateConditions | |||
// Those conditions must be added to the built-in quality gate | |||
List<QualityGateCondition> qgConditionsToBeAdded = new ArrayList<>(QUALITY_GATE_CONDITIONS); | |||
List<QualityGateCondition> qgConditionsToBeAdded = new ArrayList<>(BUILT_IN_QUALITY_GATE_CONDITIONS); | |||
qgConditionsToBeAdded.removeAll(qualityGateConditions); | |||
qgConditionsToBeAdded | |||
.forEach(qgc -> qualityGateConditionsUpdater.createCondition(dbSession, builtinQualityGate, qgc.getMetricKey(), qgc.getOperator(), | |||
qgc.getErrorThreshold())); | |||
addConditionsToQualityGate(dbSession, builtinQualityGate, qgConditionsToBeAdded); | |||
return qgConditionsToBeAdded; | |||
} | |||
private void addConditionsToQualityGate(DbSession dbSession, QualityGateDto qualityGate, List<QualityGateCondition> conditions) { | |||
conditions.forEach(condition -> qualityGateConditionsUpdater.createCondition(dbSession, qualityGate, condition.getMetricKey(), | |||
condition.getOperator(), | |||
condition.getErrorThreshold())); | |||
} | |||
@Override | |||
public void stop() { | |||
// do nothing | |||
} | |||
private QualityGateDto createQualityGate(DbSession dbSession, String name) { | |||
private QualityGateDto createQualityGate(DbSession dbSession, String name, boolean isBuiltIn) { | |||
QualityGateDto qualityGate = new QualityGateDto() | |||
.setName(name) | |||
.setBuiltIn(true) | |||
.setBuiltIn(isBuiltIn) | |||
.setUuid(uuidFactory.create()) | |||
.setCreatedAt(new Date(system2.now())); | |||
return dbClient.qualityGateDao().insert(dbSession, qualityGate); |