From d6b77d394bf5e19b89e9367e5fd621ad08b832e1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Thu, 16 Mar 2017 16:43:03 +0100 Subject: [PATCH] SONAR-8888 add DefinedQProfileCreation --- .../platformlevel/PlatformLevel4.java | 2 + .../DefinedQProfileCreation.java | 32 ++ .../DefinedQProfileCreationImpl.java | 63 ++++ .../RegisterQualityProfiles.java | 32 +- .../DefinedQProfileCreationImplTest.java | 325 ++++++++++++++++++ .../DefinedQProfileRepositoryRule.java | 3 +- .../RegisterQualityProfilesTest.java | 239 ++++++------- 7 files changed, 550 insertions(+), 146 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreation.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImpl.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImplTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index df4b1e4cb7c..c3ff7eecd92 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -140,6 +140,7 @@ import org.sonar.server.projecttag.ws.ProjectTagsWsModule; import org.sonar.server.property.InternalPropertiesImpl; import org.sonar.server.property.ws.PropertiesWs; import org.sonar.server.qualitygate.QualityGateModule; +import org.sonar.server.qualityprofile.DefinedQProfileCreationImpl; import org.sonar.server.qualityprofile.DefinedQProfileRepositoryImpl; import org.sonar.server.qualityprofile.QProfileBackuperImpl; import org.sonar.server.qualityprofile.QProfileComparison; @@ -275,6 +276,7 @@ public class PlatformLevel4 extends PlatformLevel { QProfileCopier.class, QProfileBackuperImpl.class, QProfileResetImpl.class, + DefinedQProfileCreationImpl.class, QProfilesWsModule.class, // rule diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreation.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreation.java new file mode 100644 index 00000000000..eec69901173 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreation.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualityprofile; + +import java.util.List; +import org.sonar.db.DbSession; +import org.sonar.db.organization.OrganizationDto; + +public interface DefinedQProfileCreation { + /** + * Persists the specified {@link DefinedQProfile} of the specified organization and adds any {@link ActiveRuleChange} + * to the specified list. + */ + void create(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List changes); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImpl.java new file mode 100644 index 00000000000..4058f522048 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImpl.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualityprofile; + +import java.util.List; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.ActiveRuleParam; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.loadedtemplate.LoadedTemplateDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.qualityprofile.QualityProfileDto; + +public class DefinedQProfileCreationImpl implements DefinedQProfileCreation { + private final DbClient dbClient; + private final QProfileFactory profileFactory; + private final RuleActivator ruleActivator; + + public DefinedQProfileCreationImpl(DbClient dbClient, QProfileFactory profileFactory, RuleActivator ruleActivator) { + this.dbClient = dbClient; + this.profileFactory = profileFactory; + this.ruleActivator = ruleActivator; + } + + @Override + public void create(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List changes) { + QualityProfileDto profileDto = dbClient.qualityProfileDao().selectByNameAndLanguage(organization, qualityProfile.getName(), qualityProfile.getLanguage(), session); + if (profileDto != null) { + changes.addAll(profileFactory.delete(session, profileDto.getKey(), true)); + } + QualityProfileDto newQProfileDto = profileFactory.create(session, organization, qualityProfile.getQProfileName(), qualityProfile.isDefault()); + for (org.sonar.api.rules.ActiveRule activeRule : qualityProfile.getActiveRules()) { + RuleKey ruleKey = RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey()); + RuleActivation activation = new RuleActivation(ruleKey); + activation.setSeverity(activeRule.getSeverity() != null ? activeRule.getSeverity().name() : null); + for (ActiveRuleParam param : activeRule.getActiveRuleParams()) { + activation.setParameter(param.getKey(), param.getValue()); + } + changes.addAll(ruleActivator.activate(session, activation, newQProfileDto)); + } + + LoadedTemplateDto template = new LoadedTemplateDto(organization.getUuid(), qualityProfile.getLoadedTemplateType()); + dbClient.loadedTemplateDao().insert(template, session); + session.commit(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java index 6be9d79fb91..c994d504f2f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java @@ -21,8 +21,6 @@ package org.sonar.server.qualityprofile; import java.util.ArrayList; import java.util.List; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.ActiveRuleParam; import org.sonar.api.server.ServerSide; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -30,9 +28,7 @@ import org.sonar.api.utils.log.Profiler; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.Pagination; -import org.sonar.db.loadedtemplate.LoadedTemplateDto; import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.qualityprofile.QualityProfileDto; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import static org.sonar.db.Pagination.forPage; @@ -48,17 +44,14 @@ public class RegisterQualityProfiles { private final DefinedQProfileRepository definedQProfileRepository; private final DbClient dbClient; - private final QProfileFactory profileFactory; - private final RuleActivator ruleActivator; + private final DefinedQProfileCreation definedQProfileCreation; private final ActiveRuleIndexer activeRuleIndexer; public RegisterQualityProfiles(DefinedQProfileRepository definedQProfileRepository, - DbClient dbClient, - QProfileFactory profileFactory, CachingRuleActivator ruleActivator, ActiveRuleIndexer activeRuleIndexer) { + DbClient dbClient, DefinedQProfileCreation definedQProfileCreation, ActiveRuleIndexer activeRuleIndexer) { this.definedQProfileRepository = definedQProfileRepository; this.dbClient = dbClient; - this.profileFactory = profileFactory; - this.ruleActivator = ruleActivator; + this.definedQProfileCreation = definedQProfileCreation; this.activeRuleIndexer = activeRuleIndexer; } @@ -96,24 +89,7 @@ public class RegisterQualityProfiles { private void registerPerQualityProfileAndOrganization(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List changes) { LOGGER.debug("Register profile {} for organization {}", qualityProfile.getQProfileName(), organization.getKey()); - QualityProfileDto profileDto = dbClient.qualityProfileDao().selectByNameAndLanguage(organization, qualityProfile.getName(), qualityProfile.getLanguage(), session); - if (profileDto != null) { - changes.addAll(profileFactory.delete(session, profileDto.getKey(), true)); - } - QualityProfileDto newQProfileDto = profileFactory.create(session, organization, qualityProfile.getQProfileName(), qualityProfile.isDefault()); - for (org.sonar.api.rules.ActiveRule activeRule : qualityProfile.getActiveRules()) { - RuleKey ruleKey = RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey()); - RuleActivation activation = new RuleActivation(ruleKey); - activation.setSeverity(activeRule.getSeverity() != null ? activeRule.getSeverity().name() : null); - for (ActiveRuleParam param : activeRule.getActiveRuleParams()) { - activation.setParameter(param.getKey(), param.getValue()); - } - changes.addAll(ruleActivator.activate(session, activation, newQProfileDto)); - } - - LoadedTemplateDto template = new LoadedTemplateDto(organization.getUuid(), qualityProfile.getLoadedTemplateType()); - dbClient.loadedTemplateDao().insert(template, session); - session.commit(); + definedQProfileCreation.create(session, qualityProfile, organization, changes); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImplTest.java new file mode 100644 index 00000000000..2698025c2fc --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileCreationImplTest.java @@ -0,0 +1,325 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.qualityprofile; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import javax.annotation.Nullable; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.profiles.RulesProfile; +import org.sonar.api.resources.Language; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.ActiveRule; +import org.sonar.api.rules.RuleParam; +import org.sonar.api.rules.RulePriority; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.stream.Collectors; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.qualityprofile.ActiveRuleKey; +import org.sonar.db.qualityprofile.QualityProfileDto; +import org.sonar.server.language.LanguageTesting; +import org.sonar.server.tester.UserSessionRule; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.core.util.UtcDateUtils.formatDateTime; + +public class DefinedQProfileCreationImplTest { + private static final Language FOO_LANGUAGE = LanguageTesting.newLanguage("foo", "foo", "foo"); + private static final String TABLE_RULES_PROFILES = "RULES_PROFILES"; + private static final String TABLE_LOADED_TEMPLATES = "loaded_templates"; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public UserSessionRule userSessionRule = UserSessionRule.standalone(); + @Rule + public DefinedQProfileRepositoryRule definedQProfileRepositoryRule = new DefinedQProfileRepositoryRule(); + + private DbClient dbClient = dbTester.getDbClient(); + private DbSession dbSession = dbClient.openSession(false); + private UuidFactory mockedUuidFactory = mock(UuidFactory.class); + private System2 mockedSystem2 = mock(System2.class); + private CachingRuleActivator mockedCachingRuleActivator = mock(CachingRuleActivator.class); + + private DefinedQProfileCreationImpl underTest = new DefinedQProfileCreationImpl( + dbClient, + new QProfileFactory(dbClient, mockedUuidFactory, mockedSystem2), + mockedCachingRuleActivator); + private List activeRuleChanges = new ArrayList<>(); + + @After + public void tearDown() throws Exception { + dbSession.close(); + } + + @Test + public void create_creates_qp_and_store_flag_in_loaded_templates_for_specified_organization() { + OrganizationDto organization = dbTester.organizations().insert(); + DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", false); + long date = 2_456_789L; + String uuid = "uuid 1"; + mockForSingleQPInsert(uuid, date); + + underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); + + QualityProfileDto dto = getPersistedQP(organization, FOO_LANGUAGE, "foo1"); + assertThat(dto.getId()).isNotNull(); + assertThat(dto.getOrganizationUuid()).isEqualTo(organization.getUuid()); + assertThat(dto.getLanguage()).isEqualTo(FOO_LANGUAGE.getKey()); + assertThat(dto.getName()).isEqualTo("foo1"); + assertThat(dto.getKee()).isEqualTo(uuid); + assertThat(dto.getKey()).isEqualTo(dto.getKee()); + assertThat(dto.getParentKee()).isNull(); + assertThat(dto.getRulesUpdatedAt()).isIn(formatDateTime(new Date(date))); + assertThat(dto.getLastUsed()).isNull(); + assertThat(dto.getUserUpdatedAt()).isNull(); + assertThat(dto.isDefault()).isFalse(); + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(1); + + assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(definedQProfile.getLoadedTemplateType(), organization.getUuid(), dbTester.getSession())) + .isEqualTo(1); + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_LOADED_TEMPLATES)).isEqualTo(1); + assertThat(activeRuleChanges).isEmpty(); + } + + @Test + public void create_persists_default_flag_of_DefinedQProfile() { + OrganizationDto organization = dbTester.organizations().insert(); + DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true); + mockForSingleQPInsert(); + + underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); + + assertThat(getPersistedQP(organization, FOO_LANGUAGE, "foo1").isDefault()).isTrue(); + assertThat(activeRuleChanges).isEmpty(); + } + + @Test + public void create_deletes_qp_if_already_exists() { + OrganizationDto organization = dbTester.organizations().insert(); + DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", false); + long date = 2_456_789L; + String uuid = "uuid 1"; + mockForSingleQPInsert(uuid, date); + QualityProfileDto existing = dbTester.qualityProfiles().insertQualityProfile( + QualityProfileDto.createFor("a key") + .setName(definedQProfile.getName()) + .setLanguage(definedQProfile.getLanguage()) + .setOrganizationUuid(organization.getUuid())); + + underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); + + QualityProfileDto dto = getPersistedQP(organization, FOO_LANGUAGE, "foo1"); + assertThat(dto.getId()).isNotEqualTo(existing.getId()); + assertThat(dto.getKey()).isNotEqualTo(existing.getKey()); + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(1); + } + + @Test + public void create_persists_relies_on_ruleActivator_to_persist_activerules_and_return_all_changes_in_order() { + List callLogs = new ArrayList<>(); + ActiveRuleChange[] changes = {newActiveRuleChange("0"), newActiveRuleChange("1"), newActiveRuleChange("2"), + newActiveRuleChange("3"), newActiveRuleChange("4")}; + mockRuleActivatorActivate(callLogs, + asList(changes[4], changes[1]), + Collections.emptyList(), + Collections.singletonList(changes[0]), + Collections.emptyList(), + asList(changes[2], changes[3])); + + OrganizationDto organization = dbTester.organizations().insert(); + DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, + activeRule("A", RulePriority.INFO), activeRule("B", RulePriority.MINOR), activeRule("C", RulePriority.MAJOR), + activeRule("D", RulePriority.CRITICAL), activeRule("E", RulePriority.BLOCKER)); + mockForSingleQPInsert(); + + underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); + + assertThat(callLogs) + .hasSize(5); + assertThat(activeRuleChanges) + .containsExactly(changes[4], changes[1], changes[0], changes[2], changes[3]); + } + + @Test + public void create_creates_ruleKey_in_RuleActivation_from_ActiveRule() { + List callLogs = new ArrayList<>(); + mockRuleActivatorActivate(callLogs, Collections.emptyList()); + + OrganizationDto organization = dbTester.organizations().insert(); + ActiveRule activeRule = activeRule("A", RulePriority.BLOCKER); + DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, activeRule); + mockForSingleQPInsert(); + + underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); + + assertThat(callLogs) + .hasSize(1); + assertThat(callLogs.get(0).getRuleActivation()) + .matches(s -> s.getRuleKey().equals(RuleKey.of(activeRule.getRepositoryKey(), activeRule.getRuleKey()))); + assertThat(callLogs.get(0).qualityProfileDto.getId()) + .isEqualTo(getPersistedQP(organization, FOO_LANGUAGE, "foo1").getId()); + } + + @Test + public void create_supports_all_RulePriority_values_and_null() { + List callLogs = new ArrayList<>(); + mockRuleActivatorActivate(callLogs, + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList()); + + OrganizationDto organization = dbTester.organizations().insert(); + DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, + activeRule("A", RulePriority.INFO), activeRule("B", RulePriority.MINOR), activeRule("C", RulePriority.MAJOR), + activeRule("D", RulePriority.CRITICAL), activeRule("E", RulePriority.BLOCKER), activeRule("F", null)); + mockForSingleQPInsert(); + + underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); + + assertThat(callLogs) + .extracting(CallLog::getRuleActivation) + .extracting(RuleActivation::getSeverity) + .containsExactly("INFO", "MINOR", "MAJOR", "CRITICAL", "BLOCKER", "MAJOR"); + assertThat(callLogs) + .extracting(CallLog::getQualityProfileDto) + .extracting(QualityProfileDto::getId) + .containsOnly(getPersistedQP(organization, FOO_LANGUAGE, "foo1").getId()); + } + + @Test + public void create_adds_all_parameters_to_RuleActivation() { + List callLogs = new ArrayList<>(); + mockRuleActivatorActivate(callLogs, Collections.emptyList()); + + OrganizationDto organization = dbTester.organizations().insert(); + DefinedQProfile definedQProfile = definedQProfileRepositoryRule.create(FOO_LANGUAGE, "foo1", true, + activeRule("A", RulePriority.INFO, + "param1", "value1", + "param2", "value2", + "param3", "value3", + "param4", "value4")); + mockForSingleQPInsert(); + + underTest.create(dbSession, definedQProfile, organization, activeRuleChanges); + + Set> parameters = callLogs.get(0).getRuleActivation().getParameters().entrySet(); + assertThat(parameters) + .extracting(Map.Entry::getKey) + .containsOnlyOnce("param1", "param2", "param3", "param4"); + assertThat(parameters) + .extracting(Map.Entry::getValue) + .containsOnlyOnce("value1", "value2", "value3", "value4"); + } + + private void mockRuleActivatorActivate(List callLogs, List... changesPerCall) { + Iterator> changesPerCallIt = Arrays.asList(changesPerCall).iterator(); + doAnswer(t -> { + RuleActivation ruleActivation = t.getArgumentAt(1, RuleActivation.class); + QualityProfileDto qualityProfileDto = t.getArgumentAt(2, QualityProfileDto.class); + callLogs.add(new CallLog(ruleActivation, qualityProfileDto)); + return changesPerCallIt.next(); + }).when(mockedCachingRuleActivator) + .activate(any(DbSession.class), any(RuleActivation.class), any(QualityProfileDto.class)); + } + + private static ActiveRule activeRule(String id, @Nullable RulePriority rulePriority, String... parameters) { + org.sonar.api.rules.Rule rule = new org.sonar.api.rules.Rule("plugin_name_" + id, "rule_key_" + id); + rule.setParams(Arrays.stream(parameters) + .filter(new EvenElementPredicate()) + .map(s -> new RuleParam(rule, s, "desc of s", "type of s")) + .collect(Collectors.toList(parameters.length / 2))); + ActiveRule res = new ActiveRule( + new RulesProfile("rule_profile_name_" + id, "language_" + id), + rule.setSeverity(rulePriority), + rulePriority); + for (int i = 0; i < parameters.length; i++) { + res.setParameter(parameters[i], parameters[i + 1]); + i++; + } + return res; + } + + private static final class CallLog { + private final RuleActivation ruleActivation; + private final QualityProfileDto qualityProfileDto; + + private CallLog(RuleActivation ruleActivation, QualityProfileDto qualityProfileDto) { + this.ruleActivation = ruleActivation; + this.qualityProfileDto = qualityProfileDto; + } + + public RuleActivation getRuleActivation() { + return ruleActivation; + } + + public QualityProfileDto getQualityProfileDto() { + return qualityProfileDto; + } + } + + private void mockForSingleQPInsert() { + mockForSingleQPInsert("generated uuid", 2_456_789); + } + + private void mockForSingleQPInsert(String uuid, long now) { + when(mockedUuidFactory.create()).thenReturn(uuid).thenThrow(new UnsupportedOperationException("uuidFactory should be called only once")); + when(mockedSystem2.now()).thenReturn(now).thenThrow(new UnsupportedOperationException("now should be called only once")); + } + + private QualityProfileDto getPersistedQP(OrganizationDto organization, Language language, String name) { + return dbClient.qualityProfileDao().selectByNameAndLanguage(organization, name, language.getKey(), dbTester.getSession()); + } + + private static ActiveRuleChange newActiveRuleChange(String id) { + return ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(id, RuleKey.of(id + "1", id + "2"))); + } + + private static class EvenElementPredicate implements Predicate { + private int counter = -1; + + @Override + public boolean test(String s) { + counter++; + return counter % 2 == 0; + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryRule.java index b6745a0f46a..83ecacb5588 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryRule.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/DefinedQProfileRepositoryRule.java @@ -84,11 +84,12 @@ public class DefinedQProfileRepositoryRule extends ExternalResource implements D return definedQProfile; } - private DefinedQProfile create(Language language, String profileName, boolean isDefault) { + public DefinedQProfile create(Language language, String profileName, boolean isDefault, org.sonar.api.rules.ActiveRule... rules) { return new DefinedQProfile.Builder() .setLanguage(language.getKey()) .setName(profileName) .setDeclaredDefault(isDefault) + .addRules(Arrays.asList(rules)) .build(DigestUtils.getMd5Digest()); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesTest.java index 007a1cd715c..28abb7f9052 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesTest.java @@ -19,39 +19,41 @@ */ package org.sonar.server.qualityprofile; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; import org.sonar.api.resources.Language; -import org.sonar.api.utils.System2; -import org.sonar.core.util.UuidFactory; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.internal.AlwaysIncreasingSystem2; import org.sonar.db.DbClient; +import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.loadedtemplate.LoadedTemplateDto; import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.qualityprofile.QualityProfileDto; +import org.sonar.db.qualityprofile.ActiveRuleKey; import org.sonar.server.language.LanguageTesting; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.tester.UserSessionRule; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doNothing; 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.core.util.UtcDateUtils.formatDateTime; public class RegisterQualityProfilesTest { private static final Language FOO_LANGUAGE = LanguageTesting.newLanguage("foo", "foo", "foo"); private static final Language BAR_LANGUAGE = LanguageTesting.newLanguage("bar", "bar", "bar"); - private static final String TABLE_RULES_PROFILES = "RULES_PROFILES"; - private static final String TABLE_LOADED_TEMPLATES = "loaded_templates"; @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); + public DbTester dbTester = DbTester.create(new AlwaysIncreasingSystem2()); @Rule public UserSessionRule userSessionRule = UserSessionRule.standalone(); @Rule @@ -61,177 +63,180 @@ public class RegisterQualityProfilesTest { private DbClient dbClient = dbTester.getDbClient(); private DbClient mockedDbClient = mock(DbClient.class); - private UuidFactory mockedUuidFactory = mock(UuidFactory.class); - private System2 mockedSystem2 = mock(System2.class); private ActiveRuleIndexer mockedActiveRuleIndexer = mock(ActiveRuleIndexer.class); + private DummyDefinedQProfileCreation definedQProfileCreation = new DummyDefinedQProfileCreation(); private RegisterQualityProfiles underTest = new RegisterQualityProfiles( definedQProfileRepositoryRule, dbClient, - new QProfileFactory(dbClient, mockedUuidFactory, mockedSystem2), - new CachingRuleActivator(mockedSystem2, dbClient, null, new CachingRuleActivatorContextFactory(dbClient), null, null, userSessionRule), + definedQProfileCreation, mockedActiveRuleIndexer); @Test public void start_fails_if_DefinedQProfileRepository_has_not_been_initialized() { expectedException.expect(IllegalStateException.class); expectedException.expectMessage("initialize must be called first"); - + underTest.start(); } @Test public void no_action_in_DB_nothing_to_index_when_there_is_no_DefinedQProfile() { - RegisterQualityProfiles underTest = new RegisterQualityProfiles(definedQProfileRepositoryRule, mockedDbClient, null, null, mockedActiveRuleIndexer); + RegisterQualityProfiles underTest = new RegisterQualityProfiles(definedQProfileRepositoryRule, mockedDbClient, null, mockedActiveRuleIndexer); definedQProfileRepositoryRule.initialize(); underTest.start(); - assertThat(definedQProfileRepositoryRule.isInitialized()).isTrue(); + assertThat(definedQProfileCreation.getCallLogs()).isEmpty(); verify(mockedDbClient).openSession(false); verify(mockedActiveRuleIndexer).index(Collections.emptyList()); verifyNoMoreInteractions(mockedDbClient, mockedActiveRuleIndexer); } @Test - public void start_creates_qp_and_store_flag_in_loaded_templates_for_each_organization_id_BD() { - OrganizationDto otherOrganization = dbTester.organizations().insert(); - String[] uuids = {"uuid 1", "uuid 2"}; - Long[] dates = {2_456_789L, 6_123_789L}; - String[] formattedDates = {formatDateTime(new Date(dates[0])), formatDateTime(new Date(dates[1]))}; + public void start_creates_qps_for_every_organization_in_DB_when_LoadedTemplate_table_is_empty() { + OrganizationDto organization1 = dbTester.organizations().insert(); + OrganizationDto organization2 = dbTester.organizations().insert(); DefinedQProfile definedQProfile = definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1"); - mockForQPInserts(uuids, dates); - definedQProfileRepositoryRule.initialize(); - - underTest.start(); - - assertThat(definedQProfileRepositoryRule.isInitialized()).isTrue(); - Arrays.asList(dbTester.getDefaultOrganization(), otherOrganization) - .forEach(organization -> { - QualityProfileDto dto = getPersistedQP(organization, FOO_LANGUAGE, "foo1"); - assertThat(dto.getId()).isNotNull(); - assertThat(dto.getOrganizationUuid()).isEqualTo(organization.getUuid()); - assertThat(dto.getLanguage()).isEqualTo(FOO_LANGUAGE.getKey()); - assertThat(dto.getName()).isEqualTo("foo1"); - assertThat(dto.getKee()).isIn(uuids); - assertThat(dto.getKey()).isEqualTo(dto.getKee()); - assertThat(dto.getParentKee()).isNull(); - assertThat(dto.getRulesUpdatedAt()).isIn(formattedDates); - assertThat(dto.getLastUsed()).isNull(); - assertThat(dto.getUserUpdatedAt()).isNull(); - assertThat(dto.isDefault()).isFalse(); - - assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(definedQProfile.getLoadedTemplateType(), organization.getUuid(), dbTester.getSession())) - .isEqualTo(1); - }); - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(2); - } - - @Test - public void start_persists_default_flag_of_DefinedQualityProfile() { - definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1", false); - definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo2", true); - definedQProfileRepositoryRule.add(BAR_LANGUAGE, "bar1", true); - definedQProfileRepositoryRule.add(BAR_LANGUAGE, "bar2", false); - mockForQPInserts(new String[]{"uuid1", "uuid2", "uuid3", "uuid4"}, new Long[]{ 1L, 2L, 3L, 4L}); definedQProfileRepositoryRule.initialize(); underTest.start(); - assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "foo1").isDefault()).isFalse(); - assertThat(getPersistedQP(dbTester.getDefaultOrganization(), FOO_LANGUAGE, "foo2").isDefault()).isTrue(); - assertThat(getPersistedQP(dbTester.getDefaultOrganization(), BAR_LANGUAGE, "bar1").isDefault()).isTrue(); - assertThat(getPersistedQP(dbTester.getDefaultOrganization(), BAR_LANGUAGE, "bar2").isDefault()).isFalse(); + assertThat(definedQProfileCreation.getCallLogs()) + .containsExactly( + callLog(definedQProfile, dbTester.getDefaultOrganization()), + callLog(definedQProfile, organization1), + callLog(definedQProfile, organization2)); } @Test - public void start_does_not_create_sq_if_loaded_profile_of_organization_already_exists() { + public void start_creates_qps_only_for_organizations_in_DB_without_loaded_template() { OrganizationDto org1 = dbTester.organizations().insert(); OrganizationDto org2 = dbTester.organizations().insert(); DefinedQProfile definedQProfile = definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1"); dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(dbTester.getDefaultOrganization().getUuid(), definedQProfile.getLoadedTemplateType()), dbTester.getSession()); dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(org1.getUuid(), definedQProfile.getLoadedTemplateType()), dbTester.getSession()); dbTester.commit(); - mockForSingleQPInsert(); definedQProfileRepositoryRule.initialize(); underTest.start(); - assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(definedQProfile.getLoadedTemplateType(), org2.getUuid(), dbTester.getSession())) - .isEqualTo(1); - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_LOADED_TEMPLATES)).isEqualTo(3); - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(1); + assertThat(definedQProfileCreation.getCallLogs()) + .containsExactly(callLog(definedQProfile, org2)); } @Test public void start_creates_different_qps_and_their_loaded_templates_if_several_profile_has_same_name_for_different_languages() { - String uuid1 = "uuid1"; - String uuid2 = "uuid2"; - long date1 = 2_456_789L; - long date2 = 4_231_654L; String name = "doh"; DefinedQProfile definedQProfile1 = definedQProfileRepositoryRule.add(FOO_LANGUAGE, name, true); DefinedQProfile definedQProfile2 = definedQProfileRepositoryRule.add(BAR_LANGUAGE, name, true); - mockForQPInserts(new String[]{uuid1, uuid2}, new Long[]{date1, date2}); definedQProfileRepositoryRule.initialize(); underTest.start(); - OrganizationDto organization = dbTester.getDefaultOrganization(); - QualityProfileDto dto = getPersistedQP(organization, FOO_LANGUAGE, name); - String uuidQP1 = dto.getKee(); - assertThat(dto.getId()).isNotNull(); - assertThat(dto.getLanguage()).isEqualTo(FOO_LANGUAGE.getKey()); - assertThat(dto.getName()).isEqualTo(name); - assertThat(uuidQP1).isIn(uuid1, uuid2); - assertThat(dto.getKey()).isEqualTo(uuidQP1); - assertThat(dto.getParentKee()).isNull(); - assertThat(dto.getRulesUpdatedAt()).isIn(formatDateTime(new Date(date1)), formatDateTime(new Date(date2))); - assertThat(dto.getLastUsed()).isNull(); - assertThat(dto.getUserUpdatedAt()).isNull(); - assertThat(dto.isDefault()).isTrue(); - dto = getPersistedQP(dbTester.getDefaultOrganization(), BAR_LANGUAGE, name); - assertThat(dto.getId()).isNotNull(); - assertThat(dto.getLanguage()).isEqualTo(BAR_LANGUAGE.getKey()); - assertThat(dto.getName()).isEqualTo(name); - String uuidQP2 = uuid1.equals(uuidQP1) ? uuid2 : uuid1; - long dateQP2 = uuid1.equals(uuidQP1) ? date2 : date1; - assertThat(dto.getKee()).isEqualTo(uuidQP2); - assertThat(dto.getKey()).isEqualTo(uuidQP2); - assertThat(dto.getParentKee()).isNull(); - assertThat(dto.getRulesUpdatedAt()).isEqualTo(formatDateTime(new Date(dateQP2))); - assertThat(dto.getLastUsed()).isNull(); - assertThat(dto.getUserUpdatedAt()).isNull(); - assertThat(dto.isDefault()).isTrue(); - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), TABLE_RULES_PROFILES)).isEqualTo(2); - - assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(definedQProfile1.getLoadedTemplateType(), organization.getUuid(), dbTester.getSession())) - .isEqualTo(1); - assertThat(dbClient.loadedTemplateDao().countByTypeAndKey(definedQProfile2.getLoadedTemplateType(), organization.getUuid(), dbTester.getSession())) - .isEqualTo(1); + assertThat(definedQProfileCreation.getCallLogs()) + .containsExactly(callLog(definedQProfile2, dbTester.getDefaultOrganization()), callLog(definedQProfile1, dbTester.getDefaultOrganization())); } - private void mockForSingleQPInsert() { - mockForSingleQPInsert("generated uuid", 2_456_789); - } + @Test + public void start_indexes_ActiveRuleChanges_in_order() { + dbTester.organizations().insert(); + dbTester.organizations().insert(); + dbTester.organizations().insert(); + definedQProfileRepositoryRule.add(FOO_LANGUAGE, "foo1", false); + definedQProfileRepositoryRule.initialize(); + ActiveRuleChange ruleChange1 = newActiveRuleChange("1"); + ActiveRuleChange ruleChange2 = newActiveRuleChange("2"); + ActiveRuleChange ruleChange3 = newActiveRuleChange("3"); + ActiveRuleChange ruleChange4 = newActiveRuleChange("4"); + definedQProfileCreation.addChangesPerCall(ruleChange1, ruleChange3); + // no change for second org + definedQProfileCreation.addChangesPerCall(); + definedQProfileCreation.addChangesPerCall(ruleChange2); + definedQProfileCreation.addChangesPerCall(ruleChange4); + ArgumentCaptor> indexedChangesCaptor = ArgumentCaptor.forClass((Class>) (Object) List.class); + doNothing().when(mockedActiveRuleIndexer).index(indexedChangesCaptor.capture()); - private void mockForSingleQPInsert(String uuid, long now) { - when(mockedUuidFactory.create()).thenReturn(uuid).thenThrow(new UnsupportedOperationException("uuidFactory should be called only once")); - when(mockedSystem2.now()).thenReturn(now).thenThrow(new UnsupportedOperationException("now should be called only once")); + underTest.start(); + + assertThat(indexedChangesCaptor.getValue()) + .containsExactly(ruleChange1, ruleChange3, ruleChange2, ruleChange4); } - private void mockForQPInserts(String[] uuids, Long[] dates) { - when(mockedUuidFactory.create()) - .thenReturn(uuids[0], Arrays.copyOfRange(uuids, 1, uuids.length)) - .thenThrow(new UnsupportedOperationException("uuidFactory should be called only " + uuids.length + " times")); + private static ActiveRuleChange newActiveRuleChange(String id) { + return ActiveRuleChange.createFor(ActiveRuleChange.Type.ACTIVATED, ActiveRuleKey.of(id, RuleKey.of(id + "1", id + "2"))); + } - when(mockedSystem2.now()) - .thenReturn(dates[0], Arrays.copyOfRange(dates, 1, dates.length)) - .thenThrow(new UnsupportedOperationException("now should be called only " + dates.length + " times")); + private class DummyDefinedQProfileCreation implements DefinedQProfileCreation { + private List> changesPerCall; + private Iterator> changesPerCallIterator; + private final List callLogs = new ArrayList<>(); + + @Override + public void create(DbSession session, DefinedQProfile qualityProfile, OrganizationDto organization, List changes) { + callLogs.add(callLog(qualityProfile, organization)); + + // RegisterQualityProfiles relies on the fact that DefinedQProfileCreation populates table LOADED_TEMPLATE each time create is called + // to not loop infinitely + dbClient.loadedTemplateDao().insert(new LoadedTemplateDto(organization.getUuid(), qualityProfile.getLoadedTemplateType()), session); + + if (changesPerCall != null) { + if (changesPerCallIterator == null) { + this.changesPerCallIterator = changesPerCall.iterator(); + } + changes.addAll(changesPerCallIterator.next()); + } + } + + void addChangesPerCall(ActiveRuleChange... changes) { + if (changesPerCall == null) { + this.changesPerCall = new ArrayList<>(); + } + changesPerCall.add(Arrays.asList(changes)); + } + + List getCallLogs() { + return callLogs; + } } - private QualityProfileDto getPersistedQP(OrganizationDto organization, Language language, String name) { - return dbClient.qualityProfileDao().selectByNameAndLanguage(organization, name, language.getKey(), dbTester.getSession()); + private static final class CallLog { + private final DefinedQProfile definedQProfile; + private final OrganizationDto organization; + + private CallLog(DefinedQProfile definedQProfile, OrganizationDto organization) { + this.definedQProfile = definedQProfile; + this.organization = organization; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CallLog callLog = (CallLog) o; + return definedQProfile == callLog.definedQProfile && + organization.getUuid().equals(callLog.organization.getUuid()); + } + + @Override + public int hashCode() { + return Objects.hash(definedQProfile, organization); + } + + @Override + public String toString() { + return "CallLog{" + + "qp=" + definedQProfile.getLanguage() + '-' + definedQProfile.getName() + '-' + definedQProfile.isDefault() + + ", org=" + organization.getKey() + + '}'; + } } + private static CallLog callLog(DefinedQProfile definedQProfile, OrganizationDto organizationDto) { + return new CallLog(definedQProfile, organizationDto); + } } -- 2.39.5