aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorZipeng WU <zipeng.wu@sonarsource.com>2025-02-26 15:25:48 +0100
committersonartech <sonartech@sonarsource.com>2025-02-28 10:01:25 +0000
commitd7e823a82c8cefd190a1824eeec66137c8517d13 (patch)
tree72f27a81c272aad51c0b38fe8b64e081d491935b /server
parent0702797ba3a3a9b34c0258b6e081b7236de5e3a4 (diff)
downloadsonarqube-d7e823a82c8cefd190a1824eeec66137c8517d13.tar.gz
sonarqube-d7e823a82c8cefd190a1824eeec66137c8517d13.zip
SONAR-23184 Fix deactivated rules in a Quality Profile are activated after an upgrade
Diffstat (limited to 'server')
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileUpdateImplIT.java34
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/builtin/RuleActivatorIT.java79
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileUpdateImpl.java9
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/RuleActivationContext.java16
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/RuleActivator.java15
5 files changed, 144 insertions, 9 deletions
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileUpdateImplIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileUpdateImplIT.java
index 37ebd8e5cc7..3dc6c004fd8 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileUpdateImplIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileUpdateImplIT.java
@@ -309,6 +309,40 @@ class BuiltInQProfileUpdateImplIT {
verify(qualityProfileChangeEventService).distributeRuleChangeEvent(any(), eq(changes), eq(persistedProfile.getLanguage()));
}
+ @Test
+ void deactivated_rule_should_not_be_activated_in_descendant_profiles() {
+ RuleDto rule1 = db.rules().insert(r -> r.setLanguage("xoo"));
+ RuleDto rule2 = db.rules().insert(r -> r.setLanguage("xoo"));
+
+ QProfileDto parentProfile = db.qualityProfiles().insert(p -> p.setLanguage("xoo").setIsBuiltIn(true));
+ activateRuleInDb(RulesProfileDto.from(parentProfile), rule1, RulePriority.valueOf(Severity.MINOR),
+ Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW));
+ activateRuleInDb(RulesProfileDto.from(parentProfile), rule2, RulePriority.valueOf(Severity.MINOR),
+ Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW));
+
+ // child profile has only rule1 activated, to simulate rule2 is deactivated
+ QProfileDto childProfile = createChildProfile(parentProfile);
+ activateRuleInDb(RulesProfileDto.from(childProfile), rule1, RulePriority.valueOf(Severity.MINOR),
+ Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW), INHERITED);
+
+ BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context();
+ NewBuiltInQualityProfile newQp = context.createBuiltInQualityProfile(parentProfile.getName(), parentProfile.getLanguage());
+ newQp.activateRule(rule1.getRepositoryKey(), rule1.getRuleKey());
+ newQp.activateRule(rule2.getRepositoryKey(), rule2.getRuleKey());
+ newQp.done();
+ BuiltInQProfile builtIn = builtInProfileRepository.create(context.profile(parentProfile.getLanguage(), parentProfile.getName()), rule1, rule2);
+ List<ActiveRuleChange> changes = underTest.update(db.getSession(), builtIn, RulesProfileDto.from(parentProfile));
+
+ assertThat(changes).hasSize(3);
+
+ List<ActiveRuleDto> parentActiveRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), RulesProfileDto.from(parentProfile));
+ assertThatRuleIsUpdated(parentActiveRules, rule1, RulePriority.valueOfInt(rule1.getSeverity()), rule1.getDefaultImpactsMap());
+ assertThatRuleIsUpdated(parentActiveRules, rule2, RulePriority.valueOfInt(rule2.getSeverity()), rule2.getDefaultImpactsMap());
+
+ List<ActiveRuleDto> childActiveRules = db.getDbClient().activeRuleDao().selectByRuleProfile(db.getSession(), RulesProfileDto.from(childProfile));
+ assertThatRuleIsUpdated(childActiveRules, rule1, RulePriority.valueOfInt(rule1.getSeverity()), rule1.getDefaultImpactsMap(), INHERITED);
+ }
+
// SONAR-14559
@Test
void propagate_rule_update_to_descendant_active_rule() {
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/builtin/RuleActivatorIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/builtin/RuleActivatorIT.java
index 7f730090ed2..363005b4f4e 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/builtin/RuleActivatorIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/builtin/RuleActivatorIT.java
@@ -21,6 +21,7 @@ package org.sonar.server.qualityprofile.builtin;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -65,6 +66,7 @@ import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Map.of;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.tuple;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY;
@@ -360,6 +362,83 @@ class RuleActivatorIT {
() -> underTest.activate(session, resetRequest, context));
}
+ @Test
+ void testActivate_whenProfileIsNotBuiltInAndRuleUuidInPreviousBuiltinActiveRuleUuids_shouldContainNoChange() {
+ QProfileDto builtinProfile = db.qualityProfiles().insert(p -> p.setIsBuiltIn(true).setLanguage("xoo"));
+ QProfileDto customProfile = createChildProfile(builtinProfile);
+
+ RuleDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.CRITICAL));
+ ActiveRuleDto builtinActiveRuleDto = activateRuleInDb(RulesProfileDto.from(builtinProfile), rule,
+ RulePriority.valueOf(Severity.CRITICAL), Map.of(MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH), null, null);
+
+ RuleActivationContext context = new RuleActivationContext.Builder()
+ .setProfiles(List.of(builtinProfile, customProfile))
+ .setBaseProfile(RulesProfileDto.from(builtinProfile))
+ .setPreviousBuiltinActiveRuleUuids(Set.of(rule.getUuid()))
+ .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
+ .setRules(singletonList(rule))
+ .setRuleParams(emptyList())
+ .setActiveRules(List.of(builtinActiveRuleDto))
+ .setActiveRuleParams(emptyList())
+ .build();
+
+ RuleActivation activation = RuleActivation.create(rule.getUuid());
+
+ List<ActiveRuleChange> result = underTest.activate(db.getSession(), activation, context);
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ void testActivate_whenProfileIsNotBuiltInAndRuleUuidNotInPreviousBuiltinActiveRuleUuids_shouldContainActivation() {
+ QProfileDto builtinProfile = db.qualityProfiles().insert(p -> p.setIsBuiltIn(true).setLanguage("xoo"));
+ QProfileDto customProfile = createChildProfile(builtinProfile);
+
+ RuleDto rule = db.rules().insert(r -> r.setLanguage("xoo").setSeverity(Severity.CRITICAL));
+ ActiveRuleDto builtinActiveRuleDto = activateRuleInDb(RulesProfileDto.from(builtinProfile), rule,
+ RulePriority.valueOf(Severity.CRITICAL), Map.of(MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH), null, null);
+
+ RuleActivationContext context = new RuleActivationContext.Builder()
+ .setProfiles(List.of(builtinProfile, customProfile))
+ .setBaseProfile(RulesProfileDto.from(builtinProfile))
+ .setPreviousBuiltinActiveRuleUuids(Set.of("another-rule-uuid"))
+ .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
+ .setRules(singletonList(rule))
+ .setRuleParams(emptyList())
+ .setActiveRules(List.of(builtinActiveRuleDto))
+ .setActiveRuleParams(emptyList())
+ .build();
+
+ RuleActivation activation = RuleActivation.create(rule.getUuid());
+
+ List<ActiveRuleChange> result = underTest.activate(db.getSession(), activation, context);
+ assertThat(result)
+ .hasSize(1)
+ .extracting(ActiveRuleChange::getRuleUuid, ActiveRuleChange::getKey)
+ .containsExactlyInAnyOrder(tuple(rule.getUuid(), ActiveRuleKey.of(customProfile, RuleKey.of(rule.getRepositoryKey(), rule.getRuleKey()))));
+ }
+
+ @Test
+ void testActivate_whenProfileIsBuiltIn_shouldContainActivation() {
+ QProfileDto profile = db.qualityProfiles().insert(p -> p.setIsBuiltIn(true).setLanguage("xoo"));
+ RuleDto rule = db.rules().insert(r -> r.setLanguage("xoo"));
+ RuleActivationContext context = new RuleActivationContext.Builder()
+ .setProfiles(List.of(profile))
+ .setBaseProfile(RulesProfileDto.from(profile))
+ .setPreviousBuiltinActiveRuleUuids(Set.of(rule.getUuid()))
+ .setDescendantProfilesSupplier((profiles, ruleUuids) -> new Result(emptyList(), emptyList(), emptyList()))
+ .setRules(singletonList(rule))
+ .setRuleParams(emptyList())
+ .setActiveRules(emptyList())
+ .setActiveRuleParams(emptyList())
+ .build();
+
+ RuleActivation activation = RuleActivation.create(rule.getUuid());
+
+ List<ActiveRuleChange> result = underTest.activate(db.getSession(), activation, context);
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getRuleUuid()).isEqualTo(rule.getUuid());
+ }
+
private ActiveRuleDto activateRuleInDb(RulesProfileDto ruleProfile, RuleDto rule, RulePriority severity, Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts,
@Nullable Boolean prioritizedRule, @Nullable ActiveRuleInheritance inheritance) {
ActiveRuleDto dto = new ActiveRuleDto()
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileUpdateImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileUpdateImpl.java
index de49848fa6e..9499c538626 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileUpdateImpl.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileUpdateImpl.java
@@ -21,6 +21,7 @@ package org.sonar.server.qualityprofile.builtin;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -53,10 +54,12 @@ public class BuiltInQProfileUpdateImpl implements BuiltInQProfileUpdate {
public List<ActiveRuleChange> update(DbSession dbSession, BuiltInQProfile builtInDefinition, RulesProfileDto initialRuleProfile) {
// Keep reference to all the activated rules before update
- Set<String> deactivatedRuleUuids = dbClient.activeRuleDao().selectByRuleProfile(dbSession, initialRuleProfile)
+ Set<String> previousBuiltinActiveRuleUuids = dbClient.activeRuleDao().selectByRuleProfile(dbSession, initialRuleProfile)
.stream()
.map(ActiveRuleDto::getRuleUuid)
- .collect(Collectors.toSet());
+ .collect(Collectors.toUnmodifiableSet());
+
+ Set<String> deactivatedRuleUuids = new HashSet<>(previousBuiltinActiveRuleUuids);
// all rules, including those which are removed from built-in profile
Set<String> ruleUuids = Stream.concat(
@@ -71,7 +74,7 @@ public class BuiltInQProfileUpdateImpl implements BuiltInQProfileUpdate {
deactivatedRuleUuids.remove(activation.getRuleUuid());
}
- RuleActivationContext context = ruleActivator.createContextForBuiltInProfile(dbSession, initialRuleProfile, ruleUuids);
+ RuleActivationContext context = ruleActivator.createContextForBuiltInProfile(dbSession, initialRuleProfile, ruleUuids, previousBuiltinActiveRuleUuids);
List<ActiveRuleChange> changes = new ArrayList<>();
changes.addAll(ruleActivator.activate(dbSession, activations, context));
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/RuleActivationContext.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/RuleActivationContext.java
index a2e7d50dfcf..c9f7b4b5a1f 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/RuleActivationContext.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/RuleActivationContext.java
@@ -25,6 +25,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
@@ -40,6 +41,7 @@ import org.sonar.server.qualityprofile.RuleActivation;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
+import static java.util.Collections.emptySet;
import static java.util.Objects.requireNonNull;
import static org.sonar.core.util.stream.MoreCollectors.index;
import static org.sonar.server.exceptions.BadRequestException.checkRequest;
@@ -54,6 +56,8 @@ public class RuleActivationContext {
private final long date;
+ private Set<String> previousBuiltinActiveRuleUuids;
+
// The profile that is initially targeted by the operation
private final RulesProfileDto baseRulesProfile;
@@ -79,6 +83,8 @@ public class RuleActivationContext {
private RuleActivationContext(Builder builder) {
this.date = builder.date;
+ this.previousBuiltinActiveRuleUuids = builder.previousBuiltinActiveRuleUuids == null ? emptySet() :
+ builder.previousBuiltinActiveRuleUuids;
this.descendantProfilesSupplier = builder.descendantProfilesSupplier;
ListMultimap<String, RuleParamDto> paramsByRuleId = builder.ruleParams.stream().collect(index(RuleParamDto::getRuleUuid));
@@ -243,8 +249,13 @@ public class RuleActivationContext {
.orElse(null);
}
+ public Set<String> getPreviousBuiltinActiveRuleUuids() {
+ return previousBuiltinActiveRuleUuids;
+ }
+
static final class Builder {
private long date = System.currentTimeMillis();
+ private Set<String> previousBuiltinActiveRuleUuids;
private RulesProfileDto baseRulesProfile;
private Collection<RuleDto> rules;
private Collection<RuleParamDto> ruleParams;
@@ -258,6 +269,11 @@ public class RuleActivationContext {
return this;
}
+ Builder setPreviousBuiltinActiveRuleUuids(Set<String> previousBuiltinActiveRuleUuids) {
+ this.previousBuiltinActiveRuleUuids = previousBuiltinActiveRuleUuids;
+ return this;
+ }
+
Builder setBaseProfile(RulesProfileDto p) {
this.baseRulesProfile = p;
return this;
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/RuleActivator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/RuleActivator.java
index 66bdcfb6c48..ee037c60776 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/RuleActivator.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/RuleActivator.java
@@ -116,8 +116,7 @@ public class RuleActivator {
ActiveRuleWrapper activeRule = context.getActiveRule();
ActiveRuleKey activeRuleKey = ActiveRuleKey.of(context.getRulesProfile(), rule.getKey());
if (activeRule == null) {
- if (activation.isReset()) {
- // ignore reset when rule is not activated
+ if (activation.isReset() || skipBuiltinRuleActivation(activation, context)) {
return changes;
}
change = handleNewRuleActivation(activation, context, rule, activeRuleKey);
@@ -142,9 +141,6 @@ public class RuleActivator {
if (change != null) {
changes.add(change);
persist(change, context, dbSession);
- }
-
- if (!changes.isEmpty()) {
updateProfileDates(dbSession, context);
}
@@ -157,6 +153,12 @@ public class RuleActivator {
return changes;
}
+ private static boolean skipBuiltinRuleActivation(RuleActivation activation, RuleActivationContext context) {
+ // SONAR-23184: If the rule isn't active for a child profile and it is active for the previous parent definition of the profile,
+ // it means it was deactivated on purpose by a user - we don't want to active it.
+ return context.isCascading() && context.getPreviousBuiltinActiveRuleUuids().contains(activation.getRuleUuid());
+ }
+
private void handleUpdatedRuleActivation(RuleActivation activation, RuleActivationContext context, ActiveRuleChange change,
ActiveRuleWrapper activeRule) {
if (context.isCascading() && activeRule.get().getInheritance() == null) {
@@ -515,11 +517,12 @@ public class RuleActivator {
}
public RuleActivationContext createContextForBuiltInProfile(DbSession dbSession, RulesProfileDto builtInProfile,
- Collection<String> ruleUuids) {
+ Collection<String> ruleUuids, Set<String> previousBuiltinActiveRuleUuids) {
checkArgument(builtInProfile.isBuiltIn(), "Rules profile with UUID %s is not built-in", builtInProfile.getUuid());
RuleActivationContext.Builder builder = new RuleActivationContext.Builder();
builder.setDescendantProfilesSupplier(createDescendantProfilesSupplier(dbSession));
+ builder.setPreviousBuiltinActiveRuleUuids(previousBuiltinActiveRuleUuids);
// load rules
completeWithRules(dbSession, builder, ruleUuids);