Browse Source

SONAR-11515 Add RegisterQualityProfileStatusStep

tags/7.5
Benoît Gianinetti 5 years ago
parent
commit
9d9cac94c1

+ 2
- 0
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java View File

@@ -110,6 +110,7 @@ import org.sonar.ce.task.projectanalysis.qualitymodel.NewReliabilityAndSecurityR
import org.sonar.ce.task.projectanalysis.qualitymodel.RatingSettings;
import org.sonar.ce.task.projectanalysis.qualitymodel.ReliabilityAndSecurityRatingMeasuresVisitor;
import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderImpl;
import org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepositoryImpl;
import org.sonar.ce.task.projectanalysis.scm.ScmInfoDbLoader;
import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepositoryImpl;
import org.sonar.ce.task.projectanalysis.source.DbLineHashVersion;
@@ -218,6 +219,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
NewLinesRepository.class,
FileSourceDataComputer.class,
SourceLineReadersFactory.class,
QProfileStatusRepositoryImpl.class,

// issues
RuleRepositoryImpl.class,

+ 27
- 0
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualityprofile/MutableQProfileStatusRepository.java View File

@@ -0,0 +1,27 @@
/*
* 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.ce.task.projectanalysis.qualityprofile;

public interface MutableQProfileStatusRepository extends QProfileStatusRepository {
/**
* @throws IllegalStateException if the given quality profile is already registered
*/
void register(String qpKey, Status status);
}

+ 46
- 0
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualityprofile/QProfileStatusRepository.java View File

@@ -0,0 +1,46 @@
/*
* 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.ce.task.projectanalysis.qualityprofile;

import java.util.Optional;

public interface QProfileStatusRepository {

Optional<Status> get(String qpKey);

enum Status {
/**
* the QP was used in the last analysis but not anymore in the current one.
*/
REMOVED,
/**
* the QP was not used in the last analysis
*/
ADDED,
/**
* the QP was used in the last and current analysis and a rule has changed
*/
UPDATED,
/**
* neither the QP or a rule has changed since last analysis
*/
UNCHANGED
}
}

+ 45
- 0
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualityprofile/QProfileStatusRepositoryImpl.java View File

@@ -0,0 +1,45 @@
/*
* 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.ce.task.projectanalysis.qualityprofile;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

public class QProfileStatusRepositoryImpl implements MutableQProfileStatusRepository {

private final Map<String, Status> statuses = new HashMap<>();

@Override
public Optional<Status> get(@Nullable String qpKey) {
return Optional.ofNullable(statuses.get(qpKey));
}

@Override
public void register(String qpKey, Status status) {
checkNotNull(qpKey, "qpKey can't be null");
checkNotNull(status, "status can't be null");
checkState(statuses.put(qpKey, status) == null, "Quality Profile '%s' is already registered", qpKey);
}
}

+ 123
- 0
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualityprofile/RegisterQualityProfileStatusStep.java View File

@@ -0,0 +1,123 @@
/*
* 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.ce.task.projectanalysis.qualityprofile;

import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
import org.sonar.ce.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
import org.sonar.ce.task.projectanalysis.measure.Measure;
import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
import org.sonar.ce.task.step.ComputationStep;
import org.sonar.server.qualityprofile.QPMeasureData;
import org.sonar.server.qualityprofile.QualityProfile;

import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.ADDED;
import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.REMOVED;
import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UNCHANGED;
import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UPDATED;

public class RegisterQualityProfileStatusStep implements ComputationStep {

private TreeRootHolder treeRootHolder;
private MeasureRepository measureRepository;
private MetricRepository metricRepository;
private MutableQProfileStatusRepository qProfileStatusRepository;
private AnalysisMetadataHolder analysisMetadataHolder;

public RegisterQualityProfileStatusStep(TreeRootHolder treeRootHolder, MeasureRepository measureRepository, MetricRepository metricRepository,
MutableQProfileStatusRepository qProfileStatusRepository, AnalysisMetadataHolder analysisMetadataHolder) {
this.treeRootHolder = treeRootHolder;
this.measureRepository = measureRepository;
this.metricRepository = metricRepository;
this.qProfileStatusRepository = qProfileStatusRepository;
this.analysisMetadataHolder = analysisMetadataHolder;
}

@Override
public void execute(Context context) {
new DepthTraversalTypeAwareCrawler(
new TypeAwareVisitorAdapter(CrawlerDepthLimit.PROJECT, POST_ORDER) {
@Override
public void visitProject(Component tree) {
executeForProject(tree);
}
}).visit(treeRootHolder.getRoot());
}

private void executeForProject(Component project) {
measureRepository.getBaseMeasure(project, metricRepository.getByKey(CoreMetrics.QUALITY_PROFILES_KEY)).ifPresent(baseProfilesMeasure -> {
Map<String, QualityProfile> baseProfiles = parseJsonData(baseProfilesMeasure);
Map<String, QualityProfile> rawProfiles = analysisMetadataHolder
.getQProfilesByLanguage().values().stream()
.collect(Collectors.toMap(QualityProfile::getQpKey, q -> q));

registerNoMoreUsedProfiles(baseProfiles, rawProfiles);
registerNewOrUpdatedProfiles(baseProfiles, rawProfiles);
});
}

private void registerNoMoreUsedProfiles(Map<String, QualityProfile> baseProfiles, Map<String, QualityProfile> rawProfiles) {
for (QualityProfile baseProfile : baseProfiles.values()) {
if (!rawProfiles.containsKey(baseProfile.getQpKey())) {
register(baseProfile, REMOVED);
}
}
}

private void registerNewOrUpdatedProfiles(Map<String, QualityProfile> baseProfiles, Map<String, QualityProfile> rawProfiles) {
for (QualityProfile profile : rawProfiles.values()) {
QualityProfile baseProfile = baseProfiles.get(profile.getQpKey());
if (baseProfile == null) {
register(profile, ADDED);
} else if (profile.getRulesUpdatedAt().after(baseProfile.getRulesUpdatedAt())) {
register(baseProfile, UPDATED);
} else {
register(baseProfile, UNCHANGED);
}
}
}

private void register(QualityProfile profile, QProfileStatusRepository.Status status) {
qProfileStatusRepository.register(profile.getQpKey(), status);
}

private static Map<String, QualityProfile> parseJsonData(Measure measure) {
String data = measure.getStringValue();
if (data == null) {
return Collections.emptyMap();
}
return QPMeasureData.fromJson(data).getProfilesByKey();
}

@Override
public String getDescription() {
return "Compute Quality Profile status";
}

}

+ 2
- 0
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java View File

@@ -25,6 +25,7 @@ import org.sonar.ce.task.container.TaskContainer;
import org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStep;
import org.sonar.ce.task.projectanalysis.measure.PostMeasuresComputationChecksStep;
import org.sonar.ce.task.projectanalysis.purge.PurgeDatastoresStep;
import org.sonar.ce.task.projectanalysis.qualityprofile.RegisterQualityProfileStatusStep;
import org.sonar.ce.task.projectanalysis.source.PersistFileSourcesStep;
import org.sonar.ce.task.step.ComputationStep;
import org.sonar.ce.task.step.ExecuteStatelessInitExtensionsStep;
@@ -73,6 +74,7 @@ public class ReportComputationSteps extends AbstractComputationSteps {
ComplexityMeasuresStep.class,

LoadMeasureComputersStep.class,
RegisterQualityProfileStatusStep.class,
ExecuteVisitorsStep.class,

PostMeasuresComputationChecksStep.class,

+ 101
- 0
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/QProfileStatusRepositoryImplTest.java View File

@@ -0,0 +1,101 @@
/*
* 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.ce.task.projectanalysis.qualityprofile;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Optional;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(DataProviderRunner.class)
public class QProfileStatusRepositoryImplTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();
private QProfileStatusRepositoryImpl underTest;

@Before
public void setUp() {
underTest = new QProfileStatusRepositoryImpl();
}

@Test
@UseDataProvider("qualityProfileStatuses")
public void get_return_optional_of_status(QProfileStatusRepository.Status status) {
underTest.register("key", status);

assertThat(underTest.get("key")).isEqualTo(Optional.of(status));
}

@Test
@UseDataProvider("qualityProfileStatuses")
public void get_return_empty_for_qp_not_registered(QProfileStatusRepository.Status status) {
underTest.register("key", status);

assertThat(underTest.get("other_key")).isEqualTo(Optional.empty());
}

@Test
public void get_return_empty_for_null_qp_key() {
assertThat(underTest.get(null)).isEqualTo(Optional.empty());
}

@Test
@UseDataProvider("qualityProfileStatuses")
public void register_fails_with_NPE_if_qpKey_is_null(QProfileStatusRepository.Status status) {
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("qpKey can't be null");

underTest.register(null, status);
}

@Test
public void register_fails_with_NPE_if_status_is_null() {
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("status can't be null");

underTest.register("key", null);
}

@Test
@UseDataProvider("qualityProfileStatuses")
public void register_fails_with_ISE_if_qp_is_already_registered(QProfileStatusRepository.Status status) {
underTest.register("key", status);
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Quality Profile 'key' is already registered");

underTest.register("key", status);
}

@DataProvider
public static Object[][] qualityProfileStatuses() {
return Stream.of(QProfileStatusRepository.Status.values())
.map(s -> new Object[] {s})
.toArray(Object[][]::new);
}
}

+ 179
- 0
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/RegisterQualityProfileStatusStepTest.java View File

@@ -0,0 +1,179 @@
/*
* 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.ce.task.projectanalysis.step;

import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.ReportComponent;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
import org.sonar.ce.task.projectanalysis.measure.Measure;
import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
import org.sonar.ce.task.projectanalysis.metric.Metric;
import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
import org.sonar.ce.task.projectanalysis.qualityprofile.MutableQProfileStatusRepository;
import org.sonar.ce.task.projectanalysis.qualityprofile.RegisterQualityProfileStatusStep;
import org.sonar.ce.task.step.TestComputationStepContext;
import org.sonar.server.qualityprofile.QPMeasureData;
import org.sonar.server.qualityprofile.QualityProfile;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.ADDED;
import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.REMOVED;
import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UNCHANGED;
import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UPDATED;


public class RegisterQualityProfileStatusStepTest {

private static final String QP_NAME_1 = "qp_1";
private static final String QP_NAME_2 = "qp_2";
private static final String LANGUAGE_KEY_1 = "language_key1";
private static final String LANGUAGE_KEY_2 = "language_key_2";

@Rule
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();

private MetricRepository metricRepository = mock(MetricRepository.class);
private MeasureRepository measureRepository = mock(MeasureRepository.class);
private MutableQProfileStatusRepository qProfileStatusRepository = mock(MutableQProfileStatusRepository.class);
private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
private Metric qualityProfileMetric = mock(Metric.class);

private RegisterQualityProfileStatusStep underTest = new RegisterQualityProfileStatusStep(treeRootHolder, measureRepository, metricRepository, qProfileStatusRepository, analysisMetadataHolder);

@Before
public void setUp() {
when(metricRepository.getByKey(CoreMetrics.QUALITY_PROFILES_KEY)).thenReturn(qualityProfileMetric);
treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("uuid").setKey("key").build());
}

@Test
public void register_nothing_if_no_base_measure() {
when(measureRepository.getBaseMeasure(treeRootHolder.getRoot(), qualityProfileMetric)).thenReturn(Optional.empty());

underTest.execute(new TestComputationStepContext());

verifyNoMoreInteractions(qProfileStatusRepository);
}

@Test
public void register_nothing_if_no_base_and_quality_profile_measure_is_empty() {
mockBaseQPMeasures(treeRootHolder.getRoot(), null);

underTest.execute(new TestComputationStepContext());

verifyNoMoreInteractions(qProfileStatusRepository);
}

@Test
public void register_removed_profile() {
QualityProfile qp = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date());

mockBaseQPMeasures(treeRootHolder.getRoot(), new QualityProfile[] {qp});

underTest.execute(new TestComputationStepContext());

verify(qProfileStatusRepository).register(eq(qp.getQpKey()), eq(REMOVED));
verifyNoMoreInteractions(qProfileStatusRepository);
}

@Test
public void register_added_profile() {
QualityProfile qp1 = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date(1000L));
QualityProfile qp2 = qp(QP_NAME_2, LANGUAGE_KEY_2, new Date(1000L));

mockBaseQPMeasures(treeRootHolder.getRoot(), arrayOf(qp1));
mockRawQProfiles(ImmutableList.of(qp1, qp2));
underTest.execute(new TestComputationStepContext());

verify(qProfileStatusRepository).register(eq(qp1.getQpKey()), eq(UNCHANGED));
verify(qProfileStatusRepository).register(eq(qp2.getQpKey()), eq(ADDED));
verifyNoMoreInteractions(qProfileStatusRepository);
}

@Test
public void register_updated_profile() {
QualityProfile qp1 = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date(1000L));
QualityProfile qp2 = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date(1200L));

mockBaseQPMeasures(treeRootHolder.getRoot(), arrayOf(qp1));
mockRawQProfiles(ImmutableList.of(qp2));
underTest.execute(new TestComputationStepContext());

verify(qProfileStatusRepository).register(eq(qp2.getQpKey()), eq(UPDATED));
verifyNoMoreInteractions(qProfileStatusRepository);
}

@Test
public void register_unchanged_profile() {
QualityProfile qp1 = qp(QP_NAME_1, LANGUAGE_KEY_1, new Date(1000L));

mockBaseQPMeasures(treeRootHolder.getRoot(), arrayOf(qp1));
mockRawQProfiles(ImmutableList.of(qp1));
underTest.execute(new TestComputationStepContext());

verify(qProfileStatusRepository).register(eq(qp1.getQpKey()), eq(UNCHANGED));
verifyNoMoreInteractions(qProfileStatusRepository);
}

private void mockBaseQPMeasures(Component component, @Nullable QualityProfile[] previous) {
when(measureRepository.getBaseMeasure(component, qualityProfileMetric)).thenReturn(Optional.of(newMeasure(previous)));
}

private void mockRawQProfiles(@Nullable List<QualityProfile> previous) {
Map<String, QualityProfile> qpByLanguages = previous.stream().collect(Collectors.toMap(QualityProfile::getLanguageKey, q -> q));
when(analysisMetadataHolder.getQProfilesByLanguage()).thenReturn(qpByLanguages);
}

private static QualityProfile qp(String qpName, String languageKey, Date date) {
return new QualityProfile(qpName + "-" + languageKey, qpName, languageKey, date);
}

private static QualityProfile[] arrayOf(QualityProfile... qps) {
return qps;
}

private static Measure newMeasure(@Nullable QualityProfile... qps) {
return Measure.newMeasureBuilder().create(toJson(qps));
}

private static String toJson(@Nullable QualityProfile... qps) {
List<QualityProfile> qualityProfiles = qps == null ? Collections.emptyList() : Arrays.asList(qps);
return QPMeasureData.toJson(new QPMeasureData(qualityProfiles));
}

}

Loading…
Cancel
Save