Просмотр исходного кода

SONAR-11492 Second analysis of a long-lived branch is using wrong leak version

tags/7.6
Duarte Meneses 5 лет назад
Родитель
Сommit
862136c486

+ 30
- 39
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java Просмотреть файл

@@ -19,12 +19,13 @@
*/
package org.sonar.ce.task.projectanalysis.step;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
@@ -129,7 +130,7 @@ public class LoadPeriodsStep implements ComputationStep {
if (days != null) {
return resolveByDays(dbSession, projectUuid, days, propertyValue);
}
Date date = parseDate(propertyValue);
Instant date = parseDate(propertyValue);
if (date != null) {
return resolveByDate(dbSession, projectUuid, date, propertyValue);
}
@@ -141,12 +142,12 @@ public class LoadPeriodsStep implements ComputationStep {

String mostRecentVersion = Optional.ofNullable(versions.iterator().next().getName())
.orElseThrow(() -> new IllegalStateException("selectVersionsByMostRecentFirst returned a DTO which didn't have a name"));
if (versions.size() == 1) {
return resolveWhenOnlyOneExistingVersion(dbSession, projectUuid, mostRecentVersion, propertyValue);
}

boolean previousVersionPeriod = LEAK_PERIOD_MODE_PREVIOUS_VERSION.equals(propertyValue);
if (previousVersionPeriod) {
if (versions.size() == 1) {
return resolvePreviousVersionWithOnlyOneExistingVersion(dbSession, projectUuid);
}
return resolvePreviousVersion(dbSession, analysisProjectVersion, versions, mostRecentVersion);
}

@@ -154,10 +155,10 @@ public class LoadPeriodsStep implements ComputationStep {
}

@CheckForNull
private static Date parseDate(String propertyValue) {
private static Instant parseDate(String propertyValue) {
try {
LocalDate localDate = LocalDate.parse(propertyValue);
return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
return localDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
} catch (DateTimeParseException e) {
boolean invalidDate = e.getCause() == null || e.getCause() == e || !e.getCause().getMessage().contains("Invalid date");
checkPeriodProperty(invalidDate, propertyValue, "Invalid date");
@@ -171,24 +172,23 @@ public class LoadPeriodsStep implements ComputationStep {
List<SnapshotDto> snapshots = dbClient.snapshotDao().selectAnalysesByQuery(dbSession, createCommonQuery(projectUuid).setCreatedBefore(analysisDate).setSort(BY_DATE, ASC));
ensureNotOnFirstAnalysis(!snapshots.isEmpty());

long targetDate = DateUtils.addDays(new Date(analysisDate), -days).getTime();
LOG.debug("Resolving new code period by {} days: {}", days, async(() -> logDate(targetDate)));
Instant targetDate = DateUtils.addDays(Instant.ofEpochMilli(analysisDate), -days);
LOG.debug("Resolving new code period by {} days: {}", days, supplierToString(() -> logDate(targetDate)));
SnapshotDto snapshot = findNearestSnapshotToTargetDate(snapshots, targetDate);

return Optional.of(newPeriod(LEAK_PERIOD_MODE_DAYS, String.valueOf((int) days), snapshot));
}

private Optional<Period> resolveByDate(DbSession dbSession, String projectUuid, Date date, String propertyValue) {
long now = system2.now();
checkPeriodProperty(date.compareTo(new Date(now)) <= 0, propertyValue,
"date is in the future (now: '%s')", async(() -> logDate(now)));
private Optional<Period> resolveByDate(DbSession dbSession, String projectUuid, Instant date, String propertyValue) {
Instant now = Instant.ofEpochMilli(system2.now());
checkPeriodProperty(date.compareTo(now) <= 0, propertyValue,
"date is in the future (now: '%s')", supplierToString(() -> logDate(now)));

LOG.debug("Resolving new code period by date: {}", async(() -> logDate(date)));
Optional<Period> period = findFirstSnapshot(dbSession, createCommonQuery(projectUuid).setCreatedAfter(date.getTime()).setSort(BY_DATE, ASC))
LOG.debug("Resolving new code period by date: {}", supplierToString(() -> logDate(date)));
Optional<Period> period = findFirstSnapshot(dbSession, createCommonQuery(projectUuid).setCreatedAfter(date.toEpochMilli()).setSort(BY_DATE, ASC))
.map(dto -> newPeriod(LEAK_PERIOD_MODE_DATE, DateUtils.formatDate(date), dto));

checkPeriodProperty(period.isPresent(), propertyValue, "No analysis found created after date '%s'", async(() -> logDate(date)));

checkPeriodProperty(period.isPresent(), propertyValue, "No analysis found created after date '%s'", supplierToString(() -> logDate(date)));
return period;
}

@@ -205,15 +205,8 @@ public class LoadPeriodsStep implements ComputationStep {
return findOldestAnalysis(dbSession, periodMode, projectUuid);
}

private Optional<Period> resolveWhenOnlyOneExistingVersion(DbSession dbSession, String projectUuid, String mostRecentVersion, String propertyValue) {
boolean previousVersionPeriod = LEAK_PERIOD_MODE_PREVIOUS_VERSION.equals(propertyValue);
private Optional<Period> resolvePreviousVersionWithOnlyOneExistingVersion(DbSession dbSession, String projectUuid) {
LOG.debug("Resolving first analysis as new code period as there is only one existing version");

// only one existing version. Period must either be PREVIOUS_VERSION or the only valid version: the only existing one
checkPeriodProperty(previousVersionPeriod || propertyValue.equals(mostRecentVersion), propertyValue,
"Only one existing version, but period is neither %s nor this one version '%s' (actual: '%s')",
LEAK_PERIOD_MODE_PREVIOUS_VERSION, mostRecentVersion, propertyValue);

return findOldestAnalysis(dbSession, LEAK_PERIOD_MODE_PREVIOUS_VERSION, projectUuid);
}

@@ -234,7 +227,7 @@ public class LoadPeriodsStep implements ComputationStep {
LOG.debug("Resolving new code period by version: {}", propertyValue);
Optional<EventDto> version = versions.stream().filter(t -> propertyValue.equals(t.getName())).findFirst();
checkPeriodProperty(version.isPresent(), propertyValue,
"version is none of the existing ones: %s", async(() -> toVersions(versions)));
"version is none of the existing ones: %s", supplierToString(() -> toVersions(versions)));

return newPeriod(dbSession, LEAK_PERIOD_MODE_VERSION, version.get());
}
@@ -253,7 +246,7 @@ public class LoadPeriodsStep implements ComputationStep {
return Arrays.toString(versions.stream().map(EventDto::getName).toArray(String[]::new));
}

public static Object async(Supplier<String> s) {
private static Object supplierToString(Supplier<String> s) {
return new Object() {
@Override
public String toString() {
@@ -268,7 +261,7 @@ public class LoadPeriodsStep implements ComputationStep {

private static void checkPeriodProperty(boolean test, String propertyValue, String testDescription, Object... args) {
if (!test) {
LOG.debug("Invalid code period '{}': {}", propertyValue, async(() -> format(testDescription, args)));
LOG.debug("Invalid code period '{}': {}", propertyValue, supplierToString(() -> format(testDescription, args)));
throw MessageException.of(format("Invalid new code period. '%s' is not one of: " +
"integer > 0, date before current analysis j, \"previous_version\", or version string that exists in the project' \n" +
"Please contact a project administrator to correct this setting", propertyValue));
@@ -295,13 +288,15 @@ public class LoadPeriodsStep implements ComputationStep {
}
}

private static SnapshotDto findNearestSnapshotToTargetDate(List<SnapshotDto> snapshots, Long targetDate) {
long bestDistance = Long.MAX_VALUE;
private static SnapshotDto findNearestSnapshotToTargetDate(List<SnapshotDto> snapshots, Instant targetDate) {
// FIXME shouldn't this be the first analysis after targetDate?
Duration bestDuration = null;
SnapshotDto nearest = null;
for (SnapshotDto snapshot : snapshots) {
long distance = Math.abs(snapshot.getCreatedAt() - targetDate);
if (distance <= bestDistance) {
bestDistance = distance;
Instant createdAt = Instant.ofEpochMilli(snapshot.getCreatedAt());
Duration duration = Duration.between(targetDate, createdAt).abs();
if (bestDuration == null || duration.compareTo(bestDuration) <= 0) {
bestDuration = duration;
nearest = snapshot;
}
}
@@ -312,11 +307,7 @@ public class LoadPeriodsStep implements ComputationStep {
return new SnapshotQuery().setComponentUuid(projectUuid).setStatus(STATUS_PROCESSED);
}

private static String logDate(long date) {
return logDate(new Date(date));
}

private static String logDate(Date date1) {
return DateUtils.formatDate(Date.from(date1.toInstant().truncatedTo(ChronoUnit.SECONDS)));
private static String logDate(Instant instant) {
return DateUtils.formatDate(instant.truncatedTo(ChronoUnit.SECONDS));
}
}

+ 42
- 64
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java Просмотреть файл

@@ -28,6 +28,7 @@ import java.util.Arrays;
import java.util.Date;
import java.util.Random;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Before;
import org.junit.Rule;
@@ -138,19 +139,15 @@ public class LoadPeriodsStepTest extends BaseStepTest {
settings.setProperty("sonar.leak.period", textDate);
underTest.execute(new TestComputationStepContext());

Period period = periodsHolder.getPeriod();
assertThat(period).isNotNull();
assertThat(period.getMode()).isEqualTo(LEAK_PERIOD_MODE_DATE);
assertThat(period.getModeParameter()).isEqualTo(textDate);
assertThat(period.getSnapshotDate()).isEqualTo(analysis.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis.getUuid());
assertPeriod(LEAK_PERIOD_MODE_DATE, textDate, analysis.getCreatedAt(), analysis.getUuid());
}

@Test
public void ignore_unprocessed_snapshots() {
OrganizationDto organization = dbTester.organizations().insert();
ComponentDto project = dbTester.components().insertPrivateProject(organization);
SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setStatus(STATUS_UNPROCESSED).setCreatedAt(1226379600000L).setLast(false));// 2008-11-11
SnapshotDto analysis1 = dbTester.components()
.insertSnapshot(project, snapshot -> snapshot.setStatus(STATUS_UNPROCESSED).setCreatedAt(1226379600000L).setLast(false));// 2008-11-11
SnapshotDto analysis2 = dbTester.components().insertSnapshot(project,
snapshot -> snapshot.setStatus(STATUS_PROCESSED).setVersion("not provided").setCreatedAt(1226379600000L).setLast(false));// 2008-11-29
dbTester.events().insertEvent(newEvent(analysis1).setName("not provided").setCategory(CATEGORY_VERSION).setDate(analysis1.getCreatedAt()));
@@ -162,13 +159,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
settings.setProperty("sonar.leak.period", "100");
underTest.execute(new TestComputationStepContext());

Period period = periodsHolder.getPeriod();
assertThat(period).isNotNull();
assertThat(period.getMode()).isEqualTo(LEAK_PERIOD_MODE_DAYS);
assertThat(period.getModeParameter()).isEqualTo("100");
assertThat(period.getSnapshotDate()).isEqualTo(analysis2.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis2.getUuid());

assertPeriod(LEAK_PERIOD_MODE_DAYS, "100", analysis2.getCreatedAt(), analysis2.getUuid());
verifyDebugLogs("Resolving new code period by 100 days: 2008-08-22");
}

@@ -190,12 +181,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
underTest.execute(new TestComputationStepContext());

// Return analysis from given date 2008-11-22
Period period = periodsHolder.getPeriod();
assertThat(period).isNotNull();
assertThat(period.getMode()).isEqualTo(LEAK_PERIOD_MODE_DATE);
assertThat(period.getModeParameter()).isEqualTo(textDate);
assertThat(period.getSnapshotDate()).isEqualTo(analysis4.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis4.getUuid());
assertPeriod(LEAK_PERIOD_MODE_DATE, textDate, analysis4.getCreatedAt(), analysis4.getUuid());

verifyDebugLogs("Resolving new code period by date: 2008-11-22");
}
@@ -218,13 +204,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
underTest.execute(new TestComputationStepContext());

// Analysis form 2008-11-20
Period period = periodsHolder.getPeriod();
assertThat(period).isNotNull();
assertThat(period.getMode()).isEqualTo(LEAK_PERIOD_MODE_DATE);
assertThat(period.getModeParameter()).isEqualTo(date);
assertThat(period.getSnapshotDate()).isEqualTo(analysis3.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis3.getUuid());

assertPeriod(LEAK_PERIOD_MODE_DATE, date, analysis3.getCreatedAt(), analysis3.getUuid());
verifyDebugLogs("Resolving new code period by date: 2008-11-13");
}

@@ -415,12 +395,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
underTest.execute(new TestComputationStepContext());

// return analysis from 2008-11-20
Period period = periodsHolder.getPeriod();
assertThat(period).isNotNull();
assertThat(period.getMode()).isEqualTo(LEAK_PERIOD_MODE_DAYS);
assertThat(period.getModeParameter()).isEqualTo("10");
assertThat(period.getSnapshotDate()).isEqualTo(analysis3.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis3.getUuid());
assertPeriod(LEAK_PERIOD_MODE_DAYS, "10", analysis3.getCreatedAt(), analysis3.getUuid());

assertThat(logTester.getLogs()).hasSize(1);
assertThat(logTester.getLogs(LoggerLevel.DEBUG))
@@ -448,12 +423,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
underTest.execute(new TestComputationStepContext());

// Analysis form 2008-11-12
Period period = periodsHolder.getPeriod();
assertThat(period).isNotNull();
assertThat(period.getMode()).isEqualTo(LEAK_PERIOD_MODE_PREVIOUS_VERSION);
assertThat(period.getModeParameter()).isEqualTo("1.0");
assertThat(period.getSnapshotDate()).isEqualTo(analysis2.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis2.getUuid());
assertPeriod(LEAK_PERIOD_MODE_PREVIOUS_VERSION, "1.0", analysis2.getCreatedAt(), analysis2.getUuid());

verifyDebugLogs("Resolving new code period by previous version: 1.0");
}
@@ -476,12 +446,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
underTest.execute(new TestComputationStepContext());

// Analysis form 2008-11-11
Period period = periodsHolder.getPeriod();
assertThat(period).isNotNull();
assertThat(period.getMode()).isEqualTo(LEAK_PERIOD_MODE_PREVIOUS_VERSION);
assertThat(period.getModeParameter()).isEqualTo("0.9");
assertThat(period.getSnapshotDate()).isEqualTo(analysis1.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis1.getUuid());
assertPeriod(LEAK_PERIOD_MODE_PREVIOUS_VERSION, "0.9", analysis1.getCreatedAt(), analysis1.getUuid());
}

@Test
@@ -498,12 +463,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
settings.setProperty("sonar.leak.period", "previous_version");
underTest.execute(new TestComputationStepContext());

Period period = periodsHolder.getPeriod();
assertThat(period).isNotNull();
assertThat(period.getMode()).isEqualTo(LEAK_PERIOD_MODE_PREVIOUS_VERSION);
assertThat(period.getModeParameter()).isNull();
assertThat(period.getSnapshotDate()).isEqualTo(analysis1.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis1.getUuid());
assertPeriod(LEAK_PERIOD_MODE_PREVIOUS_VERSION, null, analysis1.getCreatedAt(), analysis1.getUuid());

verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
}
@@ -521,13 +481,7 @@ public class LoadPeriodsStepTest extends BaseStepTest {
settings.setProperty("sonar.leak.period", "previous_version");
underTest.execute(new TestComputationStepContext());

Period period = periodsHolder.getPeriod();
assertThat(period).isNotNull();
assertThat(period.getMode()).isEqualTo(LEAK_PERIOD_MODE_PREVIOUS_VERSION);
assertThat(period.getModeParameter()).isNull();
assertThat(period.getSnapshotDate()).isEqualTo(analysis.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis.getUuid());

assertPeriod(LEAK_PERIOD_MODE_PREVIOUS_VERSION, null, analysis.getCreatedAt(), analysis.getUuid());
verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
}

@@ -551,14 +505,38 @@ public class LoadPeriodsStepTest extends BaseStepTest {
underTest.execute(new TestComputationStepContext());

// Analysis form 2008-11-11
assertPeriod(LEAK_PERIOD_MODE_VERSION, "1.0", analysis2.getCreatedAt(), analysis2.getUuid());
verifyDebugLogs("Resolving new code period by version: 1.0");
}

/**
* SONAR-11492
*/
@Test
public void feed_period_by_version_with_only_one_existing_version() {
OrganizationDto organization = dbTester.organizations().insert();
ComponentDto project = dbTester.components().insertPrivateProject(organization);
SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setVersion("0.9").setLast(true)); // 2008-11-11
dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION));
when(system2Mock.now()).thenReturn(november30th2008.getTime());
when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project, "0.9");

settings.setProperty("sonar.leak.period", "0.9");
underTest.execute(new TestComputationStepContext());

// Analysis form 2008-11-11
assertPeriod(LEAK_PERIOD_MODE_VERSION, "0.9", analysis1.getCreatedAt(), analysis1.getUuid());
verifyDebugLogs("Resolving new code period by version: 0.9");
}

private void assertPeriod(String mode, @Nullable String modeParameter, long snapshotDate, String analysisUuid) {
Period period = periodsHolder.getPeriod();
assertThat(period).isNotNull();
assertThat(period.getMode()).isEqualTo(LEAK_PERIOD_MODE_VERSION);
assertThat(period.getModeParameter()).isEqualTo("1.0");
assertThat(period.getSnapshotDate()).isEqualTo(analysis2.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis2.getUuid());

verifyDebugLogs("Resolving new code period by version: 1.0");
assertThat(period.getMode()).isEqualTo(mode);
assertThat(period.getModeParameter()).isEqualTo(modeParameter);
assertThat(period.getSnapshotDate()).isEqualTo(snapshotDate);
assertThat(period.getAnalysisUuid()).isEqualTo(analysisUuid);
}

private void verifyDebugLogs(String log, String... otherLogs) {

+ 0
- 1
sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java Просмотреть файл

@@ -33,7 +33,6 @@ import static org.sonar.api.PropertyType.BOOLEAN;
public class CorePropertyDefinitions {

public static final String LEAK_PERIOD = "sonar.leak.period";
public static final String LEAK_PERIOD_MODE_PREVIOUS_ANALYSIS = "previous_analysis";
public static final String LEAK_PERIOD_MODE_DATE = "date";
public static final String LEAK_PERIOD_MODE_VERSION = "version";
public static final String LEAK_PERIOD_MODE_DAYS = "days";

+ 26
- 6
sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java Просмотреть файл

@@ -54,6 +54,15 @@ public final class DateUtils {
return d.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().toString();
}

/**
* Warning: relies on default timezone!
*
* @since 7.6
*/
public static String formatDate(Instant d) {
return d.atZone(ZoneId.systemDefault()).toLocalDate().toString();
}

/**
* Warning: relies on default timezone!
*/
@@ -94,6 +103,7 @@ public final class DateUtils {

/**
* Return a date at the start of day.
*
* @param s string in format {@link #DATE_FORMAT}
* @throws SonarException when string cannot be parsed
*/
@@ -218,8 +228,9 @@ public final class DateUtils {

/**
* Warning: may rely on default timezone!
* @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime
*
* @return the datetime, {@code null} if stringDate is null
* @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime
* @since 6.1
*/
@CheckForNull
@@ -241,7 +252,8 @@ public final class DateUtils {

/**
* Warning: may rely on default timezone!
* @see #parseDateOrDateTime(String)
*
* @see #parseDateOrDateTime(String)
*/
@CheckForNull
public static Date parseStartingDateOrDateTime(@Nullable String stringDate) {
@@ -251,9 +263,10 @@ public final class DateUtils {
/**
* Return the datetime if @param stringDate is a datetime, date + 1 day if stringDate is a date.
* So '2016-09-01' would return a date equivalent to '2016-09-02T00:00:00+0000' in GMT (Warning: relies on default timezone!)
* @see #parseDateOrDateTime(String)
* @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime
*
* @return the datetime, {@code null} if stringDate is null
* @throws IllegalArgumentException if stringDate is not a correctly formed date or datetime
* @see #parseDateOrDateTime(String)
* @since 6.1
*/
@CheckForNull
@@ -277,14 +290,21 @@ public final class DateUtils {
* Adds a number of days to a date returning a new object.
* The original date object is unchanged.
*
* @param date the date, not null
* @param numberOfDays the amount to add, may be negative
* @param date the date, not null
* @param numberOfDays the amount to add, may be negative
* @return the new date object with the amount added
*/
public static Date addDays(Date date, int numberOfDays) {
return Date.from(date.toInstant().plus(numberOfDays, ChronoUnit.DAYS));
}

/**
* @since 7.6
*/
public static Instant addDays(Instant instant, int numberOfDays) {
return instant.plus(numberOfDays, ChronoUnit.DAYS);
}

@CheckForNull
public static Date truncateToSeconds(@Nullable Date d) {
if (d == null) {

Загрузка…
Отмена
Сохранить