return mapper(dbSession).selectOrderedByOrganizationUuid(organization.getUuid());
}
- public List<RulesProfileDto> selectBuiltInRulesProfiles(DbSession dbSession) {
+ public List<RulesProfileDto> selectBuiltInRuleProfiles(DbSession dbSession) {
return mapper(dbSession).selectBuiltInRuleProfiles();
}
+ public List<QProfileDto> selectBuiltInRuleProfilesWithActiveRules(DbSession dbSession) {
+ return mapper(dbSession).selectBuiltInRuleProfilesWithActiveRules();
+ }
+
@CheckForNull
public RulesProfileDto selectRuleProfile(DbSession dbSession, String ruleProfileUuid) {
return mapper(dbSession).selectRuleProfile(ruleProfileUuid);
return executeLargeInputs(languages, partition -> mapper(dbSession).selectDefaultProfiles(organization.getUuid(), partition));
}
+ public List<QProfileDto> selectDefaultBuiltInProfilesWithoutActiveRules(DbSession dbSession) {
+ return mapper(dbSession).selectDefaultBuiltInProfilesWithoutActiveRules();
+ }
+
@CheckForNull
public QProfileDto selectDefaultProfile(DbSession dbSession, OrganizationDto organization, String language) {
return mapper(dbSession).selectDefaultProfile(organization.getUuid(), language);
List<RulesProfileDto> selectBuiltInRuleProfiles();
+ List<QProfileDto> selectBuiltInRuleProfilesWithActiveRules();
+
@CheckForNull
RulesProfileDto selectRuleProfile(@Param("uuid") String ruleProfileUuid);
@CheckForNull
QProfileDto selectDefaultProfile(@Param("organizationUuid") String organizationUuid, @Param("language") String language);
+ List<QProfileDto> selectDefaultBuiltInProfilesWithoutActiveRules();
+
List<QProfileDto> selectDefaultProfiles(
@Param("organizationUuid") String organizationUuid,
@Param("languages") Collection<String> languages);
and oqp.organization_uuid = #{organizationUuid, jdbcType=VARCHAR}
</select>
+ <select id="selectBuiltInRuleProfilesWithActiveRules" resultType="org.sonar.db.qualityprofile.QProfileDto">
+ SELECT
+ <include refid="qProfileColumns"/>
+ FROM org_qprofiles oqp
+ INNER JOIN rules_profiles rp ON oqp.rules_profile_uuid = rp.kee
+ WHERE
+ rp.is_built_in = ${_true}
+ AND EXISTS (
+ SELECT 1 FROM active_rules ar
+ INNER JOIN rules r ON r.id = ar.rule_id AND r.status <> 'REMOVED'
+ WHERE profile_id=rp.id
+ )
+ </select>
+
+ <select id="selectDefaultBuiltInProfilesWithoutActiveRules" resultType="org.sonar.db.qualityprofile.QProfileDto">
+ SELECT
+ <include refid="qProfileColumns"/>
+ FROM org_qprofiles oqp
+ INNER JOIN rules_profiles rp ON oqp.rules_profile_uuid = rp.kee
+ INNER JOIN default_qprofiles dp ON dp.qprofile_uuid = oqp.uuid
+ WHERE
+ rp.is_built_in = ${_true}
+ AND NOT EXISTS (
+ SELECT 1 FROM active_rules ar
+ INNER JOIN rules r ON r.id = ar.rule_id AND r.status <> 'REMOVED'
+ WHERE profile_id=rp.id
+ )
+ </select>
+
<select id="selectByNameAndLanguage" parameterType="map" resultType="org.sonar.db.qualityprofile.QProfileDto">
select
<include refid="qProfileColumns"/>
import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.organization.OrganizationTesting;
+import org.sonar.db.rule.RuleDefinitionDto;
import static com.google.common.collect.ImmutableList.of;
import static com.google.common.collect.Lists.newArrayList;
assertThat(dto2.getParentKee()).isEqualTo("java_parent");
}
+ @Test
+ public void selectBuiltInRuleProfilesWithActiveRules() {
+ // a quality profile without active rules but not builtin
+ db.qualityProfiles().insert(db.getDefaultOrganization(), qp -> qp.setIsBuiltIn(false));
+
+ // a built-in quality profile without active rules
+ db.qualityProfiles().insert(db.getDefaultOrganization(), qp -> qp.setIsBuiltIn(true));
+
+ // a built-in quality profile with active rules
+ QProfileDto builtInQPWithActiveRules = db.qualityProfiles().insert(db.getDefaultOrganization(), qp -> qp.setIsBuiltIn(true));
+ RuleDefinitionDto ruleDefinitionDto = db.rules().insert();
+ db.qualityProfiles().activateRule(builtInQPWithActiveRules, ruleDefinitionDto);
+
+ dbSession.commit();
+
+ List<QProfileDto> qProfileDtos = underTest.selectBuiltInRuleProfilesWithActiveRules(dbSession);
+ assertThat(qProfileDtos).extracting(QProfileDto::getName)
+ .containsOnly(builtInQPWithActiveRules.getName());
+ }
+
@Test
public void selectDescendants_returns_empty_if_no_children() {
QProfileDto base = db.qualityProfiles().insert(db.getDefaultOrganization());
.collect(uniqueIndex(BuiltInQProfile::getQProfileName));
List<DefaultQProfileDto> defaults = new ArrayList<>();
- dbClient.qualityProfileDao().selectBuiltInRulesProfiles(dbSession).forEach(rulesProfile -> {
+ dbClient.qualityProfileDao().selectBuiltInRuleProfiles(dbSession).forEach(rulesProfile -> {
OrgQProfileDto dto = new OrgQProfileDto()
.setOrganizationUuid(organization.getUuid())
.setRulesProfileUuid(rulesProfile.getKee())
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.db.qualityprofile.DefaultQProfileDto;
+import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.qualityprofile.RulesProfileDto;
import static java.lang.String.format;
+import static java.util.stream.Collectors.toMap;
import static org.sonar.server.qualityprofile.ActiveRuleInheritance.NONE;
/**
Profiler profiler = Profiler.create(Loggers.get(getClass())).startInfo("Register quality profiles");
try (DbSession dbSession = dbClient.openSession(false);
- DbSession batchDbSession = dbClient.openSession(true)) {
+ DbSession batchDbSession = dbClient.openSession(true)) {
long startDate = system2.now();
Map<QProfileName, RulesProfileDto> persistedRuleProfiles = loadPersistedProfiles(dbSession);
long endDate = system2.now();
builtInQualityProfilesNotification.onChange(changedProfiles, startDate, endDate);
}
+ ensureBuiltInDefaultQPContainsRules(dbSession);
}
profiler.stopDebug();
}
private Map<QProfileName, RulesProfileDto> loadPersistedProfiles(DbSession dbSession) {
- return dbClient.qualityProfileDao().selectBuiltInRulesProfiles(dbSession).stream()
+ return dbClient.qualityProfileDao().selectBuiltInRuleProfiles(dbSession).stream()
.collect(MoreCollectors.uniqueIndex(rp -> new QProfileName(rp.getLanguage(), rp.getName())));
}
/**
* The Quality profiles created by users should be renamed when they have the same name
* as the built-in profile to be persisted.
- *
+ * <p>
* When upgrading from < 6.5 , all existing profiles are considered as "custom" (created
* by users) because the concept of built-in profile is not persisted. The "Sonar way" profiles
* are renamed to "Sonar way (outdated copy) in order to avoid conflicts with the new
dbClient.qualityProfileDao().renameRulesProfilesAndCommit(dbSession, uuids, newName);
profiler.stopDebug(format("%d Quality profiles renamed to [%s]", uuids.size(), newName));
}
+
+ /**
+ * This method ensure that if a default built-in quality profile does not have any active rules but another built-in one for the same language
+ * does have active rules, the last one will be the default one.
+ *
+ * @see <a href="https://jira.sonarsource.com/browse/SONAR-10363">SONAR-10363</a>
+ */
+ private void ensureBuiltInDefaultQPContainsRules(DbSession dbSession) {
+ Map<String, QProfileDto> qProfileByLanguage = dbClient.qualityProfileDao().selectBuiltInRuleProfilesWithActiveRules(dbSession).stream()
+ .collect(toMap(QProfileDto::getLanguage, Function.identity(), (oldValue, newValue) -> oldValue));
+
+ dbClient.qualityProfileDao().selectDefaultBuiltInProfilesWithoutActiveRules(dbSession)
+ .forEach(qp -> {
+ LOGGER.info("Built-in quality profile [{}] does not have any active rules", qp.getName());
+ QProfileDto qProfileDto = qProfileByLanguage.get(qp.getLanguage());
+ if (qProfileDto == null) {
+ return;
+ }
+
+ Set<String> uuids = dbClient.defaultQProfileDao().selectExistingQProfileUuids(dbSession, qp.getOrganizationUuid(), Collections.singleton(qp.getKee()));
+ dbClient.defaultQProfileDao().deleteByQProfileUuids(dbSession, uuids);
+ dbClient.defaultQProfileDao().insertOrUpdate(dbSession, new DefaultQProfileDto()
+ .setQProfileUuid(qProfileDto.getKee())
+ .setLanguage(qp.getLanguage())
+ .setOrganizationUuid(qp.getOrganizationUuid())
+ );
+
+ LOGGER.info("Default built-in quality profile for language [{}] has been updated from [{}] to [{}] since previous default does not have active rules.",
+ qp.getLanguage(),
+ qp.getName(),
+ qProfileDto.getName());
+ });
+
+ dbSession.commit();
+ }
}
}
private void assertThatProfileIsMarkedAsUpdated(RulesProfileDto dto) {
- RulesProfileDto reloaded = db.getDbClient().qualityProfileDao().selectBuiltInRulesProfiles(db.getSession())
+ RulesProfileDto reloaded = db.getDbClient().qualityProfileDao().selectBuiltInRuleProfiles(db.getSession())
.stream()
.filter(p -> p.getKee().equals(dto.getKee()))
.findFirst()
}
private void assertThatProfileIsNotMarkedAsUpdated(RulesProfileDto dto) {
- RulesProfileDto reloaded = db.getDbClient().qualityProfileDao().selectBuiltInRulesProfiles(db.getSession())
+ RulesProfileDto reloaded = db.getDbClient().qualityProfileDao().selectBuiltInRuleProfiles(db.getSession())
.stream()
.filter(p -> p.getKee().equals(dto.getKee()))
.findFirst()
db.getDbClient().activeRuleDao().insert(db.getSession(), dto);
db.commit();
}
-
}
}
private void assertThatRulesProfileExists(RulesProfileDto rulesProfile) {
- assertThat(db.getDbClient().qualityProfileDao().selectBuiltInRulesProfiles(dbSession))
+ assertThat(db.getDbClient().qualityProfileDao().selectBuiltInRuleProfiles(dbSession))
.extracting(RulesProfileDto::getKee)
.containsExactly(rulesProfile.getKee());
assertThat(db.countRowsOfTable(dbSession, "active_rules")).isGreaterThan(0);
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.db.qualityprofile.RulesProfileDto;
+import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.language.LanguageTesting;
import org.sonar.server.tester.UserSessionRule;
+import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
+import static org.sonar.db.qualityprofile.QualityProfileTesting.newQualityProfileDto;
import static org.sonar.db.qualityprofile.QualityProfileTesting.newRuleProfileDto;
public class RegisterQualityProfilesTest {
assertThat(logTester.logs(LoggerLevel.INFO)).contains("Update profile foo/Sonar way");
}
+ @Test
+ public void update_default_built_in_quality_profile() {
+ RulesProfileDto ruleProfileWithoutRule = newRuleProfileDto(rp -> rp.setIsBuiltIn(true).setName("Sonar way").setLanguage(FOO_LANGUAGE.getKey()));
+ RulesProfileDto ruleProfileWithOneRule = newRuleProfileDto(rp -> rp.setIsBuiltIn(true).setName("Sonar way 2").setLanguage(FOO_LANGUAGE.getKey()));
+
+ QProfileDto qProfileWithoutRule = newQualityProfileDto()
+ .setIsBuiltIn(true)
+ .setLanguage(FOO_LANGUAGE.getKey())
+ .setRulesProfileUuid(ruleProfileWithoutRule.getKee());
+ QProfileDto qProfileWithOneRule = newQualityProfileDto()
+ .setIsBuiltIn(true)
+ .setLanguage(FOO_LANGUAGE.getKey())
+ .setRulesProfileUuid(ruleProfileWithOneRule.getKee());
+
+ db.qualityProfiles().insert(qProfileWithoutRule, qProfileWithOneRule);
+ db.qualityProfiles().setAsDefault(qProfileWithoutRule);
+
+ RuleDefinitionDto ruleDefinition = db.rules().insert();
+ db.qualityProfiles().activateRule(qProfileWithOneRule, ruleDefinition);
+ db.commit();
+
+ builtInQProfileRepositoryRule.add(FOO_LANGUAGE, ruleProfileWithoutRule.getName(), true);
+ builtInQProfileRepositoryRule.add(FOO_LANGUAGE, ruleProfileWithOneRule.getName(), false);
+ builtInQProfileRepositoryRule.initialize();
+
+ underTest.start();
+
+ logTester.logs(LoggerLevel.INFO).contains(
+ format("Default built-in quality profile for language [foo] has been updated from [%s] to [%s] since previous default does not have active rules.",
+ qProfileWithoutRule.getName(), qProfileWithOneRule.getName()));
+
+ assertThat(selectUuidOfDefaultProfile(db.getDefaultOrganization(), FOO_LANGUAGE.getKey()))
+ .isPresent().get()
+ .isEqualTo(qProfileWithOneRule.getKee());
+ }
+
+ private Optional<String> selectUuidOfDefaultProfile(OrganizationDto org, String language) {
+ return db.select("select qprofile_uuid as \"profileUuid\" " +
+ " from default_qprofiles " +
+ " where language='" + language + "'") // organization_uuid='" + org.getUuid() + "' and
+ .stream()
+ .findFirst()
+ .map(m -> (String) m.get("profileUuid"));
+ }
+
private String selectPersistedName(QProfileDto profile) {
return db.qualityProfiles().selectByUuid(profile.getKee()).get().getName();
}
db.commit();
}
+
+
private static class DummyBuiltInQProfileInsert implements BuiltInQProfileInsert {
private final List<BuiltInQProfile> callLogs = new ArrayList<>();
include 'tests:plugins:fake-governance-plugin'
include 'tests:plugins:foo-plugin-v1'
include 'tests:plugins:foo-plugin-v2'
+include 'tests:plugins:foo-plugin-v3'
include 'tests:plugins:global-property-change-plugin'
include 'tests:plugins:issue-filter-plugin'
include 'tests:plugins:l10n-fr-pack'
':tests:plugins:fake-governance-plugin',
':tests:plugins:foo-plugin-v1',
':tests:plugins:foo-plugin-v2',
+ ':tests:plugins:foo-plugin-v3',
':tests:plugins:global-property-change-plugin',
':tests:plugins:issue-filter-plugin',
':tests:plugins:l10n-fr-pack',
--- /dev/null
+sonarqube {
+ skipProject = true
+}
+
+dependencies {
+ compile 'com.google.guava:guava'
+ compile 'commons-io:commons-io'
+ compile 'commons-lang:commons-lang'
+ compileOnly 'com.google.code.findbugs:jsr305'
+ compileOnly project(path: ':sonar-plugin-api', configuration: 'shadow')
+}
+
+jar {
+ manifest {
+ attributes(
+ 'Plugin-Key': 'foo',
+ 'Plugin-Version': version,
+ 'Plugin-Class': 'org.sonar.foo.FooPlugin',
+ 'Plugin-ChildFirstClassLoader': 'false',
+ 'Sonar-Version': version,
+ 'SonarLint-Supported': 'false',
+ 'Plugin-Name': 'Foo',
+ 'Plugin-License': 'GNU LGPL 3'
+ )
+ }
+ into('META-INF/lib') {
+ from configurations.compile
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.foo;
+
+import org.sonar.api.resources.Language;
+
+public class Foo implements Language {
+
+ public static final String KEY = "foo";
+ public static final String NAME = "Foo";
+
+ @Override
+ public String getKey() {
+ return KEY;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public String[] getFileSuffixes() {
+ return new String[0];
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.foo;
+
+import org.sonar.api.Plugin;
+import org.sonar.foo.rule.FooBasicProfile;
+import org.sonar.foo.rule.FooRulesDefinition;
+
+/**
+ * Plugin entry-point, as declared in pom.xml.
+ */
+public class FooPlugin implements Plugin {
+
+ @Override
+ public void define(Context context) {
+ context.addExtensions(
+ Foo.class,
+ FooRulesDefinition.class,
+ FooBasicProfile.class);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.foo;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.foo.rule;
+
+import org.sonar.api.profiles.ProfileDefinition;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.utils.ValidationMessages;
+
+import static org.sonar.api.rules.RulePriority.MAJOR;
+import static org.sonar.foo.Foo.KEY;
+import static org.sonar.foo.rule.FooRulesDefinition.FOO_REPOSITORY;
+
+public class FooBasicProfile extends ProfileDefinition {
+
+ private final RuleFinder ruleFinder;
+
+ public FooBasicProfile(RuleFinder ruleFinder) {
+ this.ruleFinder = ruleFinder;
+ }
+
+ @Override
+ public RulesProfile createProfile(ValidationMessages validation) {
+ final RulesProfile profile = RulesProfile.create("New Basic", KEY);
+ activateRule(profile, FOO_REPOSITORY, "UnchangedRule", MAJOR);
+ return profile;
+ }
+
+ private ActiveRule activateRule(RulesProfile profile, String repo, String key, RulePriority severity) {
+ return profile.activateRule(ruleFinder.findByKey(repo, key), severity);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.foo.rule;
+
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.foo.Foo;
+
+public class FooRulesDefinition implements RulesDefinition {
+
+ public static final String FOO_REPOSITORY = "foo2";
+
+ @Override
+ public void define(Context context) {
+ defineRulesXoo(context);
+ }
+
+ private static void defineRulesXoo(Context context) {
+ NewRepository repoFoo1 = context.createRepository(FOO_REPOSITORY, Foo.KEY).setName("Foo");
+ createRule(repoFoo1, "UnchangedRule");
+ repoFoo1.done();
+ }
+
+ private static NewRule createRule(NewRepository repo, String key) {
+ return repo.createRule(key).setName(key).setHtmlDescription(key);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.foo.rule;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.sonarqube.tests.qualityProfile;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+
+/**
+ * This suite is reserved to the tests that start their own instance of Orchestrator.
+ * Indeed multiple instances of Orchestrator can't be started in parallel, so this
+ * suite does not declare a shared Orchestrator.
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ QualityProfileUpdateTest.class
+})
+public class QualityProfileSuite {
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.sonarqube.tests.qualityProfile;
+
+import com.sonar.orchestrator.Orchestrator;
+import java.io.File;
+import java.util.List;
+import org.junit.After;
+import org.junit.Test;
+import org.sonarqube.qa.util.Tester;
+import org.sonarqube.ws.Qualityprofiles.SearchWsResponse;
+import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile;
+import org.sonarqube.ws.Rules;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.qualityprofiles.SearchRequest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static util.ItUtils.newOrchestratorBuilder;
+import static util.ItUtils.pluginArtifact;
+
+public class QualityProfileUpdateTest {
+ private static Orchestrator orchestrator;
+ private static Tester tester;
+
+ @After
+ public void tearDown() {
+ if (tester != null) {
+ tester.after();
+ }
+ if (orchestrator != null) {
+ orchestrator.stop();
+ }
+ }
+
+ @Test
+ // SONAR-10363
+ public void updating_an_analyzer_must_update_default_quality_profile() {
+ orchestrator = newOrchestratorBuilder()
+ .addPlugin(pluginArtifact("foo-plugin-v1"))
+// .setServerProperty("sonar.sonarcloud.enabled", "true")
+ .build();
+ orchestrator.start();
+ tester = new Tester(orchestrator);
+ tester.before();
+
+ SearchWsResponse result = tester.qProfiles().service().search(new SearchRequest());
+ assertThat(result.getProfilesList())
+ .extracting(QualityProfile::getName, QualityProfile::getLanguage, QualityProfile::getIsBuiltIn, QualityProfile::getIsDefault)
+ .containsExactlyInAnyOrder(
+ tuple("Basic", "foo", true, true));
+
+ assertThat(getRulesForProfile(result.getProfilesList(), "Basic"))
+ .extracting(Rules.Rule::getKey)
+ .containsOnly("foo:UnchangedRule",
+ "foo:ChangedRule",
+ "foo:ToBeDeactivatedRule",
+ "foo:ToBeRemovedRule",
+ "foo:RuleWithUnchangedParameter",
+ "foo:RuleWithChangedParameter",
+ "foo:RuleWithRemovedParameter",
+ "foo:RuleWithAddedParameter",
+ "foo:ToBeRenamed",
+ "foo:ToBeRenamedAndMoved");
+
+ tester.wsClient().wsConnector().call(new PostRequest("api/plugins/uninstall").setParam("key", "foo")).failIfNotSuccessful();
+
+ File pluginsDir = new File(orchestrator.getServer().getHome() + "/extensions/plugins");
+ orchestrator.getConfiguration().locators().copyToDirectory(pluginArtifact("foo-plugin-v3"), pluginsDir);
+ orchestrator.restartServer();
+
+ result = tester.qProfiles().service().search(new SearchRequest());
+ assertThat(result.getProfilesList())
+ .extracting(QualityProfile::getName, QualityProfile::getLanguage, QualityProfile::getIsBuiltIn, QualityProfile::getIsDefault)
+ .containsExactlyInAnyOrder(
+ tuple("New Basic", "foo", true, true),
+ tuple("Basic", "foo", true, false));
+
+ assertThat(getRulesForProfile(result.getProfilesList(), "Basic"))
+ .isEmpty();
+
+ assertThat(getRulesForProfile(result.getProfilesList(), "New Basic"))
+ .extracting(Rules.Rule::getKey)
+ .containsOnly("foo2:UnchangedRule");
+ }
+
+ private List<Rules.Rule> getRulesForProfile(List<QualityProfile> qualityProfiles, String profileName) {
+ return tester.wsClient().rules().search(new org.sonarqube.ws.client.rules.SearchRequest()
+ .setQprofile(getProfileKey(qualityProfiles, profileName))
+ .setActivation("true"))
+ .getRulesList();
+ }
+
+ private String getProfileKey(List<QualityProfile> qualityProfiles, String name) {
+ return qualityProfiles.stream()
+ .filter(qp -> name.equals(qp.getName()))
+ .map(QualityProfile::getKey)
+ .findFirst()
+ .orElseThrow(IllegalStateException::new);
+ }
+}