From 9c311c559136c62930e6463c525dd735b3ecac16 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Wed, 16 Dec 2015 17:37:20 +0100 Subject: [PATCH] SONAR-6990 strongly reduce HEAP usage of the DuplicationRepository --- .../benchmark/PersistFileSourcesStepTest.java | 14 +- .../duplication/DuplicationRepository.java | 45 +-- .../DuplicationRepositoryImpl.java | 353 +---------------- .../IntegrateCrossProjectDuplications.java | 59 ++- .../source/DuplicationLineReader.java | 13 +- .../step/DuplicationDataMeasuresStep.java | 6 +- .../step/DuplicationMeasuresStep.java | 5 +- .../step/LoadDuplicationsFromReportStep.java | 42 +- .../DuplicationRepositoryImplTest.java | 362 ++---------------- .../DuplicationRepositoryRule.java | 116 ++++-- ...IntegrateCrossProjectDuplicationsTest.java | 64 ++-- .../LoadDuplicationsFromReportStepTest.java | 2 +- .../step/PersistFileSourcesStepTest.java | 9 +- .../ReportDuplicationMeasuresStepTest.java | 11 +- 14 files changed, 285 insertions(+), 816 deletions(-) diff --git a/server/sonar-server-benchmarks/src/test/java/org/sonar/server/benchmark/PersistFileSourcesStepTest.java b/server/sonar-server-benchmarks/src/test/java/org/sonar/server/benchmark/PersistFileSourcesStepTest.java index 722b99b9238..8cfa29b8274 100644 --- a/server/sonar-server-benchmarks/src/test/java/org/sonar/server/benchmark/PersistFileSourcesStepTest.java +++ b/server/sonar-server-benchmarks/src/test/java/org/sonar/server/benchmark/PersistFileSourcesStepTest.java @@ -23,6 +23,7 @@ package org.sonar.server.benchmark; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.commons.io.FileUtils; import org.junit.Rule; @@ -43,7 +44,10 @@ import org.sonar.server.computation.batch.BatchReportReaderImpl; import org.sonar.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.component.ReportComponent; +import org.sonar.server.computation.duplication.Duplicate; +import org.sonar.server.computation.duplication.Duplication; import org.sonar.server.computation.duplication.DuplicationRepositoryRule; +import org.sonar.server.computation.duplication.InnerDuplicate; import org.sonar.server.computation.duplication.TextBlock; import org.sonar.server.computation.scm.ScmInfoRepositoryImpl; import org.sonar.server.computation.source.SourceHashRepositoryImpl; @@ -141,9 +145,13 @@ public class PersistFileSourcesStepTest { LineData lineData = new LineData(); for (int line = 1; line <= NUMBER_OF_LINES; line++) { lineData.generateLineData(line); - - duplicationRepository.addDuplication(fileRef, new TextBlock(line, line), new TextBlock(line + 1, line + 1)); - + duplicationRepository.add( + fileRef, + new Duplication( + new TextBlock(line, line), + Arrays.asList(new InnerDuplicate(new TextBlock(line + 1, line + 1))) + ) + ); } writer.writeComponent(BatchReport.Component.newBuilder() .setRef(fileRef) diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepository.java b/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepository.java index 0bfc05a004a..a3f1721b3f3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepository.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepository.java @@ -19,7 +19,6 @@ */ package org.sonar.server.computation.duplication; -import java.util.Set; import org.sonar.server.computation.component.Component; /** @@ -34,58 +33,20 @@ import org.sonar.server.computation.component.Component; *

*/ public interface DuplicationRepository { - /** * Returns the duplications in the specified file {@link Component}, if any. * * @throws NullPointerException if {@code file} is {@code null} * @throws IllegalArgumentException if the type of the {@link Component} argument is not {@link Component.Type#FILE} */ - Set getDuplications(Component file); + Iterable getDuplications(Component file); /** - * Adds a project duplication of the specified original {@link TextBlock} in the specified file {@link Component} - * which is duplicated by the specified duplicate {@link TextBlock} in the same file. - *

- * This method can be called multiple times with the same {@code file} and {@code original} but a different - * {@code duplicate} to add multiple duplications of the same block. - *

- *

- * It must not, however, be called twice with {@code original} and {@code duplicate} swapped, this would raise - * an {@link IllegalArgumentException} as the duplication already exists. - *

+ * Adds a project duplication in the specified file {@link Component} to the repository. * * @throws NullPointerException if any argument is {@code null} * @throws IllegalArgumentException if the type of the {@link Component} argument is not {@link Component.Type#FILE} - * @throws IllegalStateException if {@code original} and {@code duplicate} are the same - * @throws IllegalStateException if the specified duplication already exists in the repository - */ - void addDuplication(Component file, TextBlock original, TextBlock duplicate); - - /** - * Adds a project duplication of the specified original {@link TextBlock} in the specified file {@link Component} as - * duplicated by the duplicate {@link TextBlock} in the other file {@link Component}. - *

- * Note: the reverse duplication relationship between files is not added automatically (which leaves open the - * possibility of inconsistent duplication information). This means that it is the responsibility of the repository's - * user to call this method again with the {@link Component} arguments and the {@link TextBlock} arguments swapped. - *

- * - * @throws NullPointerException if any argument is {@code null} - * @throws IllegalArgumentException if the type of any of the {@link Component} arguments is not {@link Component.Type#FILE} - * @throws IllegalArgumentException if {@code file} and {@code otherFile} are the same - * @throws IllegalStateException if the specified duplication already exists in the repository */ - void addDuplication(Component file, TextBlock original, Component otherFile, TextBlock duplicate); + void add(Component file, Duplication duplication); - /** - * Adds a cross-project duplication of the specified original {@link TextBlock} in the specified file {@link Component}, - * as duplicated by the specified duplicate {@link TextBlock} in a file of another project identified by its key. - * - * @throws NullPointerException if any argument is {@code null} - * @throws IllegalArgumentException if the type of the {@link Component} argument is not {@link Component.Type#FILE} - * @throws IllegalArgumentException if {@code otherFileKey} is the key of a file in the project - * @throws IllegalStateException if the specified duplication already exists in the repository - */ - void addDuplication(Component file, TextBlock original, String otherFileKey, TextBlock duplicate); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepositoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepositoryImpl.java index bd3eeb32363..8659661d5fc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepositoryImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/DuplicationRepositoryImpl.java @@ -19,369 +19,44 @@ */ package org.sonar.server.computation.duplication; -import com.google.common.base.Function; -import com.google.common.base.Optional; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; import org.sonar.server.computation.component.Component; -import org.sonar.server.computation.component.TreeRootHolder; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.FluentIterable.from; import static java.util.Objects.requireNonNull; /** * In-memory implementation of {@link DuplicationRepository}. */ public class DuplicationRepositoryImpl implements DuplicationRepository { - private final TreeRootHolder treeRootHolder; - private final Map duplicationsByComponentUuid = new HashMap<>(); - - public DuplicationRepositoryImpl(TreeRootHolder treeRootHolder) { - this.treeRootHolder = treeRootHolder; - } - - @Override - public Set getDuplications(Component file) { - checkFileComponentArgument(file); - - Duplications duplications = duplicationsByComponentUuid.get(file.getUuid()); - if (duplications != null) { - return from(duplications.getDuplicates()) - .transform(DuplicatesEntryToDuplication.INSTANCE) - .toSet(); - } - return Collections.emptySet(); - } - - @Override - public void addDuplication(Component file, TextBlock original, TextBlock duplicate) { - checkFileComponentArgument(file); - checkOriginalTextBlockArgument(original); - checkDuplicateTextBlockArgument(duplicate); - checkArgument(!original.equals(duplicate), "original and duplicate TextBlocks can not be the same"); - - Optional duplicates = getDuplicates(file, original); - if (duplicates.isPresent()) { - checkDuplicationAlreadyExists(duplicates, file, original, duplicate); - checkReverseDuplicationAlreadyExists(file, original, duplicate); - - duplicates.get().add(duplicate); - } else { - checkReverseDuplicationAlreadyExists(file, original, duplicate); - getOrCreate(file, original).add(duplicate); - } - } + private Multimap duplications = HashMultimap.create(); @Override - public void addDuplication(Component file, TextBlock original, Component otherFile, TextBlock duplicate) { + public Iterable getDuplications(Component file) { checkFileComponentArgument(file); - checkOriginalTextBlockArgument(original); - checkComponentArgument(otherFile, "otherFile"); - checkDuplicateTextBlockArgument(duplicate); - checkArgument(!file.equals(otherFile), "file and otherFile Components can not be the same"); - Optional duplicates = getDuplicates(file, original); - if (duplicates.isPresent()) { - checkDuplicationAlreadyExists(duplicates, file, original, otherFile, duplicate); - - duplicates.get().add(otherFile, duplicate); - } else { - getOrCreate(file, original).add(otherFile, duplicate); + Collection duplications = this.duplications.asMap().get(file.getKey()); + if (duplications == null) { + return Collections.emptyList(); } + return duplications; } @Override - public void addDuplication(Component file, TextBlock original, String otherFileKey, TextBlock duplicate) { + public void add(Component file, Duplication duplication) { checkFileComponentArgument(file); - checkOriginalTextBlockArgument(original); - requireNonNull(otherFileKey, "otherFileKey can not be null"); - checkDuplicateTextBlockArgument(duplicate); - checkArgument(!treeRootHolder.hasComponentWithKey(otherFileKey), "otherFileKey '%s' can not be the key of a Component in the project", otherFileKey); - - Optional duplicates = getDuplicates(file, original); - if (duplicates.isPresent()) { - checkDuplicationAlreadyExists(duplicates, file, original, otherFileKey, duplicate); - - duplicates.get().add(otherFileKey, duplicate); - } else { - getOrCreate(file, original).add(otherFileKey, duplicate); - } - } - - private Optional getDuplicates(Component file, TextBlock original) { - Duplications duplications = duplicationsByComponentUuid.get(file.getUuid()); - if (duplications == null) { - return Optional.absent(); - } - return duplications.get(original); - } - - private void checkDuplicationAlreadyExists(Optional duplicates, Component file, TextBlock original, TextBlock duplicate) { - checkArgument(!duplicates.get().hasDuplicate(duplicate), - "Inner duplicate %s is already associated to original %s in file %s", duplicate, original, file.getKey()); - } - - private void checkReverseDuplicationAlreadyExists(Component file, TextBlock original, TextBlock duplicate) { - Optional reverseDuplication = getDuplicates(file, duplicate); - if (reverseDuplication.isPresent()) { - checkArgument(!reverseDuplication.get().hasDuplicate(original), - "Inner duplicate %s is already associated to original %s in file %s", duplicate, original, file.getKey()); - } - } - - private static void checkDuplicationAlreadyExists(Optional duplicates, Component file, TextBlock original, Component otherFile, TextBlock duplicate) { - checkArgument(!duplicates.get().hasDuplicate(otherFile, duplicate), - "In-project duplicate %s in file %s is already associated to original %s in file %s", duplicate, otherFile.getKey(), original, file.getKey()); - } - - private static void checkDuplicationAlreadyExists(Optional duplicates, Component file, TextBlock original, String otherFileKey, TextBlock duplicate) { - checkArgument(!duplicates.get().hasDuplicate(otherFileKey, duplicate), - "Cross-project duplicate %s in file %s is already associated to original %s in file %s", duplicate, otherFileKey, original, file.getKey()); - } - - private Duplicates getOrCreate(Component file, TextBlock original) { - Duplications duplications = duplicationsByComponentUuid.get(file.getUuid()); - if (duplications == null) { - duplications = new Duplications(); - duplicationsByComponentUuid.put(file.getUuid(), duplications); - } - - return duplications.getOrCreate(original); - } - - /** - * Represents the location of the file of one or more duplicate {@link TextBlock}. - */ - private interface DuplicateLocation { - - } - - private enum InnerDuplicationLocation implements DuplicateLocation { - INSTANCE - } - - @Immutable - private static final class InProjectDuplicationLocation implements DuplicateLocation { - private final Component component; - - public InProjectDuplicationLocation(Component component) { - this.component = component; - } - - public Component getComponent() { - return component; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - InProjectDuplicationLocation that = (InProjectDuplicationLocation) o; - return component.equals(that.component); - } - - @Override - public int hashCode() { - return component.hashCode(); - } - } - - @Immutable - private static final class CrossProjectDuplicationLocation implements DuplicateLocation { - private final String fileKey; - - private CrossProjectDuplicationLocation(String fileKey) { - this.fileKey = fileKey; - } - - public String getFileKey() { - return fileKey; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - CrossProjectDuplicationLocation that = (CrossProjectDuplicationLocation) o; - return fileKey.equals(that.fileKey); - } - - @Override - public int hashCode() { - return fileKey.hashCode(); - } - } - - private static final class Duplications { - private final Map duplicatesByTextBlock = new HashMap<>(); - - public Duplicates getOrCreate(TextBlock textBlock) { - Duplicates duplicates = duplicatesByTextBlock.get(textBlock); - if (duplicates != null) { - return duplicates; - } - Duplicates res = new Duplicates(); - duplicatesByTextBlock.put(textBlock, res); - return res; - } - - public Set> getDuplicates() { - return duplicatesByTextBlock.entrySet(); - } - - public Optional get(TextBlock original) { - return Optional.fromNullable(duplicatesByTextBlock.get(original)); - } - } - - private static final class Duplicates { - private final Map> duplicatesByLocation = new HashMap<>(); - - public Iterable getDuplicates() { - return from(duplicatesByLocation.entrySet()) - .transformAndConcat(MapEntryToDuplicateWithLocation.INSTANCE); - } - - public void add(TextBlock duplicate) { - add(InnerDuplicationLocation.INSTANCE, duplicate); - } - - public void add(Component otherFile, TextBlock duplicate) { - InProjectDuplicationLocation key = new InProjectDuplicationLocation(otherFile); - add(key, duplicate); - } - - public void add(String otherFileKey, TextBlock duplicate) { - CrossProjectDuplicationLocation key = new CrossProjectDuplicationLocation(otherFileKey); - add(key, duplicate); - } - - private void add(DuplicateLocation duplicateLocation, TextBlock duplicate) { - Set textBlocks = duplicatesByLocation.get(duplicateLocation); - if (textBlocks == null) { - textBlocks = new HashSet<>(1); - duplicatesByLocation.put(duplicateLocation, textBlocks); - } - textBlocks.add(duplicate); - } - - public boolean hasDuplicate(TextBlock duplicate) { - return containsEntry(InnerDuplicationLocation.INSTANCE, duplicate); - } - - public boolean hasDuplicate(Component otherFile, TextBlock duplicate) { - return containsEntry(new InProjectDuplicationLocation(otherFile), duplicate); - } - - public boolean hasDuplicate(String otherFileKey, TextBlock duplicate) { - return containsEntry(new CrossProjectDuplicationLocation(otherFileKey), duplicate); - } - - private boolean containsEntry(DuplicateLocation duplicateLocation, TextBlock duplicate) { - Set textBlocks = duplicatesByLocation.get(duplicateLocation); - return textBlocks != null && textBlocks.contains(duplicate); - } - - private enum MapEntryToDuplicateWithLocation implements Function>, Iterable> { - INSTANCE; - - @Override - @Nonnull - public Iterable apply(@Nonnull final Map.Entry> entry) { - return from(entry.getValue()) - .transform(new Function() { - @Override - @Nonnull - public DuplicateWithLocation apply(@Nonnull TextBlock input) { - return new DuplicateWithLocation(entry.getKey(), input); - } - }); - } - } - } - - @Immutable - private static final class DuplicateWithLocation { - private final DuplicateLocation location; - private final TextBlock duplicate; - - private DuplicateWithLocation(DuplicateLocation location, TextBlock duplicate) { - this.location = location; - this.duplicate = duplicate; - } - - public DuplicateLocation getLocation() { - return location; - } - - public TextBlock getDuplicate() { - return duplicate; - } + checkNotNull(duplication, "duplication can not be null"); + duplications.put(file.getKey(), duplication); } private static void checkFileComponentArgument(Component file) { - checkComponentArgument(file, "file"); - } - - private static void checkComponentArgument(Component file, String argName) { - checkNotNull(file, "%s can not be null", argName); - checkArgument(file.getType() == Component.Type.FILE, "type of %s argument must be FILE", argName); - } - - private static void checkDuplicateTextBlockArgument(TextBlock duplicate) { - requireNonNull(duplicate, "duplicate can not be null"); - } - - private static void checkOriginalTextBlockArgument(TextBlock original) { - requireNonNull(original, "original can not be null"); + requireNonNull(file, "file can not be null"); + checkArgument(file.getType() == Component.Type.FILE, "type of file must be FILE"); } - private enum DuplicatesEntryToDuplication implements Function, Duplication> { - INSTANCE; - - @Override - @Nonnull - public Duplication apply(@Nonnull Map.Entry entry) { - return new Duplication( - entry.getKey(), - from(entry.getValue().getDuplicates()).transform(DuplicateLocationEntryToDuplicate.INSTANCE)); - } - - private enum DuplicateLocationEntryToDuplicate implements Function { - INSTANCE; - - @Override - @Nonnull - public Duplicate apply(@Nonnull DuplicateWithLocation input) { - DuplicateLocation duplicateLocation = input.getLocation(); - if (duplicateLocation instanceof InnerDuplicationLocation) { - return new InnerDuplicate(input.getDuplicate()); - } - if (duplicateLocation instanceof InProjectDuplicationLocation) { - return new InProjectDuplicate(((InProjectDuplicationLocation) duplicateLocation).getComponent(), input.getDuplicate()); - } - if (duplicateLocation instanceof CrossProjectDuplicationLocation) { - return new CrossProjectDuplicate(((CrossProjectDuplicationLocation) duplicateLocation).getFileKey(), input.getDuplicate()); - } - throw new IllegalArgumentException("Unsupported DuplicationLocation type " + duplicateLocation.getClass()); - } - } - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/IntegrateCrossProjectDuplications.java b/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/IntegrateCrossProjectDuplications.java index efee7c5193d..2b2291a8685 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/IntegrateCrossProjectDuplications.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/duplication/IntegrateCrossProjectDuplications.java @@ -20,7 +20,9 @@ package org.sonar.server.computation.duplication; +import com.google.common.base.Function; import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -91,21 +93,23 @@ public class IntegrateCrossProjectDuplications { private void addDuplication(Component file, CloneGroup duplication) { ClonePart originPart = duplication.getOriginPart(); - TextBlock originTextBlock = new TextBlock(originPart.getStartLine(), originPart.getEndLine()); - int clonePartCount = 0; - for (ClonePart part : from(duplication.getCloneParts()) - .filter(new DoesNotMatchSameComponentKey(originPart.getResourceId()))) { - clonePartCount++; - if (clonePartCount > MAX_CLONE_PART_PER_GROUP) { - LOGGER.warn("Too many duplication references on file {} for block at line {}. Keeping only the first {} references.", - file.getKey(), originPart.getStartLine(), MAX_CLONE_PART_PER_GROUP); - break; - } - duplicationRepository.addDuplication(file, originTextBlock, part.getResourceId(), - new TextBlock(part.getStartLine(), part.getEndLine())); + Iterable duplicates = convertClonePartsToDuplicates(file, duplication); + if (!Iterables.isEmpty(duplicates)) { + duplicationRepository.add( + file, + new Duplication(new TextBlock(originPart.getStartLine(), originPart.getEndLine()), duplicates) + ); } } + private static Iterable convertClonePartsToDuplicates(final Component file, CloneGroup duplication) { + final ClonePart originPart = duplication.getOriginPart(); + return from(duplication.getCloneParts()) + .filter(new DoesNotMatchSameComponentKey(originPart.getResourceId())) + .filter(new DuplicateLimiter(file, originPart)) + .transform(ClonePartToCrossProjectDuplicate.INSTANCE); + } + private NumberOfUnitsNotLessThan getNumberOfUnitsNotLessThan(String language) { NumberOfUnitsNotLessThan numberOfUnitsNotLessThan = numberOfUnitsByLanguage.get(language); if (numberOfUnitsNotLessThan == null) { @@ -153,4 +157,35 @@ public class IntegrateCrossProjectDuplications { } } + private static class DuplicateLimiter implements Predicate { + private final Component file; + private final ClonePart originPart; + private int counter = 0; + + public DuplicateLimiter(Component file, ClonePart originPart) { + this.file = file; + this.originPart = originPart; + } + + @Override + public boolean apply(@Nonnull ClonePart input) { + if (counter == MAX_CLONE_PART_PER_GROUP) { + LOGGER.warn("Too many duplication references on file {} for block at line {}. Keeping only the first {} references.", + file.getKey(), originPart.getStartLine(), MAX_CLONE_PART_PER_GROUP); + } + return counter++ <= MAX_CLONE_GROUP_PER_FILE; + } + } + + private enum ClonePartToCrossProjectDuplicate implements Function { + INSTANCE; + + @Override + @Nonnull + public Duplicate apply(@Nonnull ClonePart input) { + return new CrossProjectDuplicate( + input.getResourceId(), + new TextBlock(input.getStartLine(), input.getEndLine())); + } + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/source/DuplicationLineReader.java b/server/sonar-server/src/main/java/org/sonar/server/computation/source/DuplicationLineReader.java index a6a888731a4..2c655572357 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/source/DuplicationLineReader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/source/DuplicationLineReader.java @@ -24,11 +24,9 @@ import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Ordering; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.sonar.db.protobuf.DbFileSources; @@ -37,12 +35,13 @@ import org.sonar.server.computation.duplication.InnerDuplicate; import org.sonar.server.computation.duplication.TextBlock; import static com.google.common.collect.FluentIterable.from; +import static com.google.common.collect.Iterables.size; public class DuplicationLineReader implements LineReader { private final Map duplicatedTextBlockIndexByTextBlock; - public DuplicationLineReader(Set duplications) { + public DuplicationLineReader(Iterable duplications) { this.duplicatedTextBlockIndexByTextBlock = createIndexOfDuplicatedTextBlocks(duplications); } @@ -70,7 +69,7 @@ public class DuplicationLineReader implements LineReader { * index. It avoids false detections of changes in {@link DbFileSources.Line#getDuplicationList()}. *

*/ - private static Map createIndexOfDuplicatedTextBlocks(Collection duplications) { + private static Map createIndexOfDuplicatedTextBlocks(Iterable duplications) { List duplicatedTextBlocks = extractAllDuplicatedTextBlocks(duplications); Collections.sort(duplicatedTextBlocks); return from(duplicatedTextBlocks) @@ -84,10 +83,10 @@ public class DuplicationLineReader implements LineReader { * The returned list is mutable on purpose because it will be sorted. *

* - * @see {@link #createIndexOfDuplicatedTextBlocks(Collection)} + * @see {@link #createIndexOfDuplicatedTextBlocks(Iterable)} */ - private static List extractAllDuplicatedTextBlocks(Collection duplications) { - List duplicatedBlock = new ArrayList<>(duplications.size()); + private static List extractAllDuplicatedTextBlocks(Iterable duplications) { + List duplicatedBlock = new ArrayList<>(size(duplications)); for (Duplication duplication : duplications) { duplicatedBlock.add(duplication.getOriginal()); for (InnerDuplicate duplicate : from(duplication.getDuplicates()).filter(InnerDuplicate.class)) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/DuplicationDataMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/DuplicationDataMeasuresStep.java index 0fbe43ff660..4cba9340e92 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/DuplicationDataMeasuresStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/DuplicationDataMeasuresStep.java @@ -20,7 +20,6 @@ package org.sonar.server.computation.step; -import java.util.Set; import org.apache.commons.lang.StringEscapeUtils; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.component.CrawlerDepthLimit; @@ -39,6 +38,7 @@ import org.sonar.server.computation.measure.MeasureRepository; import org.sonar.server.computation.metric.Metric; import org.sonar.server.computation.metric.MetricRepository; +import static com.google.common.collect.Iterables.isEmpty; import static org.sonar.api.measures.CoreMetrics.DUPLICATIONS_DATA_KEY; import static org.sonar.server.computation.component.ComponentVisitor.Order.PRE_ORDER; @@ -75,8 +75,8 @@ public class DuplicationDataMeasuresStep implements ComputationStep { @Override public void visitFile(Component file) { - Set duplications = duplicationRepository.getDuplications(file); - if (!duplications.isEmpty()) { + Iterable duplications = duplicationRepository.getDuplications(file); + if (!isEmpty(duplications)) { computeDuplications(file, duplications); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/DuplicationMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/DuplicationMeasuresStep.java index 897d7f231b7..d7b448510d9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/DuplicationMeasuresStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/DuplicationMeasuresStep.java @@ -45,6 +45,7 @@ import org.sonar.server.computation.metric.Metric; import org.sonar.server.computation.metric.MetricRepository; import static com.google.common.collect.FluentIterable.from; +import static com.google.common.collect.Iterables.isEmpty; import static java.util.Objects.requireNonNull; import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_KEY; import static org.sonar.api.measures.CoreMetrics.DUPLICATED_BLOCKS_KEY; @@ -123,9 +124,9 @@ public class DuplicationMeasuresStep implements ComputationStep { } private void initializeForFile(Component file) { - Set duplications = requireNonNull(this.duplicationRepository, "DuplicationRepository missing") + Iterable duplications = requireNonNull(this.duplicationRepository, "DuplicationRepository missing") .getDuplications(file); - if (duplications.isEmpty()) { + if (isEmpty(duplications)) { return; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStep.java index 6d0927f9f71..540fc96fa07 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStep.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStep.java @@ -19,6 +19,8 @@ */ package org.sonar.server.computation.step; +import com.google.common.base.Function; +import javax.annotation.Nonnull; import org.sonar.batch.protocol.output.BatchReport; import org.sonar.core.util.CloseableIterator; import org.sonar.server.computation.batch.BatchReportReader; @@ -28,9 +30,15 @@ import org.sonar.server.computation.component.DepthTraversalTypeAwareCrawler; import org.sonar.server.computation.component.TreeRootHolder; import org.sonar.server.computation.component.TypeAwareVisitorAdapter; import org.sonar.server.computation.duplication.DetailedTextBlock; +import org.sonar.server.computation.duplication.Duplicate; +import org.sonar.server.computation.duplication.Duplication; import org.sonar.server.computation.duplication.DuplicationRepository; +import org.sonar.server.computation.duplication.InProjectDuplicate; +import org.sonar.server.computation.duplication.InnerDuplicate; import org.sonar.server.computation.duplication.TextBlock; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.FluentIterable.from; import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER; /** @@ -72,14 +80,12 @@ public class LoadDuplicationsFromReportStep implements ComputationStep { } private void loadDuplications(Component file, BatchReport.Duplication duplication, int id) { - DetailedTextBlock original = convert(duplication.getOriginPosition(), id); - for (BatchReport.Duplicate duplicate : duplication.getDuplicateList()) { - if (duplicate.hasOtherFileRef()) { - duplicationRepository.addDuplication(file, original, treeRootHolder.getComponentByRef(duplicate.getOtherFileRef()), convert(duplicate.getRange())); - } else { - duplicationRepository.addDuplication(file, original, convert(duplicate.getRange())); - } - } + duplicationRepository.add(file, + new Duplication( + convert(duplication.getOriginPosition(), id), + from(duplication.getDuplicateList()) + .transform(new BatchDuplicateToCeDuplicate(file)) + )); } private static TextBlock convert(BatchReport.TextRange textRange) { @@ -89,4 +95,24 @@ public class LoadDuplicationsFromReportStep implements ComputationStep { private static DetailedTextBlock convert(BatchReport.TextRange textRange, int id) { return new DetailedTextBlock(id, textRange.getStartLine(), textRange.getEndLine()); } + + private class BatchDuplicateToCeDuplicate implements Function { + private final Component file; + + private BatchDuplicateToCeDuplicate(Component file) { + this.file = file; + } + + @Override + @Nonnull + public Duplicate apply(@Nonnull BatchReport.Duplicate input) { + if (input.hasOtherFileRef()) { + checkArgument(input.getOtherFileRef() != file.getReportAttributes().getRef(), "file and otherFile references can not be the same"); + return new InProjectDuplicate( + treeRootHolder.getComponentByRef(input.getOtherFileRef()), + convert(input.getRange())); + } + return new InnerDuplicate(convert(input.getRange())); + } + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryImplTest.java index 65cab2715b5..72e4723aab4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryImplTest.java @@ -23,12 +23,10 @@ import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.util.Arrays; -import java.util.Set; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; -import org.sonar.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.component.ReportComponent; import org.sonar.server.util.WrapInSingleElementArray; @@ -36,30 +34,20 @@ import org.sonar.server.util.WrapInSingleElementArray; import static com.google.common.base.Predicates.equalTo; import static com.google.common.base.Predicates.not; import static com.google.common.collect.FluentIterable.from; -import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(DataProviderRunner.class) public class DuplicationRepositoryImplTest { - @Rule - public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); - private static final Component FILE_COMPONENT_1 = ReportComponent.builder(Component.Type.FILE, 1).build(); private static final Component FILE_COMPONENT_2 = ReportComponent.builder(Component.Type.FILE, 2).build(); - private static final Component FILE_COMPONENT_3 = ReportComponent.builder(Component.Type.FILE, 3).build(); - private static final TextBlock ORIGINAL_TEXTBLOCK = new TextBlock(1, 2); - private static final TextBlock COPY_OF_ORIGINAL_TEXTBLOCK = new TextBlock(1, 2); - private static final TextBlock DUPLICATE_TEXTBLOCK_1 = new TextBlock(15, 15); - private static final TextBlock DUPLICATE_TEXTBLOCK_2 = new TextBlock(15, 16); - private static final String FILE_KEY_1 = "1"; - private static final String FILE_KEY_2 = "2"; + private static final Duplication SOME_DUPLICATION = createDuplication(1, 2); @Rule public ExpectedException expectedException = ExpectedException.none(); - private DuplicationRepository underTest = new DuplicationRepositoryImpl(treeRootHolder); + private DuplicationRepository underTest = new DuplicationRepositoryImpl(); @Test public void getDuplications_throws_NPE_if_Component_argument_is_null() { @@ -84,24 +72,18 @@ public class DuplicationRepositoryImplTest { } @Test - public void addDuplication_inner_throws_NPE_if_file_argument_is_null() { + public void add_throws_NPE_if_file_argument_is_null() { expectFileArgumentNPE(); - underTest.addDuplication(null, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_inner_throws_NPE_if_original_argument_is_null() { - expectOriginalArgumentNPE(); - - underTest.addDuplication(FILE_COMPONENT_1, null, DUPLICATE_TEXTBLOCK_1); + underTest.add(null, SOME_DUPLICATION); } @Test - public void addDuplication_inner_throws_NPE_if_duplicate_argument_is_null() { - expectDuplicateArgumentNPE(); + public void addDuplication_inner_throws_NPE_if_duplication_argument_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("duplication can not be null"); - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, null); + underTest.add(FILE_COMPONENT_1, null); } @Test @@ -111,317 +93,46 @@ public class DuplicationRepositoryImplTest { Component component = mockComponentGetType(type); - underTest.addDuplication(component, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1); + underTest.add(component, SOME_DUPLICATION); } @Test - public void addDuplication_inner_throws_IAE_if_original_and_duplicate_are_equal() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("original and duplicate TextBlocks can not be the same"); + public void added_duplication_is_returned_as_is_by_getDuplications() { + underTest.add(FILE_COMPONENT_1, SOME_DUPLICATION); - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, COPY_OF_ORIGINAL_TEXTBLOCK); - } - - @Test - public void addDuplication_inner_throws_IAE_if_duplication_already_exists() { - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage(format( - "Inner duplicate %s is already associated to original %s in file %s", - DUPLICATE_TEXTBLOCK_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_1.getKey())); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_inner_throws_IAE_if_reverse_duplication_already_exists() { - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage(format( - "Inner duplicate %s is already associated to original %s in file %s", - ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1, FILE_COMPONENT_1.getKey())); - - underTest.addDuplication(FILE_COMPONENT_1, DUPLICATE_TEXTBLOCK_1, ORIGINAL_TEXTBLOCK); - } - - @Test - public void addDuplication_inner_throws_IAE_if_reverse_duplication_already_exists_and_duplicate_has_duplicates_of_its_own() { - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1); - underTest.addDuplication(FILE_COMPONENT_1, DUPLICATE_TEXTBLOCK_1, DUPLICATE_TEXTBLOCK_2); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage(format( - "Inner duplicate %s is already associated to original %s in file %s", - ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1, FILE_COMPONENT_1.getKey())); - - underTest.addDuplication(FILE_COMPONENT_1, DUPLICATE_TEXTBLOCK_1, ORIGINAL_TEXTBLOCK); - } - - @Test - public void addDuplication_inner_is_returned_by_getDuplications() { - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1); - - Set duplications = underTest.getDuplications(FILE_COMPONENT_1); + Iterable duplications = underTest.getDuplications(FILE_COMPONENT_1); assertThat(duplications).hasSize(1); - assertDuplication( - duplications.iterator().next(), - ORIGINAL_TEXTBLOCK, - new InnerDuplicate(DUPLICATE_TEXTBLOCK_1)); + assertThat(duplications.iterator().next()).isSameAs(SOME_DUPLICATION); assertNoDuplication(FILE_COMPONENT_2); } @Test - public void addDuplication_inner_called_multiple_times_populate_a_single_Duplication() { - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_2); - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, DUPLICATE_TEXTBLOCK_1); + public void added_duplication_does_not_avoid_same_duplication_inserted_twice_but_only_one_is_returned() { + underTest.add(FILE_COMPONENT_1, SOME_DUPLICATION); + underTest.add(FILE_COMPONENT_1, SOME_DUPLICATION); - Set duplications = underTest.getDuplications(FILE_COMPONENT_1); + Iterable duplications = underTest.getDuplications(FILE_COMPONENT_1); assertThat(duplications).hasSize(1); - assertDuplication( - duplications.iterator().next(), - ORIGINAL_TEXTBLOCK, - new InnerDuplicate(DUPLICATE_TEXTBLOCK_1), new InnerDuplicate(DUPLICATE_TEXTBLOCK_2)); + assertThat(duplications.iterator().next()).isSameAs(SOME_DUPLICATION); assertNoDuplication(FILE_COMPONENT_2); } @Test - public void addDuplication_inProject_throws_NPE_if_file_argument_is_null() { - expectFileArgumentNPE(); - - underTest.addDuplication(null, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_inProject_throws_NPE_if_original_argument_is_null() { - expectOriginalArgumentNPE(); + public void added_duplications_are_returned_in_any_order_and_associated_to_the_right_file() { + underTest.add(FILE_COMPONENT_1, SOME_DUPLICATION); + underTest.add(FILE_COMPONENT_1, createDuplication(2, 4)); + underTest.add(FILE_COMPONENT_1, createDuplication(2, 3)); + underTest.add(FILE_COMPONENT_2, createDuplication(2, 3)); + underTest.add(FILE_COMPONENT_2, createDuplication(1, 2)); - underTest.addDuplication(FILE_COMPONENT_1, null, FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_1); + assertThat(underTest.getDuplications(FILE_COMPONENT_1)).containsOnly(SOME_DUPLICATION, createDuplication(2, 3), createDuplication(2, 4)); + assertThat(underTest.getDuplications(FILE_COMPONENT_2)).containsOnly(createDuplication(1, 2), createDuplication(2, 3)); } - @Test - public void addDuplication_inProject_throws_NPE_if_otherFile_argument_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("otherFile can not be null"); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, (Component) null, DUPLICATE_TEXTBLOCK_1); - } - - @Test - @UseDataProvider("allComponentTypesButFile") - public void addDuplication_inProject_throws_NPE_if_otherFile_type_is_not_FILE(Component.Type type) { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("type of otherFile argument must be FILE"); - - Component component = mockComponentGetType(type); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, component, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_inProject_throws_NPE_if_duplicate_argument_is_null() { - expectDuplicateArgumentNPE(); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_2, null); - } - - @Test - @UseDataProvider("allComponentTypesButFile") - public void addDuplication_inProject_throws_NPE_if_file_type_is_not_FILE(Component.Type type) { - expectFileTypeIAE(); - - Component component = mockComponentGetType(type); - - underTest.addDuplication(component, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_inProject_throws_NPE_if_file_and_otherFile_are_the_same() { - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("file and otherFile Components can not be the same"); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_1, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_inProject_throws_IAE_if_duplication_already_exists() { - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_1); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage(format( - "In-project duplicate %s in file %s is already associated to original %s in file %s", - DUPLICATE_TEXTBLOCK_1, FILE_COMPONENT_2.getKey(), ORIGINAL_TEXTBLOCK, FILE_COMPONENT_1.getKey())); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_inProject_is_returned_by_getDuplications() { - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_1); - - Set duplications = underTest.getDuplications(FILE_COMPONENT_1); - assertThat(duplications).hasSize(1); - assertDuplication( - duplications.iterator().next(), - ORIGINAL_TEXTBLOCK, - new InProjectDuplicate(FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_1)); - - assertNoDuplication(FILE_COMPONENT_2); - } - - @Test - public void addDuplication_inProject_called_multiple_times_populate_a_single_Duplication() { - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_2); - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_1); - - Set duplications = underTest.getDuplications(FILE_COMPONENT_1); - assertThat(duplications).hasSize(1); - assertDuplication( - duplications.iterator().next(), - ORIGINAL_TEXTBLOCK, - new InProjectDuplicate(FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_1), new InProjectDuplicate(FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_2)); - - assertNoDuplication(FILE_COMPONENT_2); - } - - @Test - public void addDuplication_inProject_called_multiple_times_with_different_components_populate_a_single_Duplication() { - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_2); - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_3, DUPLICATE_TEXTBLOCK_2); - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_3, DUPLICATE_TEXTBLOCK_1); - - Set duplications = underTest.getDuplications(FILE_COMPONENT_1); - assertThat(duplications).hasSize(1); - assertDuplication( - duplications.iterator().next(), - ORIGINAL_TEXTBLOCK, - new InProjectDuplicate(FILE_COMPONENT_2, DUPLICATE_TEXTBLOCK_2), new InProjectDuplicate(FILE_COMPONENT_3, DUPLICATE_TEXTBLOCK_1), new InProjectDuplicate(FILE_COMPONENT_3, - DUPLICATE_TEXTBLOCK_2)); - - assertNoDuplication(FILE_COMPONENT_2); - assertNoDuplication(FILE_COMPONENT_3); - } - - @Test - public void addDuplication_crossProject_throws_NPE_if_file_argument_is_null() { - expectFileArgumentNPE(); - - underTest.addDuplication(null, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_crossProject_throws_NPE_if_original_argument_is_null() { - expectOriginalArgumentNPE(); - - underTest.addDuplication(FILE_COMPONENT_1, null, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_crossProject_throws_NPE_if_otherFileKey_argument_is_null() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("otherFileKey can not be null"); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, (String) null, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_crossProject_throws_NPE_if_duplicate_argument_is_null() { - expectDuplicateArgumentNPE(); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, null); - } - - @Test - public void addDuplication_crossProject_throws_IAE_if_otherFileKey_is_key_of_Component_in_the_project() { - treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 999) - .addChildren( - FILE_COMPONENT_1, FILE_COMPONENT_2 - ) - .build()); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage(format("otherFileKey '%s' can not be the key of a Component in the project", FILE_COMPONENT_2.getKey())); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_2.getKey(), DUPLICATE_TEXTBLOCK_1); - } - - @Test - @UseDataProvider("allComponentTypesButFile") - public void addDuplication_crossProject_throws_NPE_if_file_type_is_not_FILE(Component.Type type) { - expectFileTypeIAE(); - - Component component = mockComponentGetType(type); - - underTest.addDuplication(component, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_crossProject_throws_IAE_if_duplication_already_exists() { - treeRootHolder.setRoot(FILE_COMPONENT_1); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage(format( - "Cross-project duplicate %s in file %s is already associated to original %s in file %s", - DUPLICATE_TEXTBLOCK_1, FILE_KEY_1, ORIGINAL_TEXTBLOCK, FILE_COMPONENT_1.getKey())); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1); - } - - @Test - public void addDuplication_crossProject_is_returned_by_getDuplications() { - treeRootHolder.setRoot(FILE_COMPONENT_1); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1); - - Set duplications = underTest.getDuplications(FILE_COMPONENT_1); - assertThat(duplications).hasSize(1); - assertDuplication( - duplications.iterator().next(), - ORIGINAL_TEXTBLOCK, - new CrossProjectDuplicate(FILE_KEY_1, DUPLICATE_TEXTBLOCK_1)); - - assertNoDuplication(FILE_COMPONENT_2); - } - - @Test - public void addDuplication_crossProject_called_multiple_times_populate_a_single_Duplication() { - treeRootHolder.setRoot(FILE_COMPONENT_1); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_2); - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_1); - - Set duplications = underTest.getDuplications(FILE_COMPONENT_1); - assertThat(duplications).hasSize(1); - assertDuplication( - duplications.iterator().next(), - ORIGINAL_TEXTBLOCK, - new CrossProjectDuplicate(FILE_KEY_1, DUPLICATE_TEXTBLOCK_1), new CrossProjectDuplicate(FILE_KEY_1, DUPLICATE_TEXTBLOCK_2)); - - assertNoDuplication(FILE_COMPONENT_2); - } - - @Test - public void addDuplication_crossProject_called_multiple_times_with_different_fileKeys_populate_a_single_Duplication() { - treeRootHolder.setRoot(FILE_COMPONENT_1); - - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_1, DUPLICATE_TEXTBLOCK_2); - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_2, DUPLICATE_TEXTBLOCK_2); - underTest.addDuplication(FILE_COMPONENT_1, ORIGINAL_TEXTBLOCK, FILE_KEY_2, DUPLICATE_TEXTBLOCK_1); - - Set duplications = underTest.getDuplications(FILE_COMPONENT_1); - assertThat(duplications).hasSize(1); - assertDuplication( - duplications.iterator().next(), - ORIGINAL_TEXTBLOCK, - new CrossProjectDuplicate(FILE_KEY_1, DUPLICATE_TEXTBLOCK_2), new CrossProjectDuplicate(FILE_KEY_2, DUPLICATE_TEXTBLOCK_1), new CrossProjectDuplicate(FILE_KEY_2, - DUPLICATE_TEXTBLOCK_2)); - - assertNoDuplication(FILE_COMPONENT_2); + private static Duplication createDuplication(int originalLine, int duplicateLine) { + return new Duplication(new TextBlock(originalLine, originalLine), Arrays.asList(new InnerDuplicate(new TextBlock(duplicateLine, duplicateLine)))); } @DataProvider @@ -432,11 +143,6 @@ public class DuplicationRepositoryImplTest { .toArray(Object[].class); } - private static void assertDuplication(Duplication duplication, TextBlock original, Duplicate... duplicates) { - assertThat(duplication.getOriginal()).isEqualTo(original); - assertThat(duplication.getDuplicates()).containsExactly(duplicates); - } - private void assertNoDuplication(Component component) { assertThat(underTest.getDuplications(component)).isEmpty(); } @@ -446,19 +152,9 @@ public class DuplicationRepositoryImplTest { expectedException.expectMessage("file can not be null"); } - private void expectOriginalArgumentNPE() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("original can not be null"); - } - - private void expectDuplicateArgumentNPE() { - expectedException.expect(NullPointerException.class); - expectedException.expectMessage("duplicate can not be null"); - } - private void expectFileTypeIAE() { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("type of file argument must be FILE"); + expectedException.expectMessage("type of file must be FILE"); } private Component mockComponentGetType(Component.Type type) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryRule.java index 9feb25dcbf8..8d8ae1d2369 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryRule.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/DuplicationRepositoryRule.java @@ -19,7 +19,12 @@ */ package org.sonar.server.computation.duplication; -import java.util.Set; +import com.google.common.base.Function; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import java.util.Arrays; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import org.junit.rules.ExternalResource; import org.sonar.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; @@ -27,78 +32,139 @@ import org.sonar.server.computation.component.ComponentProvider; import org.sonar.server.computation.component.TreeRootHolder; import org.sonar.server.computation.component.TreeRootHolderComponentProvider; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.FluentIterable.from; +import static java.util.Objects.requireNonNull; + public class DuplicationRepositoryRule extends ExternalResource implements DuplicationRepository { - private final TreeRootHolder treeRootHolder; + @CheckForNull private final ComponentProvider componentProvider; private DuplicationRepositoryImpl delegate; + private final Multimap componentRefsWithInnerDuplications = ArrayListMultimap.create(); + private final Multimap componentRefsWithInProjectDuplications = ArrayListMultimap.create(); + private final Multimap componentRefsWithCrossProjectDuplications = ArrayListMultimap.create(); private DuplicationRepositoryRule(TreeRootHolder treeRootHolder) { - this.treeRootHolder = treeRootHolder; this.componentProvider = new TreeRootHolderComponentProvider(treeRootHolder); } + public DuplicationRepositoryRule() { + this.componentProvider = null; + } + public static DuplicationRepositoryRule create(TreeRootHolderRule treeRootHolder) { return new DuplicationRepositoryRule(treeRootHolder); } + public static DuplicationRepositoryRule create() { + return new DuplicationRepositoryRule(); + } + @Override protected void before() throws Throwable { - this.delegate = new DuplicationRepositoryImpl(treeRootHolder); + this.delegate = new DuplicationRepositoryImpl(); } @Override protected void after() { - this.componentProvider.reset(); + if (this.componentProvider != null) { + this.componentProvider.reset(); + } + this.componentRefsWithInnerDuplications.clear(); + this.componentRefsWithInProjectDuplications.clear(); + this.componentRefsWithCrossProjectDuplications.clear(); this.delegate = null; } - public Set getDuplications(int fileRef) { - componentProvider.ensureInitialized(); + public Iterable getDuplications(int fileRef) { + ensureComponentProviderInitialized(); return delegate.getDuplications(componentProvider.getByRef(fileRef)); } - public DuplicationRepositoryRule addDuplication(int fileRef, TextBlock original, TextBlock duplicate) { - componentProvider.ensureInitialized(); + public void add(int fileRef, Duplication duplication) { + ensureComponentProviderInitialized(); + + delegate.add(componentProvider.getByRef(fileRef), duplication); + } + + public DuplicationRepositoryRule addDuplication(int fileRef, TextBlock original, TextBlock... duplicates) { + ensureComponentProviderInitialized(); + Component component = componentProvider.getByRef(fileRef); + checkArgument(!componentRefsWithInnerDuplications.containsEntry(component, original), "Inner duplications for file %s and original %s already set", fileRef, original); + checkArgument(!componentRefsWithInProjectDuplications.containsEntry(component, original), "InProject duplications for file %s and original %s already set. Use add(int, Duplication) instead", fileRef, original); - delegate.addDuplication(componentProvider.getByRef(fileRef), original, duplicate); + componentRefsWithInnerDuplications.put(component, original); + delegate.add( + component, + new Duplication( + original, + from(Arrays.asList(duplicates)).transform(TextBlockToInnerDuplicate.INSTANCE)) + ); return this; } public DuplicationRepositoryRule addDuplication(int fileRef, TextBlock original, int otherFileRef, TextBlock duplicate) { - componentProvider.ensureInitialized(); - - delegate.addDuplication(componentProvider.getByRef(fileRef), original, componentProvider.getByRef(otherFileRef), duplicate); + ensureComponentProviderInitialized(); + Component component = componentProvider.getByRef(fileRef); + checkArgument(!componentRefsWithInProjectDuplications.containsEntry(component, original), "InProject duplications for file %s and original %s already set", fileRef, original); + checkArgument(!componentRefsWithInnerDuplications.containsEntry(component, original), "Inner duplications for file %s and original %s already set. Use add(int, Duplication) instead", fileRef, original); + + componentRefsWithInProjectDuplications.put(component, original); + delegate.add(component, + new Duplication( + original, + Arrays.asList(new InProjectDuplicate(componentProvider.getByRef(otherFileRef), duplicate)) + ) + ); return this; } public DuplicationRepositoryRule addDuplication(int fileRef, TextBlock original, String otherFileKey, TextBlock duplicate) { - componentProvider.ensureInitialized(); - - delegate.addDuplication(componentProvider.getByRef(fileRef), original, otherFileKey, duplicate); + ensureComponentProviderInitialized(); + Component component = componentProvider.getByRef(fileRef); + checkArgument(!componentRefsWithCrossProjectDuplications.containsEntry(component, original), "CrossProject duplications for file %s and original %s already set", fileRef); + + componentRefsWithCrossProjectDuplications.put(component, original); + delegate.add(componentProvider.getByRef(fileRef), + new Duplication( + original, + Arrays.asList(new CrossProjectDuplicate(otherFileKey, duplicate)) + ) + ); return this; } @Override - public Set getDuplications(Component file) { + public Iterable getDuplications(Component file) { return delegate.getDuplications(file); } @Override - public void addDuplication(Component file, TextBlock original, TextBlock duplicate) { - delegate.addDuplication(file, original, duplicate); + public void add(Component file, Duplication duplication) { + TextBlock original = duplication.getOriginal(); + checkArgument(!componentRefsWithInnerDuplications.containsEntry(file, original), "Inner duplications for file %s and original %s already set", file, original); + checkArgument(!componentRefsWithInProjectDuplications.containsEntry(file, original), "InProject duplications for file %s and original %s already set", file, original); + checkArgument(!componentRefsWithCrossProjectDuplications.containsEntry(file, original), "CrossProject duplications for file %s and original %s already set", file, original); + + delegate.add(file, duplication); } - @Override - public void addDuplication(Component file, TextBlock original, Component otherFile, TextBlock duplicate) { - delegate.addDuplication(file, original, otherFile, duplicate); + private void ensureComponentProviderInitialized() { + requireNonNull(this.componentProvider, "Methods with component reference can not be used unless a TreeRootHolder has been provided when instantiating the rule"); + this.componentProvider.ensureInitialized(); } - @Override - public void addDuplication(Component file, TextBlock original, String otherFileKey, TextBlock duplicate) { - delegate.addDuplication(file, original, otherFileKey, duplicate); + private enum TextBlockToInnerDuplicate implements Function { + INSTANCE; + + @Override + @Nonnull + public Duplicate apply(@Nonnull TextBlock input) { + return new InnerDuplicate(input); + } } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/IntegrateCrossProjectDuplicationsTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/IntegrateCrossProjectDuplicationsTest.java index 557426a572d..49d0c2f2f7e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/IntegrateCrossProjectDuplicationsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/duplication/IntegrateCrossProjectDuplicationsTest.java @@ -21,6 +21,7 @@ package org.sonar.server.computation.duplication; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import org.junit.Rule; @@ -38,13 +39,6 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.sonar.server.computation.component.Component.Type.FILE; import static org.sonar.server.computation.component.ReportComponent.builder; @@ -52,6 +46,8 @@ public class IntegrateCrossProjectDuplicationsTest { @Rule public LogTester logTester = new LogTester(); + @Rule + public DuplicationRepositoryRule duplicationRepository = DuplicationRepositoryRule.create(); static final String XOO_LANGUAGE = "xoo"; @@ -65,8 +61,6 @@ public class IntegrateCrossProjectDuplicationsTest { Settings settings = new Settings(); - DuplicationRepository duplicationRepository = mock(DuplicationRepository.class); - IntegrateCrossProjectDuplications underTest = new IntegrateCrossProjectDuplications(settings, duplicationRepository); @Test @@ -106,11 +100,9 @@ public class IntegrateCrossProjectDuplicationsTest { underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks); - verify(duplicationRepository).addDuplication( - ORIGIN_FILE, - new TextBlock(30, 45), - OTHER_FILE_KEY, - new TextBlock(40, 55) + assertThat(duplicationRepository.getDuplications(ORIGIN_FILE)) + .containsExactly( + crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)) ); } @@ -140,11 +132,9 @@ public class IntegrateCrossProjectDuplicationsTest { underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks); - verify(duplicationRepository).addDuplication( - ORIGIN_FILE, - new TextBlock(30, 45), - OTHER_FILE_KEY, - new TextBlock(40, 55) + assertThat(duplicationRepository.getDuplications(ORIGIN_FILE)) + .containsExactly( + crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)) ); } @@ -181,7 +171,7 @@ public class IntegrateCrossProjectDuplicationsTest { underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks); - verifyNoMoreInteractions(duplicationRepository); + assertNoDuplicationAdded(ORIGIN_FILE); } @Test @@ -210,7 +200,7 @@ public class IntegrateCrossProjectDuplicationsTest { underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks); - verifyNoMoreInteractions(duplicationRepository); + assertNoDuplicationAdded(ORIGIN_FILE); } @Test @@ -229,7 +219,7 @@ public class IntegrateCrossProjectDuplicationsTest { underTest.computeCpd(ORIGIN_FILE, originBlocks, Collections.emptyList()); - verifyNoMoreInteractions(duplicationRepository); + assertNoDuplicationAdded(ORIGIN_FILE); } @Test @@ -261,11 +251,9 @@ public class IntegrateCrossProjectDuplicationsTest { underTest.computeCpd(javaFile, originBlocks, duplicatedBlocks); - verify(duplicationRepository).addDuplication( - ORIGIN_FILE, - new TextBlock(30, 45), - OTHER_FILE_KEY, - new TextBlock(40, 55) + assertThat(duplicationRepository.getDuplications(ORIGIN_FILE)) + .containsExactly( + crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)) ); } @@ -294,11 +282,9 @@ public class IntegrateCrossProjectDuplicationsTest { underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks); - verify(duplicationRepository).addDuplication( - ORIGIN_FILE, - new TextBlock(30, 45), - OTHER_FILE_KEY, - new TextBlock(40, 55) + assertThat(duplicationRepository.getDuplications(ORIGIN_FILE)) + .containsExactly( + crossProjectDuplication(new TextBlock(30, 45), OTHER_FILE_KEY, new TextBlock(40, 55)) ); } @@ -328,7 +314,9 @@ public class IntegrateCrossProjectDuplicationsTest { assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly( "Too many duplication references on file " + ORIGIN_FILE_KEY + " for block at line 30. Keeping only the first 100 references."); - verify(duplicationRepository, times(100)).addDuplication(eq(ORIGIN_FILE), any(TextBlock.class), anyString(), any(TextBlock.class)); + Iterable duplications = duplicationRepository.getDuplications(ORIGIN_FILE); + assertThat(duplications).hasSize(1); + assertThat(duplications.iterator().next().getDuplicates()).hasSize(100); } @Test @@ -359,8 +347,16 @@ public class IntegrateCrossProjectDuplicationsTest { underTest.computeCpd(ORIGIN_FILE, originBlocks, duplicatedBlocks); - verify(duplicationRepository, times(100)).addDuplication(eq(ORIGIN_FILE), any(TextBlock.class), anyString(), any(TextBlock.class)); + assertThat(duplicationRepository.getDuplications(ORIGIN_FILE)).hasSize(100); assertThat(logTester.logs(LoggerLevel.WARN)).containsOnly("Too many duplication groups on file " + ORIGIN_FILE_KEY + ". Keeping only the first 100 groups."); } + private static Duplication crossProjectDuplication(TextBlock original, String otherFileKey, TextBlock duplicate) { + return new Duplication(original, Arrays.asList(new CrossProjectDuplicate(otherFileKey, duplicate))); + } + + private void assertNoDuplicationAdded(Component file) { + assertThat(duplicationRepository.getDuplications(file)).isEmpty(); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStepTest.java index b1423876afd..42b4c918a59 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStepTest.java @@ -179,7 +179,7 @@ public class LoadDuplicationsFromReportStepTest { reportReader.putDuplications(FILE_1_REF, createDuplication(singleLineTextRange(line), createInProjectDuplicate(FILE_1_REF, line + 1))); expectedException.expect(VisitException.class); - expectedException.expectCause(hasType(IllegalArgumentException.class).andMessage("file and otherFile Components can not be the same")); + expectedException.expectCause(hasType(IllegalArgumentException.class).andMessage("file and otherFile references can not be the same")); underTest.execute(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistFileSourcesStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistFileSourcesStepTest.java index 83ba814824b..54749fb9011 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistFileSourcesStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistFileSourcesStepTest.java @@ -20,6 +20,7 @@ package org.sonar.server.computation.step; +import java.util.Arrays; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -38,7 +39,10 @@ import org.sonar.server.computation.batch.BatchReportReaderRule; import org.sonar.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.component.ReportComponent; +import org.sonar.server.computation.duplication.Duplicate; +import org.sonar.server.computation.duplication.Duplication; import org.sonar.server.computation.duplication.DuplicationRepositoryRule; +import org.sonar.server.computation.duplication.InnerDuplicate; import org.sonar.server.computation.duplication.TextBlock; import org.sonar.server.computation.scm.Changeset; import org.sonar.server.computation.scm.ScmInfoRepositoryRule; @@ -242,7 +246,10 @@ public class PersistFileSourcesStepTest extends BaseStepTest { public void persist_duplication() { initBasicReport(1); - duplicationRepository.addDuplication(FILE_REF, new TextBlock(1, 2), new TextBlock(3, 4)); + duplicationRepository.add( + FILE_REF, + new Duplication(new TextBlock(1, 2), Arrays.asList(new InnerDuplicate(new TextBlock(3, 4)))) + ); underTest.execute(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ReportDuplicationMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ReportDuplicationMeasuresStepTest.java index 05f4ca5ffeb..10d6e802dbe 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ReportDuplicationMeasuresStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ReportDuplicationMeasuresStepTest.java @@ -107,9 +107,7 @@ public class ReportDuplicationMeasuresStepTest { @Test public void compute_duplicated_blocks_one_for_original_one_for_each_InnerDuplicate() { TextBlock original = new TextBlock(1, 1); - duplicationRepository.addDuplication(FILE_1_REF, original, new TextBlock(2, 2)); - duplicationRepository.addDuplication(FILE_1_REF, original, new TextBlock(3, 3)); - duplicationRepository.addDuplication(FILE_1_REF, original, new TextBlock(2, 3)); + duplicationRepository.addDuplication(FILE_1_REF, original, new TextBlock(2, 2), new TextBlock(3, 3), new TextBlock(2, 3)); underTest.execute(); @@ -213,8 +211,7 @@ public class ReportDuplicationMeasuresStepTest { @Test public void compute_duplicated_lines_counts_lines_from_original_and_InnerDuplicate_only_once() { TextBlock original = new TextBlock(1, 12); - duplicationRepository.addDuplication(FILE_1_REF, original, new TextBlock(10, 11)); - duplicationRepository.addDuplication(FILE_1_REF, original, new TextBlock(11, 15)); + duplicationRepository.addDuplication(FILE_1_REF, original, new TextBlock(10, 11), new TextBlock(11, 15)); duplicationRepository.addDuplication(FILE_1_REF, new TextBlock(2, 2), new TextBlock(96, 96)); underTest.execute(); @@ -454,9 +451,11 @@ public class ReportDuplicationMeasuresStepTest { private void addDuplicatedBlock(int fileRef, int blockCount) { checkArgument(blockCount > 1, "BlockCount can not be less than 2"); TextBlock original = new TextBlock(1, 1); + TextBlock[] duplicates = new TextBlock[blockCount - 1]; for (int i = 10; i < blockCount + 9; i++) { - duplicationRepository.addDuplication(fileRef, original, new TextBlock(i, i)); + duplicates[i - 10] = new TextBlock(i, i); } + duplicationRepository.addDuplication(fileRef, original, duplicates); } private void addRawMeasure(int componentRef, String metricKey, int value) { -- 2.39.5