From 97ca7b19cc480b03ee9ff7d1874796814106d805 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Mon, 17 Feb 2014 16:55:45 +0100 Subject: [PATCH] SONAR-926 Improve error reporting when invalid use of sonar.profile --- .../org/sonar/batch/phases/PhaseExecutor.java | 43 ++++--- .../org/sonar/batch/phases/ProfileLogger.java | 68 +++++++++++ .../org/sonar/batch/rule/ModuleQProfiles.java | 12 +- .../batch/rule/RulesProfileProvider.java | 5 +- .../sonar/batch/scan/ModuleScanContainer.java | 2 + .../sonar/batch/phases/ProfileLoggerTest.java | 114 ++++++++++++++++++ .../sonar/batch/rule/ModuleQProfilesTest.java | 12 +- 7 files changed, 226 insertions(+), 30 deletions(-) create mode 100644 sonar-batch/src/main/java/org/sonar/batch/phases/ProfileLogger.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/phases/ProfileLoggerTest.java diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java index 9bef04cd813..8cc3ee7cee9 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java @@ -47,30 +47,31 @@ public final class PhaseExecutor { InitializersExecutor.class, ProjectInitializer.class, UpdateStatusJob.class); } - private EventBus eventBus; - private Phases phases; - private DecoratorsExecutor decoratorsExecutor; - private MavenPhaseExecutor mavenPhaseExecutor; - private MavenPluginsConfigurator mavenPluginsConfigurator; - private PostJobsExecutor postJobsExecutor; - private InitializersExecutor initializersExecutor; - private SensorsExecutor sensorsExecutor; - private UpdateStatusJob updateStatusJob; - private PersistenceManager persistenceManager; - private SensorContext sensorContext; - private DefaultIndex index; - private ProjectInitializer pi; - private ScanPersister[] persisters; - private FileSystemLogger fsLogger; + private final EventBus eventBus; + private final Phases phases; + private final DecoratorsExecutor decoratorsExecutor; + private final MavenPhaseExecutor mavenPhaseExecutor; + private final MavenPluginsConfigurator mavenPluginsConfigurator; + private final PostJobsExecutor postJobsExecutor; + private final InitializersExecutor initializersExecutor; + private final SensorsExecutor sensorsExecutor; + private final UpdateStatusJob updateStatusJob; + private final PersistenceManager persistenceManager; + private final SensorContext sensorContext; + private final DefaultIndex index; + private final ProjectInitializer pi; + private final ScanPersister[] persisters; + private final FileSystemLogger fsLogger; private final JsonReport jsonReport; - private DefaultModuleFileSystem fs; + private final DefaultModuleFileSystem fs; + private final ProfileLogger profileLogger; public PhaseExecutor(Phases phases, DecoratorsExecutor decoratorsExecutor, MavenPhaseExecutor mavenPhaseExecutor, MavenPluginsConfigurator mavenPluginsConfigurator, InitializersExecutor initializersExecutor, PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, PersistenceManager persistenceManager, SensorContext sensorContext, DefaultIndex index, EventBus eventBus, UpdateStatusJob updateStatusJob, ProjectInitializer pi, - ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport, DefaultModuleFileSystem fs) { + ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport, DefaultModuleFileSystem fs, ProfileLogger profileLogger) { this.phases = phases; this.decoratorsExecutor = decoratorsExecutor; this.mavenPhaseExecutor = mavenPhaseExecutor; @@ -88,15 +89,16 @@ public final class PhaseExecutor { this.fsLogger = fsLogger; this.jsonReport = jsonReport; this.fs = fs; + this.profileLogger = profileLogger; } public PhaseExecutor(Phases phases, DecoratorsExecutor decoratorsExecutor, MavenPhaseExecutor mavenPhaseExecutor, MavenPluginsConfigurator mavenPluginsConfigurator, InitializersExecutor initializersExecutor, PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor, PersistenceManager persistenceManager, SensorContext sensorContext, DefaultIndex index, - EventBus eventBus, ProjectInitializer pi, ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport, DefaultModuleFileSystem fs) { + EventBus eventBus, ProjectInitializer pi, ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport, DefaultModuleFileSystem fs, ProfileLogger profileLogger) { this(phases, decoratorsExecutor, mavenPhaseExecutor, mavenPluginsConfigurator, initializersExecutor, postJobsExecutor, - sensorsExecutor, persistenceManager, sensorContext, index, eventBus, null, pi, persisters, fsLogger, jsonReport, fs); + sensorsExecutor, persistenceManager, sensorContext, index, eventBus, null, pi, persisters, fsLogger, jsonReport, fs, profileLogger); } /** @@ -117,6 +119,9 @@ public final class PhaseExecutor { // Index and lock the filesystem fs.index(); + // Log detected languages and their profiles after FS is indexed and languages detected + profileLogger.execute(); + sensorsExecutor.execute(sensorContext); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/ProfileLogger.java b/sonar-batch/src/main/java/org/sonar/batch/phases/ProfileLogger.java new file mode 100644 index 00000000000..5ec98bc7245 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/ProfileLogger.java @@ -0,0 +1,68 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.phases; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.BatchComponent; +import org.sonar.api.batch.ModuleLanguages; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.Language; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.rule.ModuleQProfiles; +import org.sonar.batch.rule.ModuleQProfiles.QProfile; + +public class ProfileLogger implements BatchComponent { + + private static final Logger LOG = LoggerFactory.getLogger(ProfileLogger.class); + + private final Settings settings; + private final ModuleLanguages languages; + private final ModuleQProfiles profiles; + + public ProfileLogger(Settings settings, ModuleLanguages languages, ModuleQProfiles profiles) { + this.settings = settings; + this.languages = languages; + this.profiles = profiles; + } + + public void execute() { + execute(LOG); + } + + @VisibleForTesting + void execute(Logger logger) { + String defaultName = settings.getString(ModuleQProfiles.SONAR_PROFILE_PROP); + boolean defaultNameUsed = StringUtils.isBlank(defaultName); + for (Language lang : languages.languages()) { + QProfile profile = profiles.findByLanguage(lang.getKey()); + logger.info("Quality profile for {}: {}", lang.getName(), profile.name()); + if (StringUtils.isNotBlank(defaultName) && defaultName.equals(profile.name())) { + defaultNameUsed = true; + } + } + if (!defaultNameUsed) { + throw new SonarException("sonar.profile was set to '" + defaultName + "' but didn't match any profile for any language. Please check your configuration."); + } + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/ModuleQProfiles.java b/sonar-batch/src/main/java/org/sonar/batch/rule/ModuleQProfiles.java index e83e86acd48..24ba6b2136b 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/rule/ModuleQProfiles.java +++ b/sonar-batch/src/main/java/org/sonar/batch/rule/ModuleQProfiles.java @@ -21,8 +21,6 @@ package org.sonar.batch.rule; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.sonar.api.BatchComponent; import org.sonar.api.config.Settings; import org.sonar.api.resources.Language; @@ -41,7 +39,7 @@ import java.util.Map; */ public class ModuleQProfiles implements BatchComponent { - private static final Logger LOG = LoggerFactory.getLogger(ModuleQProfiles.class); + public static final String SONAR_PROFILE_PROP = "sonar.profile"; public static class QProfile { private final String name, language; @@ -83,17 +81,17 @@ public class ModuleQProfiles implements BatchComponent { public ModuleQProfiles(Settings settings, Languages languages, QualityProfileDao dao) { ImmutableMap.Builder builder = ImmutableMap.builder(); - String defaultName = settings.getString("sonar.profile"); + String defaultName = settings.getString(SONAR_PROFILE_PROP); for (Language language : languages.all()) { - QProfile profile; + QProfile profile = null; if (StringUtils.isNotBlank(defaultName)) { profile = loadDefaultQProfile(dao, defaultName, language.getKey()); - } else { + } + if (profile == null) { profile = loadQProfile(dao, settings, language.getKey()); } if (profile != null) { - LOG.info("Quality profile for {}: {}", profile.language(), profile.name()); builder.put(profile.language(), profile); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProfileProvider.java b/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProfileProvider.java index f8524749ec8..b6279b2ed27 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProfileProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/rule/RulesProfileProvider.java @@ -52,7 +52,10 @@ public class RulesProfileProvider extends ProviderAdapter { private RulesProfile loadSingleLanguageProfile(ModuleQProfiles qProfiles, String language, ProfilesDao dao) { ModuleQProfiles.QProfile qProfile = qProfiles.findByLanguage(language); - return new RulesProfileWrapper(select(qProfile, dao)); + if (qProfile != null) { + return new RulesProfileWrapper(select(qProfile, dao)); + } + return new RulesProfileWrapper(Lists.newArrayList()); } private RulesProfile loadProfiles(ModuleQProfiles qProfiles, ProfilesDao dao) { diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java index ab7733a95ab..fbb5c6b9465 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java @@ -47,6 +47,7 @@ import org.sonar.batch.issue.IssueFilters; import org.sonar.batch.issue.ModuleIssues; import org.sonar.batch.phases.PhaseExecutor; import org.sonar.batch.phases.PhasesTimeProfiler; +import org.sonar.batch.phases.ProfileLogger; import org.sonar.batch.rule.ActiveRulesProvider; import org.sonar.batch.rule.ModuleQProfiles; import org.sonar.batch.rule.QProfileSensor; @@ -119,6 +120,7 @@ public class ModuleScanContainer extends ComponentContainer { DefaultModuleFileSystem.class, ModuleFileSystemInitializer.class, ProjectFileSystemAdapter.class, + ProfileLogger.class, // the Snapshot component will be removed when asynchronous measures are improved (required for AsynchronousMeasureSensor) getComponentByType(ResourcePersister.class).getSnapshot(module), diff --git a/sonar-batch/src/test/java/org/sonar/batch/phases/ProfileLoggerTest.java b/sonar-batch/src/test/java/org/sonar/batch/phases/ProfileLoggerTest.java new file mode 100644 index 00000000000..eb2d4689c44 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/phases/ProfileLoggerTest.java @@ -0,0 +1,114 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.batch.phases; + +import com.google.common.collect.Lists; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.slf4j.Logger; +import org.sonar.api.batch.ModuleLanguages; +import org.sonar.api.config.Settings; +import org.sonar.api.resources.AbstractLanguage; +import org.sonar.api.resources.Language; +import org.sonar.api.utils.SonarException; +import org.sonar.batch.rule.ModuleQProfiles; +import org.sonar.batch.rule.ModuleQProfiles.QProfile; + +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ProfileLoggerTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ModuleLanguages languages; + private ModuleQProfiles profiles; + private Settings settings; + + @Before + public void prepare() { + languages = mock(ModuleLanguages.class); + List languageList = Lists.newArrayList(); + languageList.add(new AbstractLanguage("java", "Java") { + + @Override + public String[] getFileSuffixes() { + return null; + } + }); + languageList.add(new AbstractLanguage("cobol", "Cobol") { + + @Override + public String[] getFileSuffixes() { + return null; + } + }); + when(languages.languages()).thenReturn(languageList); + + profiles = mock(ModuleQProfiles.class); + QProfile javaProfile = mock(QProfile.class); + when(javaProfile.name()).thenReturn("My Java profile"); + when(profiles.findByLanguage("java")).thenReturn(javaProfile); + QProfile cobolProfile = mock(QProfile.class); + when(cobolProfile.name()).thenReturn("My Cobol profile"); + when(profiles.findByLanguage("cobol")).thenReturn(cobolProfile); + + settings = new Settings(); + } + + @Test + public void should_log_all_used_profiles() { + + ProfileLogger profileLogger = new ProfileLogger(settings, languages, profiles); + Logger logger = mock(Logger.class); + profileLogger.execute(logger); + + verify(logger).info("Quality profile for {}: {}", "Java", "My Java profile"); + verify(logger).info("Quality profile for {}: {}", "Cobol", "My Cobol profile"); + } + + @Test + public void should_fail_if_default_profile_not_used() { + settings.setProperty("sonar.profile", "Unknow"); + + ProfileLogger profileLogger = new ProfileLogger(settings, languages, profiles); + + thrown.expect(SonarException.class); + thrown.expectMessage("sonar.profile was set to 'Unknow' but didn't match any profile for any language. Please check your configuration."); + + profileLogger.execute(); + + } + + @Test + public void should_not_fail_if_default_profile_used_at_least_once() { + settings.setProperty("sonar.profile", "My Java profile"); + + ProfileLogger profileLogger = new ProfileLogger(settings, languages, profiles); + + profileLogger.execute(); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/rule/ModuleQProfilesTest.java b/sonar-batch/src/test/java/org/sonar/batch/rule/ModuleQProfilesTest.java index 09a9c7bded2..23ec48aa37a 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/rule/ModuleQProfilesTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/rule/ModuleQProfilesTest.java @@ -68,7 +68,7 @@ public class ModuleQProfilesTest extends AbstractDaoTestCase { } @Test - public void use_deprecated_property() throws Exception { + public void use_sonar_profile_property() throws Exception { setupData("shared"); QualityProfileDao dao = new QualityProfileDao(getMyBatis()); @@ -78,14 +78,20 @@ public class ModuleQProfilesTest extends AbstractDaoTestCase { ModuleQProfiles moduleQProfiles = new ModuleQProfiles(settings, languages, dao); List qProfiles = Lists.newArrayList(moduleQProfiles.findAll()); - assertThat(qProfiles).hasSize(1); + assertThat(qProfiles).hasSize(2); ModuleQProfiles.QProfile javaProfile = qProfiles.get(0); assertThat(javaProfile.id()).isEqualTo(2); assertThat(javaProfile.name()).isEqualTo("Java Two"); assertThat(javaProfile.language()).isEqualTo("java"); assertThat(javaProfile.version()).isEqualTo(20); - // the php profile is not found as sonar.profile overrides all other properties. + // Fallback to sonar.profile.php if no match for sonar.profile + ModuleQProfiles.QProfile phpProfile = qProfiles.get(1); + assertThat(phpProfile.id()).isEqualTo(3); + assertThat(phpProfile.name()).isEqualTo("Php One"); + assertThat(phpProfile.language()).isEqualTo("php"); + assertThat(phpProfile.version()).isEqualTo(30); + } @Test -- 2.39.5