diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-10-25 19:08:35 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-10-26 11:55:16 +0200 |
commit | 331558e106bc9bda51dfa39cc9a5874aa62c236c (patch) | |
tree | 31b012f1259748fdcf430c243babbc4a445f4544 /sonar-db | |
parent | 1609c61c56b527f9a2f73e1419aa5a57d9a6a06d (diff) | |
download | sonarqube-331558e106bc9bda51dfa39cc9a5874aa62c236c.tar.gz sonarqube-331558e106bc9bda51dfa39cc9a5874aa62c236c.zip |
SONAR-8283 Migrate quality gates after removall of IT/overall coverage metrics
Diffstat (limited to 'sonar-db')
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"); |