aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-db
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2016-10-25 19:08:35 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2016-10-26 11:55:16 +0200
commit331558e106bc9bda51dfa39cc9a5874aa62c236c (patch)
tree31b012f1259748fdcf430c243babbc4a445f4544 /sonar-db
parent1609c61c56b527f9a2f73e1419aa5a57d9a6a06d (diff)
downloadsonarqube-331558e106bc9bda51dfa39cc9a5874aa62c236c.tar.gz
sonarqube-331558e106bc9bda51dfa39cc9a5874aa62c236c.zip
SONAR-8283 Migrate quality gates after removall of IT/overall coverage metrics
Diffstat (limited to 'sonar-db')
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java2
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java5
-rw-r--r--sonar-db/src/main/java/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverage.java213
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql1
-rw-r--r--sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java2
-rw-r--r--sonar-db/src/test/java/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverageTest.java364
-rw-r--r--sonar-db/src/test/resources/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverageTest/schema.sql40
7 files changed, 623 insertions, 4 deletions
diff --git a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java
index 22be5a76f91..86bd8c05b1b 100644
--- a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java
+++ b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java
@@ -30,7 +30,7 @@ import org.sonar.db.MyBatis;
public class DatabaseVersion {
- public static final int LAST_VERSION = 1_418;
+ public static final int LAST_VERSION = 1_419;
/**
* The minimum supported version which can be upgraded. Lower
diff --git a/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java b/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java
index d2b9d190a08..adf927f7a26 100644
--- a/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java
+++ b/sonar-db/src/main/java/org/sonar/db/version/MigrationStepModule.java
@@ -177,6 +177,7 @@ import org.sonar.db.version.v62.PopulateOrganizationUuidOfGroupRoles;
import org.sonar.db.version.v62.PopulateOrganizationUuidOfGroups;
import org.sonar.db.version.v62.PopulateOrganizationUuidOfPermissionTemplates;
import org.sonar.db.version.v62.PopulateOrganizationUuidOfUserRoles;
+import org.sonar.db.version.v62.UpdateQualityGateConditionsOnCoverage;
public class MigrationStepModule extends Module {
@Override
@@ -374,7 +375,7 @@ public class MigrationStepModule extends Module {
MakeOrganizationUuidNotNullOnPermissionTemplates.class,
AddOrganizationUuidToGroupRoles.class,
PopulateOrganizationUuidOfGroupRoles.class,
- MakeOrganizationUuidNotNullOnGroupRoles.class
- );
+ MakeOrganizationUuidNotNullOnGroupRoles.class,
+ UpdateQualityGateConditionsOnCoverage.class);
}
}
diff --git a/sonar-db/src/main/java/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverage.java b/sonar-db/src/main/java/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverage.java
new file mode 100644
index 00000000000..0f65516e65f
--- /dev/null
+++ b/sonar-db/src/main/java/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverage.java
@@ -0,0 +1,213 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.db.version.v62;
+
+import com.google.common.collect.ImmutableList;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.db.Database;
+import org.sonar.db.version.BaseDataChange;
+import org.sonar.db.version.Select;
+
+import static java.util.Objects.requireNonNull;
+import static org.sonar.core.util.stream.Collectors.uniqueIndex;
+import static org.sonar.db.DatabaseUtils.repeatCondition;
+
+/**
+ * Migrate every quality gates that have conditions on related coverage metrics.
+ *
+ * If there's a condition on {@link CoreMetrics#OVERALL_COVERAGE}, it will be updated to {@link CoreMetrics#COVERAGE},
+ * conditions on {@link CoreMetrics#IT_COVERAGE} are removed.
+ * Else if there's condition on {@link CoreMetrics#COVERAGE}, it will be kept and conditions on {@link CoreMetrics#IT_COVERAGE} are removed.
+ * Then If there's condition on {@link CoreMetrics#IT_COVERAGE}, it will be updated to {@link CoreMetrics#COVERAGE}
+ *
+ * Same strategy is applied on new_XXX, (it_|overall_)lines_to_cover, (it_|overall_)uncovered_lines, etc. related coverage metrics.
+ */
+public class UpdateQualityGateConditionsOnCoverage extends BaseDataChange {
+
+ private static final List<String> COVERAGE_METRIC_KEYS = ImmutableList.of(
+ "coverage", "lines_to_cover", "uncovered_lines", "line_coverage", "conditions_to_cover", "uncovered_conditions", "branch_coverage");
+
+ private static final String OVERALL_PREFIX = "overall_";
+ private static final String IT_PREFIX = "it_";
+ private static final String NEW_PREFIX = "new_";
+
+ public UpdateQualityGateConditionsOnCoverage(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ List<Metric> metrics = selectMetrics(context);
+ if (metrics.isEmpty()) {
+ return;
+ }
+ List<Long> qualityGateIds = context.prepareSelect("select id from quality_gates").list(Select.LONG_READER);
+ if (qualityGateIds.isEmpty()) {
+ return;
+ }
+ new Migration(context, metrics, qualityGateIds).execute();
+ }
+
+ private static class Migration {
+ private final Context context;
+ private final Map<String, Metric> metricsByMetricKeys;
+ private final List<Long> metricIds;
+ private final List<Long> qualityGateIds;
+
+ Migration(Context context, List<Metric> metrics, List<Long> qualityGateIds) {
+ this.context = context;
+ this.metricsByMetricKeys = metrics.stream().collect(uniqueIndex(Metric::getKey, Function.identity()));
+ this.metricIds = metrics.stream().map(Metric::getId).collect(Collectors.toList());
+ this.qualityGateIds = qualityGateIds;
+ }
+
+ public void execute() {
+ qualityGateIds.forEach(this::processQualityGate);
+ }
+
+ private void processQualityGate(long qualityGateId) {
+ List<QualityGateCondition> qualityGateConditions = selectQualityGateConditions(qualityGateId, metricIds);
+ Map<Long, QualityGateCondition> qualityGateConditionsByMetricId = qualityGateConditions.stream()
+ .collect(uniqueIndex(QualityGateCondition::getMetricId, Function.identity()));
+ COVERAGE_METRIC_KEYS.forEach(metric -> {
+ processConditions(metric, OVERALL_PREFIX + metric, IT_PREFIX + metric, qualityGateConditionsByMetricId, qualityGateId);
+ processConditions(NEW_PREFIX + metric, NEW_PREFIX + OVERALL_PREFIX + metric, NEW_PREFIX + IT_PREFIX + metric, qualityGateConditionsByMetricId, qualityGateId);
+ });
+ }
+
+ private void processConditions(String coverageMetricKey, String overallMetricKey, String itMetricKey, Map<Long, QualityGateCondition> qualityGateConditionsByMetricId,
+ long qualityGateId) {
+ try {
+ Optional<QualityGateCondition> conditionOnCoverage = getConditionByMetricKey(coverageMetricKey, qualityGateConditionsByMetricId);
+ Optional<QualityGateCondition> conditionOnOverallCoverage = getConditionByMetricKey(overallMetricKey, qualityGateConditionsByMetricId);
+ Optional<QualityGateCondition> conditionOnItCoverage = getConditionByMetricKey(itMetricKey, qualityGateConditionsByMetricId);
+ if (!conditionOnCoverage.isPresent() && !conditionOnOverallCoverage.isPresent() && !conditionOnItCoverage.isPresent()) {
+ return;
+ }
+ if (conditionOnOverallCoverage.isPresent()) {
+ removeQualityGateCondition(conditionOnCoverage);
+ removeQualityGateCondition(conditionOnItCoverage);
+ updateQualityGateCondition(conditionOnOverallCoverage.get().getId(), coverageMetricKey);
+ } else if (conditionOnCoverage.isPresent()) {
+ removeQualityGateCondition(conditionOnItCoverage);
+ } else {
+ updateQualityGateCondition(conditionOnItCoverage.get().getId(), coverageMetricKey);
+ }
+ } catch (SQLException e) {
+ throw new IllegalStateException(String.format("Fail to update quality gate conditions of quality gate %s", qualityGateId), e);
+ }
+ }
+
+ private Optional<QualityGateCondition> getConditionByMetricKey(String metricKey, Map<Long, QualityGateCondition> qualityGateConditionsByMetricId) {
+ Metric metric = metricsByMetricKeys.get(metricKey);
+ if (metric == null) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(qualityGateConditionsByMetricId.get(metric.getId()));
+ }
+
+ private List<QualityGateCondition> selectQualityGateConditions(long qualityGateId, List<Long> metricIds) {
+ try {
+ Select select = context.prepareSelect("select qgc.id, qgc.metric_id " +
+ "from quality_gate_conditions qgc " +
+ "where qgc.qgate_id=? and qgc.metric_id in (" + repeatCondition("?", metricIds.size(), ",") + ")")
+ .setLong(1, qualityGateId);
+ for (int i = 0; i < metricIds.size(); i++) {
+ select.setLong(i + 2, metricIds.get(i));
+ }
+ return select.list(QualityGateCondition::new);
+ } catch (SQLException e) {
+ throw new IllegalStateException(String.format("Fail to select quality gate conditions of quality gate %s", qualityGateId), e);
+ }
+ }
+
+ private void updateQualityGateCondition(long id, String metricKey) throws SQLException {
+ context.prepareUpsert("update quality_gate_conditions set metric_id=? where id=?")
+ .setLong(1, metricsByMetricKeys.get(metricKey).getId())
+ .setLong(2, id)
+ .execute()
+ .commit();
+ }
+
+ private void removeQualityGateCondition(Optional<QualityGateCondition> condition) throws SQLException {
+ if (!condition.isPresent()) {
+ return;
+ }
+ context.prepareUpsert("delete from quality_gate_conditions where id=?").setLong(1, condition.get().getId())
+ .execute()
+ .commit();
+ }
+ }
+
+ private static List<Metric> selectMetrics(Context context) throws SQLException {
+ List<String> metricKeys = new ArrayList<>(COVERAGE_METRIC_KEYS);
+ metricKeys.addAll(COVERAGE_METRIC_KEYS.stream().map(metricKey -> IT_PREFIX + metricKey).collect(Collectors.toList()));
+ metricKeys.addAll(COVERAGE_METRIC_KEYS.stream().map(metricKey -> OVERALL_PREFIX + metricKey).collect(Collectors.toList()));
+ metricKeys.addAll(metricKeys.stream().map(metricKey -> NEW_PREFIX + metricKey).collect(Collectors.toList()));
+ Select select = context.prepareSelect("select id, name from metrics where name in (" + repeatCondition("?", metricKeys.size(), ",") + ")");
+ for (int i = 0; i < metricKeys.size(); i++) {
+ select.setString(i + 1, metricKeys.get(i));
+ }
+ return select.list(Metric::new);
+ }
+
+ private static class QualityGateCondition {
+ private final long id;
+ private final long metricId;
+
+ QualityGateCondition(Select.Row row) throws SQLException {
+ this.id = requireNonNull(row.getLong(1));
+ this.metricId = requireNonNull(row.getLong(2));
+ }
+
+ long getId() {
+ return id;
+ }
+
+ long getMetricId() {
+ return metricId;
+ }
+ }
+
+ private static class Metric {
+ private final long id;
+ private final String key;
+
+ Metric(Select.Row row) throws SQLException {
+ this.id = requireNonNull(row.getLong(1));
+ this.key = requireNonNull(row.getString(2));
+ }
+
+ long getId() {
+ return id;
+ }
+
+ String getKey() {
+ return key;
+ }
+ }
+}
diff --git a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql
index b34c27e5662..4863aba48cd 100644
--- a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql
+++ b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql
@@ -507,6 +507,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1415');
INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1416');
INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1417');
INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1418');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1419');
INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, IS_ROOT, CREATED_AT, UPDATED_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', true, '1418215735482', '1418215735482');
ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
diff --git a/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java b/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java
index 887a99aba92..70eaab00ab7 100644
--- a/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java
+++ b/sonar-db/src/test/java/org/sonar/db/version/MigrationStepModuleTest.java
@@ -29,6 +29,6 @@ public class MigrationStepModuleTest {
public void verify_count_of_added_MigrationStep_types() {
ComponentContainer container = new ComponentContainer();
new MigrationStepModule().configure(container);
- assertThat(container.size()).isEqualTo(159);
+ assertThat(container.size()).isEqualTo(160);
}
}
diff --git a/sonar-db/src/test/java/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverageTest.java b/sonar-db/src/test/java/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverageTest.java
new file mode 100644
index 00000000000..89d9663f5cb
--- /dev/null
+++ b/sonar-db/src/test/java/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverageTest.java
@@ -0,0 +1,364 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.db.version.v62;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+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.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.version.MigrationStep;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class UpdateQualityGateConditionsOnCoverageTest {
+
+ private static final String TABLE_QUALITY_GATES = "quality_gates";
+ private static final String TABLE_QUALITY_GATE_CONDITIONS = "quality_gate_conditions";
+
+ @Rule
+ public DbTester dbTester = DbTester.createForSchema(System2.INSTANCE, UpdateQualityGateConditionsOnCoverageTest.class, "schema.sql");
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private MigrationStep underTest = new UpdateQualityGateConditionsOnCoverage(dbTester.database());
+
+ @Test
+ public void move_overall_coverage_condition_to_coverage() throws SQLException {
+ Map<String, Long> metricIdsByMetricKeys = inserSampleMetrics();
+ long qualityGateId = insertQualityGate("default");
+ insertQualityGateCondition(qualityGateId, metricIdsByMetricKeys.get("overall_coverage"), null, "GT", "10", null);
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isEqualTo(1);
+ verifyConditions(qualityGateId, new QualityGateCondition("coverage", null, "GT", "10", null));
+ }
+
+ @Test
+ public void move_overall_coverage_condition_to_coverage_when_overall_coverage_exists_condition_on_overall_coverage_exists() throws SQLException {
+ Map<String, Long> metricIdsByMetricKeys = inserSampleMetrics();
+ long qualityGateId = insertQualityGate("default");
+ insertQualityGateCondition(qualityGateId, metricIdsByMetricKeys.get("overall_coverage"), null, "GT", "10", null);
+ insertQualityGateCondition(qualityGateId, metricIdsByMetricKeys.get("coverage"), null, "LT", null, "20");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isEqualTo(1);
+ verifyConditions(qualityGateId, new QualityGateCondition("coverage", null, "GT", "10", null));
+ }
+
+ @Test
+ public void remove_it_coverage_condition_when_overall_coverage_condition_exists_and_no_coverage_condition() throws Exception {
+ Map<String, Long> metricIdsByMetricKeys = inserSampleMetrics();
+ long qualityGateId = insertQualityGate("default");
+ insertQualityGateCondition(qualityGateId, metricIdsByMetricKeys.get("overall_coverage"), null, "GT", "10", null);
+ insertQualityGateCondition(qualityGateId, metricIdsByMetricKeys.get("it_coverage"), null, "LT", null, "20");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isEqualTo(1);
+ verifyConditions(qualityGateId, new QualityGateCondition("coverage", null, "GT", "10", null));
+ }
+
+ @Test
+ public void keep_coverage_condition_when_no_overall_and_it_coverage() throws SQLException {
+ Map<String, Long> metricIdsByMetricKeys = inserSampleMetrics();
+ long qualityGateId = insertQualityGate("default");
+ insertQualityGateCondition(qualityGateId, metricIdsByMetricKeys.get("coverage"), null, "GT", "10", null);
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isEqualTo(1);
+ verifyConditions(qualityGateId, new QualityGateCondition("coverage", null, "GT", "10", null));
+ }
+
+ @Test
+ public void remove_it_coverage_condition_when_coverage_condition_exists_and_no_overall_coverage_condition() throws SQLException {
+ Map<String, Long> metricIdsByMetricKeys = inserSampleMetrics();
+ long qualityGateId = insertQualityGate("default");
+ insertQualityGateCondition(qualityGateId, metricIdsByMetricKeys.get("coverage"), null, "GT", "10", null);
+ insertQualityGateCondition(qualityGateId, metricIdsByMetricKeys.get("it_coverage"), null, "LT", null, "20");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isEqualTo(1);
+ verifyConditions(qualityGateId, new QualityGateCondition("coverage", null, "GT", "10", null));
+ }
+
+ @Test
+ public void move_it_coverage_condition_to_coverage_when_only_it_coverage_condition() throws SQLException {
+ Map<String, Long> metricIdsByMetricKeys = inserSampleMetrics();
+ long qualityGateId = insertQualityGate("default");
+ insertQualityGateCondition(qualityGateId, metricIdsByMetricKeys.get("it_coverage"), null, "GT", "10", null);
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isEqualTo(1);
+ verifyConditions(qualityGateId, new QualityGateCondition("coverage", null, "GT", "10", null));
+ }
+
+ @Test
+ public void move_new_coverage_conditions() throws SQLException {
+ Map<String, Long> metricIdsByMetricKeys = insertMetrics("new_coverage", "new_overall_coverage", "new_it_coverage");
+ long qualityGate1 = insertQualityGate("qualityGate1");
+ insertQualityGateCondition(qualityGate1, metricIdsByMetricKeys.get("new_coverage"), 1L, "GT", "10", null);
+ insertQualityGateCondition(qualityGate1, metricIdsByMetricKeys.get("new_overall_coverage"), 1L, "GT", "7", "15");
+ insertQualityGateCondition(qualityGate1, metricIdsByMetricKeys.get("new_it_coverage"), 2L, "LT", "8", null);
+ long qualityGate2 = insertQualityGate("qualityGate2");
+ insertQualityGateCondition(qualityGate2, metricIdsByMetricKeys.get("new_overall_coverage"), 2L, "GT", "15", null);
+ insertQualityGateCondition(qualityGate2, metricIdsByMetricKeys.get("new_it_coverage"), 2L, "GT", null, "5");
+ long qualityGate3 = insertQualityGate("qualityGate3");
+ insertQualityGateCondition(qualityGate3, metricIdsByMetricKeys.get("new_it_coverage"), 3L, "GT", null, "5");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isEqualTo(3);
+ verifyConditions(qualityGate1, new QualityGateCondition("new_coverage", 1L, "GT", "7", "15"));
+ verifyConditions(qualityGate2, new QualityGateCondition("new_coverage", 2L, "GT", "15", null));
+ verifyConditions(qualityGate3, new QualityGateCondition("new_coverage", 3L, "GT", null, "5"));
+ }
+
+ @DataProvider
+ public static Object[][] metricKeys() {
+ List<String> metrics = ImmutableList.of(
+ "coverage", "lines_to_cover", "uncovered_lines", "line_coverage", "conditions_to_cover", "uncovered_conditions", "branch_coverage");
+ Object[][] res = new Object[metrics.size()][3];
+ int i = 0;
+ for (String metricKey : metrics) {
+ res[i][0] = metricKey;
+ res[i][1] = "overall_" + metricKey;
+ res[i][2] = "it_" + metricKey;
+ i++;
+ }
+ return res;
+ }
+
+ @Test
+ @UseDataProvider("metricKeys")
+ public void move_conditions_from_many_quality_gates_on_all_metrics(String coverageKey, String overallCoverageKey, String itCoverageKey) throws SQLException {
+ Map<String, Long> metricIdsByMetricKeys = insertMetrics(coverageKey, overallCoverageKey, itCoverageKey, "other");
+ long qualityGate1 = insertQualityGate("qualityGate1");
+ insertQualityGateCondition(qualityGate1, metricIdsByMetricKeys.get(coverageKey), null, "GT", "10", null);
+ insertQualityGateCondition(qualityGate1, metricIdsByMetricKeys.get(overallCoverageKey), null, "GT", "7", "15");
+ insertQualityGateCondition(qualityGate1, metricIdsByMetricKeys.get(itCoverageKey), null, "LT", "8", null);
+ long qualityGate2 = insertQualityGate("qualityGate2");
+ insertQualityGateCondition(qualityGate2, metricIdsByMetricKeys.get(overallCoverageKey), null, "GT", "15", null);
+ insertQualityGateCondition(qualityGate2, metricIdsByMetricKeys.get(itCoverageKey), null, "GT", null, "5");
+ long qualityGate3 = insertQualityGate("qualityGate3");
+ insertQualityGateCondition(qualityGate3, metricIdsByMetricKeys.get(itCoverageKey), null, "GT", null, "5");
+ insertQualityGateCondition(qualityGate3, metricIdsByMetricKeys.get("other"), null, "GT", "11", null);
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isEqualTo(4);
+ verifyConditions(qualityGate1, new QualityGateCondition(coverageKey, null, "GT", "7", "15"));
+ verifyConditions(qualityGate2, new QualityGateCondition(coverageKey, null, "GT", "15", null));
+ verifyConditions(qualityGate3, new QualityGateCondition(coverageKey, null, "GT", null, "5"), new QualityGateCondition("other", null, "GT", "11", null));
+ }
+
+ @Test
+ public void does_not_update_conditions_on_none_related_coverage_metrics() throws Exception {
+ insertMetrics();
+ long metric1 = insertMetric("metric1");
+ long metric2 = insertMetric("metric2");
+ long qualityGate1 = insertQualityGate("qualityGate1");
+ insertQualityGateCondition(qualityGate1, metric1, null, "GT", "10", null);
+ long qualityGate2 = insertQualityGate("qualityGate2");
+ insertQualityGateCondition(qualityGate2, metric2, null, "LT", null, "20");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isEqualTo(2);
+ verifyConditions(qualityGate1, new QualityGateCondition("metric1", null, "GT", "10", null));
+ verifyConditions(qualityGate2, new QualityGateCondition("metric2", null, "LT", null, "20"));
+ }
+
+ @Test
+ public void does_nothing_when_no_quality_gates() throws Exception {
+ insertMetrics("coverage", "new_coverage", "overall_coverage");
+
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isZero();
+ }
+
+ @Test
+ public void does_nothing_when_no_metrics() throws Exception {
+ underTest.execute();
+
+ assertThat(dbTester.countRowsOfTable(TABLE_QUALITY_GATE_CONDITIONS)).isZero();
+ }
+
+ private Map<String, Long> inserSampleMetrics() {
+ return insertMetrics("coverage", "overall_coverage", "it_coverage");
+ }
+
+ private Map<String, Long> insertMetrics(String... metrics) {
+ Map<String, Long> metricIdsByMetricKeys = new HashMap<>();
+ for (String metricKey : metrics) {
+ metricIdsByMetricKeys.put(metricKey, insertMetric(metricKey));
+ }
+ return metricIdsByMetricKeys;
+ }
+
+ private long insertMetric(String key) {
+ dbTester.executeInsert("metrics", "NAME", key);
+ return (Long) dbTester.selectFirst(dbTester.getSession(), format("select id as \"id\" from metrics where name='%s'", key)).get("id");
+ }
+
+ private long insertQualityGate(String qualityGate) {
+ dbTester.executeInsert(TABLE_QUALITY_GATES, "NAME", qualityGate);
+ return (Long) dbTester.selectFirst(dbTester.getSession(), format("select id as \"id\" from %s where name='%s'", TABLE_QUALITY_GATES, qualityGate)).get("id");
+ }
+
+ private long insertQualityGateCondition(long qualityGateId, long metricId, @Nullable Long period, String operator, @Nullable String error, @Nullable String warning) {
+ Map<String, Object> values = new HashMap<>(ImmutableMap.of("QGATE_ID", qualityGateId, "METRIC_ID", metricId, "OPERATOR", operator));
+ if (period != null) {
+ values.put("PERIOD", period);
+ }
+ if (error != null) {
+ values.put("VALUE_ERROR", error);
+ }
+ if (warning != null) {
+ values.put("VALUE_WARNING", warning);
+ }
+ dbTester.executeInsert(TABLE_QUALITY_GATE_CONDITIONS, values);
+ return (Long) dbTester
+ .selectFirst(dbTester.getSession(), format("select id as \"id\" from %s where qgate_id='%s' and metric_id='%s'", TABLE_QUALITY_GATE_CONDITIONS, qualityGateId, metricId))
+ .get("id");
+ }
+
+ private void verifyConditions(long qualityGateId, QualityGateCondition... expectedConditions) {
+ List<Map<String, Object>> results = dbTester.select(dbTester.getSession(),
+ format("select m.name as \"metricKey\", qgc.period as \"period\", qgc.operator as \"operator\", qgc.value_error as \"error\", qgc.value_warning as \"warning\" from %s qgc " +
+ "inner join metrics m on m.id=qgc.metric_id " +
+ "where qgc.qgate_id = '%s'", TABLE_QUALITY_GATE_CONDITIONS, qualityGateId));
+ List<QualityGateCondition> conditions = results.stream().map(QualityGateCondition::new).collect(Collectors.toList());
+ assertThat(conditions).containsOnly(expectedConditions);
+ }
+
+ private static class QualityGateCondition {
+ String metricKey;
+ Long period;
+ String operator;
+ String valueError;
+ String valueWarning;
+
+ public QualityGateCondition(String metricKey, @Nullable Long period, String operator, @Nullable String valueError, @Nullable String valueWarning) {
+ this.metricKey = metricKey;
+ this.period = period;
+ this.operator = operator;
+ this.valueError = valueError;
+ this.valueWarning = valueWarning;
+ }
+
+ QualityGateCondition(Map<String, Object> map) {
+ this.metricKey = (String) map.get("metricKey");
+ this.period = (Long) map.get("period");
+ this.operator = (String) map.get("operator");
+ this.valueError = (String) map.get("error");
+ this.valueWarning = (String) map.get("warning");
+ }
+
+ public String getMetricKey() {
+ return metricKey;
+ }
+
+ @CheckForNull
+ public Long getPeriod() {
+ return period;
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ @CheckForNull
+ public String getValueError() {
+ return valueError;
+ }
+
+ @CheckForNull
+ public String getValueWarning() {
+ return valueWarning;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ QualityGateCondition that = (QualityGateCondition) o;
+ return new EqualsBuilder()
+ .append(metricKey, that.getMetricKey())
+ .append(period, that.getPeriod())
+ .append(operator, that.getOperator())
+ .append(valueError, that.getValueError())
+ .append(valueWarning, that.getValueWarning())
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(15, 31)
+ .append(metricKey)
+ .append(period)
+ .append(operator)
+ .append(valueError)
+ .append(valueWarning)
+ .toHashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "QualityGateCondition{" +
+ "metricKey='" + metricKey + '\'' +
+ ", period=" + period +
+ ", operator=" + operator +
+ ", valueError='" + valueError + '\'' +
+ ", valueWarning='" + valueWarning + '\'' +
+ '}';
+ }
+ }
+
+}
diff --git a/sonar-db/src/test/resources/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverageTest/schema.sql b/sonar-db/src/test/resources/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverageTest/schema.sql
new file mode 100644
index 00000000000..39a62ddfdc6
--- /dev/null
+++ b/sonar-db/src/test/resources/org/sonar/db/version/v62/UpdateQualityGateConditionsOnCoverageTest/schema.sql
@@ -0,0 +1,40 @@
+CREATE TABLE "QUALITY_GATES" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "NAME" VARCHAR(100) NOT NULL,
+ "CREATED_AT" TIMESTAMP,
+ "UPDATED_AT" TIMESTAMP,
+);
+CREATE UNIQUE INDEX "UNIQ_QUALITY_GATES" ON "QUALITY_GATES" ("NAME");
+
+
+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,
+);
+
+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");