]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10363 New built-in quality profiles should be the default
authorEric Hartmann <hartmann.eric@gmail.com>
Mon, 4 Jun 2018 14:29:46 +0000 (16:29 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 5 Jun 2018 18:20:51 +0000 (20:20 +0200)
When removing an analyzer and adding a new one

20 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/QualityProfileMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/qualityprofile/QualityProfileMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationUpdaterImpl.java
server/sonar-server/src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImplTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/QProfileFactoryImplTest.java
server/sonar-server/src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesTest.java
settings.gradle
tests/build.gradle
tests/plugins/foo-plugin-v3/build.gradle [new file with mode: 0644]
tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/Foo.java [new file with mode: 0644]
tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/FooPlugin.java [new file with mode: 0644]
tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/package-info.java [new file with mode: 0644]
tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/rule/FooBasicProfile.java [new file with mode: 0644]
tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/rule/FooRulesDefinition.java [new file with mode: 0644]
tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/rule/package-info.java [new file with mode: 0644]
tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfileSuite.java [new file with mode: 0644]
tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfileUpdateTest.java [new file with mode: 0644]

index 1a1d7701ae1d32e26436af369c63d91e516ef5d9..db8095b83ccd3289912fe795e3fbe84448f96285 100644 (file)
@@ -71,10 +71,14 @@ public class QualityProfileDao implements Dao {
     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);
@@ -131,6 +135,10 @@ public class QualityProfileDao implements Dao {
     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);
index fd3751cc09a5259fff6023a684cb73098226ad95..dab4546fd2b936e89aceaef14d9edbe2d44ee688 100644 (file)
@@ -42,6 +42,8 @@ public interface QualityProfileMapper {
 
   List<RulesProfileDto> selectBuiltInRuleProfiles();
 
+  List<QProfileDto> selectBuiltInRuleProfilesWithActiveRules();
+
   @CheckForNull
   RulesProfileDto selectRuleProfile(@Param("uuid") String ruleProfileUuid);
 
@@ -50,6 +52,8 @@ public interface QualityProfileMapper {
   @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);
index 2bbeecc2b0b711bab5f7f1b76ba770d70a5dbc6e..e6ebcaa4e8777461bdf7aa91ef57b6b6ae1c00d9 100644 (file)
       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 &lt;&gt; '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 &lt;&gt; 'REMOVED'
+        WHERE profile_id=rp.id
+      )
+  </select>
+
   <select id="selectByNameAndLanguage" parameterType="map" resultType="org.sonar.db.qualityprofile.QProfileDto">
     select
     <include refid="qProfileColumns"/>
index 73f4c60c7b0d92231e78346726dfb71c3ccbf427..d654d6b82a64f67a62957c1ed26f0f00cc41bcb7 100644 (file)
@@ -38,6 +38,7 @@ import org.sonar.db.DbTester;
 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;
@@ -482,6 +483,26 @@ public class QualityProfileDaoTest {
     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());
index e53971d8001a8cdbc1ac5c318ea2b1f83be97393..7d7f756c18eb3d3165c35d27d3c30cfb005db539 100644 (file)
@@ -284,7 +284,7 @@ public class OrganizationUpdaterImpl implements OrganizationUpdater {
       .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())
index 7828753d7d4c8fcabb1400662ee4d780acab3423..ae08250095649f12005686d49a525c217d269d12 100644 (file)
@@ -22,8 +22,11 @@ package org.sonar.server.qualityprofile;
 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;
@@ -32,9 +35,12 @@ import org.sonar.api.utils.log.Profiler;
 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;
 
 /**
@@ -71,7 +77,7 @@ public class RegisterQualityProfiles {
 
     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);
@@ -95,12 +101,13 @@ public class RegisterQualityProfiles {
         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())));
   }
 
@@ -121,7 +128,7 @@ public class RegisterQualityProfiles {
   /**
    * 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
@@ -138,4 +145,39 @@ public class RegisterQualityProfiles {
     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();
+  }
 }
index 9a414f0930a9ea11908cc7027a6bd353c6d48059..d35a77fe14ef34c94e466ff5a406cd173fd60928 100644 (file)
@@ -322,7 +322,7 @@ public class BuiltInQProfileUpdateImplTest {
   }
 
   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()
@@ -331,7 +331,7 @@ public class BuiltInQProfileUpdateImplTest {
   }
 
   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()
@@ -355,5 +355,4 @@ public class BuiltInQProfileUpdateImplTest {
     db.getDbClient().activeRuleDao().insert(db.getSession(), dto);
     db.commit();
   }
-
 }
index edbecb079d5090f7042929789d17e4beeb77e32e..c1c0d54c7e62b3d4fd5114751bfe35ecc3fe48c0 100644 (file)
@@ -319,7 +319,7 @@ public class QProfileFactoryImplTest {
   }
 
   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);
index ff098c139d21e45f40a03c0bffe2b02a555f4700..bd1f565991a69fbbfeea67218c72c2c152706981 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.qualityprofile;
 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;
@@ -36,11 +37,14 @@ import org.sonar.db.DbTester;
 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 {
@@ -134,6 +138,51 @@ 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();
   }
@@ -147,6 +196,8 @@ public class RegisterQualityProfilesTest {
     db.commit();
   }
 
+
+
   private static class DummyBuiltInQProfileInsert implements BuiltInQProfileInsert {
     private final List<BuiltInQProfile> callLogs = new ArrayList<>();
 
index aaf676713ba8a0634d47ebeb109e14a6a1b3f68b..f19d9e246a85f577990e9f1b1b3f15ad03bc40ea 100644 (file)
@@ -43,6 +43,7 @@ include 'tests:plugins:fake-billing-plugin'
 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'
index 9fa0ac5763f4683981c487f8e400e7dff7137406..0701f734a2cb691f91a50d6575c96794bdd341cc 100644 (file)
@@ -23,6 +23,7 @@ def pluginsForITs = [
     ':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',
diff --git a/tests/plugins/foo-plugin-v3/build.gradle b/tests/plugins/foo-plugin-v3/build.gradle
new file mode 100644 (file)
index 0000000..a1eed2e
--- /dev/null
@@ -0,0 +1,29 @@
+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
+  }
+}
diff --git a/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/Foo.java b/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/Foo.java
new file mode 100644 (file)
index 0000000..0fe6566
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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];
+  }
+}
diff --git a/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/FooPlugin.java b/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/FooPlugin.java
new file mode 100644 (file)
index 0000000..673c8a0
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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);
+  }
+
+}
diff --git a/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/package-info.java b/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/package-info.java
new file mode 100644 (file)
index 0000000..7ebb0e1
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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;
diff --git a/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/rule/FooBasicProfile.java b/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/rule/FooBasicProfile.java
new file mode 100644 (file)
index 0000000..d569c5c
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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);
+  }
+
+}
diff --git a/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/rule/FooRulesDefinition.java b/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/rule/FooRulesDefinition.java
new file mode 100644 (file)
index 0000000..885734b
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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);
+  }
+
+}
diff --git a/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/rule/package-info.java b/tests/plugins/foo-plugin-v3/src/main/java/org/sonar/foo/rule/package-info.java
new file mode 100644 (file)
index 0000000..86ad399
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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;
diff --git a/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfileSuite.java b/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfileSuite.java
new file mode 100644 (file)
index 0000000..66387cc
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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 {
+}
diff --git a/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfileUpdateTest.java b/tests/src/test/java/org/sonarqube/tests/qualityProfile/QualityProfileUpdateTest.java
new file mode 100644 (file)
index 0000000..be34df9
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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);
+  }
+}