From: Duarte Meneses Date: Fri, 12 Jan 2018 08:42:31 +0000 (+0100) Subject: SONAR-10257 Support lines without SCM info X-Git-Tag: 7.5~1729 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d5481c29c12283b3ea63f8501c426db9009f0010;p=sonarqube.git SONAR-10257 Support lines without SCM info --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculator.java index f34d3d0fc73..4c3c173109c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculator.java @@ -151,17 +151,13 @@ public class IssueCreationDateCalculator extends IssueVisitor { } if (!involvedLines.isEmpty()) { return involvedLines.stream() + .filter(scmInfo::hasChangesetForLine) .map(scmInfo::getChangesetForLine) .max(Comparator.comparingLong(Changeset::getDate)); } } - Changeset latestChangeset = scmInfo.getLatestChangeset(); - if (latestChangeset != null) { - return Optional.of(latestChangeset); - } - - return Optional.empty(); + return Optional.ofNullable(scmInfo.getLatestChangeset()); } private static void addLines(Set involvedLines, TextRange range) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java index ff993ee8932..86df58c3fe9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java @@ -180,11 +180,13 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter create(Component component, Iterable lines) { + static Optional create(Iterable lines) { LineToChangeset lineToChangeset = new LineToChangeset(); - List lineChangesets = StreamSupport.stream(lines.spliterator(), false) - .map(lineToChangeset) - .filter(Objects::nonNull) - .collect(MoreCollectors.toList()); - if (lineChangesets.isEmpty()) { - return Optional.absent(); + Map lineChanges = new LinkedHashMap<>(); + + for (DbFileSources.Line line : lines) { + Changeset changeset = lineToChangeset.apply(line); + if (changeset == null) { + continue; + } + lineChanges.put(line.getLine(), changeset); } - checkState(!lineToChangeset.isEncounteredLineWithoutScmInfo(), - "Partial scm information stored in DB for component '%s'. Not all lines have SCM info. Can not proceed", component); - return Optional.of(new DbScmInfo(new ScmInfoImpl(lineChangesets))); + if (lineChanges.isEmpty()) { + return Optional.empty(); + } + return Optional.of(new DbScmInfo(new ScmInfoImpl(lineChanges))); } @Override @@ -77,37 +74,34 @@ class DbScmInfo implements ScmInfo { } @Override - public Iterable getAllChangesets() { + public Map getAllChangesets() { return delegate.getAllChangesets(); } /** - * Transforms {@link org.sonar.db.protobuf.DbFileSources.Line} into {@link Changeset} and keep a flag if it encountered - * at least one which did not have any SCM information. + * Transforms {@link org.sonar.db.protobuf.DbFileSources.Line} into {@link Changeset} */ private static class LineToChangeset implements Function { - private boolean encounteredLineWithoutScmInfo = false; - private final Map cache = new HashMap<>(); private final Changeset.Builder builder = Changeset.newChangesetBuilder(); + private final HashMap cache = new HashMap<>(); @Override @Nullable public Changeset apply(@Nonnull DbFileSources.Line input) { - if (input.hasScmRevision() && input.hasScmDate()) { - String revision = input.getScmRevision(); - return cache.computeIfAbsent(revision, k -> builder - .setRevision(revision) + if (input.hasScmDate()) { + Changeset cs = builder + .setRevision(input.hasScmRevision() ? input.getScmRevision() : null) .setAuthor(input.hasScmAuthor() ? input.getScmAuthor() : null) .setDate(input.getScmDate()) - .build()); + .build(); + if (cache.containsKey(cs)) { + return cache.get(cs); + } + cache.put(cs, cs); + return cs; } - this.encounteredLineWithoutScmInfo = true; return null; } - - boolean isEncounteredLineWithoutScmInfo() { - return encounteredLineWithoutScmInfo; - } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ReportScmInfo.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ReportScmInfo.java index a44e4c215fe..d117f344e70 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ReportScmInfo.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ReportScmInfo.java @@ -19,18 +19,17 @@ */ package org.sonar.server.computation.task.projectanalysis.scm; -import com.google.common.base.Function; import java.util.HashMap; -import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.Map; -import java.util.NoSuchElementException; -import javax.annotation.Nonnull; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import javax.annotation.concurrent.Immutable; +import org.sonar.core.util.stream.MoreCollectors; import org.sonar.scanner.protocol.output.ScannerReport; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.FluentIterable.from; import static java.util.Objects.requireNonNull; import static org.apache.commons.lang.StringUtils.isNotEmpty; @@ -44,12 +43,13 @@ class ReportScmInfo implements ScmInfo { ReportScmInfo(ScannerReport.Changesets changesets) { requireNonNull(changesets); this.delegate = convertToScmInfo(changesets); + checkState(!delegate.getAllChangesets().isEmpty(), "Report has no changesets"); } private static ScmInfo convertToScmInfo(ScannerReport.Changesets changesets) { - return new ScmInfoImpl( - from(new IntRangeIterable(changesets.getChangesetIndexByLineCount())) - .transform(new LineIndexToChangeset(changesets))); + return new ScmInfoImpl(IntStream.rangeClosed(1, changesets.getChangesetIndexByLineCount()) + .boxed() + .collect(Collectors.toMap(x -> x, new LineIndexToChangeset(changesets), MoreCollectors.mergeNotSupportedMerger(), LinkedHashMap::new))); } @Override @@ -68,7 +68,7 @@ class ReportScmInfo implements ScmInfo { } @Override - public Iterable getAllChangesets() { + public Map getAllChangesets() { return this.delegate.getAllChangesets(); } @@ -83,16 +83,9 @@ class ReportScmInfo implements ScmInfo { } @Override - @Nonnull - public Changeset apply(@Nonnull Integer lineNumber) { + public Changeset apply(Integer lineNumber) { int changesetIndex = changesets.getChangesetIndexByLine(lineNumber - 1); - Changeset changeset = changesetCache.get(changesetIndex); - if (changeset != null) { - return changeset; - } - Changeset res = convert(changesets.getChangeset(changesetIndex), lineNumber); - changesetCache.put(changesetIndex, res); - return res; + return changesetCache.computeIfAbsent(changesetIndex, idx -> convert(changesets.getChangeset(changesetIndex), lineNumber)); } private Changeset convert(ScannerReport.Changesets.Changeset changeset, int line) { @@ -105,50 +98,4 @@ class ReportScmInfo implements ScmInfo { .build(); } } - - /** - * A simple Iterable which generate integer from 0 (included) to a specific value (excluded). - */ - private static final class IntRangeIterable implements Iterable { - private final int max; - - private IntRangeIterable(int max) { - checkArgument(max >= 0, "Max value must be >= 0"); - this.max = max; - } - - @Override - public Iterator iterator() { - return new IntRangeIterator(max); - } - } - - private static class IntRangeIterator implements Iterator { - private final int max; - - private int current = 0; - - public IntRangeIterator(int max) { - this.max = max; - } - - @Override - public boolean hasNext() { - return current < max; - } - - @Override - public Integer next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - current++; - return current; - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Remove cannot be called"); - } - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfo.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfo.java index ba67f742b84..1297b674699 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfo.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfo.java @@ -19,6 +19,8 @@ */ package org.sonar.server.computation.task.projectanalysis.scm; +import java.util.Map; + /** * Represents the Scm information for a specific file. */ @@ -32,7 +34,7 @@ public interface ScmInfo { /** * Get ChangeSet of the file for given line * - * @throws IllegalArgumentException if there is no Changeset for the specified line, ie. the line number in invalid (either less than 1 or > linecount of the file) + * @throws IllegalArgumentException if there is no Changeset for the specified line */ Changeset getChangesetForLine(int lineNumber); @@ -42,8 +44,8 @@ public interface ScmInfo { boolean hasChangesetForLine(int lineNumber); /** - * Return all ChangeSets, in order, for all lines of the file + * Return all ChangeSets, in order, for all lines that have changesets. */ - Iterable getAllChangesets(); + Map getAllChangesets(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoDbLoader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoDbLoader.java index b79950b4484..de56432ae5b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoDbLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoDbLoader.java @@ -62,7 +62,7 @@ public class ScmInfoDbLoader { if (dto == null || !isDtoValid(file, dto)) { return NoScmInfo.INSTANCE; } - return DbScmInfo.create(file, dto.getSourceData().getLinesList()).or(NoScmInfo.INSTANCE); + return DbScmInfo.create(dto.getSourceData().getLinesList()).orElse(NoScmInfo.INSTANCE); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java index c4f2afc093b..df006eaf2fe 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImpl.java @@ -19,41 +19,28 @@ */ package org.sonar.server.computation.task.projectanalysis.scm; -import com.google.common.base.Predicate; -import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; -import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.FluentIterable.from; -import static com.google.common.collect.Iterables.isEmpty; -import static java.util.Arrays.asList; - @Immutable public class ScmInfoImpl implements ScmInfo { @CheckForNull private final Changeset latestChangeset; - private final Changeset[] lineChangesets; + private final Map lineChangesets; - public ScmInfoImpl(Iterable lineChangesets) { - checkState(!isEmpty(lineChangesets), "A ScmInfo must have at least one Changeset and does not support any null one"); - this.lineChangesets = from(lineChangesets) - .filter(CheckNotNull.INSTANCE) - .toArray(Changeset.class); + public ScmInfoImpl(Map lineChangesets) { + this.lineChangesets = lineChangesets; this.latestChangeset = computeLatestChangeset(lineChangesets); } - private static Changeset computeLatestChangeset(Iterable lineChangesets) { - Changeset latestChangeset = null; - for (Changeset lineChangeset : lineChangesets) { - if (latestChangeset == null || lineChangeset.getDate() > latestChangeset.getDate()) { - latestChangeset = lineChangeset; - } - } - return latestChangeset; + private static Changeset computeLatestChangeset(Map lineChangesets) { + return lineChangesets.values().stream() + .collect(Collectors.maxBy(Comparator.comparing(Changeset::getDate))) + .orElse(null); } @Override @@ -63,35 +50,28 @@ public class ScmInfoImpl implements ScmInfo { @Override public Changeset getChangesetForLine(int lineNumber) { - checkArgument(lineNumber > 0 && lineNumber <= lineChangesets.length, "There's no changeset on line %s", lineNumber); - return lineChangesets[lineNumber - 1]; + Changeset changeset = lineChangesets.get(lineNumber); + if (changeset != null) { + return changeset; + } + throw new IllegalArgumentException("Line " + lineNumber + " doesn't have a changeset"); } @Override public boolean hasChangesetForLine(int lineNumber) { - return lineNumber <= lineChangesets.length; + return lineChangesets.containsKey(lineNumber); } @Override - public Iterable getAllChangesets() { - return asList(lineChangesets); + public Map getAllChangesets() { + return lineChangesets; } @Override public String toString() { return "ScmInfoImpl{" + "latestChangeset=" + latestChangeset + - ", lineChangesets=" + Arrays.toString(lineChangesets) + + ", lineChangesets=" + lineChangesets + '}'; } - - private enum CheckNotNull implements Predicate { - INSTANCE; - - @Override - public boolean apply(@Nullable Changeset input) { - checkState(input != null, "Null changeset are not allowed"); - return true; - } - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/source/ScmLineReader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/source/ScmLineReader.java index a120a4882a3..e2a751615c3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/source/ScmLineReader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/source/ScmLineReader.java @@ -38,20 +38,22 @@ public class ScmLineReader implements LineReader { @Override public void read(DbFileSources.Line.Builder lineBuilder) { - Changeset changeset = scmReport.getChangesetForLine(lineBuilder.getLine()); - String author = changeset.getAuthor(); - if (author != null) { - lineBuilder.setScmAuthor(author); - } - String revision = changeset.getRevision(); - if (revision != null) { - lineBuilder.setScmRevision(revision); - } - lineBuilder.setScmDate(changeset.getDate()); - updateLatestChange(changeset); + if (scmReport.hasChangesetForLine(lineBuilder.getLine())) { + Changeset changeset = scmReport.getChangesetForLine(lineBuilder.getLine()); + String author = changeset.getAuthor(); + if (author != null) { + lineBuilder.setScmAuthor(author); + } + String revision = changeset.getRevision(); + if (revision != null) { + lineBuilder.setScmRevision(revision); + } + lineBuilder.setScmDate(changeset.getDate()); + updateLatestChange(changeset); - if (revision != null) { - updateLatestChangeWithRevision(changeset); + if (revision != null) { + updateLatestChangeWithRevision(changeset); + } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewCoverageMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewCoverageMeasuresStep.java index e708f4fc232..5e7b8d74bc7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewCoverageMeasuresStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewCoverageMeasuresStep.java @@ -35,6 +35,7 @@ import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader import org.sonar.server.computation.task.projectanalysis.component.Component; import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler; import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder; +import org.sonar.server.computation.task.projectanalysis.formula.Counter; import org.sonar.server.computation.task.projectanalysis.formula.CounterInitializationContext; import org.sonar.server.computation.task.projectanalysis.formula.CreateMeasureContext; import org.sonar.server.computation.task.projectanalysis.formula.Formula; @@ -226,7 +227,7 @@ public class NewCoverageMeasuresStep implements ComputationStep { } } - public static final class NewCoverageCounter implements org.sonar.server.computation.task.projectanalysis.formula.Counter { + public static final class NewCoverageCounter implements Counter { private final IntValue newLines = new IntValue(); private final IntValue newCoveredLines = new IntValue(); private final IntValue newConditions = new IntValue(); @@ -271,8 +272,9 @@ public class NewCoverageMeasuresStep implements ComputationStep { int hits = entry.getValue(); int conditions = (Integer) ObjectUtils.defaultIfNull(conditionsByLine.get(lineId), 0); int coveredConditions = (Integer) ObjectUtils.defaultIfNull(coveredConditionsByLine.get(lineId), 0); - long date = componentScm.getChangesetForLine(lineId).getDate(); - analyze(context.getPeriod(), date, hits, conditions, coveredConditions); + if (componentScm.hasChangesetForLine(lineId)) { + analyze(context.getPeriod(), componentScm.getChangesetForLine(lineId).getDate(), hits, conditions, coveredConditions); + } } } @@ -283,10 +285,7 @@ public class NewCoverageMeasuresStep implements ComputationStep { return Collections.emptyMap(); } - void analyze(Period period, @Nullable Long lineDate, int hits, int conditions, int coveredConditions) { - if (lineDate == null) { - return; - } + void analyze(Period period, long lineDate, int hits, int conditions, int coveredConditions) { if (isLineInPeriod(lineDate, period)) { incrementLines(hits); incrementConditions(conditions, coveredConditions); diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewSizeMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewSizeMeasuresStep.java index 4b0e0c92478..94881754f28 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewSizeMeasuresStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewSizeMeasuresStep.java @@ -21,11 +21,9 @@ package org.sonar.server.computation.task.projectanalysis.step; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import java.util.HashSet; import java.util.Set; import java.util.stream.IntStream; -import java.util.stream.StreamSupport; import org.sonar.server.computation.task.projectanalysis.component.Component; import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler; import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder; @@ -66,7 +64,7 @@ public class NewSizeMeasuresStep implements ComputationStep { private final NewDuplicationFormula duplicationFormula; public NewSizeMeasuresStep(TreeRootHolder treeRootHolder, PeriodHolder periodHolder, MetricRepository metricRepository, MeasureRepository measureRepository, - ScmInfoRepository scmInfoRepository, DuplicationRepository duplicationRepository) { + ScmInfoRepository scmInfoRepository, DuplicationRepository duplicationRepository) { this.treeRootHolder = treeRootHolder; this.periodHolder = periodHolder; this.metricRepository = metricRepository; @@ -128,7 +126,7 @@ public class NewSizeMeasuresStep implements ComputationStep { } private void initNewLines(ScmInfo scmInfo, Period period) { - StreamSupport.stream(scmInfo.getAllChangesets().spliterator(), false) + scmInfo.getAllChangesets().values().stream() .filter(changeset -> isLineInPeriod(changeset, period)) .forEach(changeset -> newLines.increment(1)); } @@ -162,7 +160,7 @@ public class NewSizeMeasuresStep implements ComputationStep { private DuplicationCounters(ScmInfo scmInfo, Period period) { this.scmInfo = scmInfo; this.period = period; - this.lineCounts = new HashSet<>(Iterables.size(scmInfo.getAllChangesets())); + this.lineCounts = new HashSet<>(); } void addBlock(TextBlock textBlock) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculatorTest.java index ad42fdf07aa..b4c98156d1f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculatorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculatorTest.java @@ -357,6 +357,7 @@ public class IssueCreationDateCalculatorTest { private void withScmAt(int line, long blame) { createMockScmInfo(); Changeset changeset = Changeset.newChangesetBuilder().setDate(blame).setRevision("1").build(); + when(scmInfo.hasChangesetForLine(line)).thenReturn(true); when(scmInfo.getChangesetForLine(line)).thenReturn(changeset); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/DbScmInfoTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/DbScmInfoTest.java index ed8467252f7..a3b98652874 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/DbScmInfoTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/DbScmInfoTest.java @@ -23,11 +23,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.db.protobuf.DbFileSources; -import org.sonar.server.computation.task.projectanalysis.component.Component; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.guava.api.Assertions.assertThat; -import static org.sonar.server.computation.task.projectanalysis.component.ReportComponent.builder; import static org.sonar.server.source.index.FileSourceTesting.newFakeData; public class DbScmInfoTest { @@ -35,12 +32,9 @@ public class DbScmInfoTest { @Rule public ExpectedException thrown = ExpectedException.none(); - static final int FILE_REF = 1; - static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build(); - @Test public void create_scm_info_with_some_changesets() throws Exception { - ScmInfo scmInfo = DbScmInfo.create(FILE, newFakeData(10).build().getLinesList()).get(); + ScmInfo scmInfo = DbScmInfo.create(newFakeData(10).build().getLinesList()).get(); assertThat(scmInfo.getAllChangesets()).hasSize(10); } @@ -54,7 +48,7 @@ public class DbScmInfoTest { addLine(fileDataBuilder, 4, "john", 123456789L, "rev-1"); fileDataBuilder.build(); - ScmInfo scmInfo = DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get(); + ScmInfo scmInfo = DbScmInfo.create(fileDataBuilder.getLinesList()).get(); assertThat(scmInfo.getAllChangesets()).hasSize(4); @@ -65,14 +59,14 @@ public class DbScmInfoTest { } @Test - public void return_same_changeset_objects_for_lines_with_same_revision() { + public void return_same_changeset_objects_for_lines_with_same_fields() throws Exception { DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(65L).setLine(1); fileDataBuilder.addLinesBuilder().setScmRevision("rev2").setScmDate(6541L).setLine(2); fileDataBuilder.addLinesBuilder().setScmRevision("rev1").setScmDate(6541L).setLine(3); - fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(6542L).setLine(4); + fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(65L).setLine(4); - ScmInfo scmInfo = DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get(); + ScmInfo scmInfo = DbScmInfo.create(fileDataBuilder.getLinesList()).get(); assertThat(scmInfo.getAllChangesets()).hasSize(4); @@ -88,7 +82,7 @@ public class DbScmInfoTest { addLine(fileDataBuilder, 3, "john", 123456789L, "rev-1"); fileDataBuilder.build(); - ScmInfo scmInfo = DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get(); + ScmInfo scmInfo = DbScmInfo.create(fileDataBuilder.getLinesList()).get(); Changeset latestChangeset = scmInfo.getLatestChangeset(); assertThat(latestChangeset.getAuthor()).isEqualTo("henry"); @@ -101,60 +95,40 @@ public class DbScmInfoTest { DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); fileDataBuilder.addLinesBuilder().setLine(1); - assertThat(DbScmInfo.create(FILE, fileDataBuilder.getLinesList())).isAbsent(); - } - - @Test - public void return_absent_dsm_info_when_changeset_line_has_both_revision_and_date() { - DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); - fileDataBuilder.addLinesBuilder().setLine(1); - fileDataBuilder.addLinesBuilder().setScmDate(6541L).setLine(2); - fileDataBuilder.addLinesBuilder().setScmRevision("rev").setLine(3); - fileDataBuilder.addLinesBuilder().setScmAuthor("author").setLine(4); - - assertThat(DbScmInfo.create(FILE, fileDataBuilder.getLinesList())).isAbsent(); + assertThat(DbScmInfo.create(fileDataBuilder.getLinesList())).isNotPresent(); } @Test - public void fail_with_ISE_when_changeset_has_no_field() { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Partial scm information stored in DB for component 'ReportComponent{ref=1, key='FILE_KEY', type=FILE}'. " + - "Not all lines have SCM info. Can not proceed"); - + public void should_support_some_lines_not_having_scm_info() throws Exception { DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(543L).setLine(1); fileDataBuilder.addLinesBuilder().setLine(2); fileDataBuilder.build(); - DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get().getAllChangesets(); + assertThat(DbScmInfo.create(fileDataBuilder.getLinesList()).get().getAllChangesets()).hasSize(1); } @Test - public void fail_with_ISE_when_changeset_has_only_revision_field() { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Partial scm information stored in DB for component 'ReportComponent{ref=1, key='FILE_KEY', type=FILE}'. " + - "Not all lines have SCM info. Can not proceed"); - + public void filter_out_entries_without_date() throws Exception { DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(555L).setLine(1); fileDataBuilder.addLinesBuilder().setScmRevision("rev-1").setLine(2); fileDataBuilder.build(); - DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get().getAllChangesets(); + assertThat(DbScmInfo.create(fileDataBuilder.getLinesList()).get().getAllChangesets()).hasSize(1); + assertThat(DbScmInfo.create(fileDataBuilder.getLinesList()).get().getChangesetForLine(1).getRevision()).isEqualTo("rev"); } @Test - public void fail_with_ISE_when_changeset_has_only_author_field() { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Partial scm information stored in DB for component 'ReportComponent{ref=1, key='FILE_KEY', type=FILE}'. " + - "Not all lines have SCM info. Can not proceed"); - + public void should_support_having_no_author() throws Exception { DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); + // gets filtered out fileDataBuilder.addLinesBuilder().setScmAuthor("John").setLine(1); fileDataBuilder.addLinesBuilder().setScmRevision("rev").setScmDate(555L).setLine(2); fileDataBuilder.build(); - DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get().getAllChangesets(); + assertThat(DbScmInfo.create(fileDataBuilder.getLinesList()).get().getAllChangesets()).hasSize(1); + assertThat(DbScmInfo.create(fileDataBuilder.getLinesList()).get().getChangesetForLine(2).getAuthor()).isNull(); } private static void addLine(DbFileSources.Data.Builder dataBuilder, Integer line, String author, Long date, String revision) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImplTest.java index 36ad61303dd..d5efe051080 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoImplTest.java @@ -19,13 +19,14 @@ */ package org.sonar.server.computation.task.projectanalysis.scm; -import com.google.common.collect.Lists; +import com.google.common.collect.ImmutableMap; +import java.util.Collections; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; public class ScmInfoImplTest { @@ -48,7 +49,7 @@ public class ScmInfoImplTest { public void get_all_changesets() { ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines(); - assertThat(scmInfo.getAllChangesets()).containsOnly(CHANGESET_1, CHANGESET_2, CHANGESET_1, CHANGESET_1); + assertThat(scmInfo.getAllChangesets()).containsOnly(entry(1, CHANGESET_1), entry(2, CHANGESET_2), entry(3, CHANGESET_1), entry(4, CHANGESET_1)); } @Test @@ -81,7 +82,7 @@ public class ScmInfoImplTest { thrown.expect(IllegalStateException.class); thrown.expectMessage("A ScmInfo must have at least one Changeset and does not support any null one"); - new ScmInfoImpl(Lists.newArrayList()); + new ScmInfoImpl(Collections.emptyMap()); } @Test @@ -129,7 +130,7 @@ public class ScmInfoImplTest { .setRevision("rev-2") .build(); - ScmInfo scmInfo = new ScmInfoImpl(newArrayList(changeset1, changeset2, changeset1, changeset1)); + ScmInfo scmInfo = new ScmInfoImpl(ImmutableMap.of(1, changeset1, 2, changeset2, 3, changeset1, 4, changeset1)); return scmInfo; } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoRepositoryRule.java index 6f1cc9f7dc5..516e780c50b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoRepositoryRule.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoRepositoryRule.java @@ -20,9 +20,10 @@ package org.sonar.server.computation.task.projectanalysis.scm; import com.google.common.base.Optional; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.rules.ExternalResource; import org.sonar.server.computation.task.projectanalysis.component.Component; @@ -45,7 +46,8 @@ public class ScmInfoRepositoryRule extends ExternalResource implements ScmInfoRe } public ScmInfoRepositoryRule setScmInfo(int fileRef, Changeset... changesetList) { - scmInfoByFileRef.put(fileRef, new ScmInfoImpl(Arrays.asList(changesetList))); + Map changeset = IntStream.rangeClosed(1, changesetList.length).boxed().collect(Collectors.toMap(x -> x, x -> changesetList[x-1])); + scmInfoByFileRef.put(fileRef, new ScmInfoImpl(changeset)); return this; } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/source/ScmLineReaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/source/ScmLineReaderTest.java index 769d8d3f6dd..d2273474738 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/source/ScmLineReaderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/source/ScmLineReaderTest.java @@ -19,28 +19,27 @@ */ package org.sonar.server.computation.task.projectanalysis.source; -import com.google.common.collect.ImmutableList; -import java.util.List; +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.Map; import org.junit.Test; import org.sonar.db.protobuf.DbFileSources; import org.sonar.server.computation.task.projectanalysis.scm.Changeset; import org.sonar.server.computation.task.projectanalysis.scm.ScmInfo; import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoImpl; -import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; public class ScmLineReaderTest { @Test public void set_scm() { - ScmInfo scmInfo = new ScmInfoImpl(newArrayList( + ScmInfo scmInfo = new ScmInfoImpl(Collections.singletonMap(1, Changeset.newChangesetBuilder() .setAuthor("john") .setDate(123_456_789L) .setRevision("rev-1") - .build() - )); + .build())); ScmLineReader lineScm = new ScmLineReader(scmInfo); @@ -54,12 +53,11 @@ public class ScmLineReaderTest { @Test public void set_scm_with_minim_fields() { - ScmInfo scmInfo = new ScmInfoImpl(newArrayList( + ScmInfo scmInfo = new ScmInfoImpl(Collections.singletonMap(1, Changeset.newChangesetBuilder() .setDate(123_456_789L) .setRevision("rev-1") - .build() - )); + .build())); ScmLineReader lineScm = new ScmLineReader(scmInfo); @@ -103,8 +101,16 @@ public class ScmLineReaderTest { readLineAndAssertLatestChangeDate(lineScm, 8, changeset2); } - private static List setup8LinesChangeset(Changeset changeset0, Changeset changeset1, Changeset changeset2) { - return ImmutableList.of(changeset0, changeset1, changeset1, changeset2, changeset0, changeset1, changeset0, changeset0); + private static Map setup8LinesChangeset(Changeset changeset0, Changeset changeset1, Changeset changeset2) { + return ImmutableMap.builder() + .put(1, changeset0) + .put(2, changeset1) + .put(3, changeset1) + .put(4, changeset2) + .put(5, changeset0) + .put(6, changeset1) + .put(7, changeset0) + .put(8, changeset0).build(); } private void readLineAndAssertLatestChangeDate(ScmLineReader lineScm, int line, Changeset expectedChangeset) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistFileSourcesStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistFileSourcesStepTest.java index 531f66c6b18..fbe3fc3d105 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistFileSourcesStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistFileSourcesStepTest.java @@ -183,6 +183,39 @@ public class PersistFileSourcesStepTest extends BaseStepTest { assertThat(data.getLines(0).getScmRevision()).isEqualTo("rev-1"); } + @Test + public void persist_scm_some_lines() { + initBasicReport(3); + scmInfoRepository.setScmInfo(FILE1_REF, Changeset.newChangesetBuilder() + .setAuthor("john") + .setDate(123456789L) + .setRevision("rev-1") + .build()); + + underTest.execute(); + + assertThat(dbTester.countRowsOfTable("file_sources")).isEqualTo(1); + FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectSourceByFileUuid(session, FILE1_UUID); + + assertThat(fileSourceDto.getRevision()).isEqualTo("rev-1"); + + DbFileSources.Data data = fileSourceDto.getSourceData(); + + assertThat(data.getLinesList()).hasSize(3); + + assertThat(data.getLines(0).getScmAuthor()).isEqualTo("john"); + assertThat(data.getLines(0).getScmDate()).isEqualTo(123456789L); + assertThat(data.getLines(0).getScmRevision()).isEqualTo("rev-1"); + + assertThat(data.getLines(1).getScmAuthor()).isEmpty(); + assertThat(data.getLines(1).getScmDate()).isEqualTo(0); + assertThat(data.getLines(1).getScmRevision()).isEmpty(); + + assertThat(data.getLines(2).getScmAuthor()).isEmpty(); + assertThat(data.getLines(2).getScmDate()).isEqualTo(0); + assertThat(data.getLines(2).getScmRevision()).isEmpty(); + } + @Test public void persist_highlighting() { initBasicReport(1); diff --git a/sonar-core/src/main/java/org/sonar/core/util/stream/MoreCollectors.java b/sonar-core/src/main/java/org/sonar/core/util/stream/MoreCollectors.java index d9d2e512908..649b92311cb 100644 --- a/sonar-core/src/main/java/org/sonar/core/util/stream/MoreCollectors.java +++ b/sonar-core/src/main/java/org/sonar/core/util/stream/MoreCollectors.java @@ -365,7 +365,7 @@ public final class MoreCollectors { joiner::join); } - private static BinaryOperator mergeNotSupportedMerger() { + public static BinaryOperator mergeNotSupportedMerger() { return (m1, m2) -> { throw new IllegalStateException("Parallel processing is not supported"); };