Переглянути джерело

SONAR-11577 Migrate no more supported quality gate conditions

tags/7.6
Julien Lancelot 5 роки тому
джерело
коміт
4dddfec185

+ 2
- 1
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76.java Переглянути файл

@@ -28,6 +28,7 @@ public class DbVersion76 implements DbVersion {
public void addSteps(MigrationStepRegistry registry) {
registry
.add(2500, "Create table USER_PROPERTIES", CreateUserPropertiesTable.class)
.add(2501, "Add index in table USER_PROPERTIES", AddUniqueIndexInUserPropertiesTable.class);
.add(2501, "Add index in table USER_PROPERTIES", AddUniqueIndexInUserPropertiesTable.class)
.add(2506, "Migrate quality gate conditions using warning, period and no more supported operations", MigrateNoMoreUsedQualityGateConditions.class);
}
}

+ 427
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/MigrateNoMoreUsedQualityGateConditions.java Переглянути файл

@@ -0,0 +1,427 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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.platform.db.migration.version.v76;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.SupportsBlueGreen;
import org.sonar.server.platform.db.migration.step.DataChange;
import org.sonar.server.platform.db.migration.step.Upsert;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.stream.Collectors.toSet;
import static org.sonar.core.util.stream.MoreCollectors.toList;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;

@SupportsBlueGreen
public class MigrateNoMoreUsedQualityGateConditions extends DataChange {

private static final Logger LOG = Loggers.get(MigrateNoMoreUsedQualityGateConditions.class);

private static final String OPERATOR_GREATER_THAN = "GT";
private static final String OPERATOR_LESS_THAN = "LT";

private static final int DIRECTION_WORST = -1;
private static final int DIRECTION_BETTER = 1;
private static final int DIRECTION_NONE = 0;

private static final Set<String> SUPPORTED_OPERATORS = ImmutableSet.of(OPERATOR_GREATER_THAN, OPERATOR_LESS_THAN);
private static final Set<String> SUPPORTED_METRIC_TYPES = ImmutableSet.of("INT", "FLOAT", "PERCENT", "MILLISEC", "LEVEL", "RATING", "WORK_DUR");
private static final Map<String, String> LEAK_METRIC_KEY_BY_METRIC_KEY = ImmutableMap.<String, String>builder()
.put("branch_coverage", "new_branch_coverage")
.put("conditions_to_cover", "new_conditions_to_cover")
.put("coverage", "new_coverage")
.put("line_coverage", "new_line_coverage")
.put("lines_to_cover", "new_lines_to_cover")
.put("uncovered_conditions", "new_uncovered_conditions")
.put("uncovered_lines", "new_uncovered_lines")
.put("duplicated_blocks", "new_duplicated_blocks")
.put("duplicated_lines", "new_duplicated_lines")
.put("duplicated_lines_density", "new_duplicated_lines_density")
.put("blocker_violations", "new_blocker_violations")
.put("critical_violations", "new_critical_violations")
.put("info_violations", "new_info_violations")
.put("violations", "new_violations")
.put("major_violations", "new_major_violations")
.put("minor_violations", "new_minor_violations")
.put("sqale_index", "new_technical_debt")
.put("code_smells", "new_code_smells")
.put("sqale_rating", "new_maintainability_rating")
.put("sqale_debt_ratio", "new_sqale_debt_ratio")
.put("bugs", "new_bugs")
.put("reliability_rating", "new_reliability_rating")
.put("reliability_remediation_effort", "new_reliability_remediation_effort")
.put("vulnerabilities", "new_vulnerabilities")
.put("security_rating", "new_security_rating")
.put("security_remediation_effort", "new_security_remediation_effort")
.put("lines", "new_lines")
.build();

private final System2 system2;

public MigrateNoMoreUsedQualityGateConditions(Database db, System2 system2) {
super(db);
this.system2 = system2;
}

@Override
protected void execute(Context context) throws SQLException {
MigrationContext migrationContext = new MigrationContext(context, new Date(system2.now()), loadMetrics(context));
context.prepareSelect("SELECT id FROM quality_gates qg " +
"WHERE qg.is_built_in=?")
.setBoolean(1, false)
.scroll(row -> {
List<QualityGateCondition> conditions = loadConditions(context, row.getInt(1));

markNoMoreSupportedConditionsAsToBeDeleted(migrationContext, conditions);
markConditionsHavingOnlyWarningAsToBeDeleted(conditions);
markConditionsUsingLeakPeriodHavingNoRelatedLeakMetricAsToBeDeleted(migrationContext, conditions);
markConditionsUsingLeakPeriodHavingAlreadyRelatedConditionAsToBeDeleted(migrationContext, conditions);
updateConditionsUsingLeakPeriod(migrationContext, conditions);
updateConditionsHavingErrorAndWarningByRemovingWarning(migrationContext, conditions);
dropConditionsIfNeeded(migrationContext, conditions);

migrationContext.increaseNumberOfProcessedQualityGate();
});
LOG.info("{} custom quality gates have been loaded", migrationContext.getNbOfQualityGates());
LOG.info("{} conditions have been removed", migrationContext.getNbOfRemovedConditions());
LOG.info("{} conditions have been updated", migrationContext.getNbOfUpdatedConditions());
}

private static List<Metric> loadMetrics(Context context) throws SQLException {
return context
.prepareSelect("SELECT m.id, m.name, m.val_type, m.direction FROM metrics m WHERE m.enabled=?")
.setBoolean(1, true)
.list(row -> new Metric(row.getInt(1), row.getString(2), row.getString(3), row.getInt(4)));
}

private static List<QualityGateCondition> loadConditions(Context context, int qualityGateId) throws SQLException {
return context.prepareSelect("SELECT qgc.id, qgc.metric_id, qgc.operator, qgc.value_error, qgc.value_warning, qgc.period FROM quality_gate_conditions qgc " +
"WHERE qgc.qgate_id=? ")
.setInt(1, qualityGateId)
.list(
row -> new QualityGateCondition(row.getInt(1), row.getInt(2), row.getString(3),
row.getString(4), row.getString(5), row.getInt(6)));
}

private static void markNoMoreSupportedConditionsAsToBeDeleted(MigrationContext migrationContext, List<QualityGateCondition> conditions) {
conditions.stream()
.filter(c -> !c.isToBeDeleted())
.filter(c -> !isConditionStillSupported(c, migrationContext.getMetricById(c.getMetricId())))
.forEach(QualityGateCondition::setToBeDeleted);
}

private static void markConditionsHavingOnlyWarningAsToBeDeleted(List<QualityGateCondition> conditions) {
conditions.stream()
.filter(c -> !c.isToBeDeleted())
.filter(c -> !isNullOrEmpty(c.getWarning()) && isNullOrEmpty(c.getError()))
.forEach(QualityGateCondition::setToBeDeleted);
}

private static void markConditionsUsingLeakPeriodHavingNoRelatedLeakMetricAsToBeDeleted(MigrationContext migrationContext, List<QualityGateCondition> conditions) {
conditions
.stream()
.filter(c -> !c.isToBeDeleted())
.filter(QualityGateCondition::hasLeakPeriod)
.filter(condition -> !isConditionOnLeakMetric(migrationContext, condition))
.forEach(condition -> {
String metricKey = migrationContext.getMetricById(condition.getMetricId()).getKey();
String relatedLeakMetric = LEAK_METRIC_KEY_BY_METRIC_KEY.get(metricKey);
// Metric has no related metric on leak period => delete condition
if (relatedLeakMetric == null) {
condition.setToBeDeleted();
}
});
}

private static void markConditionsUsingLeakPeriodHavingAlreadyRelatedConditionAsToBeDeleted(MigrationContext migrationContext, List<QualityGateCondition> conditions) {
Map<String, QualityGateCondition> conditionsByMetricKey = conditions.stream()
.filter(c -> !c.isToBeDeleted())
.collect(uniqueIndex(c -> migrationContext.getMetricById(c.getMetricId()).getKey()));

conditions
.stream()
.filter(condition -> !condition.isToBeDeleted())
.filter(QualityGateCondition::hasLeakPeriod)
.filter(condition -> !isConditionOnLeakMetric(migrationContext, condition))
.forEach(condition -> {
String metricKey = migrationContext.getMetricById(condition.getMetricId()).getKey();
String relatedLeakMetric = LEAK_METRIC_KEY_BY_METRIC_KEY.get(metricKey);
if (relatedLeakMetric != null) {
QualityGateCondition existingConditionUsingRelatedLeakPeriod = conditionsByMetricKey.get(relatedLeakMetric);
if (existingConditionUsingRelatedLeakPeriod != null) {
// Another condition on related leak period metric exist => delete condition
condition.setToBeDeleted();
}
}
});
}

private static void updateConditionsHavingErrorAndWarningByRemovingWarning(MigrationContext migrationContext, List<QualityGateCondition> conditions)
throws SQLException {
Set<Integer> conditionsToBeUpdated = conditions.stream()
.filter(c -> !c.isToBeDeleted())
.filter(c -> !isNullOrEmpty(c.getWarning()) && !isNullOrEmpty(c.getError()))
.map(QualityGateCondition::getId)
.collect(toSet());
if (conditionsToBeUpdated.isEmpty()) {
return;
}
migrationContext.getContext()
.prepareUpsert("UPDATE quality_gate_conditions SET value_warning = NULL, updated_at = ? WHERE id IN (" + conditionsToBeUpdated
.stream()
.map(c -> Integer.toString(c))
.collect(Collectors.joining(",")) + ")")
.setDate(1, migrationContext.getNow())
.execute()
.commit();
migrationContext.addUpdatedConditions(conditionsToBeUpdated.size());
}

private static void updateConditionsUsingLeakPeriod(MigrationContext migrationContext, List<QualityGateCondition> conditions)
throws SQLException {

Map<String, QualityGateCondition> conditionsByMetricKey = conditions.stream()
.filter(c -> !c.isToBeDeleted())
.collect(uniqueIndex(c -> migrationContext.getMetricById(c.getMetricId()).getKey()));

Upsert updateMetricId = migrationContext.getContext()
.prepareUpsert("UPDATE quality_gate_conditions SET metric_id = ?, updated_at = ? WHERE id = ? ")
.setDate(2, migrationContext.getNow());

conditions
.stream()
.filter(c -> !c.isToBeDeleted())
.filter(QualityGateCondition::hasLeakPeriod)
.filter(condition -> !isConditionOnLeakMetric(migrationContext, condition))
.forEach(condition -> {
String metricKey = migrationContext.getMetricById(condition.getMetricId()).getKey();
String relatedLeakMetric = LEAK_METRIC_KEY_BY_METRIC_KEY.get(metricKey);
QualityGateCondition existingConditionUsingRelatedLeakPeriod = conditionsByMetricKey.get(relatedLeakMetric);
// Metric has a related leak period metric => update the condition
if (existingConditionUsingRelatedLeakPeriod == null) {
try {
updateMetricId.setInt(1, migrationContext.getMetricByKey(relatedLeakMetric).getId());
updateMetricId.setInt(3, condition.getId());
updateMetricId.execute();
migrationContext.addUpdatedConditions(1);
} catch (SQLException e) {
throw new IllegalStateException("Fail to update quality gate conditions", e);
}
}
});
updateMetricId.commit();
}

private static void dropConditionsIfNeeded(MigrationContext context, List<QualityGateCondition> conditions) throws SQLException {
List<QualityGateCondition> conditionsToBeDeleted = conditions.stream()
.filter(QualityGateCondition::isToBeDeleted)
.collect(toList());
if (conditionsToBeDeleted.isEmpty()) {
return;
}
context.getContext()
.prepareUpsert("DELETE FROM quality_gate_conditions WHERE id IN (" + conditionsToBeDeleted
.stream()
.map(c -> Integer.toString(c.getId()))
.collect(Collectors.joining(",")) + ")")
.execute()
.commit();
context.addRemovedConditions(conditionsToBeDeleted.size());
}

private static boolean isConditionOnLeakMetric(MigrationContext migrationContext, QualityGateCondition condition) {
return LEAK_METRIC_KEY_BY_METRIC_KEY.containsValue(migrationContext.getMetricById(condition.getMetricId()).getKey());
}

private static boolean isConditionStillSupported(QualityGateCondition condition, Metric metric) {
return isSupportedMetricType(metric) && isSupportedOperator(condition, metric);
}

private static boolean isSupportedMetricType(Metric metric) {
return SUPPORTED_METRIC_TYPES.contains(metric.getType());
}

private static boolean isSupportedOperator(QualityGateCondition condition, Metric metric) {
String operator = condition.getOperator();
int direction = metric.getDirection();
return SUPPORTED_OPERATORS.contains(operator) &&
(direction == DIRECTION_NONE ||
(direction == DIRECTION_WORST && operator.equalsIgnoreCase(OPERATOR_GREATER_THAN)) ||
(direction == DIRECTION_BETTER && operator.equalsIgnoreCase(OPERATOR_LESS_THAN)));
}

private static class QualityGateCondition {
private final int id;
private final int metricId;
private final String operator;
private final String error;
private final String warning;
private final Integer period;

private boolean toBeDeleted = false;

public QualityGateCondition(int id, int metricId, String operator, @Nullable String error, @Nullable String warning,
@Nullable Integer period) {
this.id = id;
this.metricId = metricId;
this.operator = operator;
this.error = error;
this.warning = warning;
this.period = period;
}

public int getId() {
return id;
}

public int getMetricId() {
return metricId;
}

public String getOperator() {
return operator;
}

@CheckForNull
public String getError() {
return error;
}

@CheckForNull
public String getWarning() {
return warning;
}

public boolean hasLeakPeriod() {
return period != null && period == 1;
}

public void setToBeDeleted() {
toBeDeleted = true;
}

public boolean isToBeDeleted() {
return toBeDeleted;
}
}

private static class Metric {
private final int id;
private final String key;
private final String type;
private final int direction;

public Metric(int id, String key, String type, int direction) {
this.id = id;
this.key = key;
this.type = type;
this.direction = direction;
}

public int getId() {
return id;
}

public String getKey() {
return key;
}

public String getType() {
return type;
}

public int getDirection() {
return direction;
}
}

private static class MigrationContext {

private final Context context;
private final Date now;
private final Map<Integer, Metric> metricsById;
private final Map<String, Metric> metricsByKey;

private int nbOfQualityGates;
private int nbOfRemovedConditions;
private int nbOfUpdatedConditions;

public MigrationContext(Context context, Date now, List<Metric> metrics) {
this.context = context;
this.now = now;
this.metricsById = metrics.stream().collect(uniqueIndex(Metric::getId));
this.metricsByKey = metrics.stream().collect(uniqueIndex(Metric::getKey));
}

public Context getContext() {
return context;
}

public Date getNow() {
return now;
}

public Metric getMetricByKey(String key) {
return metricsByKey.get(key);
}

public Metric getMetricById(int id) {
return metricsById.get(id);
}

public void increaseNumberOfProcessedQualityGate() {
nbOfQualityGates += 1;
}

public int getNbOfQualityGates() {
return nbOfQualityGates;
}

public void addRemovedConditions(int removedConditions) {
nbOfRemovedConditions += removedConditions;
}

public int getNbOfRemovedConditions() {
return nbOfRemovedConditions;
}

public void addUpdatedConditions(int updatedConditions) {
nbOfUpdatedConditions += updatedConditions;
}

public int getNbOfUpdatedConditions() {
return nbOfUpdatedConditions;
}
}

}

+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76Test.java Переглянути файл

@@ -35,7 +35,7 @@ public class DbVersion76Test {

@Test
public void verify_migration_count() {
verifyMigrationCount(underTest, 2);
verifyMigrationCount(underTest, 3);
}

}

+ 311
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/MigrateNoMoreUsedQualityGateConditionsTest.java Переглянути файл

@@ -0,0 +1,311 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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.platform.db.migration.version.v76;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.sql.SQLException;
import java.util.Date;
import java.util.Random;
import javax.annotation.Nullable;
import org.assertj.core.groups.Tuple;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.db.CoreDbTester;

import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;

@RunWith(DataProviderRunner.class)
public class MigrateNoMoreUsedQualityGateConditionsTest {

private static final int DIRECTION_WORST = -1;
private static final int DIRECTION_BETTER = 1;
private static final int DIRECTION_NONE = 0;

private final static long PAST = 10_000_000_000L;
private final static long NOW = 50_000_000_000L;

@Rule
public CoreDbTester db = CoreDbTester.createForSchema(MigrateNoMoreUsedQualityGateConditionsTest.class, "qg-schema.sql");

private System2 system2 = new TestSystem2().setNow(NOW);

private Random random = new Random();

private MigrateNoMoreUsedQualityGateConditions underTest = new MigrateNoMoreUsedQualityGateConditions(db.database(), system2);

@Test
public void remove_conditions_using_only_warning() throws SQLException {
long qualityGate = insertQualityGate(false);
long ncloc = insertMetric("ncloc", DIRECTION_WORST, "INT");
long lines = insertMetric("lines", DIRECTION_WORST, "INT");
long issues = insertMetric("violations", DIRECTION_WORST, "INT");
long coverage = insertMetric("coverage", DIRECTION_BETTER, "PERCENT");
long conditionWithWarning1 = insertCondition(qualityGate, ncloc, "GT", null, "10", null);
long conditionWithWarning2 = insertCondition(qualityGate, lines, "GT", null, "15", null);
long conditionWithError = insertCondition(qualityGate, issues, "GT", "5", null, null);

underTest.execute();

assertConditions(
tuple(conditionWithError, issues, "5", null, null));
}

@Test
public void update_conditions_using_error_and_warning() throws SQLException {
long qualityGate = insertQualityGate(false);
long issues = insertMetric("violations", DIRECTION_WORST, "INT");
long coverage = insertMetric("coverage", DIRECTION_BETTER, "PERCENT");
long newLines = insertMetric("new_lines", DIRECTION_WORST, "INT");
long conditionWithError = insertCondition(qualityGate, issues, "GT", "5", null, null);
long conditionWithErrorAndWarning1 = insertCondition(qualityGate, coverage, "LT", "5", "10", null);
long conditionWithErrorAndWarning2 = insertCondition(qualityGate, newLines, "GT", "7", "13", 1);

underTest.execute();

assertConditions(
tuple(conditionWithError, issues, "5", null, null),
tuple(conditionWithErrorAndWarning1, coverage, "5", null, null),
tuple(conditionWithErrorAndWarning2, newLines, "7", null, 1L)
);
}

@Test
@UseDataProvider("metricsHavingNoLinkedLeakMetrics")
public void delete_condition_on_not_supported_leak_period_metric(String metricKey) throws SQLException {
long qualityGate = insertQualityGate(false);
long metric = insertMetric(metricKey, DIRECTION_BETTER, "INT");
insertCondition(qualityGate, metric, "LT", "5", null, 1);

underTest.execute();

assertThat(db.countRowsOfTable("quality_gate_conditions")).isZero();
}

@DataProvider
public static Object[][] metricsHavingNoLinkedLeakMetrics() {
return new Object[][] {
{"statements"},
{"functions"}
};
}

@Test
@UseDataProvider("supportedLeakPeriodMetrics")
public void update_condition_on_supported_leak_period_metric(String metricKey, String relatedMetricKeyOnLeakPeriod) throws SQLException {
long qualityGate = insertQualityGate(false);
long metric = insertMetric(metricKey, DIRECTION_BETTER, "INT");
long relatedMetricOnLeakPeriod = insertMetric(relatedMetricKeyOnLeakPeriod, DIRECTION_BETTER, "INT");
long condition = insertCondition(qualityGate, metric, "LT", "5", null, 1);

underTest.execute();

assertConditions(tuple(condition, relatedMetricOnLeakPeriod, "5", null, 1L));
}

@Test
@UseDataProvider("supportedLeakPeriodMetrics")
public void remove_condition_on_supported_leak_period_metric_when_condition_already_exists(String metricKey, String relatedMetricKeyOnLeakPeriod) throws SQLException {
long qualityGate = insertQualityGate(false);
long metric = insertMetric(metricKey, DIRECTION_BETTER, "INT");
long condition = insertCondition(qualityGate, metric, "LT", "10", null, 1);
long relatedMetricOnLeakPeriod = insertMetric(relatedMetricKeyOnLeakPeriod, DIRECTION_BETTER, "INT");
long leakCondition = insertCondition(qualityGate, relatedMetricOnLeakPeriod, "LT", "5", null, 1);

underTest.execute();

assertConditions(tuple(leakCondition, relatedMetricOnLeakPeriod, "5", null, 1L));
}

@DataProvider
public static Object[][] supportedLeakPeriodMetrics() {
return new Object[][] {
{"branch_coverage", "new_branch_coverage"},
{"conditions_to_cover", "new_conditions_to_cover"},
{"coverage", "new_coverage"},
{"line_coverage", "new_line_coverage"},
{"lines_to_cover", "new_lines_to_cover"},
{"uncovered_conditions", "new_uncovered_conditions"},
{"uncovered_lines", "new_uncovered_lines"},
{"duplicated_blocks", "new_duplicated_blocks"},
{"duplicated_lines", "new_duplicated_lines"},
{"duplicated_lines_density", "new_duplicated_lines_density"},
{"blocker_violations", "new_blocker_violations"},
{"critical_violations", "new_critical_violations"},
{"info_violations", "new_info_violations"},
{"violations", "new_violations"},
{"major_violations", "new_major_violations"},
{"minor_violations", "new_minor_violations"},
{"sqale_index", "new_technical_debt"},
{"code_smells", "new_code_smells"},
{"sqale_rating", "new_maintainability_rating"},
{"sqale_debt_ratio", "new_sqale_debt_ratio"},
{"bugs", "new_bugs"},
{"reliability_rating", "new_reliability_rating"},
{"reliability_remediation_effort", "new_reliability_remediation_effort"},
{"vulnerabilities", "new_vulnerabilities"},
{"security_rating", "new_security_rating"},
{"security_remediation_effort", "new_security_remediation_effort"},
{"lines", "new_lines"},
};
}

@Test
public void update_condition_using_leak_period_metric_when_condition_on_new_metric_exists_but_using_bad_operator() throws SQLException {
long qualityGate = insertQualityGate(false);
long linesToCover = insertMetric("lines_to_cover", DIRECTION_WORST, "INT");
long newLinesToCover = insertMetric("new_lines_to_cover", DIRECTION_WORST, "INT");
// This condition should be migrated to use new_lines_to_cover metric
long conditionOnLinesToCover = insertCondition(qualityGate, linesToCover, "GT", "10", null, 1);
// This condition should be removed as using a no more supported operator
long conditionOnNewLinesToCover = insertCondition(qualityGate, newLinesToCover, "EQ", "5", null, 1);

underTest.execute();

assertConditions(
tuple(conditionOnLinesToCover, newLinesToCover, "10", null, 1L));
}

@Test
@UseDataProvider("noMoreSupportedMetricTypes")
public void delete_condition_on_no_more_supported_metric_types(String metricKey, String metricType) throws SQLException {
long qualityGate = insertQualityGate(false);
long metric = insertMetric(metricKey, DIRECTION_BETTER, metricType);
long condition = insertCondition(qualityGate, metric, "LT", "5", null, null);

underTest.execute();

assertThat(db.countRowsOfTable("quality_gate_conditions")).isZero();
}

@DataProvider
public static Object[][] noMoreSupportedMetricTypes() {
return new Object[][] {
{"bool_type", "BOOL"},
{"development_cost", "STRING"},
{"last_change_on_maintainability_rating", "DATA"},
{"class_complexity_distribution", "DISTRIB"}
};
}

@Test
@UseDataProvider("conditionsOnNoMoreSupportedOperators")
public void delete_condition_on_no_more_supported_operators(String metricKey, int direction, String operator) throws SQLException {
long qualityGate = insertQualityGate(false);
long metric = insertMetric(metricKey, direction, "INT");
long condition = insertCondition(qualityGate, metric, operator, "5", null, null);

underTest.execute();

assertThat(db.countRowsOfTable("quality_gate_conditions")).isZero();
}

@DataProvider
public static Object[][] conditionsOnNoMoreSupportedOperators() {
return new Object[][] {
{"function_complexity_distribution", DIRECTION_NONE, "EQ"},
{"file_complexity_distribution", DIRECTION_NONE, "NE"},
{"blockers", DIRECTION_WORST, "LT"},
{"coverage", DIRECTION_BETTER, "GT"}
};
}

@Test
@UseDataProvider("conditionsOnSupportedOperators")
public void do_not_delete_condition_on_supported_operators(String metricKey, int direction, String operator) throws SQLException {
long qualityGate = insertQualityGate(false);
long metric = insertMetric(metricKey, direction, "INT");
long condition = insertCondition(qualityGate, metric, operator, "5", null, null);

underTest.execute();

assertConditions(condition);
}

@DataProvider
public static Object[][] conditionsOnSupportedOperators() {
return new Object[][] {
{"function_complexity_distribution", DIRECTION_NONE, "LT"},
{"file_complexity_distribution", DIRECTION_NONE, "GT"},
{"blockers", DIRECTION_BETTER, "LT"},
{"coverage", DIRECTION_WORST, "GT"}
};
}

private void assertConditions(Long... expectedIds) {
assertThat(db.select("SELECT id FROM quality_gate_conditions")
.stream()
.map(row -> (long) row.get("ID"))
.collect(toList()))
.containsExactlyInAnyOrder(expectedIds);
}

private void assertConditions(Tuple... expectedTuples) {
assertThat(db.select("SELECT id, metric_id, value_error, value_warning, period FROM quality_gate_conditions")
.stream()
.map(row -> new Tuple(row.get("ID"), row.get("METRIC_ID"), row.get("VALUE_ERROR"), row.get("VALUE_WARNING"), row.get("PERIOD")))
.collect(toList()))
.containsExactlyInAnyOrder(expectedTuples);
}

private long insertQualityGate(boolean isBuiltIn) {
long id = random.nextInt(1_000_000);
db.executeInsert("QUALITY_GATES",
"UUID", id,
"ID", id,
"NAME", "name " + id,
"IS_BUILT_IN", isBuiltIn,
"CREATED_AT", new Date(PAST),
"UPDATED_AT", new Date(PAST));
return id;
}

private long insertMetric(String name, int direction, String metricType) {
long id = random.nextInt(1_000_000);
db.executeInsert("METRICS",
"ID", id,
"NAME", name,
"VAL_TYPE", metricType,
"DIRECTION", direction);
return id;
}

private long insertCondition(long qualityGateId, long metricId, String operator, @Nullable String error, @Nullable String warning, @Nullable Integer period) {
long id = random.nextInt(1_000_000);
db.executeInsert("QUALITY_GATE_CONDITIONS",
"ID", id,
"QGATE_ID", qualityGateId,
"METRIC_ID", metricId,
"OPERATOR", operator,
"VALUE_ERROR", error,
"VALUE_WARNING", warning,
"PERIOD", period,
"CREATED_AT", new Date(PAST),
"UPDATED_AT", new Date(PAST));
return id;
}

}

+ 41
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v76/MigrateNoMoreUsedQualityGateConditionsTest/qg-schema.sql Переглянути файл

@@ -0,0 +1,41 @@
CREATE TABLE "METRICS" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"NAME" VARCHAR(64) NOT NULL,
"DESCRIPTION" VARCHAR(255),
"DIRECTION" INTEGER NOT NULL DEFAULT 0,
"DOMAIN" VARCHAR(64),
"SHORT_NAME" VARCHAR(64),
"QUALITATIVE" BOOLEAN NOT NULL DEFAULT FALSE,
"VAL_TYPE" VARCHAR(8),
"USER_MANAGED" BOOLEAN DEFAULT FALSE,
"ENABLED" BOOLEAN DEFAULT TRUE,
"WORST_VALUE" DOUBLE,
"BEST_VALUE" DOUBLE,
"OPTIMIZED_BEST_VALUE" BOOLEAN,
"HIDDEN" BOOLEAN,
"DELETE_HISTORICAL_DATA" BOOLEAN,
"DECIMAL_SCALE" INTEGER
);
CREATE UNIQUE INDEX "METRICS_UNIQUE_NAME" ON "METRICS" ("NAME");

CREATE TABLE "QUALITY_GATES" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"UUID" VARCHAR(40) NOT NULL,
"NAME" VARCHAR(100) NOT NULL,
"IS_BUILT_IN" BOOLEAN NOT NULL,
"CREATED_AT" TIMESTAMP,
"UPDATED_AT" TIMESTAMP,
);
CREATE UNIQUE INDEX "UNIQ_QUALITY_GATES_UUID" ON "QUALITY_GATES" ("UUID");

CREATE TABLE "QUALITY_GATE_CONDITIONS" (
"ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
"QGATE_ID" INTEGER,
"METRIC_ID" INTEGER,
"OPERATOR" VARCHAR(3),
"VALUE_ERROR" VARCHAR(64),
"VALUE_WARNING" VARCHAR(64),
"PERIOD" INTEGER,
"CREATED_AT" TIMESTAMP,
"UPDATED_AT" TIMESTAMP,
);

Завантаження…
Відмінити
Зберегти