diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2014-05-16 16:46:50 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2014-05-19 11:31:11 +0200 |
commit | 0bab6ec48f16ad6b83c80997cfa6fb69edd3930d (patch) | |
tree | e263a4a3717057ed03bbbe4accb6dc4f568c9f6a /sonar-batch | |
parent | 11ef0dc4eacb45f3caad754ea5d190b77b033a3a (diff) | |
download | sonarqube-0bab6ec48f16ad6b83c80997cfa6fb69edd3930d.tar.gz sonarqube-0bab6ec48f16ad6b83c80997cfa6fb69edd3930d.zip |
SONAR-5216 Store and display used quality profiles for multi-language analysis
Diffstat (limited to 'sonar-batch')
7 files changed, 425 insertions, 18 deletions
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<ModuleQProfiles.QProfile> 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<String, Map<Integer, ModuleQProfiles.QProfile>> profilesByLanguage = Maps.newLinkedHashMap(); + + private UsedQProfiles() { + } + + public static final UsedQProfiles fromProfiles(Iterable<QProfile> 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.<Integer, ModuleQProfiles.QProfile>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<QProfile> profiles) { + for (QProfile profile : profiles) { + this.add(profile); + } + return this; + } + + private UsedQProfiles mergeInPlace(UsedQProfiles other) { + for (Map<Integer, QProfile> 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.<Project>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\"}]"); + } + +} |