]> source.dussan.org Git - sonarqube.git/blob
74813eea032f90a3e77585d4e3d1c9f8d29279eb
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.server.platform.db.migration.version.v108;
21
22 import com.google.gson.Gson;
23 import java.nio.charset.StandardCharsets;
24 import java.sql.SQLException;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.api.extension.RegisterExtension;
32 import org.slf4j.event.Level;
33 import org.sonar.api.measures.CoreMetrics;
34 import org.sonar.api.testfixtures.log.LogTesterJUnit5;
35 import org.sonar.api.utils.System2;
36 import org.sonar.core.metric.SoftwareQualitiesMetrics;
37 import org.sonar.core.util.SequenceUuidFactory;
38 import org.sonar.db.MigrationDbTester;
39 import org.sonar.server.platform.db.migration.step.DataChange;
40
41 import static java.lang.String.format;
42 import static org.assertj.core.api.Assertions.assertThat;
43 import static org.assertj.core.api.Assertions.assertThatCode;
44 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
45 import static org.assertj.core.api.Assertions.tuple;
46 import static org.mockito.Mockito.mock;
47
48 class MigrateBranchesLiveMeasuresToMeasuresIT {
49
50   private static final String MEASURES_MIGRATED_COLUMN = "measures_migrated";
51   public static final String SELECT_MEASURE = "select component_uuid, branch_uuid, json_value, json_value_hash, created_at, updated_at " +
52     "from measures where component_uuid = '%s'";
53
54   @RegisterExtension
55   public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(MigrateBranchesLiveMeasuresToMeasures.class);
56
57   @RegisterExtension
58   private final LogTesterJUnit5 logTester = new LogTesterJUnit5();
59
60   private final SequenceUuidFactory uuidFactory = new SequenceUuidFactory();
61   private final System2 system2 = mock();
62   private final DataChange underTest = new MigrateBranchesLiveMeasuresToMeasures(db.database(), system2);
63
64   @Test
65   void shall_complete_when_tables_are_empty() throws SQLException {
66     underTest.execute();
67
68     assertThat(db.countRowsOfTable("measures")).isZero();
69   }
70
71   @Test
72   void migration_does_nothing_if_live_measures_table_is_missing() {
73     db.executeDdl("drop table live_measures");
74     db.assertTableDoesNotExist("live_measures");
75     String branch = "branch_3";
76     insertNotMigratedBranch(branch);
77
78     assertThatCode(underTest::execute)
79       .doesNotThrowAnyException();
80   }
81
82   @Test
83   void log_the_item_uuid_when_the_migration_fails() {
84     String nclocMetricUuid = insertMetric("ncloc", "INT");
85     String branch1 = "branch_1";
86     insertNotMigratedBranch(branch1);
87     insertMeasure(branch1, nclocMetricUuid, Map.of("value", 120));
88
89     db.executeDdl("drop table measures");
90     db.assertTableDoesNotExist("measures");
91
92     assertThatExceptionOfType(SQLException.class)
93       .isThrownBy(underTest::execute);
94
95     assertThat(logTester.logs(Level.ERROR))
96       .contains("Migration of branch branch_1 failed");
97   }
98
99   @Test
100   void shall_not_migrate_when_branch_is_already_flagged() throws SQLException {
101     String nclocMetricUuid = insertMetric("ncloc", "INT");
102     String qgStatusMetricUuid = insertMetric("quality_gate_status", "STRING");
103     String metricWithDataUuid = insertMetric("metric_with_data", "DATA");
104     String branch1 = "branch_1";
105     insertMigratedBranch(branch1);
106     insertMeasure(branch1, nclocMetricUuid, Map.of("value", 120));
107     insertMeasure(branch1, qgStatusMetricUuid, Map.of("text_value", "ok"));
108     insertMeasure(branch1, metricWithDataUuid, Map.of("measure_data", "some data".getBytes(StandardCharsets.UTF_8)));
109
110     insertMigratedBranch("branch_2");
111     insertMeasure("branch_2", nclocMetricUuid, Map.of("value", 14220));
112
113     underTest.execute();
114
115     assertThat(db.countRowsOfTable("measures")).isZero();
116   }
117
118   @Test
119   void should_flag_branch_with_no_measures() throws SQLException {
120     String branch = "branch_3";
121     insertNotMigratedBranch(branch);
122
123     underTest.execute();
124
125     assertBranchMigrated(branch);
126     assertThat(db.countRowsOfTable("measures")).isZero();
127   }
128
129   @Test
130   void should_migrate_branch_with_measures() throws SQLException {
131     String nclocMetricUuid = insertMetric("ncloc", "INT");
132     String qgStatusMetricUuid = insertMetric("quality_gate_status", "STRING");
133     String metricWithDataUuid = insertMetric("metric_with_data", "DATA");
134
135     String branch1 = "branch_4";
136     insertNotMigratedBranch(branch1);
137     String component1 = uuidFactory.create();
138     String component2 = uuidFactory.create();
139     insertMeasure(branch1, component1, nclocMetricUuid, Map.of("value", 120));
140     insertMeasure(branch1, component1, qgStatusMetricUuid, Map.of("text_value", "ok"));
141     insertMeasure(branch1, component2, metricWithDataUuid, Map.of("measure_data", "some data".getBytes(StandardCharsets.UTF_8)));
142
143     String branch2 = "branch_5";
144     insertNotMigratedBranch(branch2);
145     insertMeasure(branch2, nclocMetricUuid, Map.of("value", 64));
146
147     String migratedBranch = "branch_6";
148     insertMigratedBranch(migratedBranch);
149     insertMeasure(migratedBranch, nclocMetricUuid, Map.of("value", 3684));
150
151     underTest.execute();
152
153     assertBranchMigrated(branch1);
154     assertBranchMigrated(branch2);
155     assertThat(db.countRowsOfTable("measures")).isEqualTo(3);
156
157     assertThat(db.select(format(SELECT_MEASURE, component1)))
158       .hasSize(1)
159       .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
160       .containsOnly(tuple(component1, branch1, "{\"ncloc\":120.0,\"quality_gate_status\":\"ok\"}", 6033012287291512746L));
161
162     assertThat(db.select(format(SELECT_MEASURE, component2)))
163       .hasSize(1)
164       .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
165       .containsOnly(tuple(component2, branch1, "{\"metric_with_data\":\"some data\"}", -4524184678167636687L));
166   }
167
168   @Test
169   void should_not_migrate_measures_planned_for_deletion() throws SQLException {
170     String nclocMetricUuid = insertMetric("ncloc", "INT");
171     Set<String> deletedMetricUuid = DeleteSoftwareQualityRatingFromProjectMeasures.SOFTWARE_QUALITY_METRICS_TO_DELETE.stream().map(e -> insertMetric(e, "INT"))
172       .collect(Collectors.toSet());
173
174     String branch1 = "branch_4";
175     insertNotMigratedBranch(branch1);
176     String component1 = uuidFactory.create();
177     String component2 = uuidFactory.create();
178     insertMeasure(branch1, component1, nclocMetricUuid, Map.of("value", 120));
179     deletedMetricUuid.forEach(metricUuid -> insertMeasure(branch1, component1, metricUuid, Map.of("value", 120)));
180     deletedMetricUuid.forEach(metricUuid -> insertMeasure(branch1, component2, metricUuid, Map.of("value", 120)));
181
182     underTest.execute();
183
184     assertBranchMigrated(branch1);
185     assertThat(db.countRowsOfTable("measures")).isEqualTo(1);
186
187     assertThat(db.select(format(SELECT_MEASURE, component1)))
188       .hasSize(1)
189       .extracting(t -> t.get("component_uuid"), t -> t.get("branch_uuid"), t -> t.get("json_value"), t -> t.get("json_value_hash"))
190       .containsOnly(tuple(component1, branch1, "{\"ncloc\":120.0}", -1557106439558598045L));
191
192     assertThat(db.select(format(SELECT_MEASURE, component2)))
193       .isEmpty();
194   }
195
196   @Test
197   void should_include_new_measures_based_on_previous_available_measures() throws SQLException {
198     Set<String> metricsToMigrate = MeasureMigration.MIGRATION_MAP.keySet().stream().map(e -> insertMetric(e, "DATA"))
199       .collect(Collectors.toSet());
200
201     String branch = "branch_4";
202     insertNotMigratedBranch(branch);
203     String component1 = uuidFactory.create();
204     metricsToMigrate.forEach(metricUuid -> insertMeasure(branch, component1, metricUuid,
205       Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":7}")));
206
207     underTest.execute();
208
209     assertBranchMigrated(branch);
210     assertThat(db.countRowsOfTable("measures")).isEqualTo(1);
211
212     List<Map<String, Object>> measuresFromDB = db.select(format(SELECT_MEASURE, component1));
213     assertThat(measuresFromDB).hasSize(1);
214
215     Gson gson = new Gson();
216     Map<String, Object> jsonValue = gson.fromJson((String) measuresFromDB.get(0).get("json_value"), Map.class);
217
218     Map<String, Object> expectedExistingMetrics = MeasureMigration.MIGRATION_MAP.keySet().stream().collect(
219       Collectors.toMap(s -> s, s -> "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":7}"));
220
221     Map<String, Object> expectedNewMetrics = MeasureMigration.MIGRATION_MAP.values().stream().collect(
222       Collectors.toMap(s -> s, s -> 7.0));
223
224     assertThat(jsonValue).containsAllEntriesOf(expectedExistingMetrics).containsAllEntriesOf(expectedNewMetrics);
225   }
226
227   @Test
228   void should_migrate_other_measures_when_there_is_an_error_converting_previous_measures() throws SQLException {
229     String nclocMetricUuid = insertMetric("ncloc", "INT");
230     String maintainabilityMetricUuid = insertMetric(CoreMetrics.MAINTAINABILITY_ISSUES_KEY, "DATA");
231     String reliabilityMetricUuid = insertMetric(CoreMetrics.RELIABILITY_ISSUES_KEY, "DATA");
232     String securityMetricUuid = insertMetric(CoreMetrics.SECURITY_ISSUES_KEY, "DATA");
233
234     String branch = "branch_4";
235     insertNotMigratedBranch(branch);
236     String component1 = uuidFactory.create();
237     insertMeasure(branch, component1, nclocMetricUuid, Map.of("value", 120));
238     // total is not a number
239     insertMeasure(branch, component1, maintainabilityMetricUuid, Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4," +
240       "\"total\":\"ABC\"}"));
241     // total cannot fit in a long
242     insertMeasure(branch, component1, reliabilityMetricUuid, Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4," +
243       "\"total\":98723987498723987429874928748748}"));
244     insertMeasure(branch, component1, securityMetricUuid, Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":37}"));
245
246     logTester.setLevel(Level.DEBUG);
247     underTest.execute();
248
249     assertBranchMigrated(branch);
250     assertThat(db.countRowsOfTable("measures")).isEqualTo(1);
251
252     List<Map<String, Object>> measuresFromDB = db.select(format(SELECT_MEASURE, component1));
253     assertThat(measuresFromDB).hasSize(1);
254
255     Gson gson = new Gson();
256     Map<String, Object> jsonValue = gson.fromJson((String) measuresFromDB.get(0).get("json_value"), Map.class);
257
258     Map<String, Object> expectedExistingMetrics = Map.of(
259       "ncloc", 120.0,
260       CoreMetrics.MAINTAINABILITY_ISSUES_KEY, "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":\"ABC\"}",
261       CoreMetrics.RELIABILITY_ISSUES_KEY, "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":98723987498723987429874928748748}",
262       CoreMetrics.SECURITY_ISSUES_KEY, "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":37}"
263     );
264
265     Map<String, Object> expectedNewMetrics = Map.of(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES_KEY, 37.0);
266
267     assertThat(jsonValue).hasSize(5).containsAllEntriesOf(expectedExistingMetrics).containsAllEntriesOf(expectedNewMetrics);
268     assertThat(logTester.logs(Level.DEBUG)).contains("Failed to migrate metric reliability_issues with value {\"LOW\":3,\"MEDIUM\":0," +
269       "\"HIGH\":4,\"total\":98723987498723987429874928748748}");
270   }
271
272   private void assertBranchMigrated(String branch) {
273     List<Map<String, Object>> result = db.select(format("select %s as \"MIGRATED\" from project_branches where uuid = '%s'", MEASURES_MIGRATED_COLUMN, branch));
274     assertThat(result)
275       .hasSize(1)
276       .extracting(t -> t.get("MIGRATED"))
277       .containsOnly(true);
278   }
279
280   private String insertMetric(String metricName, String valueType) {
281     String metricUuid = uuidFactory.create();
282     db.executeInsert("metrics",
283       "uuid", metricUuid,
284       "name", metricName,
285       "val_type", valueType);
286     return metricUuid;
287   }
288
289   private void insertMeasure(String branchUuid, String metricUuid, Map<String, Object> data) {
290     insertMeasure(branchUuid, uuidFactory.create(), metricUuid, data);
291   }
292
293   private void insertMeasure(String branchUuid, String componentUuid, String metricUuid, Map<String, Object> data) {
294     Map<String, Object> dataMap = new HashMap<>(data);
295     dataMap.put("uuid", uuidFactory.create());
296     dataMap.put("component_uuid", componentUuid);
297     dataMap.put("project_uuid", branchUuid);
298     dataMap.put("metric_uuid", metricUuid);
299     dataMap.put("created_at", 12L);
300     dataMap.put("updated_at", 12L);
301
302     db.executeInsert("live_measures", dataMap);
303   }
304
305   private void insertNotMigratedBranch(String branchUuid) {
306     insertBranch(branchUuid, false);
307   }
308
309   private void insertMigratedBranch(String branchUuid) {
310     insertBranch(branchUuid, true);
311   }
312
313   private void insertBranch(String branchUuid, boolean migrated) {
314     db.executeInsert("project_branches",
315       "uuid", branchUuid,
316       "kee", branchUuid,
317       "branch_type", "LONG",
318       "project_uuid", uuidFactory.create(),
319       MEASURES_MIGRATED_COLUMN, migrated,
320       "need_issue_sync", false,
321       "is_main", true,
322       "created_at", 12L,
323       "updated_at", 12L);
324   }
325
326 }