*/
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;
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);
}
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);
}
}
@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");
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;
}
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);
}
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());
}
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() {
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));
}
}
- 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;
}
}
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));
}
}
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;
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()));
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");
}
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");
}
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");
}
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))
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");
}
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
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");
}
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");
}
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) {
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";
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!
*/
/**
* Return a date at the start of day.
+ *
* @param s string in format {@link #DATE_FORMAT}
* @throws SonarException when string cannot be parsed
*/
/**
* 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
/**
* Warning: may rely on default timezone!
- * @see #parseDateOrDateTime(String)
+ *
+ * @see #parseDateOrDateTime(String)
*/
@CheckForNull
public static Date parseStartingDateOrDateTime(@Nullable String stringDate) {
/**
* 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
* 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) {