diff options
author | Pierre Guillot <pierre.guillot@sonarsource.com> | 2024-12-10 15:29:09 +0100 |
---|---|---|
committer | Steve Marion <steve.marion@sonarsource.com> | 2024-12-18 11:13:21 +0100 |
commit | d639a965bce7acafb004906cd07a8f0b5f7af993 (patch) | |
tree | 647cd646abddb12dfeeef7e637aa33b4658f1049 /sonar-scanner-engine | |
parent | 451c1c2e4856ec3df87f86189fcdb25b31794027 (diff) | |
download | sonarqube-d639a965bce7acafb004906cd07a8f0b5f7af993.tar.gz sonarqube-d639a965bce7acafb004906cd07a8f0b5f7af993.zip |
SONAR-22998 fetch active rules with a dedicated endpoint
Co-authored-by: Julien HENRY <julien.henry@sonarsource.com>
Diffstat (limited to 'sonar-scanner-engine')
11 files changed, 270 insertions, 271 deletions
diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java index 4af42659e6e..5df0d03b111 100644 --- a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java +++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java @@ -35,7 +35,6 @@ import org.sonar.api.testfixtures.log.LogTesterJUnit5; import org.sonar.scanner.bootstrap.ScannerMain; import org.sonarqube.ws.Ce; import org.sonarqube.ws.Qualityprofiles; -import org.sonarqube.ws.Rules; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; @@ -81,10 +80,10 @@ class BootstrapMediumIT { .build()) .build())))); - sonarqube.stubFor(get("/api/rules/list.protobuf?qprofile=" + QPROFILE_KEY + "&ps=500&p=1") - .willReturn(aResponse() - .withResponseBody(protobufBody(Rules.ListResponse.newBuilder() - .build())))); + sonarqube.stubFor(get("/api/v2/analysis/active_rules?projectKey=" + PROJECT_KEY) + .willReturn(okJson(""" + [] + """))); sonarqube.stubFor(get("/api/languages/list") .willReturn(okJson(""" diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ChecksMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ChecksMediumIT.java index 7883b4c064f..035267f43fe 100644 --- a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ChecksMediumIT.java +++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/ChecksMediumIT.java @@ -30,11 +30,11 @@ import org.apache.commons.io.FileUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.scanner.rule.LoadedActiveRule; import org.sonar.api.rule.RuleKey; import org.sonar.scanner.mediumtest.AnalysisResult; import org.sonar.scanner.mediumtest.ScannerMediumTester; import org.sonar.scanner.protocol.output.ScannerReport.Issue; +import org.sonar.scanner.rule.LoadedActiveRule; import org.sonar.xoo.XooPlugin; import org.sonar.xoo.rule.XooRulesDefinition; @@ -100,6 +100,7 @@ public class ChecksMediumIT { r.setLanguage(languag); r.setSeverity(severity); r.setDeprecatedKeys(emptySet()); + r.setQProfileKey("whatever"); Map<String, String> params = new HashMap<>(); params.put(paramKey, paramValue); diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java index 39c65da4861..541a690a63c 100644 --- a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java +++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java @@ -30,13 +30,13 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.slf4j.event.Level; -import org.sonar.scanner.rule.LoadedActiveRule; import org.sonar.api.rule.RuleKey; import org.sonar.api.testfixtures.log.LogTester; import org.sonar.scanner.mediumtest.AnalysisResult; import org.sonar.scanner.mediumtest.ScannerMediumTester; import org.sonar.scanner.protocol.output.ScannerReport.ExternalIssue; import org.sonar.scanner.protocol.output.ScannerReport.Issue; +import org.sonar.scanner.rule.LoadedActiveRule; import org.sonar.xoo.XooPlugin; import org.sonar.xoo.rule.HasTagSensor; import org.sonar.xoo.rule.OneExternalIssueOnProjectSensor; @@ -414,9 +414,9 @@ public class IssuesMediumIT { r.setName("TODO"); r.setLanguage("xoo"); r.setSeverity("MAJOR"); - r.setDeprecatedKeys(emptySet() - ); + r.setDeprecatedKeys(emptySet()); r.setParams(ImmutableMap.of("tag", "TODO")); + r.setQProfileKey("whatever"); tester.activateRule(r); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/language/DefaultLanguagesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/language/DefaultLanguagesLoader.java index 58aeb9695f8..4a34d21d016 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/language/DefaultLanguagesLoader.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/language/DefaultLanguagesLoader.java @@ -39,8 +39,7 @@ public class DefaultLanguagesLoader implements LanguagesLoader { "js", "javascript", "ts", "typescript", "py", "python", - "web", "html" - ); + "web", "html"); private final DefaultScannerWsClient wsClient; @@ -76,7 +75,6 @@ public class DefaultLanguagesLoader implements LanguagesLoader { return new Language(lang); } - private String[] getFileSuffixes(String languageKey) { return getPropertyForLanguage("sonar.%s.file.suffixes", languageKey); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesLoader.java index 1bd49f3ec27..06358376c69 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesLoader.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesLoader.java @@ -22,5 +22,5 @@ package org.sonar.scanner.rule; import java.util.List; public interface ActiveRulesLoader { - List<LoadedActiveRule> load(String qualityProfileKey); + List<LoadedActiveRule> load(String projectKey); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesProvider.java index eb703522dc3..a6d7f7666aa 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesProvider.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesProvider.java @@ -19,21 +19,16 @@ */ package org.sonar.scanner.rule; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Set; import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; import org.sonar.api.batch.rule.internal.DefaultActiveRules; import org.sonar.api.batch.rule.internal.NewActiveRule; import org.sonar.api.issue.impact.Severity; import org.sonar.api.issue.impact.SoftwareQuality; -import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Profiler; +import org.sonar.scanner.bootstrap.ScannerProperties; import org.springframework.context.annotation.Bean; /** @@ -45,34 +40,22 @@ public class ActiveRulesProvider { private static final String LOG_MSG = "Load active rules"; @Bean("ActiveRules") - public DefaultActiveRules provide(ActiveRulesLoader loader, QualityProfiles qProfiles) { + public DefaultActiveRules provide(ActiveRulesLoader loader, ScannerProperties props) { Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG); - DefaultActiveRules activeRules = load(loader, qProfiles); + DefaultActiveRules activeRules = load(loader, props.getProjectKey()); profiler.stopInfo(); return activeRules; } - private static DefaultActiveRules load(ActiveRulesLoader loader, QualityProfiles qProfiles) { - - Collection<String> qProfileKeys = getKeys(qProfiles); - Set<RuleKey> loadedRulesKey = new HashSet<>(); + private static DefaultActiveRules load(ActiveRulesLoader loader, String projectKey) { ActiveRulesBuilder builder = new ActiveRulesBuilder(); - - for (String qProfileKey : qProfileKeys) { - Collection<LoadedActiveRule> qProfileRules = load(loader, qProfileKey); - - for (LoadedActiveRule r : qProfileRules) { - if (!loadedRulesKey.contains(r.getRuleKey())) { - loadedRulesKey.add(r.getRuleKey()); - builder.addRule(transform(r, qProfileKey, r.getDeprecatedKeys())); - } - } - } - + loader.load(projectKey).stream() + .map(ActiveRulesProvider::transform) + .forEach(builder::addRule); return builder.build(); } - private static NewActiveRule transform(LoadedActiveRule activeRule, String qProfileKey, Set<RuleKey> deprecatedKeys) { + private static NewActiveRule transform(LoadedActiveRule activeRule) { NewActiveRule.Builder builder = new NewActiveRule.Builder(); builder .setRuleKey(activeRule.getRuleKey()) @@ -83,8 +66,8 @@ public class ActiveRulesProvider { .setLanguage(activeRule.getLanguage()) .setInternalKey(activeRule.getInternalKey()) .setTemplateRuleKey(activeRule.getTemplateRuleKey()) - .setQProfileKey(qProfileKey) - .setDeprecatedKeys(deprecatedKeys); + .setQProfileKey(activeRule.getQProfileKey()) + .setDeprecatedKeys(activeRule.getDeprecatedKeys()); // load parameters if (activeRule.getParams() != null) { for (Map.Entry<String, String> params : activeRule.getParams().entrySet()) { @@ -100,18 +83,4 @@ public class ActiveRulesProvider { return builder.build(); } - - private static List<LoadedActiveRule> load(ActiveRulesLoader loader, String qProfileKey) { - return loader.load(qProfileKey); - } - - private static Collection<String> getKeys(QualityProfiles qProfiles) { - List<String> keys = new ArrayList<>(qProfiles.findAll().size()); - - for (QProfile qp : qProfiles.findAll()) { - keys.add(qp.getKey()); - } - - return keys; - } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/DefaultActiveRulesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/DefaultActiveRulesLoader.java index 78c047d5903..dad400fb11b 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/DefaultActiveRulesLoader.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/DefaultActiveRulesLoader.java @@ -19,34 +19,27 @@ */ package org.sonar.scanner.rule; -import java.io.IOException; -import java.io.InputStream; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.LinkedList; +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import java.io.Reader; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; -import org.apache.commons.io.IOUtils; -import org.sonar.api.impl.utils.ScannerUtils; +import javax.annotation.Nullable; import org.sonar.api.issue.impact.Severity; import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.DateUtils; -import org.sonar.core.rule.ImpactFormatter; import org.sonar.scanner.http.ScannerWsClient; -import org.sonarqube.ws.Common; -import org.sonarqube.ws.Common.Paging; -import org.sonarqube.ws.Rules; -import org.sonarqube.ws.Rules.Active; -import org.sonarqube.ws.Rules.Active.Param; -import org.sonarqube.ws.Rules.ActiveList; -import org.sonarqube.ws.Rules.ListResponse; -import org.sonarqube.ws.Rules.Rule; import org.sonarqube.ws.client.GetRequest; +import static java.util.Optional.ofNullable; + public class DefaultActiveRulesLoader implements ActiveRulesLoader { - private static final String RULES_SEARCH_URL = "/api/rules/list.protobuf?"; + private static final String RULES_ACTIVE_URL = "/api/v2/analysis/active_rules?"; private final ScannerWsClient wsClient; @@ -55,101 +48,83 @@ public class DefaultActiveRulesLoader implements ActiveRulesLoader { } @Override - public List<LoadedActiveRule> load(String qualityProfileKey) { - List<LoadedActiveRule> ruleList = new LinkedList<>(); - int page = 1; - int pageSize = 500; - long loaded = 0; - - while (true) { - GetRequest getRequest = new GetRequest(getUrl(qualityProfileKey, page, pageSize)); - ListResponse response = loadFromStream(wsClient.call(getRequest).contentStream()); - List<LoadedActiveRule> pageRules = readPage(response); - ruleList.addAll(pageRules); - - Paging paging = response.getPaging(); - loaded += paging.getPageSize(); - - if (paging.getTotal() <= loaded) { - break; - } - page++; + public List<LoadedActiveRule> load(String projectKey) { + GetRequest getRequest = new GetRequest(getUrl(projectKey)); + List<ActiveRuleGson> jsonResponse; + try (Reader reader = wsClient.call(getRequest).contentReader()) { + jsonResponse = new Gson().fromJson(reader, new TypeToken<ArrayList<ActiveRuleGson>>() { + }.getType()); + } catch (Exception e) { + throw new IllegalStateException("Unable to load active rules", e); } - - return ruleList; + return convert(jsonResponse); } - private static String getUrl(String qualityProfileKey, int page, int pageSize) { - StringBuilder builder = new StringBuilder(1024); - builder.append(RULES_SEARCH_URL); - builder.append("qprofile=").append(ScannerUtils.encodeForUrl(qualityProfileKey)); - builder.append("&ps=").append(pageSize); - builder.append("&p=").append(page); - return builder.toString(); + private static String getUrl(String projectKey) { + return RULES_ACTIVE_URL + "projectKey=" + projectKey; } - private static ListResponse loadFromStream(InputStream is) { - try { - return ListResponse.parseFrom(is); - } catch (IOException e) { - throw new IllegalStateException("Failed to load quality profiles", e); - } finally { - IOUtils.closeQuietly(is); - } + private static List<LoadedActiveRule> convert(List<ActiveRuleGson> activeRuleGsonList) { + return activeRuleGsonList.stream() + .map(DefaultActiveRulesLoader::convertActiveRule) + .toList(); } - private static List<LoadedActiveRule> readPage(ListResponse response) { - List<LoadedActiveRule> loadedRules = new LinkedList<>(); - - List<Rule> rulesList = response.getRulesList(); - Map<String, ActiveList> actives = response.getActives().getActivesMap(); - - for (Rule r : rulesList) { - ActiveList activeList = actives.get(r.getKey()); - Active active = activeList.getActiveList(0); - - LoadedActiveRule loadedRule = new LoadedActiveRule(); - - loadedRule.setRuleKey(RuleKey.parse(r.getKey())); - loadedRule.setName(r.getName()); - loadedRule.setSeverity(active.getSeverity()); - - loadedRule.setCreatedAt(DateUtils.dateToLong(DateUtils.parseDateTime(active.getCreatedAt()))); - loadedRule.setUpdatedAt(DateUtils.dateToLong(DateUtils.parseDateTime(active.getUpdatedAt()))); - loadedRule.setLanguage(r.getLang()); - loadedRule.setInternalKey(r.getInternalKey()); - if (r.hasTemplateKey()) { - RuleKey templateRuleKey = RuleKey.parse(r.getTemplateKey()); - loadedRule.setTemplateRuleKey(templateRuleKey.rule()); - } - - Map<String, String> params = new HashMap<>(); + private static LoadedActiveRule convertActiveRule(ActiveRuleGson activeRule) { + LoadedActiveRule loadedRule = new LoadedActiveRule(); + loadedRule.setRuleKey(convertRuleKey(activeRule.ruleKey())); + loadedRule.setName(activeRule.name()); + loadedRule.setSeverity(activeRule.severity()); + loadedRule.setCreatedAt(DateUtils.dateToLong(DateUtils.parseDateTime(activeRule.createdAt()))); + loadedRule.setUpdatedAt(DateUtils.dateToLong(DateUtils.parseDateTime(activeRule.updatedAt()))); + loadedRule.setLanguage(activeRule.language()); + loadedRule.setInternalKey(activeRule.internalKey()); + loadedRule.setQProfileKey(activeRule.qProfileKey()); + ofNullable(activeRule.templateRuleKey()) + .map(RuleKey::parse) + .map(RuleKey::rule) + .ifPresent(loadedRule::setTemplateRuleKey); + loadedRule.setParams(activeRule.params() != null ? convertParams(activeRule.params()) : Map.of()); + loadedRule.setImpacts(activeRule.impacts() != null ? activeRule.impacts() : Map.of()); + loadedRule.setDeprecatedKeys(convertDeprecatedKeys(activeRule.deprecatedKeys())); + return loadedRule; + } - for (Rules.Rule.Param param : r.getParams().getParamsList()) { - params.put(param.getKey(), param.getDefaultValue()); - } + private static Map<String, String> convertParams(List<ParamGson> params) { + return params.stream().collect(Collectors.toMap(ParamGson::key, ParamGson::value)); + } - // overrides defaultValue if the key is the same - for (Param param : active.getParamsList()) { - params.put(param.getKey(), param.getValue()); - } + private static Set<RuleKey> convertDeprecatedKeys(@Nullable List<RuleKeyGson> deprecatedKeysList) { + return ofNullable(deprecatedKeysList) + .orElse(List.of()) + .stream() + .map(value -> RuleKey.of(value.repository(), value.rule())) + .collect(Collectors.toSet()); + } - loadedRule.setParams(params); + private static RuleKey convertRuleKey(RuleKeyGson ruleKey) { + return RuleKey.of(ruleKey.repository(), ruleKey.rule()); + } - Map<SoftwareQuality, Severity> impacts = new EnumMap<>(SoftwareQuality.class); - for (Common.Impact impact : active.getImpacts().getImpactsList()) { - impacts.put(SoftwareQuality.valueOf(impact.getSoftwareQuality().name()), ImpactFormatter.mapImpactSeverity(impact.getSeverity())); - } - loadedRule.setImpacts(impacts); + record ActiveRuleGson( + @SerializedName("ruleKey") RuleKeyGson ruleKey, + @SerializedName("name") String name, + @SerializedName("severity") String severity, + @SerializedName("createdAt") String createdAt, + @SerializedName("updatedAt") String updatedAt, + @SerializedName("internalKey") @Nullable String internalKey, + @SerializedName("language") String language, + @SerializedName("templateRuleKey") @Nullable String templateRuleKey, + @SerializedName("qProfileKey") String qProfileKey, + @SerializedName("deprecatedKeys") @Nullable List<RuleKeyGson> deprecatedKeys, + @SerializedName("params") @Nullable List<ParamGson> params, + @SerializedName("impacts") @Nullable Map<SoftwareQuality, Severity> impacts) { + } - loadedRule.setDeprecatedKeys(r.getDeprecatedKeys().getDeprecatedKeyList() - .stream() - .map(RuleKey::parse) - .collect(Collectors.toSet())); - loadedRules.add(loadedRule); - } + record RuleKeyGson(@SerializedName("repository") String repository, @SerializedName("rule") String rule) { + } - return loadedRules; + record ParamGson(@SerializedName("key") String key, @SerializedName("value") String value) { } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/LoadedActiveRule.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/LoadedActiveRule.java index 8f2c3643237..b0d1ee6d3b8 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/LoadedActiveRule.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/LoadedActiveRule.java @@ -39,6 +39,7 @@ public class LoadedActiveRule { private String templateRuleKey; private String internalKey; private Set<RuleKey> deprecatedKeys; + private String qProfileKey; public LoadedActiveRule() { // nothing to do here @@ -117,11 +118,12 @@ public class LoadedActiveRule { this.templateRuleKey = templateRuleKey; } + @CheckForNull public String getInternalKey() { return internalKey; } - public void setInternalKey(String internalKey) { + public void setInternalKey(@Nullable String internalKey) { this.internalKey = internalKey; } @@ -132,4 +134,12 @@ public class LoadedActiveRule { public void setDeprecatedKeys(Set<RuleKey> deprecatedKeys) { this.deprecatedKeys = deprecatedKeys; } + + public String getQProfileKey() { + return qProfileKey; + } + + public void setQProfileKey(String qProfileKey) { + this.qProfileKey = qProfileKey; + } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/ActiveRulesProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/ActiveRulesProviderTest.java index 4072d462f92..c5f3fceedbc 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/ActiveRulesProviderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/ActiveRulesProviderTest.java @@ -19,13 +19,12 @@ */ package org.sonar.scanner.rule; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import java.util.Date; -import java.util.LinkedList; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.assertj.core.groups.Tuple; import org.junit.Test; @@ -36,16 +35,17 @@ import org.sonar.api.batch.rule.internal.DefaultActiveRules; import org.sonar.api.issue.impact.Severity; import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; -import org.sonar.api.utils.DateUtils; -import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile; +import org.sonar.scanner.bootstrap.ScannerProperties; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static org.sonar.api.CoreProperties.PROJECT_KEY_PROPERTY; public class ActiveRulesProviderTest { + public static final String PROJECT_KEY = "my-awesome-project"; private final ActiveRulesProvider provider = new ActiveRulesProvider(); private final DefaultActiveRulesLoader loader = mock(DefaultActiveRulesLoader.class); @@ -57,21 +57,19 @@ public class ActiveRulesProviderTest { r1.setImpacts(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)); - List<LoadedActiveRule> qp1Rules = ImmutableList.of(r1, r2); - List<LoadedActiveRule> qp2Rules = ImmutableList.of(r2, r3); - List<LoadedActiveRule> qp3Rules = ImmutableList.of(r1, r3); + when(loader.load(PROJECT_KEY)).thenReturn(List.of(r1, r2, r3)); - when(loader.load("qp1")).thenReturn(qp1Rules); - when(loader.load("qp2")).thenReturn(qp2Rules); - when(loader.load("qp3")).thenReturn(qp3Rules); - - QualityProfiles profiles = mockProfiles("qp1", "qp2", "qp3"); - DefaultActiveRules activeRules = provider.provide(loader, profiles); + HashMap<String, String> propertiesMap = new HashMap<>(); + propertiesMap.put(PROJECT_KEY_PROPERTY, PROJECT_KEY); + ScannerProperties scannerProperties = new ScannerProperties(propertiesMap); + DefaultActiveRules activeRules = provider.provide(loader, scannerProperties); assertThat(activeRules.findAll()).hasSize(3); assertThat(activeRules.findAll()).extracting("ruleKey").containsOnly( RuleKey.of("rule1", "rule1"), RuleKey.of("rule2", "rule2"), RuleKey.of("rule3", "rule3")); + verify(loader).load(PROJECT_KEY); + Map<String, ActiveRule> activeRuleByKey = activeRules.findAll().stream().collect(Collectors.toMap(e -> e.ruleKey().rule(), e -> e)); assertThat(((DefaultActiveRule) activeRuleByKey.get("rule1")).impacts()) .containsExactlyInAnyOrderEntriesOf(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)); @@ -79,10 +77,6 @@ public class ActiveRulesProviderTest { assertThat(((DefaultActiveRule) activeRuleByKey.get("rule2")).impacts()).isEmpty(); assertThat(((DefaultActiveRule) activeRuleByKey.get("rule3")).impacts()).isEmpty(); - verify(loader).load("qp1"); - verify(loader).load("qp2"); - verify(loader).load("qp3"); - assertThat(activeRules.getDeprecatedRuleKeys(RuleKey.of("rule1", "rule1"))).containsOnly("rule1old:rule1old"); verifyNoMoreInteractions(loader); } @@ -90,40 +84,33 @@ public class ActiveRulesProviderTest { @Test public void testParamsAreTransformed() { LoadedActiveRule r1 = mockRule("rule1"); - LoadedActiveRule r2 = mockRule("rule2"); - r2.setParams(ImmutableMap.of("foo1", "bar1", "foo2", "bar2")); + LoadedActiveRule r2 = mockRule("rule2", b -> b.setParams(Map.of("foo1", "bar1", "foo2", "bar2"))); - List<LoadedActiveRule> qpRules = ImmutableList.of(r1, r2); - when(loader.load("qp")).thenReturn(qpRules); + when(loader.load(PROJECT_KEY)).thenReturn(List.of(r1, r2)); - QualityProfiles profiles = mockProfiles("qp"); - ActiveRules activeRules = provider.provide(loader, profiles); + HashMap<String, String> propertiesMap = new HashMap<>(); + propertiesMap.put(PROJECT_KEY_PROPERTY, PROJECT_KEY); + ScannerProperties scannerProperties = new ScannerProperties(propertiesMap); + ActiveRules activeRules = provider.provide(loader, scannerProperties); assertThat(activeRules.findAll()).hasSize(2); assertThat(activeRules.findAll()).extracting("ruleKey", "params").containsOnly( Tuple.tuple(RuleKey.of("rule1", "rule1"), ImmutableMap.of()), Tuple.tuple(RuleKey.of("rule2", "rule2"), ImmutableMap.of("foo1", "bar1", "foo2", "bar2"))); - verify(loader).load("qp"); + verify(loader).load(PROJECT_KEY); verifyNoMoreInteractions(loader); } - private static QualityProfiles mockProfiles(String... keys) { - List<QualityProfile> profiles = new LinkedList<>(); - - for (String k : keys) { - QualityProfile p = QualityProfile.newBuilder().setKey(k).setLanguage(k).setRulesUpdatedAt(DateUtils.formatDateTime(new Date())).build(); - profiles.add(p); + @SafeVarargs + private static LoadedActiveRule mockRule(String name, Consumer<LoadedActiveRule>... consumers) { + LoadedActiveRule rule = new LoadedActiveRule(); + rule.setName(name); + rule.setRuleKey(RuleKey.of(name, name)); + rule.setDeprecatedKeys(ImmutableSet.of(RuleKey.of(name + "old", name + "old"))); + for (Consumer<LoadedActiveRule> consumer : consumers) { + consumer.accept(rule); } - - return new QualityProfiles(profiles); - } - - private static LoadedActiveRule mockRule(String name) { - LoadedActiveRule r = new LoadedActiveRule(); - r.setName(name); - r.setRuleKey(RuleKey.of(name, name)); - r.setDeprecatedKeys(ImmutableSet.of(RuleKey.of(name + "old", name + "old"))); - return r; + return rule; } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/DefaultActiveRulesLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/DefaultActiveRulesLoaderTest.java index 59a49084742..cf66faeb655 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/DefaultActiveRulesLoaderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/DefaultActiveRulesLoaderTest.java @@ -19,46 +19,49 @@ */ package org.sonar.scanner.rule; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.Collection; +import com.google.gson.Gson; +import java.io.Reader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.IntStream; -import org.junit.Before; -import org.junit.Test; -import org.sonar.scanner.rule.LoadedActiveRule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; import org.sonar.scanner.WsTestUtil; import org.sonar.scanner.http.DefaultScannerWsClient; import org.sonar.scanner.scan.branch.BranchConfiguration; -import org.sonarqube.ws.Common; -import org.sonarqube.ws.Rules; -import org.sonarqube.ws.Rules.Active; -import org.sonarqube.ws.Rules.ActiveList; -import org.sonarqube.ws.Rules.Actives; -import org.sonarqube.ws.Rules.Rule; +import static java.util.Map.entry; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static org.sonar.api.issue.impact.Severity.HIGH; +import static org.sonar.scanner.rule.DefaultActiveRulesLoader.ActiveRuleGson; +import static org.sonar.scanner.rule.DefaultActiveRulesLoader.ParamGson; +import static org.sonar.scanner.rule.DefaultActiveRulesLoader.RuleKeyGson; -public class DefaultActiveRulesLoaderTest { +class DefaultActiveRulesLoaderTest { - private static final int PAGE_SIZE_1 = 150; - private static final int PAGE_SIZE_2 = 76; + private static final int NUMBER_OF_RULES = 150; private static final RuleKey EXAMPLE_KEY = RuleKey.of("java", "S108"); + private static final RuleKey CUSTOM_RULE_KEY = RuleKey.of("java", "my-custom-rule"); private static final String FORMAT_KEY = "format"; private static final String FORMAT_VALUE = "^[a-z][a-zA-Z0-9]*$"; + private static final String PROJECT_KEY = "myProjectKey"; private static final String SEVERITY_VALUE = Severity.MINOR; private DefaultActiveRulesLoader loader; private DefaultScannerWsClient wsClient; - @Before - public void setUp() { + @BeforeEach + void setUp() { wsClient = mock(DefaultScannerWsClient.class); BranchConfiguration branchConfig = mock(BranchConfiguration.class); when(branchConfig.isPullRequest()).thenReturn(false); @@ -66,78 +69,131 @@ public class DefaultActiveRulesLoaderTest { } @Test - public void load_shouldRequestRulesAndParseResponse() { - int total = PAGE_SIZE_1 + PAGE_SIZE_2; - - WsTestUtil.mockStream(wsClient, urlOfPage(1), responseOfSize(1, PAGE_SIZE_1, total)); - WsTestUtil.mockStream(wsClient, urlOfPage(2), responseOfSize(2, PAGE_SIZE_2, total)); - - Collection<LoadedActiveRule> activeRules = loader.load("c+-test_c+-values-17445"); - assertThat(activeRules).hasSize(total); - assertThat(activeRules) - .filteredOn(r -> r.getRuleKey().equals(EXAMPLE_KEY)) - .extracting(LoadedActiveRule::getParams) - .extracting(p -> p.get(FORMAT_KEY)) - .containsExactly(FORMAT_VALUE); - assertThat(activeRules) - .filteredOn(r -> r.getRuleKey().equals(EXAMPLE_KEY)) - .extracting(LoadedActiveRule::getSeverity) - .containsExactly(SEVERITY_VALUE); - assertThat(activeRules) - .filteredOn(r -> r.getRuleKey().equals(EXAMPLE_KEY)) - .extracting(LoadedActiveRule::getImpacts) - .containsExactlyInAnyOrder(Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH)); - - WsTestUtil.verifyCall(wsClient, urlOfPage(1)); - WsTestUtil.verifyCall(wsClient, urlOfPage(2)); + void load_shouldRequestRulesAndParseResponse() { + WsTestUtil.mockReader(wsClient, getUrl(), response()); + + Map<RuleKey, LoadedActiveRule> activeRulesByKey = loader.load(PROJECT_KEY).stream().collect(Collectors.toMap(LoadedActiveRule::getRuleKey, r -> r)); + assertThat(activeRulesByKey).hasSize(NUMBER_OF_RULES); + + var exampleRule = activeRulesByKey.get(EXAMPLE_KEY); + assertThat(exampleRule.getParams()).containsEntry(FORMAT_KEY, FORMAT_VALUE); + assertThat(exampleRule.getSeverity()).isEqualTo(SEVERITY_VALUE); + assertThat(exampleRule.getImpacts()).containsExactly(entry(SoftwareQuality.MAINTAINABILITY, HIGH)); + + var customRule = activeRulesByKey.get(CUSTOM_RULE_KEY); + assertThat(customRule.getTemplateRuleKey()).isEqualTo("ruleTemplate"); + + WsTestUtil.verifyCall(wsClient, getUrl()); verifyNoMoreInteractions(wsClient); } - private String urlOfPage(int page) { - return "/api/rules/list.protobuf?qprofile=c%2B-test_c%2B-values-17445&ps=500&p=" + page + ""; + private String getUrl() { + return "/api/v2/analysis/active_rules?projectKey=" + PROJECT_KEY; } - /** - * Generates an imaginary protobuf result. - * - * @param pageIndex page index, that the response should contain - * @param numberOfRules the number of rules, that the response should contain - * @param total the number of results on all pages - * @return the binary stream - */ - private InputStream responseOfSize(int pageIndex, int numberOfRules, int total) { - Rules.ListResponse.Builder rules = Rules.ListResponse.newBuilder(); - Actives.Builder actives = Actives.newBuilder(); - - IntStream.rangeClosed(1, numberOfRules) + private Reader response() { + List<ActiveRuleGson> activeRules = new ArrayList<>(); + + IntStream.rangeClosed(1, NUMBER_OF_RULES - 1) .mapToObj(i -> RuleKey.of("java", "S" + i)) .forEach(key -> { + ActiveRuleGsonBuilder builder = new ActiveRuleGsonBuilder(); - Rule.Builder ruleBuilder = Rule.newBuilder(); - ruleBuilder.setKey(key.toString()); - rules.addRules(ruleBuilder); + builder.setRuleKey(new RuleKeyGson(key.repository(), key.rule())); - Active.Builder activeBuilder = Active.newBuilder(); - activeBuilder.setCreatedAt("2014-05-27T15:50:45+0100"); - activeBuilder.setUpdatedAt("2014-05-27T15:50:45+0100"); + builder.setCreatedAt("2014-05-27T15:50:45+0100"); + builder.setUpdatedAt("2014-05-27T15:50:45+0100"); if (EXAMPLE_KEY.equals(key)) { - activeBuilder.addParams(Rules.Active.Param.newBuilder().setKey(FORMAT_KEY).setValue(FORMAT_VALUE)); - activeBuilder.setSeverity(SEVERITY_VALUE); - activeBuilder.setImpacts(Rules.Impacts.newBuilder().addImpacts(Common.Impact.newBuilder() - .setSoftwareQuality(Common.SoftwareQuality.MAINTAINABILITY) - .setSeverity(Common.ImpactSeverity.HIGH).build()).build()); + builder.setParams(List.of(new ParamGson(FORMAT_KEY, FORMAT_VALUE))); + builder.setSeverity(SEVERITY_VALUE); + builder.setImpacts(Map.of(SoftwareQuality.MAINTAINABILITY, HIGH)); } - ActiveList activeList = Rules.ActiveList.newBuilder().addActiveList(activeBuilder).build(); - actives.putAllActives(Map.of(key.toString(), activeList)); + + activeRules.add(builder.build()); }); - rules.setActives(actives); - rules.setPaging(Common.Paging.newBuilder() - .setTotal(total) - .setPageIndex(pageIndex) - .setPageSize(numberOfRules) - .build()); - return new ByteArrayInputStream(rules.build().toByteArray()); + ActiveRuleGsonBuilder builder = new ActiveRuleGsonBuilder(); + builder.setRuleKey(new RuleKeyGson(CUSTOM_RULE_KEY.repository(), CUSTOM_RULE_KEY.rule())); + builder.setCreatedAt("2014-05-27T15:50:45+0100"); + builder.setUpdatedAt("2014-05-27T15:50:45+0100"); + builder.setTemplateRuleKey("java:ruleTemplate"); + activeRules.add(builder.build()); + + return toReader(activeRules); + } + + private static Reader toReader(List<ActiveRuleGson> activeRules) { + String json = new Gson().toJson(activeRules); + return new StringReader(json); + } + + private static class ActiveRuleGsonBuilder { + private RuleKeyGson ruleKey; + private String name; + private String severity; + private String createdAt; + private String updatedAt; + private String internalKey; + private String language; + private String templateRuleKey; + private String qProfilKey; + private final List<RuleKeyGson> deprecatedKeys = new ArrayList<>(); + private final List<ParamGson> params = new ArrayList<>(); + private final Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts = new EnumMap<>(SoftwareQuality.class); + + public void setRuleKey(RuleKeyGson ruleKey) { + this.ruleKey = ruleKey; + } + + public void setName(String name) { + this.name = name; + } + + public void setSeverity(String severity) { + this.severity = severity; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public void setUpdatedAt(String updatedAt) { + this.updatedAt = updatedAt; + } + + public void setInternalKey(String internalKey) { + this.internalKey = internalKey; + } + + public void setLanguage(String language) { + this.language = language; + } + + public void setTemplateRuleKey(String templateRuleKey) { + this.templateRuleKey = templateRuleKey; + } + + public void setQProfilKey(String qProfilKey) { + this.qProfilKey = qProfilKey; + } + + public void setParams(List<ParamGson> params) { + this.params.clear(); + this.params.addAll(params); + } + + public void addAllDeprecatedKeys(List<RuleKeyGson> deprecatedKeys) { + this.deprecatedKeys.addAll(deprecatedKeys); + } + + public void setImpacts(Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> impacts) { + this.impacts.clear(); + this.impacts.putAll(impacts); + } + + public ActiveRuleGson build() { + return new ActiveRuleGson(ruleKey, name, severity, createdAt, updatedAt, internalKey, language, templateRuleKey, qProfilKey, deprecatedKeys, params, impacts); + } } } diff --git a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java index 89f54283c45..0dc09835a06 100644 --- a/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java +++ b/sonar-scanner-engine/src/testFixtures/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -49,6 +50,7 @@ import org.sonar.api.SonarEdition; import org.sonar.api.SonarProduct; import org.sonar.api.SonarQubeSide; import org.sonar.api.SonarRuntime; +import org.sonar.api.batch.rule.internal.NewActiveRule; import org.sonar.api.impl.server.RulesDefinitionContext; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Metric; @@ -223,6 +225,7 @@ public class ScannerMediumTester extends ExternalResource implements BeforeTestE r.setLanguage(language); r.setSeverity(severity); r.setDeprecatedKeys(emptySet()); + r.setQProfileKey("whatever"); activeRules.addActiveRule(r); return this; @@ -280,6 +283,7 @@ public class ScannerMediumTester extends ExternalResource implements BeforeTestE } } + public AnalysisBuilder newAnalysis() { return new AnalysisBuilder(this); } |