3 * Copyright (C) 2009-2019 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.ce.task.projectanalysis.step;
22 import com.tngtech.java.junit.dataprovider.DataProvider;
23 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
24 import com.tngtech.java.junit.dataprovider.UseDataProvider;
25 import java.time.ZoneId;
26 import java.time.ZonedDateTime;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.List;
30 import java.util.Random;
31 import java.util.stream.Stream;
32 import javax.annotation.Nullable;
33 import org.junit.Before;
34 import org.junit.Rule;
35 import org.junit.Test;
36 import org.junit.rules.ExpectedException;
37 import org.junit.runner.RunWith;
38 import org.sonar.api.utils.MessageException;
39 import org.sonar.api.utils.System2;
40 import org.sonar.api.utils.log.LogAndArguments;
41 import org.sonar.api.utils.log.LogTester;
42 import org.sonar.api.utils.log.LoggerLevel;
43 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
44 import org.sonar.ce.task.projectanalysis.component.Component;
45 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
46 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
47 import org.sonar.ce.task.projectanalysis.period.NewCodePeriodResolver;
48 import org.sonar.ce.task.projectanalysis.period.Period;
49 import org.sonar.ce.task.projectanalysis.period.PeriodHolderImpl;
50 import org.sonar.ce.task.step.ComputationStep;
51 import org.sonar.ce.task.step.TestComputationStepContext;
52 import org.sonar.core.util.SequenceUuidFactory;
53 import org.sonar.db.DbTester;
54 import org.sonar.db.component.ComponentDto;
55 import org.sonar.db.component.SnapshotDto;
56 import org.sonar.db.event.EventTesting;
57 import org.sonar.db.newcodeperiod.NewCodePeriodDao;
58 import org.sonar.db.newcodeperiod.NewCodePeriodType;
59 import org.sonar.db.organization.OrganizationDto;
60 import org.sonar.server.project.Project;
62 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
63 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
64 import static org.assertj.core.api.Assertions.assertThat;
65 import static org.assertj.core.api.Assertions.fail;
66 import static org.mockito.Mockito.mock;
67 import static org.mockito.Mockito.verify;
68 import static org.mockito.Mockito.verifyNoMoreInteractions;
69 import static org.mockito.Mockito.when;
70 import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
71 import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
72 import static org.sonar.db.event.EventDto.CATEGORY_VERSION;
73 import static org.sonar.db.event.EventTesting.newEvent;
75 @RunWith(DataProviderRunner.class)
76 public class LoadPeriodsStepTest extends BaseStepTest {
78 public DbTester dbTester = DbTester.create(System2.INSTANCE);
80 public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
82 public LogTester logTester = new LogTester();
84 public ExpectedException expectedException = ExpectedException.none();
86 private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
87 private PeriodHolderImpl periodsHolder = new PeriodHolderImpl();
88 private System2 system2Mock = mock(System2.class);
89 private NewCodePeriodDao dao = new NewCodePeriodDao(system2Mock, new SequenceUuidFactory());
90 private NewCodePeriodResolver newCodePeriodResolver = new NewCodePeriodResolver(dbTester.getDbClient());
91 private ZonedDateTime analysisDate = ZonedDateTime.of(2019, 3, 20, 5, 30, 40, 0, ZoneId.systemDefault());
93 private LoadPeriodsStep underTest = new LoadPeriodsStep(analysisMetadataHolder, dao, treeRootHolder, periodsHolder, dbTester.getDbClient(), newCodePeriodResolver);
95 private OrganizationDto organization;
96 private ComponentDto project;
99 protected ComputationStep step() {
104 public void setUp() {
105 organization = dbTester.organizations().insert();
106 project = dbTester.components().insertMainBranch(organization);
108 when(analysisMetadataHolder.isBranch()).thenReturn(true);
109 when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
110 when(analysisMetadataHolder.getAnalysisDate()).thenReturn(analysisDate.toInstant().toEpochMilli());
114 public void no_period_on_first_analysis() {
115 when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true);
116 underTest.execute(new TestComputationStepContext());
118 verify(analysisMetadataHolder).isFirstAnalysis();
119 assertThat(periodsHolder.hasPeriod()).isFalse();
120 verifyNoMoreInteractions(analysisMetadataHolder);
124 public void no_period_if_not_LLB() {
125 when(analysisMetadataHolder.isBranch()).thenReturn(false);
126 underTest.execute(new TestComputationStepContext());
128 verify(analysisMetadataHolder).isFirstAnalysis();
129 verify(analysisMetadataHolder).isBranch();
130 assertThat(periodsHolder.hasPeriod()).isFalse();
131 verifyNoMoreInteractions(analysisMetadataHolder);
135 public void load_default_if_nothing_defined() {
138 SnapshotDto analysis = dbTester.components().insertSnapshot(project,
139 snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)));
141 underTest.execute(new TestComputationStepContext());
143 assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, null, analysis.getCreatedAt());
144 verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
148 public void load_number_of_days_global() {
149 setGlobalPeriod(NewCodePeriodType.NUMBER_OF_DAYS, "10");
151 testNumberOfDays(project);
155 public void load_number_of_days_on_project() {
156 setGlobalPeriod(NewCodePeriodType.PREVIOUS_VERSION, null);
157 setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
159 testNumberOfDays(project);
163 public void load_number_of_days_on_branch() {
164 ComponentDto branch = dbTester.components().insertProjectBranch(project);
166 setGlobalPeriod(NewCodePeriodType.PREVIOUS_VERSION, null);
167 setProjectPeriod(project.uuid(), NewCodePeriodType.PREVIOUS_VERSION, null);
168 setBranchPeriod(project.uuid(), branch.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
170 testNumberOfDays(branch);
173 private void testNumberOfDays(ComponentDto projectOrBranch) {
174 setupRoot(projectOrBranch);
176 SnapshotDto analysis = dbTester.components().insertSnapshot(projectOrBranch,
177 snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)));
179 underTest.execute(new TestComputationStepContext());
181 assertPeriod(NewCodePeriodType.NUMBER_OF_DAYS, "10", analysis.getCreatedAt());
182 verifyDebugLogs("Resolving new code period by 10 days: 2019-03-10");
186 public void load_specific_analysis() {
187 ComponentDto branch = dbTester.components().insertProjectBranch(project);
188 SnapshotDto selectedAnalysis = dbTester.components().insertSnapshot(branch);
189 SnapshotDto aVersionAnalysis = dbTester.components().insertSnapshot(branch, snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 12, 0)).setLast(false));
190 dbTester.events().insertEvent(EventTesting.newEvent(aVersionAnalysis).setName("a_version").setCategory(CATEGORY_VERSION));
191 dbTester.components().insertSnapshot(branch, snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)).setLast(true));
193 setBranchPeriod(project.uuid(), branch.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, selectedAnalysis.getUuid());
196 underTest.execute(new TestComputationStepContext());
198 assertPeriod(NewCodePeriodType.SPECIFIC_ANALYSIS, selectedAnalysis.getUuid(), selectedAnalysis.getCreatedAt());
199 verifyDebugLogs("Resolving new code period with a specific analysis");
203 public void throw_ISE_if_no_analysis_found_for_number_of_days() {
204 setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
208 expectedException.expect(IllegalStateException.class);
209 expectedException.expectMessage("Attempting to resolve period while no analysis exist");
211 underTest.execute(new TestComputationStepContext());
215 public void throw_ISE_if_no_analysis_found_with_default() {
218 expectedException.expect(IllegalStateException.class);
219 expectedException.expectMessage("Attempting to resolve period while no analysis exist");
221 underTest.execute(new TestComputationStepContext());
225 public void ignore_unprocessed_snapshots() {
226 SnapshotDto analysis1 = dbTester.components()
227 .insertSnapshot(project, snapshot -> snapshot.setStatus(STATUS_UNPROCESSED).setCreatedAt(milisSinceEpoch(2019, 3, 12, 0)).setLast(false));
228 SnapshotDto analysis2 = dbTester.components().insertSnapshot(project,
229 snapshot -> snapshot.setStatus(STATUS_PROCESSED).setProjectVersion("not provided").setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)).setLast(false));
230 dbTester.events().insertEvent(newEvent(analysis1).setName("not provided").setCategory(CATEGORY_VERSION).setDate(analysis1.getCreatedAt()));
231 dbTester.events().insertEvent(newEvent(analysis2).setName("not provided").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
233 setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
235 underTest.execute(new TestComputationStepContext());
237 assertPeriod(NewCodePeriodType.NUMBER_OF_DAYS, "10", analysis2.getCreatedAt());
238 verifyDebugLogs("Resolving new code period by 10 days: 2019-03-10");
242 public void throw_ISE_when_specific_analysis_is_set_but_does_not_exist_in_DB() {
243 OrganizationDto organization = dbTester.organizations().insert();
244 ComponentDto project = dbTester.components().insertMainBranch(organization);
245 setProjectPeriod(project.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, "nonexistent");
248 expectedException.expect(IllegalStateException.class);
249 expectedException.expectMessage("Analysis 'nonexistent' of project '" + project.uuid() + "' defined as the baseline does not exist");
251 underTest.execute(new TestComputationStepContext());
255 public void throw_ISE_when_specific_analysis_is_set_but_does_not_belong_to_current_project() {
256 ComponentDto otherProject = dbTester.components().insertMainBranch(organization);
257 SnapshotDto otherProjectAnalysis = dbTester.components().insertSnapshot(otherProject);
258 setBranchPeriod(project.uuid(), project.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, otherProjectAnalysis.getUuid());
261 expectedException.expect(IllegalStateException.class);
262 expectedException.expectMessage("Analysis '" + otherProjectAnalysis.getUuid() + "' of project '" + project.uuid()
263 + "' defined as the baseline does not exist");
265 underTest.execute(new TestComputationStepContext());
269 public void throw_ISE_when_specific_analysis_is_set_but_does_not_belong_to_current_branch() {
270 ComponentDto otherBranch = dbTester.components().insertProjectBranch(project);
271 SnapshotDto otherBranchAnalysis = dbTester.components().insertSnapshot(otherBranch);
272 setBranchPeriod(project.uuid(), project.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, otherBranchAnalysis.getUuid());
275 expectedException.expect(IllegalStateException.class);
276 expectedException.expectMessage("Analysis '" + otherBranchAnalysis.getUuid() + "' of project '" + project.uuid()
277 + "' defined as the baseline does not exist");
279 underTest.execute(new TestComputationStepContext());
283 public void load_previous_version() {
284 SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(false)); // 2008-11-11
285 SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setProjectVersion("1.0").setLast(false)); // 2008-11-12
286 SnapshotDto analysis3 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setProjectVersion("1.1").setLast(false)); // 2008-11-20
287 SnapshotDto analysis4 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227358680000L).setProjectVersion("1.1").setLast(false)); // 2008-11-22
288 SnapshotDto analysis5 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setProjectVersion("1.1").setLast(true)); // 2008-11-29
289 dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION).setDate(analysis1.getCreatedAt()));
290 dbTester.events().insertEvent(newEvent(analysis2).setName("1.0").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
291 dbTester.events().insertEvent(newEvent(analysis5).setName("1.1").setCategory(CATEGORY_VERSION).setDate(analysis4.getCreatedAt()));
292 setupRoot(project, "1.1");
293 setProjectPeriod(project.uuid(), NewCodePeriodType.PREVIOUS_VERSION, null);
295 underTest.execute(new TestComputationStepContext());
297 assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, "1.0", analysis2.getCreatedAt());
299 verifyDebugLogs("Resolving new code period by previous version: 1.0");
303 @UseDataProvider("zeroOrLess")
304 public void fail_with_MessageException_if_period_is_0_or_less(int zeroOrLess) {
306 setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, String.valueOf(zeroOrLess));
308 verifyFailWithInvalidValueMessageException(String.valueOf(zeroOrLess),
309 "Invalid code period '" + zeroOrLess + "': number of days is <= 0");
313 public void load_previous_version_with_previous_version_deleted() {
314 SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(false)); // 2008-11-11
315 SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setProjectVersion("1.0").setLast(false)); // 2008-11-12
316 SnapshotDto analysis3 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setProjectVersion("1.1").setLast(false)); // 2008-11-20
317 dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION));
318 // The "1.0" version was deleted from the history
319 dbTester.events().insertEvent(newEvent(analysis3).setName("1.1").setCategory(CATEGORY_VERSION));
320 setupRoot(project, "1.1");
322 underTest.execute(new TestComputationStepContext());
324 // Analysis form 2008-11-11
325 assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, "0.9", analysis1.getCreatedAt());
329 public void load_previous_version_with_first_analysis_when_no_previous_version_found() {
330 SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("1.1").setLast(false)); // 2008-11-11
331 SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setProjectVersion("1.1").setLast(true)); // 2008-11-29
332 dbTester.events().insertEvent(newEvent(analysis2).setName("1.1").setCategory(CATEGORY_VERSION));
333 setupRoot(project, "1.1");
335 underTest.execute(new TestComputationStepContext());
337 assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, null, analysis1.getCreatedAt());
339 verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
343 public void load_previous_version_with_first_analysis_when_previous_snapshot_is_the_last_one() {
344 SnapshotDto analysis = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(true)); // 2008-11-11
345 dbTester.events().insertEvent(newEvent(analysis).setName("0.9").setCategory(CATEGORY_VERSION));
346 setupRoot(project, "1.1");
348 dbTester.newCodePeriods().insert(NewCodePeriodType.PREVIOUS_VERSION, null);
350 underTest.execute(new TestComputationStepContext());
352 assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, null, analysis.getCreatedAt());
353 verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
357 @UseDataProvider("anyValidLeakPeriodSettingValue")
358 public void leak_period_setting_is_ignored_for_PR(NewCodePeriodType type, @Nullable String value) {
359 when(analysisMetadataHolder.isBranch()).thenReturn(false);
361 dbTester.newCodePeriods().insert(type, value);
363 underTest.execute(new TestComputationStepContext());
365 assertThat(periodsHolder.hasPeriod()).isFalse();
368 private void verifyFailWithInvalidValueMessageException(String propertyValue, String debugLog, String... otherDebugLogs) {
370 underTest.execute(new TestComputationStepContext());
371 fail("a Message Exception should have been thrown");
372 } catch (MessageException e) {
373 verifyInvalidValueMessage(e, propertyValue);
374 verifyDebugLogs(debugLog, otherDebugLogs);
379 public static Object[][] zeroOrLess() {
380 return new Object[][]{
382 {-1 - new Random().nextInt(30)}
387 public static Object[][] stringConsideredAsVersions() {
388 return new Object[][]{
389 {randomAlphabetic(5)},
394 {"01-12-2018"}, // unsupported date format
399 public static Object[][] projectVersionNullOrNot() {
400 return new Object[][]{
402 {randomAlphabetic(15)},
407 public static Object[][] anyValidLeakPeriodSettingValue() {
408 return new Object[][]{
410 {NewCodePeriodType.NUMBER_OF_DAYS, "100"},
412 {NewCodePeriodType.PREVIOUS_VERSION, null}
416 private List<SnapshotDto> createSnapshots(ComponentDto project) {
417 ArrayList<SnapshotDto> list = new ArrayList<>();
418 list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setLast(false))); // 2008-11-11
419 list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setLast(false))); // 2008-11-12
420 list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setLast(false))); // 2008-11-20
421 list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227358680000L).setLast(false))); // 2008-11-22
422 list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setLast(true))); // 2008-11-29
426 private long milisSinceEpoch(int year, int month, int day, int hour) {
427 return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZoneId.systemDefault())
428 .toInstant().toEpochMilli();
431 private void setProjectPeriod(String projectUuid, NewCodePeriodType type, @Nullable String value) {
432 dbTester.newCodePeriods().insert(projectUuid, type, value);
435 private void setBranchPeriod(String projectUuid, String branchUuid, NewCodePeriodType type, @Nullable String value) {
436 dbTester.newCodePeriods().insert(projectUuid, branchUuid, type, value);
439 private void setGlobalPeriod(NewCodePeriodType type, @Nullable String value) {
440 dbTester.newCodePeriods().insert(type, value);
443 private void assertPeriod(NewCodePeriodType type, @Nullable String value, long snapshotDate) {
444 Period period = periodsHolder.getPeriod();
445 assertThat(period).isNotNull();
446 assertThat(period.getMode()).isEqualTo(type.name());
447 assertThat(period.getModeParameter()).isEqualTo(value);
448 assertThat(period.getSnapshotDate()).isEqualTo(snapshotDate);
451 private void verifyDebugLogs(String log, String... otherLogs) {
452 assertThat(logTester.getLogs()).hasSize(1 + otherLogs.length);
453 assertThat(logTester.getLogs(LoggerLevel.DEBUG))
454 .extracting(LogAndArguments::getFormattedMsg)
455 .containsOnly(Stream.concat(Stream.of(log), Arrays.stream(otherLogs)).toArray(String[]::new));
458 private void setupRoot(ComponentDto project) {
459 setupRoot(project, randomAlphanumeric(3));
462 private void setupRoot(ComponentDto projectDto, String version) {
463 treeRootHolder.setRoot(ReportComponent
464 .builder(Component.Type.PROJECT, 1)
465 .setUuid(projectDto.uuid())
466 .setKey(projectDto.getKey())
467 .setProjectVersion(version)
470 Project project = mock(Project.class);
471 when(project.getUuid()).thenReturn(projectDto.getMainBranchProjectUuid() != null ? projectDto.getMainBranchProjectUuid() : projectDto.uuid());
472 when(analysisMetadataHolder.getProject()).thenReturn(project);
475 private static void verifyInvalidValueMessage(MessageException e, String propertyValue) {
476 assertThat(e).hasMessage("Invalid new code period. '" + propertyValue
477 + "' is not one of: integer > 0, date before current analysis j, \"previous_version\", or version string that exists in the project' \n" +
478 "Please contact a project administrator to correct this setting");