From 0bab6ec48f16ad6b83c80997cfa6fb69edd3930d Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Fri, 16 May 2014 16:46:50 +0200 Subject: [PATCH] SONAR-5216 Store and display used quality profiles for multi-language analysis --- .../plugins/core/widgets/description.html.erb | 26 +++- .../sonar/batch/rule/QProfileDecorator.java | 59 +++++++++ .../org/sonar/batch/rule/QProfileSensor.java | 31 +++-- .../org/sonar/batch/rule/UsedQProfiles.java | 119 ++++++++++++++++++ .../sonar/batch/scan/ModuleScanContainer.java | 29 ++++- .../batch/rule/QProfileDecoratorTest.java | 103 +++++++++++++++ .../sonar/batch/rule/QProfileSensorTest.java | 30 ++++- .../sonar/batch/rule/UsedQProfilesTest.java | 72 +++++++++++ .../resources/org/sonar/l10n/core.properties | 1 + .../org/sonar/api/measures/CoreMetrics.java | 28 ++++- .../sonar/api/resources/CoreMetricsTest.java | 2 +- .../main/webapp/WEB-INF/app/models/metric.rb | 1 + 12 files changed, 478 insertions(+), 23 deletions(-) create mode 100644 sonar-batch/src/main/java/org/sonar/batch/rule/QProfileDecorator.java create mode 100644 sonar-batch/src/main/java/org/sonar/batch/rule/UsedQProfiles.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/rule/QProfileDecoratorTest.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/rule/UsedQProfilesTest.java diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb index 2032027f451..77931f40854 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/plugins/core/widgets/description.html.erb @@ -15,15 +15,35 @@ <%= Api::Utils.language_name(@project.language) -%> <% end %> + <% - profile_measure=@snapshot.measure(Metric::PROFILE) - if profile_measure + profiles_measure = measure(Metric::PROFILES) + if profiles_measure && !profiles_measure.data.blank? + profiles = JSON.parse profiles_measure.data + %> + + <%= message('widget.description.profiles') -%>: + + <% profiles.each_with_index do |profile, i| %> + <%= Api::Utils.language_name(profile['language']) -%>: <%= link_to profile['name'], {:controller => '/rules_configuration', :action => 'index', :id => profile['id']}, :id => profile['language'] + '_profile_link' -%> (<%= message('widget.description.profile_version_x', :params => profile['version']) -%>) + <% if i < (profiles.size - 1) %> +
+ <% end %> + <% end %> + + + <% + else + profile_measure=@snapshot.measure(Metric::PROFILE) + if profile_measure %> <%= message('widget.description.profile') -%>: <%= link_to profile_measure.data, {:controller => '/rules_configuration', :action => 'index', :id => profile_measure.value.to_i}, :id => 'profile_link' -%> (<%= message('widget.description.profile_version_x', :params => format_measure('profile_version', :default => '1')) -%>) - <% end %> + <% end + end %> + <% using_default=false quality_gate=Property.value('sonar.qualitygate', @resource && @resource.id, nil) diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileDecorator.java new file mode 100644 index 00000000000..7c31d566b4c --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileDecorator.java @@ -0,0 +1,59 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 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.rule; + +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; + +/** + * Aggregate which Quality profiles have been used on the current module. + */ +public class QProfileDecorator implements Decorator { + + public boolean shouldExecuteOnProject(Project project) { + return project.getModules().size() > 0; + } + + @Override + public void decorate(Resource resource, DecoratorContext context) { + if (!ResourceUtils.isProject(resource)) { + return; + } + UsedQProfiles profiles = UsedQProfiles.empty(); + for (Measure childProfilesMeasure : context.getChildrenMeasures(CoreMetrics.PROFILES)) { + UsedQProfiles childProfiles = UsedQProfiles.fromJSON(childProfilesMeasure.getData()); + profiles = profiles.merge(childProfiles); + } + + Measure detailsMeasure = new Measure(CoreMetrics.PROFILES, profiles.toJSON()); + context.saveMeasure(detailsMeasure); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileSensor.java b/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileSensor.java index e931163b31e..7b44ab64926 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileSensor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileSensor.java @@ -19,7 +19,7 @@ */ package org.sonar.batch.rule; -import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import org.sonar.api.batch.Sensor; import org.sonar.api.batch.SensorContext; import org.sonar.api.batch.fs.FileSystem; @@ -28,6 +28,8 @@ import org.sonar.api.measures.Measure; import org.sonar.api.resources.Project; import org.sonar.core.qualityprofile.db.QualityProfileDao; +import java.util.List; + /** * Stores which Quality profiles have been used on the current module. */ @@ -44,25 +46,32 @@ public class QProfileSensor implements Sensor { } public boolean shouldExecuteOnProject(Project project) { - return true; + // Should be only executed on leaf modules + return project.getModules().isEmpty(); } public void analyse(Project project, SensorContext context) { + List profiles = Lists.newArrayList(); for (String language : fs.languages()) { ModuleQProfiles.QProfile qProfile = moduleQProfiles.findByLanguage(language); if (qProfile != null) { dao.updateUsedColumn(qProfile.id(), true); + profiles.add(qProfile); } } - if (fs.languages().size() == 1) { - String language = Iterables.getOnlyElement(fs.languages()); - ModuleQProfiles.QProfile qProfile = moduleQProfiles.findByLanguage(language); - if (qProfile != null) { - Measure measure = new Measure(CoreMetrics.PROFILE, qProfile.name()).setValue((double)qProfile.id()); - Measure measureVersion = new Measure(CoreMetrics.PROFILE_VERSION, qProfile.version().doubleValue()); - context.saveMeasure(measure); - context.saveMeasure(measureVersion); - } + if (profiles.size() > 0) { + UsedQProfiles used = UsedQProfiles.fromProfiles(profiles); + Measure detailsMeasure = new Measure(CoreMetrics.PROFILES, used.toJSON()); + context.saveMeasure(detailsMeasure); + } + + // For backward compatibility + if (profiles.size() == 1) { + ModuleQProfiles.QProfile qProfile = profiles.get(0); + Measure measure = new Measure(CoreMetrics.PROFILE, qProfile.name()).setValue((double) qProfile.id()); + Measure measureVersion = new Measure(CoreMetrics.PROFILE_VERSION, qProfile.version().doubleValue()); + context.saveMeasure(measure); + context.saveMeasure(measureVersion); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/UsedQProfiles.java b/sonar-batch/src/main/java/org/sonar/batch/rule/UsedQProfiles.java new file mode 100644 index 00000000000..13893c64811 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/rule/UsedQProfiles.java @@ -0,0 +1,119 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 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.rule; + +import com.google.common.collect.Maps; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.batch.rule.ModuleQProfiles.QProfile; + +import javax.annotation.concurrent.Immutable; + +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Map; + +@Immutable +public class UsedQProfiles { + + private Map> profilesByLanguage = Maps.newLinkedHashMap(); + + private UsedQProfiles() { + } + + public static final UsedQProfiles fromProfiles(Iterable profiles) { + UsedQProfiles result = new UsedQProfiles(); + for (QProfile qProfile : profiles) { + result.add(qProfile); + } + return result; + } + + public static final UsedQProfiles empty() { + return new UsedQProfiles(); + } + + public static final UsedQProfiles fromProfiles(QProfile... profiles) { + return fromProfiles(Arrays.asList(profiles)); + } + + public static final UsedQProfiles fromJSON(String json) { + UsedQProfiles result = new UsedQProfiles(); + JsonArray root = new JsonParser().parse(json).getAsJsonArray(); + for (JsonElement elt : root) { + JsonObject profile = elt.getAsJsonObject(); + result.add(new QProfile(profile.get("id").getAsInt(), profile.get("name").getAsString(), profile.get("language").getAsString(), profile.get("version").getAsInt())); + } + return result; + } + + public final String toJSON() { + StringWriter json = new StringWriter(); + JsonWriter writer = JsonWriter.of(json); + writer.beginArray(); + for (String languageKey : profilesByLanguage.keySet()) { + for (ModuleQProfiles.QProfile qProfile : profilesByLanguage.get(languageKey).values()) { + writer.beginObject() + .prop("id", qProfile.id()) + .prop("name", qProfile.name()) + .prop("version", qProfile.version()) + .prop("language", qProfile.language()) + .endObject(); + } + } + writer.endArray(); + writer.close(); + return json.toString(); + } + + public final UsedQProfiles merge(UsedQProfiles other) { + return empty().mergeInPlace(this).mergeInPlace(other); + } + + private void add(ModuleQProfiles.QProfile profile) { + if (!profilesByLanguage.containsKey(profile.language())) { + profilesByLanguage.put(profile.language(), Maps.newLinkedHashMap()); + } + QProfile alreadyAdded = profilesByLanguage.get(profile.language()).get(profile.id()); + if (alreadyAdded == null + // Keep only latest version + || profile.version() > alreadyAdded.version()) { + profilesByLanguage.get(profile.language()).put(profile.id(), profile); + } + } + + private UsedQProfiles addAll(Iterable profiles) { + for (QProfile profile : profiles) { + this.add(profile); + } + return this; + } + + private UsedQProfiles mergeInPlace(UsedQProfiles other) { + for (Map byIds : other.profilesByLanguage.values()) { + this.addAll(byIds.values()); + } + return this; + } + +} 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 1db4a9a5054..c2fb81ba982 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 @@ -28,7 +28,12 @@ import org.sonar.api.batch.rule.CheckFactory; import org.sonar.api.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.api.scan.filesystem.FileExclusions; -import org.sonar.batch.*; +import org.sonar.batch.DefaultProjectClasspath; +import org.sonar.batch.DefaultSensorContext; +import org.sonar.batch.DefaultTimeMachine; +import org.sonar.batch.ProjectTree; +import org.sonar.batch.ResourceFilters; +import org.sonar.batch.ViolationFilters; import org.sonar.batch.bootstrap.BatchExtensionDictionnary; import org.sonar.batch.bootstrap.ExtensionInstaller; import org.sonar.batch.bootstrap.ExtensionMatcher; @@ -52,8 +57,25 @@ import org.sonar.batch.phases.PhasesTimeProfiler; import org.sonar.batch.qualitygate.GenerateQualityGateEvents; import org.sonar.batch.qualitygate.QualityGateProvider; import org.sonar.batch.qualitygate.QualityGateVerifier; -import org.sonar.batch.rule.*; -import org.sonar.batch.scan.filesystem.*; +import org.sonar.batch.rule.ActiveRulesProvider; +import org.sonar.batch.rule.ModuleQProfiles; +import org.sonar.batch.rule.QProfileDecorator; +import org.sonar.batch.rule.QProfileSensor; +import org.sonar.batch.rule.QProfileVerifier; +import org.sonar.batch.rule.RulesProfileProvider; +import org.sonar.batch.scan.filesystem.ComponentIndexer; +import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.batch.scan.filesystem.DeprecatedFileFilters; +import org.sonar.batch.scan.filesystem.ExclusionFilters; +import org.sonar.batch.scan.filesystem.FileIndexer; +import org.sonar.batch.scan.filesystem.FileSystemLogger; +import org.sonar.batch.scan.filesystem.InputFileBuilderFactory; +import org.sonar.batch.scan.filesystem.LanguageDetectionFactory; +import org.sonar.batch.scan.filesystem.ModuleFileSystemInitializer; +import org.sonar.batch.scan.filesystem.ModuleInputFileCache; +import org.sonar.batch.scan.filesystem.PreviousFileHashLoader; +import org.sonar.batch.scan.filesystem.ProjectFileSystemAdapter; +import org.sonar.batch.scan.filesystem.StatusDetectionFactory; import org.sonar.batch.scan.report.JsonReport; import org.sonar.core.component.ScanPerspectives; import org.sonar.core.measure.MeasurementFilters; @@ -134,6 +156,7 @@ public class ModuleScanContainer extends ComponentContainer { new ActiveRulesProvider(), new RulesProfileProvider(), QProfileSensor.class, + QProfileDecorator.class, CheckFactory.class, // report diff --git a/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileDecoratorTest.java new file mode 100644 index 00000000000..30c8023002b --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileDecoratorTest.java @@ -0,0 +1,103 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 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.rule; + +import org.junit.Test; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Scopes; +import org.sonar.api.test.IsMeasure; + +import java.util.Arrays; +import java.util.Collections; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class QProfileDecoratorTest { + + Project project = mock(Project.class); + Project moduleA = mock(Project.class); + Project moduleB = mock(Project.class); + DecoratorContext decoratorContext = mock(DecoratorContext.class); + + @Test + public void don_t_run_on_leaf() throws Exception { + QProfileDecorator decorator = new QProfileDecorator(); + when(project.getModules()).thenReturn(Collections.emptyList()); + assertThat(decorator.shouldExecuteOnProject(project)).isFalse(); + + when(project.getModules()).thenReturn(Arrays.asList(moduleA, moduleB)); + assertThat(decorator.shouldExecuteOnProject(project)).isTrue(); + } + + @Test + public void aggregate() throws Exception { + Measure measureModuleA = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"}]"); + Measure measureModuleB = new Measure(CoreMetrics.PROFILES, "[{\"id\":3,\"name\":\"Php One\",\"version\":30,\"language\":\"php\"}]"); + when(decoratorContext.getChildrenMeasures(CoreMetrics.PROFILES)).thenReturn(Arrays.asList(measureModuleA, measureModuleB)); + + when(project.getScope()).thenReturn(Scopes.PROJECT); + + QProfileDecorator decorator = new QProfileDecorator(); + decorator.decorate(project, decoratorContext); + + verify(decoratorContext).saveMeasure( + argThat(new IsMeasure(CoreMetrics.PROFILES, + "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"},{\"id\":3,\"name\":\"Php One\",\"version\":30,\"language\":\"php\"}]"))); + } + + @Test + public void aggregate_several_profile_same_language() throws Exception { + Measure measureModuleA = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"}]"); + Measure measureModuleB = new Measure(CoreMetrics.PROFILES, "[{\"id\":3,\"name\":\"Java Three\",\"version\":30,\"language\":\"java\"}]"); + when(decoratorContext.getChildrenMeasures(CoreMetrics.PROFILES)).thenReturn(Arrays.asList(measureModuleA, measureModuleB)); + + when(project.getScope()).thenReturn(Scopes.PROJECT); + + QProfileDecorator decorator = new QProfileDecorator(); + decorator.decorate(project, decoratorContext); + + verify(decoratorContext).saveMeasure( + argThat(new IsMeasure(CoreMetrics.PROFILES, + "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"},{\"id\":3,\"name\":\"Java Three\",\"version\":30,\"language\":\"java\"}]"))); + } + + @Test + public void aggregate_several_profile_same_id() throws Exception { + Measure measureModuleA = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"}]"); + Measure measureModuleB = new Measure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":30,\"language\":\"java\"}]"); + when(decoratorContext.getChildrenMeasures(CoreMetrics.PROFILES)).thenReturn(Arrays.asList(measureModuleA, measureModuleB)); + + when(project.getScope()).thenReturn(Scopes.PROJECT); + + QProfileDecorator decorator = new QProfileDecorator(); + decorator.decorate(project, decoratorContext); + + verify(decoratorContext).saveMeasure( + argThat(new IsMeasure(CoreMetrics.PROFILES, + "[{\"id\":2,\"name\":\"Java Two\",\"version\":30,\"language\":\"java\"}]"))); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileSensorTest.java b/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileSensorTest.java index f12d753fb5f..85f2509d874 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileSensorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileSensorTest.java @@ -31,7 +31,11 @@ import org.sonar.core.qualityprofile.db.QualityProfileDao; import java.util.Collections; import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; public class QProfileSensorTest extends AbstractDaoTestCase { @@ -76,9 +80,6 @@ public class QProfileSensorTest extends AbstractDaoTestCase { sensor.analyse(project, sensorContext); checkTable("mark_profiles_as_used", "rules_profiles"); - - // no measures on multi-language modules - verifyZeroInteractions(sensorContext); } @Test @@ -97,5 +98,26 @@ public class QProfileSensorTest extends AbstractDaoTestCase { verify(sensorContext).saveMeasure(argThat(new IsMeasure(CoreMetrics.PROFILE, "Java Two"))); verify(sensorContext).saveMeasure(argThat(new IsMeasure(CoreMetrics.PROFILE_VERSION, 20.0))); + verify(sensorContext).saveMeasure( + argThat(new IsMeasure(CoreMetrics.PROFILES, "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"}]"))); + } + + @Test + public void store_measures_on_multi_lang_module() throws Exception { + setupData("shared"); + + QualityProfileDao dao = new QualityProfileDao(getMyBatis()); + when(moduleQProfiles.findByLanguage("java")).thenReturn(new ModuleQProfiles.QProfile(dao.selectById(2))); + when(moduleQProfiles.findByLanguage("php")).thenReturn(new ModuleQProfiles.QProfile(dao.selectById(3))); + when(moduleQProfiles.findByLanguage("abap")).thenReturn(null); + fs.addLanguages("java", "php"); + + QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, dao); + assertThat(sensor.shouldExecuteOnProject(project)).isTrue(); + sensor.analyse(project, sensorContext); + + verify(sensorContext).saveMeasure( + argThat(new IsMeasure(CoreMetrics.PROFILES, + "[{\"id\":2,\"name\":\"Java Two\",\"version\":20,\"language\":\"java\"},{\"id\":3,\"name\":\"Php One\",\"version\":30,\"language\":\"php\"}]"))); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/rule/UsedQProfilesTest.java b/sonar-batch/src/test/java/org/sonar/batch/rule/UsedQProfilesTest.java new file mode 100644 index 00000000000..b3cab37a325 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/rule/UsedQProfilesTest.java @@ -0,0 +1,72 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 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.rule; + +import org.junit.Test; + +import static org.fest.assertions.Assertions.assertThat; + +public class UsedQProfilesTest { + + @Test + public void serialization() throws Exception { + + ModuleQProfiles.QProfile java = new ModuleQProfiles.QProfile(1, "Sonar Way", "java", 1); + ModuleQProfiles.QProfile php = new ModuleQProfiles.QProfile(2, "Sonar Way", "php", 1); + + UsedQProfiles used = UsedQProfiles.fromProfiles(java, php); + assertThat(used.toJSON()).isEqualTo( + "[{\"id\":1,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"java\"},{\"id\":2,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"php\"}]"); + } + + @Test + public void deserialization() throws Exception { + UsedQProfiles used = UsedQProfiles + .fromJSON("[{\"id\":1,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"java\"},{\"id\":2,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"php\"}]"); + + assertThat(used.toJSON()).isEqualTo( + "[{\"id\":1,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"java\"},{\"id\":2,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"php\"}]"); + } + + @Test + public void merge() throws Exception { + UsedQProfiles first = UsedQProfiles + .fromJSON("[{\"id\":1,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"java\"}]"); + + UsedQProfiles second = UsedQProfiles + .fromJSON("[{\"id\":2,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"php\"}]"); + + assertThat(first.merge(second).toJSON()).isEqualTo( + "[{\"id\":1,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"java\"},{\"id\":2,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"php\"}]"); + } + + @Test + public void merge_no_duplicate_ids() throws Exception { + UsedQProfiles first = UsedQProfiles + .fromJSON("[{\"id\":1,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"java\"},{\"id\":2,\"name\":\"Sonar Way\",\"version\":2,\"language\":\"php\"}]"); + + UsedQProfiles second = UsedQProfiles + .fromJSON("[{\"id\":1,\"name\":\"Sonar Way\",\"version\":2,\"language\":\"java\"},{\"id\":2,\"name\":\"Sonar Way\",\"version\":1,\"language\":\"php\"}]"); + + assertThat(first.merge(second).toJSON()).isEqualTo( + "[{\"id\":1,\"name\":\"Sonar Way\",\"version\":2,\"language\":\"java\"},{\"id\":2,\"name\":\"Sonar Way\",\"version\":2,\"language\":\"php\"}]"); + } + +} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 1320f51583f..3e7898358e4 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1053,6 +1053,7 @@ widget.description.description=Displays general project information. widget.description.key=Key widget.description.language=Language widget.description.profile=Profile +widget.description.profiles=Profiles widget.description.profile_version_x=version {0} widget.description.qualitygate=Quality Gate widget.description.alerts=Displays a summary of the project's quality gate status. diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java index 4a5d14473ec..6399bff81b3 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java @@ -2216,7 +2216,15 @@ public final class CoreMetrics { .setDomain(DOMAIN_GENERAL) .create(); + /** + * @deprecated since 4.4 doesn't support multi-language. See {@link #PROFILES_KEY} + */ + @Deprecated public static final String PROFILE_KEY = "profile"; + /** + * @deprecated since 4.4 doesn't support multi-language. See {@link #PROFILES_KEY} + */ + @Deprecated public static final Metric PROFILE = new Metric.Builder(PROFILE_KEY, "Profile", Metric.ValueType.DATA) .setDescription("Selected quality profile") .setDomain(DOMAIN_GENERAL) @@ -2224,12 +2232,15 @@ public final class CoreMetrics { /** * @since 2.9 + * @deprecated since 4.4 doesn't support multi-language. See {@link #PROFILES_KEY} */ + @Deprecated public static final String PROFILE_VERSION_KEY = "profile_version"; - /** * @since 2.9 + * @deprecated since 4.4 doesn't support multi-language. See {@link #PROFILES_KEY} */ + @Deprecated public static final Metric PROFILE_VERSION = new Metric.Builder(PROFILE_VERSION_KEY, "Profile version", Metric.ValueType.INT) .setDescription("Selected quality profile version") .setQualitative(false) @@ -2237,6 +2248,21 @@ public final class CoreMetrics { .setHidden(true) .create(); + /** + * @since 4.4 + */ + public static final String PROFILES_KEY = "profiles"; + + /** + * @since 4.4 + */ + public static final Metric PROFILES = new Metric.Builder(PROFILES_KEY, "Profiles", Metric.ValueType.DATA) + .setDescription("Details of quality profiles used during analysis") + .setQualitative(false) + .setDomain(DOMAIN_GENERAL) + .setHidden(true) + .create(); + private static final List METRICS; static { diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java index e004ec258cc..086005be0f8 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/resources/CoreMetricsTest.java @@ -32,7 +32,7 @@ public class CoreMetricsTest { @Test public void read_metrics_from_class_reflection() { List metrics = CoreMetrics.getMetrics(); - assertThat(metrics).hasSize(151); + assertThat(metrics).hasSize(152); assertThat(metrics).contains(CoreMetrics.NCLOC, CoreMetrics.DIRECTORIES); } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/metric.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/metric.rb index a58c1498217..d228a5eaf51 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/metric.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/metric.rb @@ -378,6 +378,7 @@ class Metric < ActiveRecord::Base ALERT_STATUS = 'alert_status' QUALITY_GATE_DETAILS = 'quality_gate_details' PROFILE='profile' + PROFILES='profiles' private -- 2.39.5