From a79d83f7ee9092352cd4581caccf6a1b269935a9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Fri, 24 Aug 2018 13:58:10 +0200 Subject: [PATCH] SONAR-10555 fail analysis if leak period is invalid --- .../projectanalysis/step/LoadPeriodsStep.java | 287 ++++++++++++-- .../projectanalysis/step/PeriodResolver.java | 196 ---------- .../step/LoadPeriodsStepTest.java | 350 +++++++++++++----- .../org/sonar/db/component/SnapshotDao.java | 15 +- .../sonar/db/component/SnapshotMapper.java | 2 - .../java/org/sonar/db/event/EventDao.java | 6 +- .../java/org/sonar/db/event/EventMapper.java | 2 + .../org/sonar/db/component/SnapshotMapper.xml | 11 - .../org/sonar/db/event/EventMapper.xml | 14 + .../sonar/db/component/SnapshotDaoTest.java | 22 +- .../resources/org/sonar/l10n/core.properties | 4 - .../sonar/api/utils/log/LogAndArguments.java | 10 + 12 files changed, 545 insertions(+), 374 deletions(-) delete mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PeriodResolver.java diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java index 7e11474941a..8fe58c7ec6b 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java @@ -19,22 +19,46 @@ */ 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} @@ -42,62 +66,257 @@ import org.sonar.db.DbSession; * 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 resolvePeriod(Component projectOrView) { + String currentVersion = projectOrView.getProjectAttributes().getVersion(); + Optional 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 resolve(String projectUuid, String analysisProjectVersion) { + Optional 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 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 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 resolveByDays(DbSession dbSession, String projectUuid, Integer days, String propertyValue) { + checkPeriodProperty(days > 0, propertyValue, "number of days is <= 0"); + long analysisDate = analysisMetadataHolder.getAnalysisDate(); + List 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 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 = 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 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 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 findOldestAnalysis(DbSession dbSession, String periodMode, String projectUuid) { + Optional period = dbClient.snapshotDao().selectOldestSnapshot(dbSession, projectUuid) + .map(dto -> newPeriod(periodMode, null, dto)); + ensureNotOnFirstAnalysis(period.isPresent()); + return period; + } + + private Optional resolvePreviousVersion(DbSession dbSession, String currentVersion, List 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 resolveVersion(DbSession dbSession, List versions, String propertyValue) { + LOG.debug("Resolving new code period by version: {}", propertyValue); + Optional 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 newPeriod(DbSession dbSession, String periodMode, EventDto previousVersion) { + Optional 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 versions) { + return Arrays.toString(versions.stream().map(EventDto::getName).toArray(String[]::new)); + } + + public static Object async(Supplier 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 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 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))); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PeriodResolver.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PeriodResolver.java deleted file mode 100644 index 0f628517fce..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PeriodResolver.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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 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 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 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 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(); - } -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java index adfb980b2d1..a7cba5bc23e 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java @@ -19,8 +19,15 @@ */ 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; @@ -28,12 +35,12 @@ import org.junit.Test; 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; @@ -49,15 +56,19 @@ import org.sonar.db.component.ComponentDto; 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; @@ -72,18 +83,20 @@ public class LoadPeriodsStepTest extends BaseStepTest { @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() { @@ -92,25 +105,24 @@ public class LoadPeriodsStepTest extends BaseStepTest { @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 @@ -118,6 +130,8 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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"; @@ -132,43 +146,30 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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 @@ -180,6 +181,8 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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"; @@ -194,8 +197,7 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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 @@ -207,6 +209,8 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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"; @@ -220,24 +224,182 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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 @@ -245,6 +407,8 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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"); @@ -258,20 +422,10 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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 @@ -286,6 +440,8 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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"); @@ -299,8 +455,7 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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 @@ -313,6 +468,8 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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"); @@ -327,25 +484,15 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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"); @@ -357,6 +504,8 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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 @@ -364,6 +513,9 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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"); @@ -375,6 +527,8 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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 @@ -389,6 +543,8 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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"); @@ -402,29 +558,29 @@ public class LoadPeriodsStepTest extends BaseStepTest { 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"); } } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotDao.java index ae2e55d4f54..00012373481 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotDao.java @@ -83,17 +83,10 @@ public class SnapshotDao implements Dao { return dtos.get(0); } - /** - * Since this relies on tables EVENTS, this can return results only for root components (PROJECT, VIEW or DEVELOPER). - */ - public List selectPreviousVersionSnapshots(DbSession session, String componentUuid, String lastVersion) { - return mapper(session).selectPreviousVersionSnapshots(componentUuid, lastVersion); - } - - @CheckForNull - public SnapshotDto selectOldestSnapshot(DbSession session, String componentUuid) { - List snapshotDtos = mapper(session).selectOldestSnapshots(componentUuid, new RowBounds(0, 1)); - return snapshotDtos.isEmpty() ? null : snapshotDtos.get(0); + public Optional selectOldestSnapshot(DbSession session, String componentUuid) { + return mapper(session).selectOldestSnapshots(componentUuid, new RowBounds(0, 1)) + .stream() + .findFirst(); } /** diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotMapper.java index 66eacc9af67..4636283747c 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/SnapshotMapper.java @@ -42,8 +42,6 @@ public interface SnapshotMapper { List selectSnapshotsByQuery(@Param("query") SnapshotQuery query); - List selectPreviousVersionSnapshots(@Param("componentUuid") String componentUuid, @Param("lastVersion") String lastVersion); - List selectOldestSnapshots(@Param("componentUuid") String componentUuid, RowBounds rowBounds); List selectSnapshotBefore(@Param("componentUuid") String componentUuid, @Param("date") long date); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDao.java index dcb7d715110..e01dbe42bd4 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventDao.java @@ -45,8 +45,12 @@ public class EventDao implements Dao { return executeLargeInputs(analyses, mapper(dbSession)::selectByAnalysisUuids); } + public List 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; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventMapper.java index 244ff09244a..0695fd4281e 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/event/EventMapper.java @@ -33,6 +33,8 @@ public interface EventMapper { List selectByAnalysisUuids(@Param("analysisUuids") List list); + List 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); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/SnapshotMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/SnapshotMapper.xml index 03f78b74776..d5e8bca8378 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/SnapshotMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/SnapshotMapper.xml @@ -114,17 +114,6 @@ s.created_at - - + + INSERT INTO events (uuid, analysis_uuid, component_uuid, name, category, description, event_data, event_date, created_at) VALUES ( diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/SnapshotDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/SnapshotDaoTest.java index a4a00f53aba..4b4151cee5b 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/SnapshotDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/SnapshotDaoTest.java @@ -176,20 +176,6 @@ public class SnapshotDaoTest { underTest.selectAnalysisByQuery(db.getSession(), new SnapshotQuery()); } - @Test - public void select_previous_version_snapshots() { - db.prepareDbUnit(getClass(), "select_previous_version_snapshots.xml"); - - List 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()); @@ -201,11 +187,11 @@ public class SnapshotDaoTest { newAnalysis(project).setCreatedAt(1L)); dbSession.commit(); - SnapshotDto dto = underTest.selectOldestSnapshot(dbSession, project.uuid()); - assertThat(dto).isNotNull(); - assertThat(dto.getCreatedAt()).isEqualTo(1L); + Optional 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 diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 76f5cb99b84..88d2afea145 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -247,10 +247,6 @@ show_more=Show More 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}) diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogAndArguments.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogAndArguments.java index 5e4ad44a45b..56aa05566c1 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogAndArguments.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogAndArguments.java @@ -19,6 +19,7 @@ */ package org.sonar.api.utils.log; +import java.util.Arrays; import java.util.Optional; import javax.annotation.Nullable; @@ -62,4 +63,13 @@ public final class LogAndArguments { public String getFormattedMsg() { return msg; } + + @Override + public String toString() { + return "LogAndArguments{" + + "rawMsg='" + rawMsg + '\'' + + ", args=" + Arrays.toString(args) + + ", msg='" + msg + '\'' + + '}'; + } } -- 2.39.5