]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11515 Backdate issue when QP has changed
authorBenoît Gianinetti <benoit.gianinetti@sonarsource.com>
Fri, 30 Nov 2018 16:07:12 +0000 (17:07 +0100)
committerSonarTech <sonartech@sonarsource.com>
Mon, 3 Dec 2018 19:20:59 +0000 (20:20 +0100)
15 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCreationDateCalculator.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualityprofile/ActiveRule.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadQualityProfilesStep.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/QualityProfileEventsStep.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueCreationDateCalculatorTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/commonrule/CommentDensityRuleTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/commonrule/CommonRuleTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/commonrule/CoverageRuleTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/commonrule/DuplicatedBlockRuleTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/commonrule/SkippedTestRuleTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/commonrule/TestErrorRuleTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/ActiveRulesHolderImplTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/AlwaysActiveRulesHolderImpl.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/QualityProfileEventsStepTest.java

index f84ceb2d40f51070f6422635ac4b2d0ac97281f7..e2d79139f42967032261df91c05ccd9e2989c355 100644 (file)
@@ -33,6 +33,7 @@ import org.sonar.ce.task.projectanalysis.component.Component;
 import org.sonar.ce.task.projectanalysis.filemove.AddedFileRepository;
 import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRule;
 import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder;
+import org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository;
 import org.sonar.ce.task.projectanalysis.scm.Changeset;
 import org.sonar.ce.task.projectanalysis.scm.ScmInfo;
 import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository;
@@ -40,6 +41,7 @@ import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.server.issue.IssueFieldsSetter;
 
+import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UNCHANGED;
 import static org.sonar.core.issue.IssueChangeContext.createScan;
 
 /**
@@ -55,10 +57,11 @@ public class IssueCreationDateCalculator extends IssueVisitor {
   private final ActiveRulesHolder activeRulesHolder;
   private final RuleRepository ruleRepository;
   private final AddedFileRepository addedFileRepository;
+  private QProfileStatusRepository qProfileStatusRepository;
 
   public IssueCreationDateCalculator(AnalysisMetadataHolder analysisMetadataHolder, ScmInfoRepository scmInfoRepository,
     IssueFieldsSetter issueUpdater, ActiveRulesHolder activeRulesHolder, RuleRepository ruleRepository,
-    AddedFileRepository addedFileRepository) {
+    AddedFileRepository addedFileRepository, QProfileStatusRepository qProfileStatusRepository) {
     this.scmInfoRepository = scmInfoRepository;
     this.issueUpdater = issueUpdater;
     this.analysisMetadataHolder = analysisMetadataHolder;
@@ -66,6 +69,7 @@ public class IssueCreationDateCalculator extends IssueVisitor {
     this.changeContext = createScan(new Date(analysisMetadataHolder.getAnalysisDate()));
     this.activeRulesHolder = activeRulesHolder;
     this.addedFileRepository = addedFileRepository;
+    this.qProfileStatusRepository = qProfileStatusRepository;
   }
 
   @Override
@@ -89,12 +93,22 @@ public class IssueCreationDateCalculator extends IssueVisitor {
       // Rule can't be inactive (see contract of IssueVisitor)
       ActiveRule activeRule = activeRulesHolder.get(issue.getRuleKey()).get();
       if (activeRuleIsNewOrChanged(activeRule, lastAnalysisOptional.get())
-        || ruleImplementationChanged(activeRule.getRuleKey(), activeRule.getPluginKey(), lastAnalysisOptional.get())) {
+        || ruleImplementationChanged(activeRule.getRuleKey(), activeRule.getPluginKey(), lastAnalysisOptional.get())
+        || qualityProfileChanged(activeRule.getQProfileKey())) {
         backdateIssue(component, issue);
       }
     }
   }
 
+  private boolean qualityProfileChanged(@Nullable String qpKey) {
+    // Support issue from report created before scanner protocol update -> no backdating
+    if (qpKey == null) {
+      return false;
+    }
+
+    return qProfileStatusRepository.get(qpKey).filter(s -> !s.equals(UNCHANGED)).isPresent();
+  }
+
   private boolean isNewFile(Component component) {
     return component.getType() == Component.Type.FILE && addedFileRepository.isAdded(component);
   }
index 1fc26b321ae189c6f48bbaa63f97e4985c64b683..961875b0342e228d4a165e48e25482d06b2b0123 100644 (file)
@@ -33,13 +33,15 @@ public class ActiveRule {
   private final Map<String, String> params;
   private final String pluginKey;
   private final long updatedAt;
+  private final String qProfileKey;
 
-  public ActiveRule(RuleKey ruleKey, String severity, Map<String, String> params, long updatedAt, @Nullable String pluginKey) {
+  public ActiveRule(RuleKey ruleKey, String severity, Map<String, String> params, long updatedAt, @Nullable String pluginKey, @Nullable String qProfileKey) {
     this.ruleKey = ruleKey;
     this.severity = severity;
     this.pluginKey = pluginKey;
     this.params = ImmutableMap.copyOf(params);
     this.updatedAt = updatedAt;
+    this.qProfileKey = qProfileKey;
   }
 
   public RuleKey getRuleKey() {
@@ -62,4 +64,9 @@ public class ActiveRule {
   public String getPluginKey() {
     return pluginKey;
   }
+
+  @CheckForNull
+  public String getQProfileKey() {
+    return qProfileKey;
+  }
 }
index 9c4bd118967c5c0e8ae0d2acfd96962dc865c452..9f656608f467f9595b20d027be0302a85e5bf64c 100644 (file)
@@ -35,6 +35,8 @@ import org.sonar.ce.task.step.ComputationStep;
 import org.sonar.core.util.CloseableIterator;
 import org.sonar.scanner.protocol.output.ScannerReport;
 
+import static com.google.common.base.Strings.emptyToNull;
+
 public class LoadQualityProfilesStep implements ComputationStep {
 
   private final BatchReportReader batchReportReader;
@@ -55,8 +57,7 @@ public class LoadQualityProfilesStep implements ComputationStep {
         ScannerReport.ActiveRule scannerReportActiveRule = batchActiveRules.next();
         Optional<Rule> rule = ruleRepository.findByKey(RuleKey.of(scannerReportActiveRule.getRuleRepository(), scannerReportActiveRule.getRuleKey()));
         if (rule.isPresent() && rule.get().getStatus() != RuleStatus.REMOVED && !rule.get().isExternal()) {
-          ActiveRule activeRule = convert(scannerReportActiveRule, rule.get());
-          activeRules.add(activeRule);
+          activeRules.add(convert(scannerReportActiveRule, rule.get()));
         }
       }
     }
@@ -73,6 +74,6 @@ public class LoadQualityProfilesStep implements ComputationStep {
     RuleKey key = RuleKey.of(input.getRuleRepository(), input.getRuleKey());
     Map<String, String> params = new HashMap<>(input.getParamsByKeyMap());
     long updatedAt = input.getUpdatedAt();
-    return new ActiveRule(key, input.getSeverity().name(), params, updatedAt == 0 ? input.getCreatedAt() : updatedAt, rule.getPluginKey());
+    return new ActiveRule(key, input.getSeverity().name(), params, updatedAt == 0 ? input.getCreatedAt() : updatedAt, rule.getPluginKey(), emptyToNull(input.getQProfileKey()));
   }
 }
index 66ad67230efc63244189548e25ffd81c2f62064f..6cc4c51cada50549a965f14f0ae7cf46f65289e6 100644 (file)
@@ -40,12 +40,16 @@ import org.sonar.ce.task.projectanalysis.language.LanguageRepository;
 import org.sonar.ce.task.projectanalysis.measure.Measure;
 import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
 import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
+import org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository;
 import org.sonar.ce.task.step.ComputationStep;
 import org.sonar.core.util.UtcDateUtils;
 import org.sonar.server.qualityprofile.QPMeasureData;
 import org.sonar.server.qualityprofile.QualityProfile;
 
 import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
+import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.ADDED;
+import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.REMOVED;
+import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UPDATED;
 
 /**
  * Computation of quality profile events
@@ -58,15 +62,17 @@ public class QualityProfileEventsStep implements ComputationStep {
   private final MeasureRepository measureRepository;
   private final EventRepository eventRepository;
   private final LanguageRepository languageRepository;
+  private QProfileStatusRepository qProfileStatusRepository;
 
   public QualityProfileEventsStep(TreeRootHolder treeRootHolder,
     MetricRepository metricRepository, MeasureRepository measureRepository, LanguageRepository languageRepository,
-    EventRepository eventRepository) {
+    EventRepository eventRepository, QProfileStatusRepository qProfileStatusRepository) {
     this.treeRootHolder = treeRootHolder;
     this.metricRepository = metricRepository;
     this.measureRepository = measureRepository;
     this.eventRepository = eventRepository;
     this.languageRepository = languageRepository;
+    this.qProfileStatusRepository = qProfileStatusRepository;
   }
 
   @Override
@@ -87,7 +93,7 @@ public class QualityProfileEventsStep implements ComputationStep {
       return;
     }
 
-    // Load base profiles
+    // Load profiles used in current analysis for which at least one file of the corresponding language exists
     Optional<Measure> rawMeasure = measureRepository.getRawMeasure(projectComponent, metricRepository.getByKey(CoreMetrics.QUALITY_PROFILES_KEY));
     if (!rawMeasure.isPresent()) {
       // No qualify profile computed on the project
@@ -97,7 +103,7 @@ public class QualityProfileEventsStep implements ComputationStep {
 
     Map<String, QualityProfile> baseProfiles = parseJsonData(baseMeasure.get());
     detectNewOrUpdatedProfiles(projectComponent, baseProfiles, rawProfiles);
-    detectNoMoreUsedProfiles(projectComponent, baseProfiles, rawProfiles);
+    detectNoMoreUsedProfiles(projectComponent, baseProfiles);
   }
 
   private static Map<String, QualityProfile> parseJsonData(Measure measure) {
@@ -108,9 +114,9 @@ public class QualityProfileEventsStep implements ComputationStep {
     return QPMeasureData.fromJson(data).getProfilesByKey();
   }
 
-  private void detectNoMoreUsedProfiles(Component context, Map<String, QualityProfile> baseProfiles, Map<String, QualityProfile> rawProfiles) {
+  private void detectNoMoreUsedProfiles(Component context, Map<String, QualityProfile> baseProfiles) {
     for (QualityProfile baseProfile : baseProfiles.values()) {
-      if (!rawProfiles.containsKey(baseProfile.getQpKey())) {
+      if (qProfileStatusRepository.get(baseProfile.getQpKey()).filter(REMOVED::equals).isPresent()) {
         markAsRemoved(context, baseProfile);
       }
     }
@@ -118,12 +124,13 @@ public class QualityProfileEventsStep implements ComputationStep {
 
   private void detectNewOrUpdatedProfiles(Component component, Map<String, QualityProfile> baseProfiles, Map<String, QualityProfile> rawProfiles) {
     for (QualityProfile profile : rawProfiles.values()) {
-      QualityProfile baseProfile = baseProfiles.get(profile.getQpKey());
-      if (baseProfile == null) {
-        markAsAdded(component, profile);
-      } else if (profile.getRulesUpdatedAt().after(baseProfile.getRulesUpdatedAt())) {
-        markAsChanged(component, baseProfile, profile);
-      }
+      qProfileStatusRepository.get(profile.getQpKey()).ifPresent(status -> {
+        if (status.equals(ADDED)) {
+          markAsAdded(component, profile);
+        } else if (status.equals(UPDATED)) {
+          markAsChanged(component, baseProfiles.get(profile.getQpKey()), profile);
+        }
+      });
     }
   }
 
index 7d0b82ac9016c51d9dd115f9d465d52685e4ea72..44925d6edff137fd3feaaffc96f8c2e87d33d54d 100644 (file)
@@ -28,6 +28,8 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
 import java.util.function.BiConsumer;
+import java.util.stream.Stream;
+import org.apache.commons.lang.ArrayUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -40,6 +42,7 @@ import org.sonar.ce.task.projectanalysis.component.Component;
 import org.sonar.ce.task.projectanalysis.filemove.AddedFileRepository;
 import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRule;
 import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder;
+import org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository;
 import org.sonar.ce.task.projectanalysis.scm.Changeset;
 import org.sonar.ce.task.projectanalysis.scm.ScmInfo;
 import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository;
@@ -60,6 +63,7 @@ import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UNCHANGED;
 
 @RunWith(DataProviderRunner.class)
 public class IssueCreationDateCalculatorTest {
@@ -84,6 +88,7 @@ public class IssueCreationDateCalculatorTest {
   private Map<String, ScannerPlugin> scannerPlugins = new HashMap<>();
   private RuleRepository ruleRepository = mock(RuleRepository.class);
   private AddedFileRepository addedFileRepository = mock(AddedFileRepository.class);
+  private QProfileStatusRepository qProfileStatusRepository = mock(QProfileStatusRepository.class);
   private ScmInfo scmInfo;
   private Rule rule = mock(Rule.class);
 
@@ -92,15 +97,14 @@ public class IssueCreationDateCalculatorTest {
     analysisMetadataHolder.setScannerPluginsByKey(scannerPlugins);
     analysisMetadataHolder.setAnalysisDate(new Date());
     when(component.getUuid()).thenReturn(COMPONENT_UUID);
-    underTest = new IssueCreationDateCalculator(analysisMetadataHolder, scmInfoRepository, issueUpdater, activeRulesHolder, ruleRepository, addedFileRepository);
+    underTest = new IssueCreationDateCalculator(analysisMetadataHolder, scmInfoRepository, issueUpdater, activeRulesHolder, ruleRepository, addedFileRepository, qProfileStatusRepository);
 
     when(ruleRepository.findByKey(ruleKey)).thenReturn(Optional.of(rule));
-    when(activeRulesHolder.get(any(RuleKey.class)))
-      .thenReturn(Optional.empty());
-    when(activeRulesHolder.get(ruleKey))
-      .thenReturn(Optional.of(activeRule));
-    when(issue.getRuleKey())
-      .thenReturn(ruleKey);
+    when(activeRulesHolder.get(any(RuleKey.class))).thenReturn(Optional.empty());
+    when(activeRulesHolder.get(ruleKey)).thenReturn(Optional.of(activeRule));
+    when(activeRule.getQProfileKey()).thenReturn("qpKey");
+    when(issue.getRuleKey()).thenReturn(ruleKey);
+    when(qProfileStatusRepository.get(any())).thenReturn(Optional.of(UNCHANGED));
   }
 
   @Test
@@ -251,6 +255,22 @@ public class IssueCreationDateCalculatorTest {
 
     assertChangeOfCreationDateTo(expectedDate);
   }
+
+  @Test
+  @UseDataProvider("backdatingDateAndChangedQPStatusCases")
+  public void should_backdate_if_qp_of_the_rule_which_raised_the_issue_has_changed(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate, QProfileStatusRepository.Status status) {
+    previousAnalysisWas(2000L);
+    currentAnalysisIs(3000L);
+
+    makeIssueNew();
+    configure.accept(issue, createMockScmInfo());
+    changeQualityProfile(status);
+
+    run();
+
+    assertChangeOfCreationDateTo(expectedDate);
+  }
+
   @Test
   @UseDataProvider("backdatingDateCases")
   public void should_backdate_if_scm_is_available_and_plugin_is_new(BiConsumer<DefaultIssue, ScmInfo> configure, long expectedDate) {
@@ -302,6 +322,16 @@ public class IssueCreationDateCalculatorTest {
     verifyZeroInteractions(activeRulesHolder);
   }
 
+  @DataProvider
+  public static Object[][] backdatingDateAndChangedQPStatusCases() {
+    return Stream.of(backdatingDateCases())
+      .flatMap(datesCases ->
+        Stream.of(QProfileStatusRepository.Status.values())
+          .filter(s -> !UNCHANGED.equals(s))
+          .map(s -> ArrayUtils.add(datesCases, s)))
+      .toArray(Object[][]::new);
+  }
+
   @DataProvider
   public static Object[][] backdatingDateCases() {
     return new Object[][] {
@@ -401,6 +431,10 @@ public class IssueCreationDateCalculatorTest {
       .thenReturn(false);
   }
 
+  private void changeQualityProfile(QProfileStatusRepository.Status status) {
+    when(qProfileStatusRepository.get(any())).thenReturn(Optional.of(status));
+  }
+
   private void setIssueBelongToNonExistingRule() {
     when(issue.getRuleKey())
       .thenReturn(RuleKey.of("repo", "disabled"));
index 2e5d7c4b1cee5ddf206581a8e92f9402ea025cb9..bfc199048105060ae4aeddbb9959a6ac717a2c15 100644 (file)
@@ -367,6 +367,6 @@ public class TrackerRawInputFactoryTest {
   }
 
   private void markRuleAsActive(RuleKey ruleKey) {
-    activeRulesHolder.put(new ActiveRule(ruleKey, Severity.CRITICAL, emptyMap(), 1_000L, null));
+    activeRulesHolder.put(new ActiveRule(ruleKey, Severity.CRITICAL, emptyMap(), 1_000L, null, "qp1"));
   }
 }
index 5c5883ec5efbe852e52c9d48ffd32d1feb4cb527..9a0241cc7e1fda13a6ccc83b4fcbb82d8a518986 100644 (file)
@@ -44,6 +44,7 @@ import static org.sonar.ce.task.projectanalysis.component.ReportComponent.DUMB_P
 public class CommentDensityRuleTest {
 
   private static final String PLUGIN_KEY = "java";
+  private static final String QP_KEY = "qp1";
 
   static RuleKey RULE_KEY = RuleKey.of(CommonRuleKeys.commonRepositoryForLang(PLUGIN_KEY), CommonRuleKeys.INSUFFICIENT_COMMENT_DENSITY);
 
@@ -77,7 +78,7 @@ public class CommentDensityRuleTest {
 
   @Test
   public void no_issues_if_enough_comments() {
-    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, ImmutableMap.of(CommonRuleKeys.INSUFFICIENT_COMMENT_DENSITY_PROPERTY, "25"), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, ImmutableMap.of(CommonRuleKeys.INSUFFICIENT_COMMENT_DENSITY_PROPERTY, "25"), 1_000L, PLUGIN_KEY, QP_KEY));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), CoreMetrics.COMMENT_LINES_DENSITY_KEY, Measure.newMeasureBuilder().create(90.0, 1));
 
     DefaultIssue issue = underTest.processFile(FILE, PLUGIN_KEY);
@@ -135,7 +136,7 @@ public class CommentDensityRuleTest {
   }
 
   private void prepareForIssue(String minDensity, ReportComponent file, double commentLineDensity, int commentLines, int ncloc) {
-    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, ImmutableMap.of(CommonRuleKeys.INSUFFICIENT_COMMENT_DENSITY_PROPERTY, minDensity), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, ImmutableMap.of(CommonRuleKeys.INSUFFICIENT_COMMENT_DENSITY_PROPERTY, minDensity), 1_000L, PLUGIN_KEY, QP_KEY));
     measureRepository.addRawMeasure(file.getReportAttributes().getRef(), CoreMetrics.COMMENT_LINES_DENSITY_KEY, Measure.newMeasureBuilder().create(commentLineDensity, 1));
     measureRepository.addRawMeasure(file.getReportAttributes().getRef(), CoreMetrics.COMMENT_LINES_KEY, Measure.newMeasureBuilder().create(commentLines));
     measureRepository.addRawMeasure(file.getReportAttributes().getRef(), CoreMetrics.NCLOC_KEY, Measure.newMeasureBuilder().create(ncloc));
index 128c0ae3b35cff9e23ff388c1e68d6909bb9e214..feb67554e58921d732e0071ce7a41a95a7fef03d 100644 (file)
@@ -32,13 +32,14 @@ import static org.assertj.core.api.Assertions.assertThat;
 public class CommonRuleTest {
 
   private static final String PLUGIN_KEY = "java";
+  private static final String QP_KEY = "qp1";
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
   @Test
   public void test_getMinDensityParam() {
-    ActiveRule activeRule = new ActiveRule(RuleTesting.XOO_X1, Severity.MAJOR, ImmutableMap.of("minDensity", "30.5"), 1_000L, PLUGIN_KEY);
+    ActiveRule activeRule = new ActiveRule(RuleTesting.XOO_X1, Severity.MAJOR, ImmutableMap.of("minDensity", "30.5"), 1_000L, PLUGIN_KEY, QP_KEY);
     double minDensity = CommonRule.getMinDensityParam(activeRule, "minDensity");
 
     assertThat(minDensity).isEqualTo(30.5);
@@ -49,7 +50,7 @@ public class CommonRuleTest {
     thrown.expect(IllegalStateException.class);
     thrown.expectMessage("Required parameter [minDensity] is missing on rule [xoo:x1]");
 
-    ActiveRule activeRule = new ActiveRule(RuleTesting.XOO_X1, Severity.MAJOR, ImmutableMap.of(), 1_000L, PLUGIN_KEY);
+    ActiveRule activeRule = new ActiveRule(RuleTesting.XOO_X1, Severity.MAJOR, ImmutableMap.of(), 1_000L, PLUGIN_KEY, QP_KEY);
     CommonRule.getMinDensityParam(activeRule, "minDensity");
   }
 
@@ -58,7 +59,7 @@ public class CommonRuleTest {
     thrown.expect(IllegalStateException.class);
     thrown.expectMessage("Minimum density of rule [xoo:x1] is incorrect. Got [-30.5] but must be between 0 and 100.");
 
-    ActiveRule activeRule = new ActiveRule(RuleTesting.XOO_X1, Severity.MAJOR, ImmutableMap.of("minDensity", "-30.5"), 1_000L, PLUGIN_KEY);
+    ActiveRule activeRule = new ActiveRule(RuleTesting.XOO_X1, Severity.MAJOR, ImmutableMap.of("minDensity", "-30.5"), 1_000L, PLUGIN_KEY, QP_KEY);
     CommonRule.getMinDensityParam(activeRule, "minDensity");
   }
 
@@ -67,7 +68,7 @@ public class CommonRuleTest {
     thrown.expect(IllegalStateException.class);
     thrown.expectMessage("Minimum density of rule [xoo:x1] is incorrect. Got [305] but must be between 0 and 100.");
 
-    ActiveRule activeRule = new ActiveRule(RuleTesting.XOO_X1, Severity.MAJOR, ImmutableMap.of("minDensity", "305"), 1_000L, PLUGIN_KEY);
+    ActiveRule activeRule = new ActiveRule(RuleTesting.XOO_X1, Severity.MAJOR, ImmutableMap.of("minDensity", "305"), 1_000L, PLUGIN_KEY, QP_KEY);
     CommonRule.getMinDensityParam(activeRule, "minDensity");
   }
 }
index 8324ae7e1abb0281b9011ab3a32f7ed5cb7e16de..f80373fb45cfdb4bd165593d3d72476979a2a6bb 100644 (file)
@@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 public abstract class CoverageRuleTest {
 
   private static final String PLUGIN_KEY = "java";
+  private static final String QP_KEY = "qp1";
 
   static ReportComponent FILE = ReportComponent.builder(Component.Type.FILE, 1)
     .setFileAttributes(new FileAttributes(false, "java", 1))
@@ -86,7 +87,7 @@ public abstract class CoverageRuleTest {
 
   @Test
   public void no_issue_if_enough_coverage() {
-    activeRuleHolder.put(new ActiveRule(getRuleKey(), Severity.CRITICAL, ImmutableMap.of(getMinPropertyKey(), "65"), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(getRuleKey(), Severity.CRITICAL, ImmutableMap.of(getMinPropertyKey(), "65"), 1_000L, PLUGIN_KEY, QP_KEY));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), getCoverageMetricKey(), Measure.newMeasureBuilder().create(90.0, 1));
 
     DefaultIssue issue = underTest.processFile(FILE, "java");
@@ -96,7 +97,7 @@ public abstract class CoverageRuleTest {
 
   @Test
   public void issue_if_coverage_is_too_low() {
-    activeRuleHolder.put(new ActiveRule(getRuleKey(), Severity.CRITICAL, ImmutableMap.of(getMinPropertyKey(), "65"), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(getRuleKey(), Severity.CRITICAL, ImmutableMap.of(getMinPropertyKey(), "65"), 1_000L, PLUGIN_KEY, QP_KEY));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), getCoverageMetricKey(), Measure.newMeasureBuilder().create(20.0, 1));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), getUncoveredMetricKey(), Measure.newMeasureBuilder().create(40));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), getToCoverMetricKey(), Measure.newMeasureBuilder().create(50));
@@ -114,7 +115,7 @@ public abstract class CoverageRuleTest {
 
   @Test
   public void no_issue_if_coverage_is_not_set() {
-    activeRuleHolder.put(new ActiveRule(getRuleKey(), Severity.CRITICAL, ImmutableMap.of(getMinPropertyKey(), "65"), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(getRuleKey(), Severity.CRITICAL, ImmutableMap.of(getMinPropertyKey(), "65"), 1_000L, PLUGIN_KEY, QP_KEY));
 
     DefaultIssue issue = underTest.processFile(FILE, "java");
 
index b90cc1556bf34158b11349a55d978ceeca38eb8a..58882a608b236620240b45e23c8a5460f3215ee6 100644 (file)
@@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 public class DuplicatedBlockRuleTest {
 
   private static final String PLUGIN_KEY = "java";
+  private static final String QP_KEY = "qp1";
 
   static RuleKey RULE_KEY = RuleKey.of(CommonRuleKeys.commonRepositoryForLang("java"), CommonRuleKeys.DUPLICATED_BLOCKS);
 
@@ -66,7 +67,7 @@ public class DuplicatedBlockRuleTest {
 
   @Test
   public void no_issue_if_no_duplicated_blocks() {
-    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY, QP_KEY));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), CoreMetrics.DUPLICATED_BLOCKS_KEY, Measure.newMeasureBuilder().create(0));
 
     DefaultIssue issue = underTest.processFile(FILE, "java");
@@ -76,7 +77,7 @@ public class DuplicatedBlockRuleTest {
 
   @Test
   public void issue_if_duplicated_blocks() {
-    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY, QP_KEY));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), CoreMetrics.DUPLICATED_BLOCKS_KEY, Measure.newMeasureBuilder().create(3));
 
     DefaultIssue issue = underTest.processFile(FILE, "java");
index 80c7dda0685d6356bbcb59e05b6f4d13b80e2fba..c912b2e9c64ef19c50fd3e96f8b70701bf86b46e 100644 (file)
@@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 public class SkippedTestRuleTest {
 
   private static final String PLUGIN_KEY = "java";
+  private static final String QP_KEY = "qp1";
 
   static RuleKey RULE_KEY = RuleKey.of(CommonRuleKeys.commonRepositoryForLang("java"), CommonRuleKeys.SKIPPED_UNIT_TESTS);
 
@@ -67,7 +68,7 @@ public class SkippedTestRuleTest {
 
   @Test
   public void issue_if_skipped_tests() {
-    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY, QP_KEY));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), CoreMetrics.SKIPPED_TESTS_KEY, Measure.newMeasureBuilder().create(2));
 
     DefaultIssue issue = underTest.processFile(FILE, "java");
@@ -80,7 +81,7 @@ public class SkippedTestRuleTest {
 
   @Test
   public void no_issues_if_zero_skipped_tests() {
-    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY, QP_KEY));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), CoreMetrics.SKIPPED_TESTS_KEY, Measure.newMeasureBuilder().create(0));
 
     DefaultIssue issue = underTest.processFile(FILE, "java");
@@ -90,7 +91,7 @@ public class SkippedTestRuleTest {
 
   @Test
   public void no_issues_if_measure_is_absent() {
-    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY, QP_KEY));
 
     DefaultIssue issue = underTest.processFile(FILE, "java");
 
index b58fea4996ecbb2b03db3fb57f24e168b4cd76d6..98152451dccbfb3baa108e3572661c6c485c6b95 100644 (file)
@@ -43,6 +43,7 @@ import static org.sonar.ce.task.projectanalysis.component.ReportComponent.DUMB_P
 public class TestErrorRuleTest {
 
   private static final String PLUGIN_KEY = "java";
+  private static final String QP_KEY = "qp1";
 
   static RuleKey RULE_KEY = RuleKey.of(CommonRuleKeys.commonRepositoryForLang("java"), CommonRuleKeys.FAILED_UNIT_TESTS);
 
@@ -69,7 +70,7 @@ public class TestErrorRuleTest {
 
   @Test
   public void issue_if_errors_or_failures() {
-    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY, QP_KEY));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), CoreMetrics.TEST_ERRORS_KEY, Measure.newMeasureBuilder().create(2));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), CoreMetrics.TEST_FAILURES_KEY, Measure.newMeasureBuilder().create(1));
 
@@ -83,7 +84,7 @@ public class TestErrorRuleTest {
 
   @Test
   public void no_issues_if_zero_errors_and_failures() {
-    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY, QP_KEY));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), CoreMetrics.TEST_ERRORS_KEY, Measure.newMeasureBuilder().create(0));
     measureRepository.addRawMeasure(FILE.getReportAttributes().getRef(), CoreMetrics.TEST_FAILURES_KEY, Measure.newMeasureBuilder().create(0));
 
@@ -94,7 +95,7 @@ public class TestErrorRuleTest {
 
   @Test
   public void no_issues_if_test_measures_are_absent() {
-    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY));
+    activeRuleHolder.put(new ActiveRule(RULE_KEY, Severity.CRITICAL, Collections.emptyMap(), 1_000L, PLUGIN_KEY, QP_KEY));
 
     DefaultIssue issue = underTest.processFile(FILE, "java");
 
index 9ce242d11136b937140a0d754c80382c731555a6..c2fa33f399c83c571aef128a7be54e9aaee496e4 100644 (file)
@@ -37,6 +37,7 @@ public class ActiveRulesHolderImplTest {
   private static final long SOME_DATE = 1_000L;
 
   static final RuleKey RULE_KEY = RuleKey.of("squid", "S001");
+  private static final String QP_KEY = "qp1";
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
@@ -52,7 +53,7 @@ public class ActiveRulesHolderImplTest {
 
   @Test
   public void get_active_rule() {
-    underTest.set(asList(new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY)));
+    underTest.set(asList(new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY)));
 
     Optional<ActiveRule> activeRule = underTest.get(RULE_KEY);
     assertThat(activeRule.isPresent()).isTrue();
@@ -65,7 +66,7 @@ public class ActiveRulesHolderImplTest {
     thrown.expect(IllegalStateException.class);
     thrown.expectMessage("Active rules have already been initialized");
 
-    underTest.set(asList(new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY)));
+    underTest.set(asList(new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY)));
     underTest.set(Collections.emptyList());
 
   }
@@ -84,7 +85,7 @@ public class ActiveRulesHolderImplTest {
     thrown.expectMessage("Active rule must not be declared multiple times: squid:S001");
 
     underTest.set(asList(
-      new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY),
-      new ActiveRule(RULE_KEY, Severity.MAJOR, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY)));
+      new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY),
+      new ActiveRule(RULE_KEY, Severity.MAJOR, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY)));
   }
 }
index 66ad2e989019c2a469efd916d78de7178402297b..027858cbe4dae6183150ed7f0f4deb233d13ae00 100644 (file)
@@ -28,7 +28,7 @@ import static java.util.Collections.emptyMap;
 public class AlwaysActiveRulesHolderImpl implements ActiveRulesHolder {
   @Override
   public Optional<ActiveRule> get(RuleKey ruleKey) {
-    return Optional.of(new ActiveRule(ruleKey, Severity.MAJOR, emptyMap(), 1_000L, null));
+    return Optional.of(new ActiveRule(ruleKey, Severity.MAJOR, emptyMap(), 1_000L, null, "qp1"));
   }
 
 }
index fc772bc3dd00c40a022ee92bdce65bf06f3118a4..4230d88fdc63bd87cdeca2a62953bd03ab1de698 100644 (file)
@@ -44,6 +44,9 @@ import org.sonar.ce.task.projectanalysis.measure.Measure;
 import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
 import org.sonar.ce.task.projectanalysis.metric.Metric;
 import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
+import org.sonar.ce.task.projectanalysis.qualityprofile.MutableQProfileStatusRepository;
+import org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository;
+import org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepositoryImpl;
 import org.sonar.ce.task.step.TestComputationStepContext;
 import org.sonar.core.util.UtcDateUtils;
 import org.sonar.server.qualityprofile.QPMeasureData;
@@ -60,6 +63,10 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
+import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.ADDED;
+import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.REMOVED;
+import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UNCHANGED;
+import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UPDATED;
 
 public class QualityProfileEventsStepTest {
   @Rule
@@ -76,10 +83,11 @@ public class QualityProfileEventsStepTest {
   private LanguageRepository languageRepository = mock(LanguageRepository.class);
   private EventRepository eventRepository = mock(EventRepository.class);
   private ArgumentCaptor<Event> eventArgumentCaptor = ArgumentCaptor.forClass(Event.class);
+  private MutableQProfileStatusRepository qProfileStatusRepository = new QProfileStatusRepositoryImpl();
 
   private Metric qualityProfileMetric = mock(Metric.class);
 
-  private QualityProfileEventsStep underTest = new QualityProfileEventsStep(treeRootHolder, metricRepository, measureRepository, languageRepository, eventRepository);
+  private QualityProfileEventsStep underTest = new QualityProfileEventsStep(treeRootHolder, metricRepository, measureRepository, languageRepository, eventRepository, qProfileStatusRepository);
 
   @Before
   public void setUp() {
@@ -116,8 +124,9 @@ public class QualityProfileEventsStepTest {
   }
 
   @Test
-  public void added_event_if_one_new_qp() {
+  public void added_event_if_qp_is_added() {
     QualityProfile qp = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date());
+    qProfileStatusRepository.register(qp.getQpKey(), ADDED);
 
     Language language = mockLanguageInRepository(LANGUAGE_KEY_1);
     mockMeasures(treeRootHolder.getRoot(), null, arrayOf(qp));
@@ -132,6 +141,7 @@ public class QualityProfileEventsStepTest {
   @Test
   public void added_event_uses_language_key_in_message_if_language_not_found() {
     QualityProfile qp = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date());
+    qProfileStatusRepository.register(qp.getQpKey(), ADDED);
 
     mockLanguageNotInRepository(LANGUAGE_KEY_1);
     mockMeasures(treeRootHolder.getRoot(), null, arrayOf(qp));
@@ -144,8 +154,9 @@ public class QualityProfileEventsStepTest {
   }
 
   @Test
-  public void no_more_used_event_if_qp_no_more_listed() {
+  public void no_more_used_event_if_qp_is_removed() {
     QualityProfile qp = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date());
+    qProfileStatusRepository.register(qp.getQpKey(), REMOVED);
 
     mockMeasures(treeRootHolder.getRoot(), arrayOf(qp), null);
     Language language = mockLanguageInRepository(LANGUAGE_KEY_1);
@@ -160,7 +171,7 @@ public class QualityProfileEventsStepTest {
   @Test
   public void no_more_used_event_uses_language_key_in_message_if_language_not_found() {
     QualityProfile qp = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date());
-
+    qProfileStatusRepository.register(qp.getQpKey(), REMOVED);
     mockMeasures(treeRootHolder.getRoot(), arrayOf(qp), null);
     mockLanguageNotInRepository(LANGUAGE_KEY_1);
 
@@ -172,9 +183,9 @@ public class QualityProfileEventsStepTest {
   }
 
   @Test
-  public void no_event_if_same_qp_with_same_date() {
+  public void no_event_if_qp_is_unchanged() {
     QualityProfile qp = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date());
-
+    qProfileStatusRepository.register(qp.getQpKey(), UNCHANGED);
     mockMeasures(treeRootHolder.getRoot(), arrayOf(qp), arrayOf(qp));
 
     underTest.execute(new TestComputationStepContext());
@@ -183,10 +194,10 @@ public class QualityProfileEventsStepTest {
   }
 
   @Test
-  public void changed_event_if_same_qp_but_no_same_date() {
+  public void changed_event_if_qp_has_been_updated() {
     QualityProfile qp1 = qp(QP_NAME_1, LANGUAGE_KEY_1, parseDateTime("2011-04-25T01:05:13+0100"));
     QualityProfile qp2 = qp(QP_NAME_1, LANGUAGE_KEY_1, parseDateTime("2011-04-25T01:05:17+0100"));
-
+    qProfileStatusRepository.register(qp2.getQpKey(), UPDATED);
     mockMeasures(treeRootHolder.getRoot(), arrayOf(qp1), arrayOf(qp2));
     Language language = mockLanguageInRepository(LANGUAGE_KEY_1);
 
@@ -209,17 +220,21 @@ public class QualityProfileEventsStepTest {
     }).when(eventRepository).add(eq(treeRootHolder.getRoot()), any(Event.class));
 
     Date date = new Date();
+    QualityProfile qp1 = qp(QP_NAME_2, LANGUAGE_KEY_1, date);
+    QualityProfile qp2 = qp(QP_NAME_2, LANGUAGE_KEY_2, date);
+    QualityProfile qp3 = qp(QP_NAME_1, LANGUAGE_KEY_1, parseDateTime("2011-04-25T01:05:13+0100"));
+    QualityProfile qp3_updated = qp(QP_NAME_1, LANGUAGE_KEY_1, parseDateTime("2011-04-25T01:05:17+0100"));
+    QualityProfile qp4 = qp(QP_NAME_2, LANGUAGE_KEY_3, date);
+
     mockMeasures(
       treeRootHolder.getRoot(),
-      arrayOf(
-        qp(QP_NAME_2, LANGUAGE_KEY_1, date),
-        qp(QP_NAME_2, LANGUAGE_KEY_2, date),
-        qp(QP_NAME_1, LANGUAGE_KEY_1, parseDateTime("2011-04-25T01:05:13+0100"))),
-      arrayOf(
-        qp(QP_NAME_1, LANGUAGE_KEY_1, parseDateTime("2011-04-25T01:05:17+0100")),
-        qp(QP_NAME_2, LANGUAGE_KEY_2, date),
-        qp(QP_NAME_2, LANGUAGE_KEY_3, date)));
+      arrayOf(qp1, qp2, qp3),
+      arrayOf(qp3_updated, qp2, qp4));
     mockNoLanguageInRepository();
+    qProfileStatusRepository.register(qp1.getQpKey(), REMOVED);
+    qProfileStatusRepository.register(qp2.getQpKey(), UNCHANGED);
+    qProfileStatusRepository.register(qp3.getQpKey(), UPDATED);
+    qProfileStatusRepository.register(qp4.getQpKey(), ADDED);
 
     underTest.execute(new TestComputationStepContext());
 
@@ -227,7 +242,6 @@ public class QualityProfileEventsStepTest {
       "Stop using '" + QP_NAME_2 + "' (" + LANGUAGE_KEY_1 + ")",
       "Use '" + QP_NAME_2 + "' (" + LANGUAGE_KEY_3 + ")",
       "Changes in '" + QP_NAME_1 + "' (" + LANGUAGE_KEY_1 + ")");
-
   }
 
   private Language mockLanguageInRepository(String languageKey) {