*/
package org.sonar.ce.task.projectanalysis.step;
+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;
import javax.annotation.CheckForNull;
-import org.sonar.api.config.Configuration;
+import javax.annotation.Nullable;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ComponentVisitor;
import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository;
-import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
-import org.sonar.ce.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
-import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
import org.sonar.ce.task.projectanalysis.period.Period;
import org.sonar.ce.task.projectanalysis.period.PeriodHolder;
import org.sonar.ce.task.projectanalysis.period.PeriodHolderImpl;
import org.sonar.ce.task.step.ComputationStep;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.component.SnapshotQuery;
+import org.sonar.db.event.EventDto;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD;
+import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_DATE;
+import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_DAYS;
+import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_PREVIOUS_VERSION;
+import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_VERSION;
+import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
+import static org.sonar.db.component.SnapshotQuery.SORT_FIELD.BY_DATE;
+import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.ASC;
/**
* Populates the {@link PeriodHolder}
* Here is how these periods are computed :
* - Read the period property ${@link org.sonar.core.config.CorePropertyDefinitions#LEAK_PERIOD}
* - Try to find the matching snapshots from the property
- * - If a snapshot is found, a period is set to the repository
+ * - If a snapshot is found, a period is set to the repository, otherwise fail with MessageException
*/
public class LoadPeriodsStep implements ComputationStep {
+ private static final Logger LOG = Loggers.get(LoadPeriodsStep.class);
private final AnalysisMetadataHolder analysisMetadataHolder;
- private final DbClient dbClient;
- private final ConfigurationRepository configRepository;
private final TreeRootHolder treeRootHolder;
private final PeriodHolderImpl periodsHolder;
+ private final System2 system2;
+ private final DbClient dbClient;
+ private final ConfigurationRepository configRepository;
- public LoadPeriodsStep(AnalysisMetadataHolder analysisMetadataHolder, DbClient dbClient,
- ConfigurationRepository configurationRepository, TreeRootHolder treeRootHolder, PeriodHolderImpl periodsHolder) {
+ public LoadPeriodsStep(AnalysisMetadataHolder analysisMetadataHolder, TreeRootHolder treeRootHolder, PeriodHolderImpl periodsHolder,
+ System2 system2, DbClient dbClient, ConfigurationRepository configRepository) {
this.analysisMetadataHolder = analysisMetadataHolder;
- this.dbClient = dbClient;
- this.configRepository = configurationRepository;
this.treeRootHolder = treeRootHolder;
this.periodsHolder = periodsHolder;
+ this.system2 = system2;
+ this.dbClient = dbClient;
+ this.configRepository = configRepository;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Load new code period";
}
@Override
public void execute(ComputationStep.Context context) {
if (analysisMetadataHolder.isFirstAnalysis()) {
+ periodsHolder.setPeriod(null);
return;
}
- new DepthTraversalTypeAwareCrawler(
- new TypeAwareVisitorAdapter(CrawlerDepthLimit.PROJECT, ComponentVisitor.Order.PRE_ORDER) {
- @Override
- public void visitProject(Component project) {
- execute(project);
- }
- }).visit(treeRootHolder.getRoot());
+ periodsHolder.setPeriod(resolvePeriod(treeRootHolder.getRoot()).orElse(null));
+ }
+
+ private Optional<Period> resolvePeriod(Component projectOrView) {
+ String currentVersion = projectOrView.getProjectAttributes().getVersion();
+ Optional<String> propertyValue = configRepository.getConfiguration().get(LEAK_PERIOD)
+ .filter(t -> !t.isEmpty());
+ checkPeriodProperty(propertyValue.isPresent(), "", "property is undefined or value is empty");
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ return resolve(dbSession, projectOrView.getUuid(), currentVersion, propertyValue.get());
+ }
}
- public void execute(Component projectOrView) {
+ public Optional<Period> resolve(String projectUuid, String analysisProjectVersion) {
+ Optional<String> propertyValue = configRepository.getConfiguration().get(LEAK_PERIOD)
+ .filter(t -> !t.isEmpty());
+ checkPeriodProperty(propertyValue.isPresent(), "", "property is undefined or value is empty");
+
try (DbSession dbSession = dbClient.openSession(false)) {
- periodsHolder.setPeriod(buildPeriod(projectOrView, dbSession));
+ return resolve(dbSession, projectUuid, analysisProjectVersion, propertyValue.get());
+ }
+ }
+
+ private Optional<Period> resolve(DbSession dbSession, String projectUuid, String analysisProjectVersion, String propertyValue) {
+ Integer days = parseDaysQuietly(propertyValue);
+ if (days != null) {
+ return resolveByDays(dbSession, projectUuid, days, propertyValue);
}
+ Date date = parseDate(propertyValue);
+ if (date != null) {
+ return resolveByDate(dbSession, projectUuid, date, propertyValue);
+ }
+
+ List<EventDto> versions = dbClient.eventDao().selectVersionsByMostRecentFirst(dbSession, projectUuid);
+ if (versions.isEmpty()) {
+ return resolveWhenNoExistingVersion(dbSession, projectUuid, analysisProjectVersion, 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) {
+ return resolvePreviousVersion(dbSession, analysisProjectVersion, versions, mostRecentVersion);
+ }
+
+ return resolveVersion(dbSession, versions, propertyValue);
}
@CheckForNull
- private Period buildPeriod(Component projectOrView, DbSession session) {
- PeriodResolver periodResolver = new PeriodResolver(dbClient, session, projectOrView.getUuid(), analysisMetadataHolder.getAnalysisDate(),
- projectOrView.getProjectAttributes().getVersion());
-
- Configuration config = configRepository.getConfiguration();
- Period period = periodResolver.resolve(config);
- // SONAR-4700 Add a past snapshot only if it exists
- if (period != null) {
- return period;
+ private static Date parseDate(String propertyValue) {
+ try {
+ LocalDate localDate = LocalDate.parse(propertyValue);
+ return Date.from(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");
+ return null;
}
- return null;
}
- @Override
- public String getDescription() {
- return "Load new code period";
+ private Optional<Period> resolveByDays(DbSession dbSession, String projectUuid, Integer days, String propertyValue) {
+ checkPeriodProperty(days > 0, propertyValue, "number of days is <= 0");
+ long analysisDate = analysisMetadataHolder.getAnalysisDate();
+ 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)));
+ 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)));
+
+ 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))
+ .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)));
+
+ return period;
+ }
+
+ private Optional<Period> resolveWhenNoExistingVersion(DbSession dbSession, String projectUuid, String currentVersion, String propertyValue) {
+ LOG.debug("Resolving first analysis as new code period as there is no existing version");
+
+ boolean previousVersionPeriod = LEAK_PERIOD_MODE_PREVIOUS_VERSION.equals(propertyValue);
+ boolean currentVersionPeriod = currentVersion.equals(propertyValue);
+ checkPeriodProperty(previousVersionPeriod || currentVersionPeriod, propertyValue,
+ "No existing version. Property should be either '%s' or the current version '%s' (actual: '%s')",
+ LEAK_PERIOD_MODE_PREVIOUS_VERSION, currentVersion, propertyValue);
+
+ String periodMode = previousVersionPeriod ? LEAK_PERIOD_MODE_PREVIOUS_VERSION : LEAK_PERIOD_MODE_VERSION;
+ 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);
+ 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);
+ }
+
+ private Optional<Period> findOldestAnalysis(DbSession dbSession, String periodMode, String projectUuid) {
+ Optional<Period> period = dbClient.snapshotDao().selectOldestSnapshot(dbSession, projectUuid)
+ .map(dto -> newPeriod(periodMode, null, dto));
+ ensureNotOnFirstAnalysis(period.isPresent());
+ return period;
+ }
+
+ private Optional<Period> resolvePreviousVersion(DbSession dbSession, String currentVersion, List<EventDto> versions, String mostRecentVersion) {
+ EventDto previousVersion = versions.get(currentVersion.equals(mostRecentVersion) ? 1 : 0);
+ LOG.debug("Resolving new code period by previous version: {}", previousVersion.getName());
+ return newPeriod(dbSession, LEAK_PERIOD_MODE_PREVIOUS_VERSION, previousVersion);
+ }
+
+ private Optional<Period> resolveVersion(DbSession dbSession, List<EventDto> versions, String propertyValue) {
+ 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)));
+
+ return newPeriod(dbSession, LEAK_PERIOD_MODE_VERSION, version.get());
+ }
+
+ private Optional<Period> newPeriod(DbSession dbSession, String periodMode, EventDto previousVersion) {
+ Optional<Period> period = dbClient.snapshotDao().selectByUuid(dbSession, previousVersion.getAnalysisUuid())
+ .map(dto -> newPeriod(periodMode, previousVersion.getName(), dto));
+ if (!period.isPresent()) {
+ throw new IllegalStateException(format("Analysis '%s' for version event '%s' has been deleted",
+ previousVersion.getAnalysisUuid(), previousVersion.getName()));
+ }
+ return period;
+ }
+
+ private static String toVersions(List<EventDto> versions) {
+ return Arrays.toString(versions.stream().map(EventDto::getName).toArray(String[]::new));
+ }
+
+ public static Object async(Supplier<String> s) {
+ return new Object() {
+ @Override
+ public String toString() {
+ return s.get();
+ }
+ };
+ }
+
+ private static Period newPeriod(String mode, @Nullable String modeParameter, SnapshotDto dto) {
+ return new Period(mode, modeParameter, dto.getCreatedAt(), dto.getUuid());
+ }
+
+ private static void checkPeriodProperty(boolean test, String propertyValue, String testDescription, Object... args) {
+ if (!test) {
+ LOG.debug("Invalid code period '{}': {}", propertyValue, async(() -> 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 Optional<SnapshotDto> findFirstSnapshot(DbSession session, SnapshotQuery query) {
+ return dbClient.snapshotDao().selectAnalysesByQuery(session, query)
+ .stream()
+ .findFirst();
+ }
+
+ private static void ensureNotOnFirstAnalysis(boolean expression) {
+ checkState(expression, "Attempting to resolve period while no analysis exist for project");
+ }
+
+ @CheckForNull
+ private static Integer parseDaysQuietly(String property) {
+ try {
+ return Integer.parseInt(property);
+ } catch (NumberFormatException e) {
+ // Nothing to, it means that the property is not a number of days
+ return null;
+ }
+ }
+
+ private static SnapshotDto findNearestSnapshotToTargetDate(List<SnapshotDto> snapshots, Long targetDate) {
+ long bestDistance = Long.MAX_VALUE;
+ SnapshotDto nearest = null;
+ for (SnapshotDto snapshot : snapshots) {
+ long distance = Math.abs(snapshot.getCreatedAt() - targetDate);
+ if (distance <= bestDistance) {
+ bestDistance = distance;
+ nearest = snapshot;
+ }
+ }
+ return nearest;
+ }
+
+ private static SnapshotQuery createCommonQuery(String projectUuid) {
+ 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)));
}
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info 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.ce.task.projectanalysis.step;
-
-import java.time.temporal.ChronoUnit;
-import java.util.Date;
-import java.util.List;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.ce.task.projectanalysis.period.Period;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.component.SnapshotQuery;
-import org.sonar.ce.task.projectanalysis.period.Period;
-
-import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD;
-import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_DATE;
-import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_DAYS;
-import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_PREVIOUS_VERSION;
-import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_VERSION;
-import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
-import static org.sonar.db.component.SnapshotQuery.SORT_FIELD.BY_DATE;
-import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.ASC;
-import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.DESC;
-
-public class PeriodResolver {
- private static final Logger LOG = Loggers.get(PeriodResolver.class);
-
- private final DbClient dbClient;
- private final DbSession session;
- private final String projectUuid;
- private final long analysisDate;
- @CheckForNull
- private final String currentVersion;
-
- public PeriodResolver(DbClient dbClient, DbSession session, String projectUuid, long analysisDate, @Nullable String currentVersion) {
- this.dbClient = dbClient;
- this.session = session;
- this.projectUuid = projectUuid;
- this.analysisDate = analysisDate;
- this.currentVersion = currentVersion;
- }
-
- @CheckForNull
- public Period resolve(Configuration config) {
- String propertyValue = getPropertyValue(config);
- if (StringUtils.isBlank(propertyValue)) {
- return null;
- }
- Period period = resolve(propertyValue);
- if (period == null && StringUtils.isNotBlank(propertyValue)) {
- LOG.debug("Property " + LEAK_PERIOD + " is not valid: " + propertyValue);
- }
- return period;
- }
-
- @CheckForNull
- private Period resolve(String property) {
- Integer days = tryToResolveByDays(property);
- if (days != null) {
- return findByDays(days);
- }
- Date date = DateUtils.parseDateQuietly(property);
- if (date != null) {
- return findByDate(date);
- }
- if (StringUtils.equals(LEAK_PERIOD_MODE_PREVIOUS_VERSION, property)) {
- return findByPreviousVersion();
- }
- return findByVersion(property);
- }
-
- private Period findByDate(Date date) {
- SnapshotDto snapshot = findFirstSnapshot(session, createCommonQuery(projectUuid).setCreatedAfter(date.getTime()).setSort(BY_DATE, ASC));
- if (snapshot == null) {
- return null;
- }
- LOG.debug("Compare to date {} (analysis of {})", formatDate(date.getTime()), formatDate(snapshot.getCreatedAt()));
- return new Period(LEAK_PERIOD_MODE_DATE, DateUtils.formatDate(date), snapshot.getCreatedAt(), snapshot.getUuid());
- }
-
- @CheckForNull
- private Period findByDays(int days) {
- List<SnapshotDto> snapshots = dbClient.snapshotDao().selectAnalysesByQuery(session, createCommonQuery(projectUuid).setCreatedBefore(analysisDate).setSort(BY_DATE, ASC));
- long targetDate = DateUtils.addDays(new Date(analysisDate), -days).getTime();
- SnapshotDto snapshot = findNearestSnapshotToTargetDate(snapshots, targetDate);
- if (snapshot == null) {
- return null;
- }
- LOG.debug("Compare over {} days ({}, analysis of {})", String.valueOf(days), formatDate(targetDate), formatDate(snapshot.getCreatedAt()));
- return new Period(LEAK_PERIOD_MODE_DAYS, String.valueOf(days), snapshot.getCreatedAt(), snapshot.getUuid());
- }
-
- @CheckForNull
- private Period findByPreviousVersion() {
- if (currentVersion == null) {
- return null;
- }
- List<SnapshotDto> snapshotDtos = dbClient.snapshotDao().selectPreviousVersionSnapshots(session, projectUuid, currentVersion);
- if (snapshotDtos.isEmpty()) {
- // If no previous version is found, the first analysis is returned
- return findByFirstAnalysis();
- }
- SnapshotDto snapshotDto = snapshotDtos.get(0);
- LOG.debug("Compare to previous version ({})", formatDate(snapshotDto.getCreatedAt()));
- return new Period(LEAK_PERIOD_MODE_PREVIOUS_VERSION, snapshotDto.getVersion(), snapshotDto.getCreatedAt(), snapshotDto.getUuid());
- }
-
- @CheckForNull
- private Period findByFirstAnalysis() {
- SnapshotDto snapshotDto = dbClient.snapshotDao().selectOldestSnapshot(session, projectUuid);
- if (snapshotDto == null) {
- return null;
- }
- LOG.debug("Compare to first analysis ({})", formatDate(snapshotDto.getCreatedAt()));
- return new Period(LEAK_PERIOD_MODE_PREVIOUS_VERSION, null, snapshotDto.getCreatedAt(), snapshotDto.getUuid());
- }
-
- @CheckForNull
- private Period findByVersion(String version) {
- SnapshotDto snapshot = findFirstSnapshot(session, createCommonQuery(projectUuid).setVersion(version).setSort(BY_DATE, DESC));
- if (snapshot == null) {
- return null;
- }
- LOG.debug("Compare to version ({}) ({})", version, formatDate(snapshot.getCreatedAt()));
- return new Period(LEAK_PERIOD_MODE_VERSION, version, snapshot.getCreatedAt(), snapshot.getUuid());
- }
-
- @CheckForNull
- private SnapshotDto findFirstSnapshot(DbSession session, SnapshotQuery query) {
- List<SnapshotDto> snapshots = dbClient.snapshotDao().selectAnalysesByQuery(session, query);
- if (!snapshots.isEmpty()) {
- return snapshots.get(0);
- }
- return null;
- }
-
- @CheckForNull
- private static Integer tryToResolveByDays(String property) {
- try {
- return Integer.parseInt(property);
- } catch (NumberFormatException e) {
- // Nothing to, it means that the property is not a number of days
- return null;
- }
- }
-
- @CheckForNull
- private static SnapshotDto findNearestSnapshotToTargetDate(List<SnapshotDto> snapshots, Long targetDate) {
- long bestDistance = Long.MAX_VALUE;
- SnapshotDto nearest = null;
- for (SnapshotDto snapshot : snapshots) {
- long distance = Math.abs(snapshot.getCreatedAt() - targetDate);
- if (distance <= bestDistance) {
- bestDistance = distance;
- nearest = snapshot;
- }
- }
- return nearest;
- }
-
- private static SnapshotQuery createCommonQuery(String projectUuid) {
- return new SnapshotQuery().setComponentUuid(projectUuid).setStatus(STATUS_PROCESSED);
- }
-
- private static String formatDate(long date) {
- return DateUtils.formatDate(Date.from(new Date(date).toInstant().truncatedTo(ChronoUnit.SECONDS)));
- }
-
- private static String getPropertyValue(Configuration config) {
- return config.get(LEAK_PERIOD).get();
- }
-}
*/
package org.sonar.ce.task.projectanalysis.step;
+import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Random;
+import java.util.stream.Stream;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogAndArguments;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.ce.task.projectanalysis.analysis.Analysis;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository;
import org.sonar.ce.task.projectanalysis.component.ReportComponent;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.organization.OrganizationDto;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_DATE;
import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_DAYS;
import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_PREVIOUS_VERSION;
import static org.sonar.core.config.CorePropertyDefinitions.LEAK_PERIOD_MODE_VERSION;
+import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
import static org.sonar.db.event.EventDto.CATEGORY_VERSION;
import static org.sonar.db.event.EventTesting.newEvent;
@Rule
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
@Rule
- public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
- @Rule
public LogTester logTester = new LogTester();
@Rule
public ExpectedException expectedException = ExpectedException.none();
+ private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
private PeriodHolderImpl periodsHolder = new PeriodHolderImpl();
- private DbClient dbClient = dbTester.getDbClient();
private MapSettings settings = new MapSettings();
- private ConfigurationRepository settingsRepository = mock(ConfigurationRepository.class);
+ private ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class);
+ private System2 system2Mock = mock(System2.class);
+
+ private LoadPeriodsStep underTest = new LoadPeriodsStep(analysisMetadataHolder, treeRootHolder, periodsHolder, system2Mock,
+ dbTester.getDbClient(), configurationRepository);
- private LoadPeriodsStep underTest = new LoadPeriodsStep(analysisMetadataHolder, dbClient, settingsRepository, treeRootHolder, periodsHolder);
+ private Date november30th2008;
@Override
protected ComputationStep step() {
@Before
public void setUp() throws Exception {
- analysisMetadataHolder.setAnalysisDate(DATE_FORMAT.parse("2008-11-30").getTime());
- analysisMetadataHolder.setBaseAnalysis(new Analysis.Builder().setUuid(RandomStringUtils.randomAlphabetic(15)).setId(888).setCreatedAt(5_666_777_888L).build());
+ november30th2008 = DATE_FORMAT.parse("2008-11-30");
}
@Test
- public void no_period_on_first_analysis() {
- DbClient dbClientMock = mock(DbClient.class);
- ConfigurationRepository configurationRepositoryMock = mock(ConfigurationRepository.class);
+ public void no_period_on_first_analysis_and_execute_has_no_effect() {
TreeRootHolder treeRootHolderMock = mock(TreeRootHolder.class);
PeriodHolderImpl periodHolderMock = mock(PeriodHolderImpl.class);
- AnalysisMetadataHolder analysisMetadataHolderMock = mock(AnalysisMetadataHolder.class);
- when(analysisMetadataHolderMock.isFirstAnalysis()).thenReturn(true);
+ DbClient dbClientMock = mock(DbClient.class);
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true);
- LoadPeriodsStep underTest = new LoadPeriodsStep(analysisMetadataHolderMock, dbClientMock, configurationRepositoryMock, treeRootHolderMock, periodHolderMock);
+ LoadPeriodsStep underTest = new LoadPeriodsStep(analysisMetadataHolder, treeRootHolderMock, periodHolderMock, system2Mock, dbClientMock, configurationRepository);
underTest.execute(new TestComputationStepContext());
- verify(analysisMetadataHolderMock).isFirstAnalysis();
- verifyZeroInteractions(dbClientMock, configurationRepositoryMock, treeRootHolderMock, periodHolderMock);
+ verify(analysisMetadataHolder).isFirstAnalysis();
+ verify(periodHolderMock).setPeriod(null);
+ verifyNoMoreInteractions(analysisMetadataHolder, periodHolderMock);
+ verifyZeroInteractions(treeRootHolderMock, system2Mock, dbClientMock, configurationRepository);
}
@Test
OrganizationDto organization = dbTester.organizations().insert();
ComponentDto project = dbTester.components().insertPrivateProject(organization);
SnapshotDto analysis = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L)); // 2008-11-29
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project);
String textDate = "2008-11-22";
assertThat(period.getAnalysisUuid()).isEqualTo(analysis.getUuid());
}
- @Test
- public void no_period_when_settings_match_no_analysis() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPrivateProject(organization);
- dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L)); // 2008-11-29
- setupRoot(project);
-
- settings.setProperty("sonar.leak.period", "UNKNWOWN VERSION");
- underTest.execute(new TestComputationStepContext());
-
- assertThat(periodsHolder.hasPeriod()).isFalse();
- }
-
- @Test
- public void no_period_when_settings_is_empty() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPrivateProject(organization);
- dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L)); // 2008-11-29
- setupRoot(project);
-
- settings.setProperty("sonar.leak.period", "");
- underTest.execute(new TestComputationStepContext());
-
- assertThat(periodsHolder.hasPeriod()).isFalse();
- }
-
@Test
public void ignore_unprocessed_snapshots() {
OrganizationDto organization = dbTester.organizations().insert();
ComponentDto project = dbTester.components().insertPrivateProject(organization);
- dbTester.components().insertSnapshot(project, snapshot -> snapshot.setStatus(STATUS_UNPROCESSED).setCreatedAt(1226379600000L)); // 2008-11-29
+ 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()));
+ dbTester.events().insertEvent(newEvent(analysis2).setName("not provided").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
+ when(analysisMetadataHolder.getAnalysisDate()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project);
settings.setProperty("sonar.leak.period", "100");
underTest.execute(new TestComputationStepContext());
- assertThat(periodsHolder.hasPeriod()).isFalse();
+ 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());
+
+ verifyDebugLogs("Resolving new code period by 100 days: 2008-08-22");
}
@Test
SnapshotDto analysis3 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setLast(false)); // 2008-11-20
SnapshotDto analysis4 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227358680000L).setLast(false)); // 2008-11-22
SnapshotDto analysis5 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setLast(true)); // 2008-11-29
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project);
String textDate = "2008-11-22";
assertThat(period.getSnapshotDate()).isEqualTo(analysis4.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis4.getUuid());
- assertThat(logTester.logs()).hasSize(1);
- assertThat(logTester.logs().get(0)).startsWith("Compare to date 2008-11-22 (analysis of ");
+ verifyDebugLogs("Resolving new code period by date: 2008-11-22");
}
@Test
SnapshotDto analysis3 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setLast(false)); // 2008-11-20
SnapshotDto analysis4 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227358680000L).setLast(false)); // 2008-11-22
SnapshotDto analysis5 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setLast(true)); // 2008-11-29
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project);
String date = "2008-11-13";
assertThat(period.getModeParameter()).isEqualTo(date);
assertThat(period.getSnapshotDate()).isEqualTo(analysis3.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis3.getUuid());
+
+ verifyDebugLogs("Resolving new code period by date: 2008-11-13");
}
@Test
- public void no_period_by_date() {
+ public void fail_with_MessageException_if_period_is_date_after_today() {
OrganizationDto organization = dbTester.organizations().insert();
ComponentDto project = dbTester.components().insertPrivateProject(organization);
dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L)); // 2008-11-29
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project);
+ String propertyValue = "2008-12-01";
+ settings.setProperty("sonar.leak.period", propertyValue);
+
+ verifyFailWithInvalidValueMessageException(propertyValue,
+ "Invalid code period '2008-12-01': date is in the future (now: '2008-11-30')");
+ }
+
+ @Test
+ public void fail_with_MessageException_if_date_does_not_exist() {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentDto project = dbTester.components().insertPrivateProject(organization);
+ dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L)); // 2008-11-29
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
+ setupRoot(project);
+ String propertyValue = "2008-11-31";
+ settings.setProperty("sonar.leak.period", propertyValue);
+
+ verifyFailWithInvalidValueMessageException(propertyValue,
+ "Invalid code period '2008-11-31': Invalid date");
+ }
+
+ @Test
+ public void fail_with_MessageException_if_period_is_today_but_no_analysis_today() {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentDto project = dbTester.components().insertPrivateProject(organization);
+ dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L)); // 2008-11-29
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
+ setupRoot(project);
+ String propertyValue = "2008-11-30";
+ settings.setProperty("sonar.leak.period", propertyValue);
+
+ verifyFailWithInvalidValueMessageException(propertyValue,
+ "Resolving new code period by date: 2008-11-30",
+ "Invalid code period '2008-11-30': No analysis found created after date '2008-11-30'");
+ }
+
+ @Test
+ @UseDataProvider("zeroOrLess")
+ public void fail_with_MessageException_if_period_is_0_or_less(int zeroOrLess) {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentDto project = dbTester.components().insertPrivateProject(organization);
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
+ setupRoot(project);
+ String propertyValue = String.valueOf(zeroOrLess);
+ settings.setProperty("sonar.leak.period", propertyValue);
+
+ verifyFailWithInvalidValueMessageException(propertyValue,
+ "Invalid code period '" + zeroOrLess + "': number of days is <= 0");
+ }
+
+ @DataProvider
+ public static Object[][] zeroOrLess() {
+ return new Object[][] {
+ {0},
+ {-1 - new Random().nextInt(30)}
+ };
+ }
+
+ @Test
+ @UseDataProvider("previousVersionOrVersion")
+ public void fail_with_ISE_if_not_firstAnalysis_but_no_snapshot_in_DB(String propertyValue) {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentDto project = dbTester.components().insertPrivateProject(organization);
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
+ setupRoot(project);
+ settings.setProperty("sonar.leak.period", "previous_version");
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Attempting to resolve period while no analysis exist");
- // No analysis at and after this date
- settings.setProperty("sonar.leak.period", "2008-11-30");
underTest.execute(new TestComputationStepContext());
+ }
+
+ @DataProvider
+ public static Object[][] previousVersionOrVersion() {
+ return new Object[][] {
+ {"previous_version"},
+ {randomAlphabetic(3)}
+ };
+ }
- assertThat(periodsHolder.hasPeriod()).isFalse();
+ @Test
+ @UseDataProvider("stringConsideredAsVersions")
+ public void fail_with_MessageException_if_string_is_not_an_existing_version_event(String propertyValue) {
+ 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(false)); // 2008-11-11
+ SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setVersion("1.0").setLast(false)); // 2008-11-12
+ SnapshotDto analysis3 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setVersion("1.1").setLast(false)); // 2008-11-20
+ SnapshotDto analysis4 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227358680000L).setVersion("1.1").setLast(false)); // 2008-11-22
+ SnapshotDto analysis5 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setVersion("1.1").setLast(true)); // 2008-11-29
+ dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION).setDate(analysis1.getCreatedAt()));
+ dbTester.events().insertEvent(newEvent(analysis2).setName("1.0").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
+ dbTester.events().insertEvent(newEvent(analysis5).setName("1.1").setCategory(CATEGORY_VERSION).setDate(analysis4.getCreatedAt()));
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
+ setupRoot(project);
+ settings.setProperty("sonar.leak.period", propertyValue);
+
+ try {
+ underTest.execute(new TestComputationStepContext());
+ fail("a Message Exception should have been thrown");
+ } catch (MessageException e) {
+ verifyInvalidValueMessage(e, propertyValue);
+ assertThat(logTester.getLogs()).hasSize(2);
+ assertThat(logTester.getLogs(LoggerLevel.DEBUG))
+ .hasSize(2)
+ .extracting(LogAndArguments::getFormattedMsg)
+ .contains("Invalid code period '" + propertyValue + "': version is none of the existing ones: [1.1, 1.0, 0.9]");
+ }
+ }
+
+ @DataProvider
+ public static Object[][] stringConsideredAsVersions() {
+ return new Object[][] {
+ {RandomStringUtils.randomAlphabetic(5)},
+ {"1,3"},
+ {"1.3"},
+ {"0 1"},
+ {"1-SNAPSHOT"},
+ {"01-12-2018"}, // unsupported date format
+ };
}
@Test
- public void feed_period_by_days() {
+ public void fail_with_MessageException_if_property_does_not_exist() {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentDto project = dbTester.components().insertPrivateProject(organization);
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
+ setupRoot(project);
+
+ verifyFailWithInvalidValueMessageException("", "Invalid code period '': property is undefined or value is empty");
+ }
+
+ @Test
+ public void fail_with_MessageException_if_property_is_empty() {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentDto project = dbTester.components().insertPrivateProject(organization);
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
+ setupRoot(project);
+ String propertyValue = "";
+ settings.setProperty("sonar.leak.period", propertyValue);
+
+ verifyFailWithInvalidValueMessageException(propertyValue, "Invalid code period '': property is undefined or value is empty");
+ }
+
+ private void verifyFailWithInvalidValueMessageException(String propertyValue, String debugLog, String... otherDebugLogs) {
+ try {
+ underTest.execute(new TestComputationStepContext());
+ fail("a Message Exception should have been thrown");
+ } catch (MessageException e) {
+ verifyInvalidValueMessage(e, propertyValue);
+ verifyDebugLogs(debugLog, otherDebugLogs);
+ }
+ }
+
+ @Test
+ public void feed_period_by_days() throws ParseException {
OrganizationDto organization = dbTester.organizations().insert();
ComponentDto project = dbTester.components().insertPrivateProject(organization);
SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setLast(false)); // 2008-11-11
SnapshotDto analysis3 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setLast(false)); // 2008-11-20
SnapshotDto analysis4 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227358680000L).setLast(false)); // 2008-11-22
SnapshotDto analysis5 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setLast(true)); // 2008-11-29
+ when(analysisMetadataHolder.getAnalysisDate()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project);
settings.setProperty("sonar.leak.period", "10");
assertThat(period.getSnapshotDate()).isEqualTo(analysis3.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis3.getUuid());
- assertThat(logTester.logs()).hasSize(1);
- assertThat(logTester.logs().get(0)).startsWith("Compare over 10 days (2008-11-20, analysis of ");
- }
-
- @Test
- public void no_period_by_days() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPrivateProject(organization);
- setupRoot(project);
-
- settings.setProperty("sonar.leak.period", "0");
- underTest.execute(new TestComputationStepContext());
-
- assertThat(periodsHolder.hasPeriod()).isFalse();
+ assertThat(logTester.getLogs()).hasSize(1);
+ assertThat(logTester.getLogs(LoggerLevel.DEBUG))
+ .extracting(LogAndArguments::getFormattedMsg)
+ .containsOnly("Resolving new code period by 10 days: 2008-11-20");
}
@Test
dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION).setDate(analysis1.getCreatedAt()));
dbTester.events().insertEvent(newEvent(analysis2).setName("1.0").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
dbTester.events().insertEvent(newEvent(analysis5).setName("1.1").setCategory(CATEGORY_VERSION).setDate(analysis4.getCreatedAt()));
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project, "1.1");
settings.setProperty("sonar.leak.period", "previous_version");
assertThat(period.getSnapshotDate()).isEqualTo(analysis2.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis2.getUuid());
- assertThat(logTester.logs(LoggerLevel.DEBUG)).hasSize(1);
- assertThat(logTester.logs(LoggerLevel.DEBUG).get(0)).startsWith("Compare to previous version (");
+ verifyDebugLogs("Resolving new code period by previous version: 1.0");
}
@Test
dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION));
// The "1.0" version was deleted from the history
dbTester.events().insertEvent(newEvent(analysis3).setName("1.1").setCategory(CATEGORY_VERSION));
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project, "1.1");
settings.setProperty("sonar.leak.period", "previous_version");
assertThat(period.getAnalysisUuid()).isEqualTo(analysis1.getUuid());
}
- @Test
- public void no_period_by_previous_version() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPrivateProject(organization);
- setupRoot(project);
-
- settings.setProperty("sonar.leak.period", "previous_version");
- underTest.execute(new TestComputationStepContext());
-
- assertThat(periodsHolder.hasPeriod()).isFalse();
- }
-
@Test
public void feed_period_by_previous_version_with_first_analysis_when_no_previous_version_found() {
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(false)); // 2008-11-11
- SnapshotDto analysis5 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setVersion("1.1").setLast(true)); // 2008-11-29
- dbTester.events().insertEvent(newEvent(analysis5).setName("1.1").setCategory(CATEGORY_VERSION));
+ SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setVersion("1.1").setLast(false)); // 2008-11-11
+ SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setVersion("1.1").setLast(true)); // 2008-11-29
+ dbTester.events().insertEvent(newEvent(analysis2).setName("1.1").setCategory(CATEGORY_VERSION));
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project, "1.1");
settings.setProperty("sonar.leak.period", "previous_version");
assertThat(period.getModeParameter()).isNull();
assertThat(period.getSnapshotDate()).isEqualTo(analysis1.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis1.getUuid());
+
+ verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
}
@Test
OrganizationDto organization = dbTester.organizations().insert();
ComponentDto project = dbTester.components().insertPrivateProject(organization);
SnapshotDto analysis = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setVersion("0.9").setLast(true)); // 2008-11-11
+ dbTester.events().insertEvent(newEvent(analysis).setName("0.9").setCategory(CATEGORY_VERSION));
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project, "1.1");
settings.setProperty("sonar.leak.period", "previous_version");
assertThat(period.getModeParameter()).isNull();
assertThat(period.getSnapshotDate()).isEqualTo(analysis.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis.getUuid());
+
+ verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
}
@Test
dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION));
dbTester.events().insertEvent(newEvent(analysis2).setName("1.0").setCategory(CATEGORY_VERSION));
dbTester.events().insertEvent(newEvent(analysis5).setName("1.1").setCategory(CATEGORY_VERSION));
+ when(system2Mock.now()).thenReturn(november30th2008.getTime());
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
setupRoot(project, "1.1");
settings.setProperty("sonar.leak.period", "1.0");
assertThat(period.getSnapshotDate()).isEqualTo(analysis2.getCreatedAt());
assertThat(period.getAnalysisUuid()).isEqualTo(analysis2.getUuid());
- assertThat(logTester.logs()).hasSize(1);
- assertThat(logTester.logs().get(0)).startsWith("Compare to version (1.0) (");
+ verifyDebugLogs("Resolving new code period by version: 1.0");
}
- @Test
- public void no_period_by_version() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPrivateProject(organization);
- setupRoot(project);
-
- settings.setProperty("sonar.leak.period", "0.8");
- underTest.execute(new TestComputationStepContext());
-
- assertThat(periodsHolder.hasPeriod()).isFalse();
+ private void verifyDebugLogs(String log, String... otherLogs) {
+ assertThat(logTester.getLogs()).hasSize(1 + otherLogs.length);
+ assertThat(logTester.getLogs(LoggerLevel.DEBUG))
+ .extracting(LogAndArguments::getFormattedMsg)
+ .containsOnly(Stream.concat(Stream.of(log), Arrays.stream(otherLogs)).toArray(String[]::new));
}
private void setupRoot(ComponentDto project) {
- setupRoot(project, "1.1");
+ setupRoot(project, RandomStringUtils.randomAlphanumeric(3));
}
private void setupRoot(ComponentDto project, String version) {
treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey()).setProjectVersion(version).build());
- when(settingsRepository.getConfiguration()).thenReturn(settings.asConfig());
+ when(configurationRepository.getConfiguration()).thenReturn(settings.asConfig());
+ }
+
+ private static void verifyInvalidValueMessage(MessageException e, String propertyValue) {
+ assertThat(e).hasMessage("Invalid new code period. '" + propertyValue
+ + "' 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");
}
}
return dtos.get(0);
}
- /**
- * Since this relies on tables EVENTS, this can return results only for root components (PROJECT, VIEW or DEVELOPER).
- */
- public List<SnapshotDto> selectPreviousVersionSnapshots(DbSession session, String componentUuid, String lastVersion) {
- return mapper(session).selectPreviousVersionSnapshots(componentUuid, lastVersion);
- }
-
- @CheckForNull
- public SnapshotDto selectOldestSnapshot(DbSession session, String componentUuid) {
- List<SnapshotDto> snapshotDtos = mapper(session).selectOldestSnapshots(componentUuid, new RowBounds(0, 1));
- return snapshotDtos.isEmpty() ? null : snapshotDtos.get(0);
+ public Optional<SnapshotDto> selectOldestSnapshot(DbSession session, String componentUuid) {
+ return mapper(session).selectOldestSnapshots(componentUuid, new RowBounds(0, 1))
+ .stream()
+ .findFirst();
}
/**
List<SnapshotDto> selectSnapshotsByQuery(@Param("query") SnapshotQuery query);
- List<SnapshotDto> selectPreviousVersionSnapshots(@Param("componentUuid") String componentUuid, @Param("lastVersion") String lastVersion);
-
List<SnapshotDto> selectOldestSnapshots(@Param("componentUuid") String componentUuid, RowBounds rowBounds);
List<ViewsSnapshotDto> selectSnapshotBefore(@Param("componentUuid") String componentUuid, @Param("date") long date);
return executeLargeInputs(analyses, mapper(dbSession)::selectByAnalysisUuids);
}
+ public List<EventDto> selectVersionsByMostRecentFirst(DbSession session, String componentUuid) {
+ return mapper(session).selectVersions(componentUuid);
+ }
+
public EventDto insert(DbSession session, EventDto dto) {
- session.getMapper(EventMapper.class).insert(dto);
+ mapper(session).insert(dto);
return dto;
}
List<EventDto> selectByAnalysisUuids(@Param("analysisUuids") List<String> list);
+ List<EventDto> selectVersions(@Param("componentUuid") String componentUuid);
+
void insert(EventDto dto);
void update(@Param("uuid") String uuid, @Param("name") @Nullable String name, @Param("description") @Nullable String description);
s.created_at
</select>
- <select id="selectPreviousVersionSnapshots" parameterType="map" resultType="Snapshot">
- SELECT
- <include refid="snapshotColumns" />
- FROM snapshots s
- INNER JOIN events e ON s.uuid = e.analysis_uuid AND e.name <> #{lastVersion} AND e.category='Version'
- <where>
- s.component_uuid=#{componentUuid,jdbcType=VARCHAR}
- </where>
- ORDER BY e.event_date DESC
- </select>
-
<select id="selectOldestSnapshots" parameterType="map" resultType="Snapshot">
SELECT
<include refid="snapshotColumns" />
</where>
</select>
+ <select id="selectVersions" resultType="Event" parameterType="map">
+ select
+ <include refid="eventColumns"/>
+ from events e
+ inner join snapshots s on
+ s.uuid = e.analysis_uuid
+ and s.status = 'P'
+ and s.component_uuid = #{componentUuid,jdbcType=VARCHAR}
+ where
+ e.category = 'Version'
+ order by
+ e.event_date desc
+ </select>
+
<insert id="insert" parameterType="Event" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
INSERT INTO events (uuid, analysis_uuid, component_uuid, name, category, description, event_data, event_date, created_at)
VALUES (
underTest.selectAnalysisByQuery(db.getSession(), new SnapshotQuery());
}
- @Test
- public void select_previous_version_snapshots() {
- db.prepareDbUnit(getClass(), "select_previous_version_snapshots.xml");
-
- List<SnapshotDto> snapshots = underTest.selectPreviousVersionSnapshots(db.getSession(), "ABCD", "1.2-SNAPSHOT");
- assertThat(snapshots).hasSize(2);
-
- SnapshotDto firstSnapshot = snapshots.get(0);
- assertThat(firstSnapshot.getVersion()).isEqualTo("1.1");
-
- // All snapshots are returned on an unknown version
- assertThat(underTest.selectPreviousVersionSnapshots(db.getSession(), "ABCD", "UNKNOWN")).hasSize(3);
- }
-
@Test
public void select_first_snapshots() {
ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization());
newAnalysis(project).setCreatedAt(1L));
dbSession.commit();
- SnapshotDto dto = underTest.selectOldestSnapshot(dbSession, project.uuid());
- assertThat(dto).isNotNull();
- assertThat(dto.getCreatedAt()).isEqualTo(1L);
+ Optional<SnapshotDto> dto = underTest.selectOldestSnapshot(dbSession, project.uuid());
+ assertThat(dto).isNotEmpty();
+ assertThat(dto.get().getCreatedAt()).isEqualTo(1L);
- assertThat(underTest.selectOldestSnapshot(dbSession, "blabla")).isNull();
+ assertThat(underTest.selectOldestSnapshot(dbSession, "blabla")).isEmpty();
}
@Test
show_all=Show All
should_be_unique=Should be unique
since_x=since {0}
-since_previous_analysis=since previous analysis
-since_previous_analysis_detailed=since previous analysis ({0})
-since_previous_analysis.short=\u0394 last
-since_previous_analysis_detailed.short=\u0394 last ({0})
since_version=since version {0}
since_version.short={0}
since_version_detailed=since version {0} ({1})
*/
package org.sonar.api.utils.log;
+import java.util.Arrays;
import java.util.Optional;
import javax.annotation.Nullable;
public String getFormattedMsg() {
return msg;
}
+
+ @Override
+ public String toString() {
+ return "LogAndArguments{" +
+ "rawMsg='" + rawMsg + '\'' +
+ ", args=" + Arrays.toString(args) +
+ ", msg='" + msg + '\'' +
+ '}';
+ }
}