]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11458 Log analysis warning when cross-project duplication detection is used
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 20 Jun 2019 20:05:37 +0000 (15:05 -0500)
committersonartech <sonartech@sonarsource.com>
Fri, 28 Jun 2019 06:45:45 +0000 (08:45 +0200)
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/analysis/Branch.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/duplication/Duplication.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/duplication/IntegrateCrossProjectDuplications.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStep.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/duplication/DuplicationRepositoryRule.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/duplication/DuplicationTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/duplication/IntegrateCrossProjectDuplicationsTest.java

index 22c0702dd150958b8d171d2d964f9deea5fe516c..c4a022f0724168acc9ab0112d5cd1bb595946597 100644 (file)
@@ -49,7 +49,7 @@ public interface Branch extends ComponentKeyGenerator {
   String getMergeBranchUuid();
 
   /**
-   * Whether the cross-project duplication tracker must be enabled
+   * Whether the cross-project duplication tracker can be enabled
    * or not.
    */
   boolean supportsCrossProjectCpd();
index c858b2d68fcdaf23402ae24926497b891b0c6099..8f129042d84fec56d03350df60238206fdbc77bc 100644 (file)
  */
 package org.sonar.ce.task.projectanalysis.duplication;
 
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Ordering;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Objects;
 import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.Function;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.FluentIterable.from;
 import static java.util.Objects.requireNonNull;
 
 @Immutable
 public final class Duplication {
-  private static final Ordering<Duplicate> DUPLICATE_ORDERING = Ordering.from(DuplicateComparatorByType.INSTANCE)
-    .compound(Ordering.natural().onResultOf(DuplicateToFileKey.INSTANCE))
-    .compound(Ordering.natural().onResultOf(DuplicateToTextBlock.INSTANCE));
+  private static final Comparator<Duplicate> DUPLICATE_COMPARATOR = DuplicateComparatorByType.INSTANCE
+    .thenComparing(DuplicateToFileKey.INSTANCE).thenComparing(DuplicateToTextBlock.INSTANCE);
 
   private final TextBlock original;
   private final SortedSet<Duplicate> duplicates;
 
   /**
-   * @throws NullPointerException if {@code original} is {@code null} or {@code duplicates} is {@code null} or {@code duplicates} contains {@code null}
+   * @throws NullPointerException     if {@code original} is {@code null} or {@code duplicates} is {@code null} or {@code duplicates} contains {@code null}
    * @throws IllegalArgumentException if {@code duplicates} is empty
    * @throws IllegalArgumentException if {@code duplicates} contains a {@link InnerDuplicate} with {@code original}
    */
-  public Duplication(final TextBlock original, final Iterable<Duplicate> duplicates) {
+  public Duplication(TextBlock original, List<Duplicate> duplicates) {
     this.original = requireNonNull(original, "original TextBlock can not be null");
-    this.duplicates = from(requireNonNull(duplicates, "duplicates can not be null"))
-      .filter(FailOnNullDuplicate.INSTANCE)
-      .filter(new EnsureInnerDuplicateIsNotOriginalTextBlock(original))
-      .toSortedSet(DUPLICATE_ORDERING);
-    checkArgument(!this.duplicates.isEmpty(), "duplicates can not be empty");
+    validateDuplicates(original, duplicates);
+    this.duplicates = new TreeSet<>(DUPLICATE_COMPARATOR);
+    this.duplicates.addAll(duplicates);
+  }
+
+  private static void validateDuplicates(TextBlock original, List<Duplicate> duplicates) {
+    requireNonNull(duplicates, "duplicates can not be null");
+    checkArgument(!duplicates.isEmpty(), "duplicates can not be empty");
+
+    for (Duplicate dup : duplicates) {
+      requireNonNull(dup, "duplicates can not contain null");
+      if (dup instanceof InnerDuplicate) {
+        checkArgument(!original.equals(dup.getTextBlock()), "TextBlock of an InnerDuplicate can not be the original TextBlock");
+      }
+    }
   }
 
   /**
@@ -67,8 +75,8 @@ public final class Duplication {
    * The duplicates of the original, sorted by inner duplicates, then project duplicates, then cross-project duplicates.
    * For each category of duplicate, they are sorted by:
    * <ul>
-   *   <li>file key (unless it's an InnerDuplicate)</li>
-   *   <li>then by TextBlocks by start line and in case of same line, by shortest first</li>
+   * <li>file key (unless it's an InnerDuplicate)</li>
+   * <li>then by TextBlocks by start line and in case of same line, by shortest first</li>
    * </ul
    * <p>The returned set can not be empty and no inner duplicate can contain the original {@link TextBlock}.</p>
    */
@@ -101,16 +109,6 @@ public final class Duplication {
       '}';
   }
 
-  private enum FailOnNullDuplicate implements Predicate<Duplicate> {
-    INSTANCE;
-
-    @Override
-    public boolean apply(@Nullable Duplicate input) {
-      requireNonNull(input, "duplicates can not contain null");
-      return true;
-    }
-  }
-
   private enum DuplicateComparatorByType implements Comparator<Duplicate> {
     INSTANCE;
 
@@ -143,22 +141,6 @@ public final class Duplication {
     }
   }
 
-  private static class EnsureInnerDuplicateIsNotOriginalTextBlock implements Predicate<Duplicate> {
-    private final TextBlock original;
-
-    public EnsureInnerDuplicateIsNotOriginalTextBlock(TextBlock original) {
-      this.original = original;
-    }
-
-    @Override
-    public boolean apply(@Nullable Duplicate input) {
-      if (input instanceof InnerDuplicate) {
-        checkArgument(!original.equals(input.getTextBlock()), "TextBlock of an InnerDuplicate can not be the original TextBlock");
-      }
-      return true;
-    }
-  }
-
   private enum DuplicateToFileKey implements Function<Duplicate, String> {
     INSTANCE;
 
index 7cc2e8d5d1791636c47d7592309f071d2313b99f..c362762edc6d5bb42be8d5faa65d86470c29cf3c 100644 (file)
  */
 package org.sonar.ce.task.projectanalysis.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;
 import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
+import org.sonar.ce.task.log.CeTaskMessages;
 import org.sonar.ce.task.projectanalysis.component.Component;
 import org.sonar.duplications.block.Block;
 import org.sonar.duplications.detector.suffixtree.SuffixTreeCloneDetectionAlgorithm;
@@ -39,16 +41,15 @@ import org.sonar.duplications.index.CloneIndex;
 import org.sonar.duplications.index.ClonePart;
 import org.sonar.duplications.index.PackedMemoryCloneIndex;
 
-import static com.google.common.collect.FluentIterable.from;
-
 /**
  * Transform a list of duplication blocks into clone groups, then add these clone groups into the duplication repository.
  */
 public class IntegrateCrossProjectDuplications {
 
   private static final Logger LOGGER = Loggers.get(IntegrateCrossProjectDuplications.class);
-
   private static final String JAVA_KEY = "java";
+  private static final String DEPRECATED_WARNING = "This analysis uses the deprecated cross-project duplication feature.";
+  private static final String DEPRECATED_WARNING_DASHBOARD = "This project uses the deprecated cross-project duplication feature.";
 
   private static final int MAX_CLONE_GROUP_PER_FILE = 100;
   private static final int MAX_CLONE_PART_PER_GROUP = 100;
@@ -58,11 +59,12 @@ public class IntegrateCrossProjectDuplications {
 
   private Map<String, NumberOfUnitsNotLessThan> numberOfUnitsByLanguage = new HashMap<>();
 
-  public IntegrateCrossProjectDuplications(Configuration config, DuplicationRepository duplicationRepository) {
+  public IntegrateCrossProjectDuplications(Configuration config, DuplicationRepository duplicationRepository, CeTaskMessages ceTaskMessages, System2 system) {
     this.config = config;
     this.duplicationRepository = duplicationRepository;
     if (config.getBoolean(CoreProperties.CPD_CROSS_PROJECT).orElse(false)) {
-      LOGGER.warn("This analysis uses the deprecated cross-project duplication feature.");
+      LOGGER.warn(DEPRECATED_WARNING);
+      ceTaskMessages.add(new CeTaskMessages.Message(DEPRECATED_WARNING_DASHBOARD, system.now()));
     }
   }
 
@@ -72,7 +74,9 @@ public class IntegrateCrossProjectDuplications {
     populateIndex(duplicationIndex, duplicationBlocks);
 
     List<CloneGroup> duplications = SuffixTreeCloneDetectionAlgorithm.detect(duplicationIndex, originBlocks);
-    Iterable<CloneGroup> filtered = from(duplications).filter(getNumberOfUnitsNotLessThan(component.getFileAttributes().getLanguageKey()));
+    Iterable<CloneGroup> filtered = duplications.stream()
+      .filter(getNumberOfUnitsNotLessThan(component.getFileAttributes().getLanguageKey()))
+      .collect(Collectors.toList());
     addDuplications(component, filtered);
   }
 
@@ -96,20 +100,21 @@ public class IntegrateCrossProjectDuplications {
 
   private void addDuplication(Component file, CloneGroup duplication) {
     ClonePart originPart = duplication.getOriginPart();
-    Iterable<Duplicate> duplicates = convertClonePartsToDuplicates(file, duplication);
-    if (!Iterables.isEmpty(duplicates)) {
+    List<Duplicate> duplicates = convertClonePartsToDuplicates(file, duplication);
+    if (!duplicates.isEmpty()) {
       duplicationRepository.add(
         file,
         new Duplication(new TextBlock(originPart.getStartLine(), originPart.getEndLine()), duplicates));
     }
   }
 
-  private static Iterable<Duplicate> convertClonePartsToDuplicates(final Component file, CloneGroup duplication) {
+  private static List<Duplicate> convertClonePartsToDuplicates(final Component file, CloneGroup duplication) {
     final ClonePart originPart = duplication.getOriginPart();
-    return from(duplication.getCloneParts())
+    return duplication.getCloneParts().stream()
       .filter(new DoesNotMatchSameComponentKey(originPart.getResourceId()))
       .filter(new DuplicateLimiter(file, originPart))
-      .transform(ClonePartToCrossProjectDuplicate.INSTANCE);
+      .map(ClonePartToCrossProjectDuplicate.INSTANCE)
+      .collect(Collectors.toList());
   }
 
   private NumberOfUnitsNotLessThan getNumberOfUnitsNotLessThan(String language) {
@@ -132,12 +137,12 @@ public class IntegrateCrossProjectDuplications {
   private static class NumberOfUnitsNotLessThan implements Predicate<CloneGroup> {
     private final int min;
 
-    public NumberOfUnitsNotLessThan(int min) {
+    NumberOfUnitsNotLessThan(int min) {
       this.min = min;
     }
 
     @Override
-    public boolean apply(@Nonnull CloneGroup input) {
+    public boolean test(@Nonnull CloneGroup input) {
       return input.getLengthInUnits() >= min;
     }
   }
@@ -150,7 +155,7 @@ public class IntegrateCrossProjectDuplications {
     }
 
     @Override
-    public boolean apply(@Nonnull ClonePart part) {
+    public boolean test(@Nonnull ClonePart part) {
       return !part.getResourceId().equals(componentKey);
     }
   }
@@ -160,18 +165,18 @@ public class IntegrateCrossProjectDuplications {
     private final ClonePart originPart;
     private int counter = 0;
 
-    public DuplicateLimiter(Component file, ClonePart originPart) {
+    DuplicateLimiter(Component file, ClonePart originPart) {
       this.file = file;
       this.originPart = originPart;
     }
 
     @Override
-    public boolean apply(@Nonnull ClonePart input) {
+    public boolean test(@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.getDbKey(), originPart.getStartLine(), MAX_CLONE_PART_PER_GROUP);
       }
-      boolean res = counter <= MAX_CLONE_GROUP_PER_FILE;
+      boolean res = counter < MAX_CLONE_GROUP_PER_FILE;
       counter++;
       return res;
     }
index adcfdc744bb338944d855e86fd4ef82d200b922a..09d56ae2f7e13ddf3ee1724db6b0bae3266f2cbc 100644 (file)
  */
 package org.sonar.ce.task.projectanalysis.step;
 
-import com.google.common.base.Function;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
@@ -44,12 +46,8 @@ import org.sonar.duplications.block.Block;
 import org.sonar.duplications.block.ByteArray;
 import org.sonar.scanner.protocol.output.ScannerReport.CpdTextBlock;
 
-import static com.google.common.collect.FluentIterable.from;
-import static com.google.common.collect.Lists.newArrayList;
-
 /**
  * Feed the duplications repository from the cross project duplication blocks computed with duplications blocks of the analysis report.
- *
  * Blocks can be empty if :
  * - The file is excluded from the analysis using {@link org.sonar.api.CoreProperties#CPD_EXCLUSIONS}
  * - On Java, if the number of statements of the file is too small, nothing will be sent.
@@ -96,23 +94,25 @@ public class LoadCrossProjectDuplicationsRepositoryStep implements ComputationSt
 
     @Override
     public void visitFile(Component file) {
-      List<CpdTextBlock> cpdTextBlocks;
+      List<CpdTextBlock> cpdTextBlocks = new ArrayList<>();
       try (CloseableIterator<CpdTextBlock> blocksIt = reportReader.readCpdTextBlocks(file.getReportAttributes().getRef())) {
-        cpdTextBlocks = newArrayList(blocksIt);
-        LOGGER.trace("Found {} cpd blocks on file {}", cpdTextBlocks.size(), file.getDbKey());
-        if (cpdTextBlocks.isEmpty()) {
-          return;
+        while(blocksIt.hasNext()) {
+          cpdTextBlocks.add(blocksIt.next());
         }
       }
+      LOGGER.trace("Found {} cpd blocks on file {}", cpdTextBlocks.size(), file.getDbKey());
+      if (cpdTextBlocks.isEmpty()) {
+        return;
+      }
 
-      Collection<String> hashes = from(cpdTextBlocks).transform(CpdTextBlockToHash.INSTANCE).toList();
+      Collection<String> hashes = cpdTextBlocks.stream().map(CpdTextBlockToHash.INSTANCE).collect(Collectors.toList());
       List<DuplicationUnitDto> dtos = selectDuplicates(file, hashes);
       if (dtos.isEmpty()) {
         return;
       }
 
-      Collection<Block> duplicatedBlocks = from(dtos).transform(DtoToBlock.INSTANCE).toList();
-      Collection<Block> originBlocks = from(cpdTextBlocks).transform(new CpdTextBlockToBlock(file.getDbKey())).toList();
+      Collection<Block> duplicatedBlocks = dtos.stream().map(DtoToBlock.INSTANCE).collect(Collectors.toList());
+      Collection<Block> originBlocks = cpdTextBlocks.stream().map(new CpdTextBlockToBlock(file.getDbKey())).collect(Collectors.toList());
       LOGGER.trace("Found {} duplicated cpd blocks on file {}", duplicatedBlocks.size(), file.getDbKey());
 
       integrateCrossProjectDuplications.computeCpd(file, originBlocks, duplicatedBlocks);
@@ -155,7 +155,7 @@ public class LoadCrossProjectDuplicationsRepositoryStep implements ComputationSt
     private final String fileKey;
     private int indexInFile = 0;
 
-    public CpdTextBlockToBlock(String fileKey) {
+    CpdTextBlockToBlock(String fileKey) {
       this.fileKey = fileKey;
     }
 
index 453f987127d49f3d1453b30d3ad20f43d0e36844..79e8d1d28e697513c749a9965c0b58ff73a78de3 100644 (file)
@@ -101,7 +101,7 @@ public class DuplicationRepositoryRule extends ExternalResource implements Dupli
       component,
       new Duplication(
         original,
-        from(Arrays.asList(duplicates)).transform(TextBlockToInnerDuplicate.INSTANCE)));
+        from(Arrays.asList(duplicates)).transform(TextBlockToInnerDuplicate.INSTANCE).toList()));
 
     return this;
   }
index c68e857d7dc66145a408536b3550d0a070893189..7fb700bd8657b28d9758bff28492e759de6bb9ed 100644 (file)
  */
 package org.sonar.ce.task.projectanalysis.duplication;
 
-import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import org.junit.Rule;
 import org.junit.Test;
@@ -51,7 +49,7 @@ public class DuplicationTest {
     expectedException.expect(NullPointerException.class);
     expectedException.expectMessage("original TextBlock can not be null");
 
-    new Duplication(null, Collections.emptySet());
+    new Duplication(null, Collections.emptyList());
   }
 
   @Test
@@ -67,7 +65,7 @@ public class DuplicationTest {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("duplicates can not be empty");
 
-    new Duplication(SOME_ORIGINAL_TEXTBLOCK, Collections.emptySet());
+    new Duplication(SOME_ORIGINAL_TEXTBLOCK, Collections.emptyList());
   }
 
   @Test
@@ -75,7 +73,7 @@ public class DuplicationTest {
     expectedException.expect(NullPointerException.class);
     expectedException.expectMessage("duplicates can not contain null");
 
-    new Duplication(SOME_ORIGINAL_TEXTBLOCK, new HashSet<>(Arrays.asList(mock(Duplicate.class), null, mock(Duplicate.class))));
+    new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.asList(mock(Duplicate.class), null, mock(Duplicate.class)));
   }
 
   @Test
@@ -83,7 +81,7 @@ public class DuplicationTest {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("TextBlock of an InnerDuplicate can not be the original TextBlock");
 
-    new Duplication(SOME_ORIGINAL_TEXTBLOCK, new HashSet<>(Arrays.asList(mock(Duplicate.class), new InnerDuplicate(SOME_ORIGINAL_TEXTBLOCK), mock(Duplicate.class))));
+    new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.asList(mock(Duplicate.class), new InnerDuplicate(SOME_ORIGINAL_TEXTBLOCK), mock(Duplicate.class)));
   }
 
   @Test
@@ -91,7 +89,7 @@ public class DuplicationTest {
     expectedException.expect(IllegalArgumentException.class);
     expectedException.expectMessage("Unsupported type of Duplicate " + MyDuplicate.class.getName());
 
-    new Duplication(SOME_ORIGINAL_TEXTBLOCK, ImmutableSet.of(new MyDuplicate(), new MyDuplicate()));
+    new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.asList(new MyDuplicate(), new MyDuplicate()));
   }
 
   private static final class MyDuplicate implements Duplicate {
@@ -104,7 +102,8 @@ public class DuplicationTest {
 
   @Test
   public void getOriginal_returns_original() {
-    assertThat(new Duplication(SOME_ORIGINAL_TEXTBLOCK, ImmutableSet.of(mock(Duplicate.class))).getOriginal()).isSameAs(SOME_ORIGINAL_TEXTBLOCK);
+    assertThat(new Duplication(SOME_ORIGINAL_TEXTBLOCK, Arrays.asList(new InnerDuplicate(TEXT_BLOCK_1)))
+      .getOriginal()).isSameAs(SOME_ORIGINAL_TEXTBLOCK);
   }
 
   @Test
index 4f1b01ebfa26098b136fdce72abd33131b15a3d3..a2a66c69f7cfd33882ca5987c746747c852e7721 100644 (file)
@@ -26,8 +26,10 @@ import java.util.Collections;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.internal.TestSystem2;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.ce.task.log.CeTaskMessages;
 import org.sonar.ce.task.projectanalysis.component.Component;
 import org.sonar.ce.task.projectanalysis.component.FileAttributes;
 import org.sonar.duplications.block.Block;
@@ -38,29 +40,29 @@ 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.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
 
 public class IntegrateCrossProjectDuplicationsTest {
+  private static final String XOO_LANGUAGE = "xoo";
+  private static final String ORIGIN_FILE_KEY = "ORIGIN_FILE_KEY";
+  private static final Component ORIGIN_FILE = builder(FILE, 1)
+    .setKey(ORIGIN_FILE_KEY)
+    .setFileAttributes(new FileAttributes(false, XOO_LANGUAGE, 1))
+    .build();
+  private static final String OTHER_FILE_KEY = "OTHER_FILE_KEY";
 
   @Rule
   public LogTester logTester = new LogTester();
   @Rule
   public DuplicationRepositoryRule duplicationRepository = DuplicationRepositoryRule.create();
 
-  static final String XOO_LANGUAGE = "xoo";
-
-  static final String ORIGIN_FILE_KEY = "ORIGIN_FILE_KEY";
-  static final Component ORIGIN_FILE = builder(FILE, 1)
-    .setKey(ORIGIN_FILE_KEY)
-    .setFileAttributes(new FileAttributes(false, XOO_LANGUAGE, 1))
-    .build();
-
-  static final String OTHER_FILE_KEY = "OTHER_FILE_KEY";
-
-  MapSettings settings = new MapSettings();
-
-  IntegrateCrossProjectDuplications underTest = new IntegrateCrossProjectDuplications(settings.asConfig(), duplicationRepository);
+  private TestSystem2 system = new TestSystem2();
+  private MapSettings settings = new MapSettings();
+  private CeTaskMessages ceTaskMessages = mock(CeTaskMessages.class);
+  private IntegrateCrossProjectDuplications underTest = new IntegrateCrossProjectDuplications(settings.asConfig(), duplicationRepository, ceTaskMessages, system);
 
   @Test
   public void add_duplications_from_two_blocks() {
@@ -335,10 +337,12 @@ public class IntegrateCrossProjectDuplicationsTest {
   @Test
   public void log_warning_if_this_deprecated_feature_is_enabled() {
     settings.setProperty("sonar.cpd.cross_project", "true");
+    system.setNow(1000L);
 
-    new IntegrateCrossProjectDuplications(settings.asConfig(), duplicationRepository);
+    new IntegrateCrossProjectDuplications(settings.asConfig(), duplicationRepository, ceTaskMessages, system);
 
     assertThat(logTester.logs()).containsExactly("This analysis uses the deprecated cross-project duplication feature.");
+    verify(ceTaskMessages).add(new CeTaskMessages.Message("This project uses the deprecated cross-project duplication feature.", 1000L));
   }
 
   private static Duplication crossProjectDuplication(TextBlock original, String otherFileKey, TextBlock duplicate) {