@@ -19,7 +19,9 @@ | |||
*/ | |||
package org.sonar.server.computation.analysis; | |||
import java.util.Map; | |||
import javax.annotation.CheckForNull; | |||
import org.sonar.server.computation.qualityprofile.QualityProfile; | |||
import org.sonar.server.computation.snapshot.Snapshot; | |||
public interface AnalysisMetadataHolder { | |||
@@ -60,4 +62,6 @@ public interface AnalysisMetadataHolder { | |||
*/ | |||
int getRootComponentRef(); | |||
Map<String, QualityProfile> getQProfilesByLanguage(); | |||
} |
@@ -19,8 +19,11 @@ | |||
*/ | |||
package org.sonar.server.computation.analysis; | |||
import com.google.common.collect.ImmutableMap; | |||
import java.util.Map; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.sonar.server.computation.qualityprofile.QualityProfile; | |||
import org.sonar.server.computation.snapshot.Snapshot; | |||
import org.sonar.server.computation.util.InitializedProperty; | |||
@@ -38,6 +41,8 @@ public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder | |||
private InitializedProperty<Integer> rootComponentRef = new InitializedProperty<>(); | |||
private InitializedProperty<Map<String, QualityProfile>> qProfilesPerLanguage = new InitializedProperty<>(); | |||
@Override | |||
public MutableAnalysisMetadataHolder setAnalysisDate(long date) { | |||
checkState(!analysisDate.isInitialized(), "Analysis date has already been set"); | |||
@@ -109,4 +114,17 @@ public class AnalysisMetadataHolderImpl implements MutableAnalysisMetadataHolder | |||
return rootComponentRef.getProperty(); | |||
} | |||
@Override | |||
public MutableAnalysisMetadataHolder setQProfilesByLanguage(Map<String, QualityProfile> qprofilesByLanguage) { | |||
checkState(!this.qProfilesPerLanguage.isInitialized(), "QProfiles by language has already been set"); | |||
this.qProfilesPerLanguage.setProperty(ImmutableMap.copyOf(qprofilesByLanguage)); | |||
return this; | |||
} | |||
@Override | |||
public Map<String, QualityProfile> getQProfilesByLanguage() { | |||
checkState(qProfilesPerLanguage.isInitialized(), "QProfiles by language has not been set"); | |||
return qProfilesPerLanguage.getProperty(); | |||
} | |||
} |
@@ -19,7 +19,9 @@ | |||
*/ | |||
package org.sonar.server.computation.analysis; | |||
import java.util.Map; | |||
import javax.annotation.Nullable; | |||
import org.sonar.server.computation.qualityprofile.QualityProfile; | |||
import org.sonar.server.computation.snapshot.Snapshot; | |||
public interface MutableAnalysisMetadataHolder extends AnalysisMetadataHolder { | |||
@@ -49,4 +51,9 @@ public interface MutableAnalysisMetadataHolder extends AnalysisMetadataHolder { | |||
*/ | |||
MutableAnalysisMetadataHolder setRootComponentRef(int rootComponentRef); | |||
/** | |||
* @throws IllegalStateException if QProfile by language has already been set | |||
*/ | |||
MutableAnalysisMetadataHolder setQProfilesByLanguage(Map<String, QualityProfile> qprofilesByLanguage); | |||
} |
@@ -19,10 +19,10 @@ | |||
*/ | |||
package org.sonar.server.computation.step; | |||
import com.google.common.base.Optional; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import org.sonar.api.measures.CoreMetrics; | |||
import org.sonar.server.computation.analysis.AnalysisMetadataHolder; | |||
import org.sonar.server.computation.component.Component; | |||
import org.sonar.server.computation.component.CrawlerDepthLimit; | |||
import org.sonar.server.computation.component.PathAwareCrawler; | |||
@@ -38,33 +38,36 @@ import org.sonar.server.computation.qualityprofile.QualityProfile; | |||
import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER; | |||
/** | |||
* Aggregates quality profile on lower-level module nodes on their parent modules and project | |||
* Compute quality profile measure per module based on present languages | |||
*/ | |||
public class ComputeQProfileMeasureStep implements ComputationStep { | |||
private final TreeRootHolder treeRootHolder; | |||
private final MeasureRepository measureRepository; | |||
private final MetricRepository metricRepository; | |||
private final AnalysisMetadataHolder analysisMetadataHolder; | |||
public ComputeQProfileMeasureStep(TreeRootHolder treeRootHolder, MeasureRepository measureRepository, MetricRepository metricRepository) { | |||
public ComputeQProfileMeasureStep(TreeRootHolder treeRootHolder, MeasureRepository measureRepository, MetricRepository metricRepository, | |||
AnalysisMetadataHolder analysisMetadataHolder) { | |||
this.treeRootHolder = treeRootHolder; | |||
this.measureRepository = measureRepository; | |||
this.metricRepository = metricRepository; | |||
this.analysisMetadataHolder = analysisMetadataHolder; | |||
} | |||
@Override | |||
public void execute() { | |||
Metric qProfilesMetric = metricRepository.getByKey(CoreMetrics.QUALITY_PROFILES_KEY); | |||
new PathAwareCrawler<>(new NewCoverageAggregationComponentVisitor(qProfilesMetric)) | |||
new PathAwareCrawler<>(new QProfileAggregationComponentVisitor(qProfilesMetric)) | |||
.visit(treeRootHolder.getRoot()); | |||
} | |||
private class NewCoverageAggregationComponentVisitor extends PathAwareVisitorAdapter<QProfiles> { | |||
private class QProfileAggregationComponentVisitor extends PathAwareVisitorAdapter<QProfiles> { | |||
private final Metric qProfilesMetric; | |||
public NewCoverageAggregationComponentVisitor(Metric qProfilesMetric) { | |||
super(CrawlerDepthLimit.MODULE, POST_ORDER, new SimpleStackElementFactory<QProfiles>() { | |||
public QProfileAggregationComponentVisitor(Metric qProfilesMetric) { | |||
super(CrawlerDepthLimit.FILE, POST_ORDER, new SimpleStackElementFactory<QProfiles>() { | |||
@Override | |||
public QProfiles createForAny(Component component) { | |||
return new QProfiles(); | |||
@@ -73,6 +76,25 @@ public class ComputeQProfileMeasureStep implements ComputationStep { | |||
this.qProfilesMetric = qProfilesMetric; | |||
} | |||
@Override | |||
public void visitFile(Component file, Path<QProfiles> path) { | |||
String languageKey = file.getFileAttributes().getLanguageKey(); | |||
if (languageKey == null) { | |||
// No qprofile for unknown languages | |||
return; | |||
} | |||
if (!analysisMetadataHolder.getQProfilesByLanguage().containsKey(languageKey)) { | |||
throw new IllegalStateException("Report contains a file with language '" + languageKey + "' but no matching quality profile"); | |||
} | |||
path.parent().add(analysisMetadataHolder.getQProfilesByLanguage().get(languageKey)); | |||
} | |||
@Override | |||
public void visitDirectory(Component directory, Path<QProfiles> path) { | |||
QProfiles qProfiles = path.current(); | |||
path.parent().add(qProfiles); | |||
} | |||
@Override | |||
public void visitProject(Component project, Path<QProfiles> path) { | |||
addMeasure(project, path.current()); | |||
@@ -80,13 +102,8 @@ public class ComputeQProfileMeasureStep implements ComputationStep { | |||
@Override | |||
public void visitModule(Component module, Path<QProfiles> path) { | |||
Optional<Measure> measure = measureRepository.getRawMeasure(module, qProfilesMetric); | |||
QProfiles qProfiles = path.current(); | |||
if (measure.isPresent()) { | |||
qProfiles.add(measure.get()); | |||
} else { | |||
addMeasure(module, path.current()); | |||
} | |||
addMeasure(module, path.current()); | |||
path.parent().add(qProfiles); | |||
} | |||
@@ -100,14 +117,14 @@ public class ComputeQProfileMeasureStep implements ComputationStep { | |||
private static class QProfiles { | |||
private final Map<String, QualityProfile> profilesByKey = new HashMap<>(); | |||
public void add(Measure measure) { | |||
profilesByKey.putAll(QPMeasureData.fromJson(measure.getStringValue()).getProfilesByKey()); | |||
} | |||
public void add(QProfiles qProfiles) { | |||
profilesByKey.putAll(qProfiles.profilesByKey); | |||
} | |||
public void add(QualityProfile qProfile) { | |||
profilesByKey.put(qProfile.getQpKey(), qProfile); | |||
} | |||
public Measure createMeasure() { | |||
return Measure.newMeasureBuilder().create(QPMeasureData.toJson(new QPMeasureData(profilesByKey.values()))); | |||
} |
@@ -19,12 +19,17 @@ | |||
*/ | |||
package org.sonar.server.computation.step; | |||
import com.google.common.base.Function; | |||
import java.util.Date; | |||
import org.sonar.api.utils.MessageException; | |||
import org.sonar.ce.queue.CeTask; | |||
import org.sonar.scanner.protocol.output.ScannerReport; | |||
import org.sonar.scanner.protocol.output.ScannerReport.Metadata.QProfile; | |||
import org.sonar.server.computation.analysis.MutableAnalysisMetadataHolder; | |||
import org.sonar.server.computation.batch.BatchReportReader; | |||
import org.sonar.server.computation.qualityprofile.QualityProfile; | |||
import static com.google.common.collect.Maps.transformValues; | |||
import static java.lang.String.format; | |||
import static org.elasticsearch.common.lang3.StringUtils.isNotEmpty; | |||
@@ -33,6 +38,15 @@ import static org.elasticsearch.common.lang3.StringUtils.isNotEmpty; | |||
*/ | |||
public class LoadReportAnalysisMetadataHolderStep implements ComputationStep { | |||
private static final ToComputeQProfile TO_COMPUTE_QPROFILE = new ToComputeQProfile(); | |||
private static final class ToComputeQProfile implements Function<QProfile, QualityProfile> { | |||
@Override | |||
public QualityProfile apply(QProfile input) { | |||
return new QualityProfile(input.getKey(), input.getName(), input.getLanguage(), new Date(input.getRulesUpdatedAt())); | |||
} | |||
} | |||
private final CeTask ceTask; | |||
private final BatchReportReader reportReader; | |||
private final MutableAnalysisMetadataHolder mutableAnalysisMetadataHolder; | |||
@@ -53,6 +67,7 @@ public class LoadReportAnalysisMetadataHolderStep implements ComputationStep { | |||
mutableAnalysisMetadataHolder.setBranch(isNotEmpty(reportMetadata.getBranch()) ? reportMetadata.getBranch() : null); | |||
mutableAnalysisMetadataHolder.setAnalysisDate(reportMetadata.getAnalysisDate()); | |||
mutableAnalysisMetadataHolder.setCrossProjectDuplicationEnabled(reportMetadata.getCrossProjectDuplicationActivated()); | |||
mutableAnalysisMetadataHolder.setQProfilesByLanguage(transformValues(reportMetadata.getQprofilesPerLanguage(), TO_COMPUTE_QPROFILE)); | |||
} | |||
private void checkProjectKeyConsistency(ScannerReport.Metadata reportMetadata) { |
@@ -72,6 +72,7 @@ public class ReportComputationSteps extends AbstractComputationSteps { | |||
// Must be executed after computation of differential measures | |||
QualityGateMeasuresStep.class, | |||
// Must be executed after computation of language distribution | |||
ComputeQProfileMeasureStep.class, | |||
// Must be executed after computation of quality profile measure | |||
QualityProfileEventsStep.class, | |||
@@ -104,8 +105,7 @@ public class ReportComputationSteps extends AbstractComputationSteps { | |||
// notifications are sent at the end, so that webapp displays up-to-date information | |||
SendIssueNotificationsStep.class, | |||
PublishTaskResultStep.class | |||
); | |||
PublishTaskResultStep.class); | |||
private final ComputeEngineContainer computeEngineContainer; | |||
@@ -26,7 +26,7 @@ public class InitializedProperty<E> { | |||
private E property; | |||
private boolean initialized = false; | |||
public InitializedProperty setProperty(@Nullable E property) { | |||
public InitializedProperty<E> setProperty(@Nullable E property) { | |||
this.property = property; | |||
this.initialized = true; | |||
return this; |
@@ -20,9 +20,11 @@ | |||
package org.sonar.server.computation.analysis; | |||
import java.util.Date; | |||
import java.util.Map; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.junit.rules.ExternalResource; | |||
import org.sonar.server.computation.qualityprofile.QualityProfile; | |||
import org.sonar.server.computation.snapshot.Snapshot; | |||
import org.sonar.server.computation.util.InitializedProperty; | |||
@@ -41,6 +43,8 @@ public class AnalysisMetadataHolderRule extends ExternalResource implements Anal | |||
private InitializedProperty<Integer> rootComponentRef = new InitializedProperty<>(); | |||
private InitializedProperty<Map<String, QualityProfile>> qProfilesPerLanguage = new InitializedProperty<>(); | |||
public AnalysisMetadataHolderRule setAnalysisDate(Date date) { | |||
checkNotNull(date, "Date must not be null"); | |||
this.analysisDate.setProperty(date.getTime()); | |||
@@ -108,4 +112,15 @@ public class AnalysisMetadataHolderRule extends ExternalResource implements Anal | |||
checkState(rootComponentRef.isInitialized(), "Root component ref has not been set"); | |||
return rootComponentRef.getProperty(); | |||
} | |||
public AnalysisMetadataHolderRule setQProfilesByLanguage(Map<String, QualityProfile> qProfilesPerLanguage) { | |||
this.qProfilesPerLanguage.setProperty(qProfilesPerLanguage); | |||
return this; | |||
} | |||
@Override | |||
public Map<String, QualityProfile> getQProfilesByLanguage() { | |||
checkState(qProfilesPerLanguage.isInitialized(), "QProfile per language has not been set"); | |||
return qProfilesPerLanguage.getProperty(); | |||
} | |||
} |
@@ -19,9 +19,11 @@ | |||
*/ | |||
package org.sonar.server.computation.analysis; | |||
import java.util.Map; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.junit.rules.ExternalResource; | |||
import org.sonar.server.computation.qualityprofile.QualityProfile; | |||
import org.sonar.server.computation.snapshot.Snapshot; | |||
public class MutableAnalysisMetadataHolderRule extends ExternalResource implements MutableAnalysisMetadataHolder { | |||
@@ -92,4 +94,15 @@ public class MutableAnalysisMetadataHolderRule extends ExternalResource implemen | |||
public int getRootComponentRef() { | |||
return delegate.getRootComponentRef(); | |||
} | |||
@Override | |||
public MutableAnalysisMetadataHolder setQProfilesByLanguage(Map<String, QualityProfile> qprofilesByLanguage) { | |||
delegate.setQProfilesByLanguage(qprofilesByLanguage); | |||
return this; | |||
} | |||
@Override | |||
public Map<String, QualityProfile> getQProfilesByLanguage() { | |||
return delegate.getQProfilesByLanguage(); | |||
} | |||
} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
package org.sonar.server.computation.step; | |||
import com.google.common.collect.ImmutableMap; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import java.util.List; | |||
@@ -26,8 +27,10 @@ import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.server.computation.analysis.AnalysisMetadataHolderRule; | |||
import org.sonar.server.computation.batch.TreeRootHolderRule; | |||
import org.sonar.server.computation.component.Component; | |||
import org.sonar.server.computation.component.FileAttributes; | |||
import org.sonar.server.computation.component.ReportComponent; | |||
import org.sonar.server.computation.measure.Measure; | |||
import org.sonar.server.computation.measure.MeasureRepositoryRule; | |||
@@ -36,9 +39,12 @@ import org.sonar.server.computation.qualityprofile.QPMeasureData; | |||
import org.sonar.server.computation.qualityprofile.QualityProfile; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.fail; | |||
import static org.assertj.guava.api.Assertions.assertThat; | |||
import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES; | |||
import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES_KEY; | |||
import static org.sonar.server.computation.component.Component.Type.DIRECTORY; | |||
import static org.sonar.server.computation.component.Component.Type.FILE; | |||
import static org.sonar.server.computation.component.Component.Type.MODULE; | |||
import static org.sonar.server.computation.component.Component.Type.PROJECT; | |||
import static org.sonar.server.computation.measure.Measure.newMeasureBuilder; | |||
@@ -47,22 +53,38 @@ public class ComputeQProfileMeasureStepTest { | |||
private static final String QP_NAME_1 = "qp1"; | |||
private static final String QP_NAME_2 = "qp1"; | |||
private static final String LANGUAGE_KEY_1 = "language_key1"; | |||
private static final String LANGUAGE_KEY_2 = "language_key2"; | |||
private static final String LANGUAGE_KEY_1 = "java"; | |||
private static final String LANGUAGE_KEY_2 = "php"; | |||
private static final String PROJECT_KEY = "PROJECT KEY"; | |||
private static final int PROJECT_REF = 1; | |||
private static final int MODULE_REF = 11; | |||
private static final int SUB_MODULE_REF = 111; | |||
private static final int FOLDER_1_REF = 1111; | |||
private static final int FOLDER_2_REF = 1112; | |||
private static final int FILE_1_1_REF = 11111; | |||
private static final int FILE_1_2_REF = 11112; | |||
private static final int FILE_2_1_REF = 11121; | |||
private static final int FILE_2_2_REF = 11122; | |||
private static final Component MULTI_MODULE_PROJECT = ReportComponent.builder(PROJECT, PROJECT_REF).setKey(PROJECT_KEY) | |||
.addChildren( | |||
ReportComponent.builder(MODULE, MODULE_REF) | |||
.addChildren( | |||
ReportComponent.builder(MODULE, SUB_MODULE_REF).build() | |||
) | |||
.build() | |||
).build(); | |||
ReportComponent.builder(MODULE, SUB_MODULE_REF) | |||
.addChildren(ReportComponent.builder(DIRECTORY, FOLDER_1_REF) | |||
.addChildren( | |||
ReportComponent.builder(FILE, FILE_1_1_REF).setFileAttributes(new FileAttributes(false, "java")).build(), | |||
ReportComponent.builder(FILE, FILE_1_2_REF).setFileAttributes(new FileAttributes(false, "java")).build()) | |||
.build(), | |||
ReportComponent.builder(DIRECTORY, FOLDER_2_REF) | |||
.addChildren( | |||
ReportComponent.builder(FILE, FILE_2_1_REF).setFileAttributes(new FileAttributes(false, null)).build(), | |||
ReportComponent.builder(FILE, FILE_2_2_REF).setFileAttributes(new FileAttributes(false, "php")).build()) | |||
.build()) | |||
.build()) | |||
.build()) | |||
.build(); | |||
@Rule | |||
public ExpectedException thrown = ExpectedException.none(); | |||
@@ -76,80 +98,52 @@ public class ComputeQProfileMeasureStepTest { | |||
@Rule | |||
public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); | |||
@Rule | |||
public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); | |||
ComputeQProfileMeasureStep underTest; | |||
@Before | |||
public void setUp() throws Exception { | |||
underTest = new ComputeQProfileMeasureStep(treeRootHolder, measureRepository, metricRepository); | |||
underTest = new ComputeQProfileMeasureStep(treeRootHolder, measureRepository, metricRepository, analysisMetadataHolder); | |||
} | |||
@Test | |||
public void add_quality_profile_measure_on_project() throws Exception { | |||
treeRootHolder.setRoot(MULTI_MODULE_PROJECT); | |||
QualityProfile qp = createQProfile(QP_NAME_1, LANGUAGE_KEY_1); | |||
addMeasure(SUB_MODULE_REF, qp); | |||
QualityProfile qpJava = createQProfile(QP_NAME_1, LANGUAGE_KEY_1); | |||
QualityProfile qpPhp = createQProfile(QP_NAME_2, LANGUAGE_KEY_2); | |||
analysisMetadataHolder.setQProfilesByLanguage(ImmutableMap.of(LANGUAGE_KEY_1, qpJava, LANGUAGE_KEY_2, qpPhp)); | |||
underTest.execute(); | |||
assertThat(measureRepository.getAddedRawMeasures(PROJECT_REF).get(QUALITY_PROFILES_KEY)).extracting("data").containsOnly(toJson(qp)); | |||
assertThat(measureRepository.getAddedRawMeasures(PROJECT_REF).get(QUALITY_PROFILES_KEY)) | |||
.extracting("data").containsOnly(toJson(qpJava, qpPhp)); | |||
} | |||
@Test | |||
public void add_quality_profile_measure_from_multiple_modules() throws Exception { | |||
ReportComponent project = ReportComponent.builder(PROJECT, PROJECT_REF) | |||
.addChildren( | |||
ReportComponent.builder(MODULE, MODULE_REF) | |||
.addChildren( | |||
ReportComponent.builder(MODULE, SUB_MODULE_REF).build() | |||
) | |||
.build(), | |||
ReportComponent.builder(MODULE, 12).build() | |||
).build(); | |||
treeRootHolder.setRoot(project); | |||
QualityProfile qp1 = createQProfile(QP_NAME_1, LANGUAGE_KEY_1); | |||
addMeasure(SUB_MODULE_REF, qp1); | |||
QualityProfile qp2 = createQProfile(QP_NAME_2, LANGUAGE_KEY_2); | |||
addMeasure(12, qp2); | |||
underTest.execute(); | |||
assertThat(measureRepository.getAddedRawMeasures(PROJECT_REF).get(QUALITY_PROFILES_KEY)).extracting("data").containsOnly(toJson(qp1, qp2)); | |||
} | |||
@Test | |||
public void nothing_to_add_when_measure_already_exists_on_project() throws Exception { | |||
public void nothing_to_add_when_no_files() throws Exception { | |||
ReportComponent project = ReportComponent.builder(PROJECT, PROJECT_REF).build(); | |||
treeRootHolder.setRoot(project); | |||
QualityProfile qp = createQProfile(QP_NAME_1, LANGUAGE_KEY_1); | |||
addMeasure(PROJECT_REF, qp); | |||
underTest.execute(); | |||
assertThat(measureRepository.getAddedRawMeasures(PROJECT_REF)).isEmpty(); | |||
} | |||
@Test | |||
public void nothing_to_add_when_no_qprofile_computed_on_project() throws Exception { | |||
treeRootHolder.setRoot(MULTI_MODULE_PROJECT); | |||
underTest.execute(); | |||
assertThat(measureRepository.getAddedRawMeasures(PROJECT_REF)).isEmpty(); | |||
} | |||
@Test | |||
public void nothing_to_add_when_qprofiles_computed_on_project_are_empty() throws Exception { | |||
public void fail_if_report_inconsistant() throws Exception { | |||
treeRootHolder.setRoot(MULTI_MODULE_PROJECT); | |||
measureRepository.addRawMeasure(PROJECT_REF, QUALITY_PROFILES_KEY, newMeasureBuilder().create(toJson())); | |||
QualityProfile qpJava = createQProfile(QP_NAME_1, LANGUAGE_KEY_1); | |||
analysisMetadataHolder.setQProfilesByLanguage(ImmutableMap.of(LANGUAGE_KEY_1, qpJava)); | |||
underTest.execute(); | |||
try { | |||
underTest.execute(); | |||
fail("Expected exception"); | |||
} catch (Exception e) { | |||
assertThat(e).hasCause(new IllegalStateException("Report contains a file with language 'php' but no matching quality profile")); | |||
} | |||
assertThat(measureRepository.getAddedRawMeasures(PROJECT_REF)).isEmpty(); | |||
} | |||
private static QualityProfile createQProfile(String qpName, String languageKey) { | |||
@@ -165,4 +159,5 @@ public class ComputeQProfileMeasureStepTest { | |||
List<QualityProfile> qualityProfiles = Arrays.asList(qps); | |||
return QPMeasureData.toJson(new QPMeasureData(qualityProfiles)); | |||
} | |||
} |
@@ -72,7 +72,6 @@ import static org.sonar.api.measures.CoreMetrics.OVERALL_UNCOVERED_CONDITIONS; | |||
import static org.sonar.api.measures.CoreMetrics.OVERALL_UNCOVERED_LINES; | |||
import static org.sonar.api.measures.CoreMetrics.PUBLIC_API; | |||
import static org.sonar.api.measures.CoreMetrics.PUBLIC_UNDOCUMENTED_API; | |||
import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES; | |||
import static org.sonar.api.measures.CoreMetrics.SKIPPED_TESTS; | |||
import static org.sonar.api.measures.CoreMetrics.STATEMENTS; | |||
import static org.sonar.api.measures.CoreMetrics.TESTS; | |||
@@ -146,9 +145,7 @@ public class BatchMetrics { | |||
OVERALL_CONDITIONS_TO_COVER, | |||
OVERALL_UNCOVERED_CONDITIONS, | |||
OVERALL_COVERED_CONDITIONS_BY_LINE, | |||
OVERALL_CONDITIONS_BY_LINE, | |||
QUALITY_PROFILES); | |||
OVERALL_CONDITIONS_BY_LINE); | |||
private final Set<Metric> metrics; | |||
@@ -36,7 +36,7 @@ public class BatchMetricsTest { | |||
@Test | |||
public void check_number_of_allowed_core_metrics() throws Exception { | |||
assertThat(SENSOR_METRICS_WITHOUT_METRIC_PLUGIN.getMetrics()).hasSize(49); | |||
assertThat(SENSOR_METRICS_WITHOUT_METRIC_PLUGIN.getMetrics()).hasSize(48); | |||
} | |||
@Test |
@@ -2554,12 +2554,16 @@ public final class CoreMetrics { | |||
/** | |||
* @since 4.4 | |||
* @deprecated since 5.5 | |||
*/ | |||
@Deprecated | |||
public static final String QUALITY_PROFILES_KEY = "quality_profiles"; | |||
/** | |||
* @since 4.4 | |||
* @deprecated since 5.5 | |||
*/ | |||
@Deprecated | |||
public static final Metric<String> QUALITY_PROFILES = new Metric.Builder(QUALITY_PROFILES_KEY, "Profiles", Metric.ValueType.DATA) | |||
.setDescription("Details of quality profiles used during analysis") | |||
.setQualitative(false) |
@@ -26,6 +26,8 @@ import org.sonar.api.resources.Project; | |||
import org.sonar.batch.cpd.index.SonarCpdBlockIndex; | |||
import org.sonar.batch.index.BatchComponent; | |||
import org.sonar.batch.index.BatchComponentCache; | |||
import org.sonar.batch.rule.ModuleQProfiles; | |||
import org.sonar.batch.rule.QProfile; | |||
import org.sonar.batch.scan.ImmutableProjectReactor; | |||
import org.sonar.scanner.protocol.output.ScannerReport; | |||
import org.sonar.scanner.protocol.output.ScannerReportWriter; | |||
@@ -35,11 +37,13 @@ public class MetadataPublisher implements ReportPublisherStep { | |||
private final BatchComponentCache componentCache; | |||
private final ImmutableProjectReactor reactor; | |||
private final Settings settings; | |||
private final ModuleQProfiles qProfiles; | |||
public MetadataPublisher(BatchComponentCache componentCache, ImmutableProjectReactor reactor, Settings settings) { | |||
public MetadataPublisher(BatchComponentCache componentCache, ImmutableProjectReactor reactor, Settings settings, ModuleQProfiles qProfiles) { | |||
this.componentCache = componentCache; | |||
this.reactor = reactor; | |||
this.settings = settings; | |||
this.qProfiles = qProfiles; | |||
} | |||
@Override | |||
@@ -56,6 +60,13 @@ public class MetadataPublisher implements ReportPublisherStep { | |||
if (branch != null) { | |||
builder.setBranch(branch); | |||
} | |||
for (QProfile qp : qProfiles.findAll()) { | |||
builder.getMutableQprofilesPerLanguage().put(qp.getLanguage(), org.sonar.scanner.protocol.output.ScannerReport.Metadata.QProfile.newBuilder() | |||
.setKey(qp.getKey()) | |||
.setLanguage(qp.getLanguage()) | |||
.setName(qp.getName()) | |||
.setRulesUpdatedAt(qp.getRulesUpdatedAt().getTime()).build()); | |||
} | |||
writer.writeMetadata(builder.build()); | |||
} | |||
} |
@@ -1,72 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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.batch.rule; | |||
import org.sonar.api.batch.AnalysisMode; | |||
import org.sonar.api.batch.Sensor; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.batch.fs.FileSystem; | |||
import org.sonar.api.measures.CoreMetrics; | |||
import org.sonar.api.measures.Measure; | |||
import org.sonar.api.resources.Project; | |||
/** | |||
* Stores which Quality profiles have been used on the current module. | |||
* | |||
* TODO This information should not be stored as a measure but should be send as metadata in the {@link org.sonar.scanner.protocol.output.ScannerReport} | |||
*/ | |||
public class QProfileSensor implements Sensor { | |||
private final ModuleQProfiles moduleQProfiles; | |||
private final FileSystem fs; | |||
private final AnalysisMode analysisMode; | |||
public QProfileSensor(ModuleQProfiles moduleQProfiles, FileSystem fs, AnalysisMode analysisMode) { | |||
this.moduleQProfiles = moduleQProfiles; | |||
this.fs = fs; | |||
this.analysisMode = analysisMode; | |||
} | |||
@Override | |||
public boolean shouldExecuteOnProject(Project project) { | |||
// Should be only executed on leaf modules | |||
return project.getModules().isEmpty() | |||
// Useless in issues mode | |||
&& !analysisMode.isIssues(); | |||
} | |||
@Override | |||
public void analyse(Project project, SensorContext context) { | |||
UsedQProfiles used = new UsedQProfiles(); | |||
for (String language : fs.languages()) { | |||
QProfile profile = moduleQProfiles.findByLanguage(language); | |||
if (profile != null) { | |||
used.add(profile); | |||
} | |||
} | |||
Measure<?> detailsMeasure = new Measure<>(CoreMetrics.QUALITY_PROFILES, used.toJson()); | |||
context.saveMeasure(detailsMeasure); | |||
} | |||
@Override | |||
public String toString() { | |||
return getClass().getSimpleName(); | |||
} | |||
} |
@@ -1,112 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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.batch.rule; | |||
import com.google.common.collect.Sets; | |||
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.core.util.UtcDateUtils; | |||
import javax.annotation.concurrent.Immutable; | |||
import java.io.StringWriter; | |||
import java.util.Collection; | |||
import java.util.Comparator; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.SortedSet; | |||
@Immutable | |||
public class UsedQProfiles { | |||
private final SortedSet<QProfile> profiles = Sets.newTreeSet(new Comparator<QProfile>() { | |||
@Override | |||
public int compare(QProfile o1, QProfile o2) { | |||
int c = o1.getLanguage().compareTo(o2.getLanguage()); | |||
if (c == 0) { | |||
c = o1.getName().compareTo(o2.getName()); | |||
} | |||
return c; | |||
} | |||
}); | |||
public static UsedQProfiles fromJson(String json) { | |||
UsedQProfiles result = new UsedQProfiles(); | |||
JsonArray jsonRoot = new JsonParser().parse(json).getAsJsonArray(); | |||
for (JsonElement jsonElt : jsonRoot) { | |||
JsonObject jsonProfile = jsonElt.getAsJsonObject(); | |||
QProfile profile = new QProfile(); | |||
profile.setKey(jsonProfile.get("key").getAsString()); | |||
profile.setName(jsonProfile.get("name").getAsString()); | |||
profile.setLanguage(jsonProfile.get("language").getAsString()); | |||
profile.setRulesUpdatedAt(UtcDateUtils.parseDateTime(jsonProfile.get("rulesUpdatedAt").getAsString())); | |||
result.add(profile); | |||
} | |||
return result; | |||
} | |||
public String toJson() { | |||
StringWriter json = new StringWriter(); | |||
JsonWriter writer = JsonWriter.of(json); | |||
writer.beginArray(); | |||
for (QProfile profile : profiles) { | |||
writer | |||
.beginObject() | |||
.prop("key", profile.getKey()) | |||
.prop("language", profile.getLanguage()) | |||
.prop("name", profile.getName()) | |||
.prop("rulesUpdatedAt", UtcDateUtils.formatDateTime(profile.getRulesUpdatedAt())) | |||
.endObject(); | |||
} | |||
writer.endArray(); | |||
writer.close(); | |||
return json.toString(); | |||
} | |||
public UsedQProfiles add(UsedQProfiles other) { | |||
addAll(other.profiles); | |||
return this; | |||
} | |||
public UsedQProfiles add(QProfile profile) { | |||
profiles.add(profile); | |||
return this; | |||
} | |||
public UsedQProfiles addAll(Collection<QProfile> profiles) { | |||
this.profiles.addAll(profiles); | |||
return this; | |||
} | |||
public SortedSet<QProfile> profiles() { | |||
return profiles; | |||
} | |||
public Map<String, QProfile> profilesByKey() { | |||
Map<String, QProfile> map = new HashMap<>(); | |||
for (QProfile profile : profiles) { | |||
map.put(profile.getKey(), profile); | |||
} | |||
return map; | |||
} | |||
} |
@@ -56,7 +56,6 @@ import org.sonar.batch.phases.PublishPhaseExecutor; | |||
import org.sonar.batch.phases.SensorsExecutor; | |||
import org.sonar.batch.postjob.DefaultPostJobContext; | |||
import org.sonar.batch.postjob.PostJobOptimizer; | |||
import org.sonar.batch.rule.QProfileSensor; | |||
import org.sonar.batch.rule.QProfileVerifier; | |||
import org.sonar.batch.rule.RuleFinderCompatibility; | |||
import org.sonar.batch.rule.RulesProfileProvider; | |||
@@ -151,7 +150,6 @@ public class ModuleScanContainer extends ComponentContainer { | |||
// rules | |||
new RulesProfileProvider(), | |||
QProfileSensor.class, | |||
CheckFactory.class, | |||
// issues |
@@ -324,7 +324,7 @@ public class CpdMediumTest { | |||
Map<String, List<Measure>> allMeasures = result.allMeasures(); | |||
assertThat(allMeasures.get("com.foo.project")).extracting("metricKey").containsOnly(CoreMetrics.QUALITY_PROFILES_KEY); | |||
assertThat(allMeasures.get("com.foo.project")).extracting("metricKey").isEmpty(); | |||
assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue.value").containsOnly( | |||
tuple(CoreMetrics.LINES_KEY, blockCount * 2 + 1)); |
@@ -90,8 +90,7 @@ public class MeasuresMediumTest { | |||
Map<String, List<Measure>> allMeasures = result.allMeasures(); | |||
assertThat(allMeasures.get("com.foo.project")).extracting("metricKey", "stringValue.value").containsOnly( | |||
tuple(CoreMetrics.QUALITY_PROFILES_KEY, "[{\"key\":\"Sonar Way\",\"language\":\"xoo\",\"name\":\"Sonar Way\",\"rulesUpdatedAt\":\"2009-02-13T23:31:31+0000\"}]")); | |||
assertThat(allMeasures.get("com.foo.project")).extracting("metricKey", "stringValue.value").isEmpty(); | |||
assertThat(allMeasures.get("com.foo.project:src/sample.xoo")).extracting("metricKey", "intValue.value").containsOnly( | |||
tuple(CoreMetrics.LINES_KEY, 2)); |
@@ -30,12 +30,18 @@ import org.sonar.api.batch.bootstrap.ProjectDefinition; | |||
import org.sonar.api.config.Settings; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.batch.index.BatchComponentCache; | |||
import org.sonar.batch.rule.ModuleQProfiles; | |||
import org.sonar.batch.rule.QProfile; | |||
import org.sonar.batch.scan.ImmutableProjectReactor; | |||
import org.sonar.scanner.protocol.output.ScannerReport; | |||
import org.sonar.scanner.protocol.output.ScannerReportReader; | |||
import org.sonar.scanner.protocol.output.ScannerReportWriter; | |||
import static java.util.Arrays.asList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.entry; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class MetadataPublisherTest { | |||
@@ -46,6 +52,7 @@ public class MetadataPublisherTest { | |||
private Project project; | |||
private MetadataPublisher underTest; | |||
private Settings settings; | |||
private ModuleQProfiles qProfiles; | |||
@Before | |||
public void prepare() { | |||
@@ -56,12 +63,19 @@ public class MetadataPublisherTest { | |||
componentCache.add(project, null); | |||
componentCache.add(sampleFile, project); | |||
settings = new Settings(); | |||
underTest = new MetadataPublisher(componentCache, new ImmutableProjectReactor(projectDef), settings); | |||
qProfiles = mock(ModuleQProfiles.class); | |||
underTest = new MetadataPublisher(componentCache, new ImmutableProjectReactor(projectDef), settings, qProfiles); | |||
} | |||
@Test | |||
public void write_metadata() throws Exception { | |||
settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true"); | |||
Date date = new Date(); | |||
when(qProfiles.findAll()).thenReturn(asList(new QProfile() | |||
.setKey("q1") | |||
.setName("Q1") | |||
.setLanguage("java") | |||
.setRulesUpdatedAt(date))); | |||
File outputDir = temp.newFolder(); | |||
ScannerReportWriter writer = new ScannerReportWriter(outputDir); | |||
@@ -73,6 +87,12 @@ public class MetadataPublisherTest { | |||
assertThat(metadata.getProjectKey()).isEqualTo("foo"); | |||
assertThat(metadata.getProjectKey()).isEqualTo("foo"); | |||
assertThat(metadata.getCrossProjectDuplicationActivated()).isTrue(); | |||
assertThat(metadata.getQprofilesPerLanguage()).containsOnly(entry("java", org.sonar.scanner.protocol.output.ScannerReport.Metadata.QProfile.newBuilder() | |||
.setKey("q1") | |||
.setName("Q1") | |||
.setLanguage("java") | |||
.setRulesUpdatedAt(date.getTime()) | |||
.build())); | |||
} | |||
@Test |
@@ -1,135 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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.batch.rule; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.api.batch.AnalysisMode; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.batch.fs.internal.DefaultFileSystem; | |||
import org.sonar.api.measures.CoreMetrics; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.test.IsMeasure; | |||
import org.sonar.core.util.UtcDateUtils; | |||
import java.util.Collections; | |||
import java.util.Date; | |||
import static org.assertj.core.api.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 QProfileSensorTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
static final Date DATE = UtcDateUtils.parseDateTime("2014-01-15T12:00:00+0000"); | |||
static final QProfile JAVA_PROFILE = new QProfile().setKey("java-two").setName("Java Two").setLanguage("java") | |||
.setRulesUpdatedAt(DATE); | |||
static final QProfile PHP_PROFILE = new QProfile().setKey("php-one").setName("Php One").setLanguage("php") | |||
.setRulesUpdatedAt(DATE); | |||
ModuleQProfiles moduleQProfiles = mock(ModuleQProfiles.class); | |||
Project project = mock(Project.class); | |||
SensorContext sensorContext = mock(SensorContext.class); | |||
DefaultFileSystem fs; | |||
@Before | |||
public void prepare() throws Exception { | |||
fs = new DefaultFileSystem(temp.newFolder().toPath()); | |||
} | |||
@Test | |||
public void to_string() { | |||
QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, mock(AnalysisMode.class)); | |||
assertThat(sensor.toString()).isEqualTo("QProfileSensor"); | |||
} | |||
@Test | |||
public void no_execution_in_issues_mode() { | |||
AnalysisMode analysisMode = mock(AnalysisMode.class); | |||
when(analysisMode.isIssues()).thenReturn(true); | |||
QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, analysisMode); | |||
assertThat(sensor.shouldExecuteOnProject(project)).isFalse(); | |||
} | |||
@Test | |||
public void no_qprofiles() { | |||
when(moduleQProfiles.findAll()).thenReturn(Collections.<QProfile>emptyList()); | |||
QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, mock(AnalysisMode.class)); | |||
assertThat(sensor.shouldExecuteOnProject(project)).isTrue(); | |||
sensor.analyse(project, sensorContext); | |||
// measures are not saved | |||
verify(sensorContext).saveMeasure(argThat(new IsMeasure(CoreMetrics.QUALITY_PROFILES, "[]"))); | |||
} | |||
@Test | |||
public void mark_profiles_as_used() { | |||
when(moduleQProfiles.findByLanguage("java")).thenReturn(JAVA_PROFILE); | |||
when(moduleQProfiles.findByLanguage("php")).thenReturn(PHP_PROFILE); | |||
when(moduleQProfiles.findByLanguage("abap")).thenReturn(null); | |||
fs.addLanguages("java", "php", "abap"); | |||
QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, mock(AnalysisMode.class)); | |||
assertThat(sensor.shouldExecuteOnProject(project)).isTrue(); | |||
sensor.analyse(project, sensorContext); | |||
} | |||
@Test | |||
public void store_measures_on_single_lang_module() { | |||
when(moduleQProfiles.findByLanguage("java")).thenReturn(JAVA_PROFILE); | |||
when(moduleQProfiles.findByLanguage("php")).thenReturn(PHP_PROFILE); | |||
when(moduleQProfiles.findByLanguage("abap")).thenReturn(null); | |||
fs.addLanguages("java"); | |||
QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, mock(AnalysisMode.class)); | |||
assertThat(sensor.shouldExecuteOnProject(project)).isTrue(); | |||
sensor.analyse(project, sensorContext); | |||
verify(sensorContext).saveMeasure( | |||
argThat(new IsMeasure(CoreMetrics.QUALITY_PROFILES, | |||
"[{\"key\":\"java-two\",\"language\":\"java\",\"name\":\"Java Two\",\"rulesUpdatedAt\":\"2014-01-15T12:00:00+0000\"}]"))); | |||
} | |||
@Test | |||
public void store_measures_on_multi_lang_module() { | |||
when(moduleQProfiles.findByLanguage("java")).thenReturn(JAVA_PROFILE); | |||
when(moduleQProfiles.findByLanguage("php")).thenReturn(PHP_PROFILE); | |||
when(moduleQProfiles.findByLanguage("abap")).thenReturn(null); | |||
fs.addLanguages("java", "php"); | |||
QProfileSensor sensor = new QProfileSensor(moduleQProfiles, fs, mock(AnalysisMode.class)); | |||
assertThat(sensor.shouldExecuteOnProject(project)).isTrue(); | |||
sensor.analyse(project, sensorContext); | |||
verify(sensorContext).saveMeasure( | |||
argThat(new IsMeasure(CoreMetrics.QUALITY_PROFILES, | |||
"[{\"key\":\"java-two\",\"language\":\"java\",\"name\":\"Java Two\",\"rulesUpdatedAt\":\"2014-01-15T12:00:00+0000\"}," + | |||
"{\"key\":\"php-one\",\"language\":\"php\",\"name\":\"Php One\",\"rulesUpdatedAt\":\"2014-01-15T12:00:00+0000\"}]"))); | |||
} | |||
} |
@@ -1,72 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact 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.batch.rule; | |||
import org.junit.Test; | |||
import org.sonar.core.util.UtcDateUtils; | |||
import java.util.Arrays; | |||
import java.util.Map; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class UsedQProfilesTest { | |||
static final String JAVA_JSON = "{\"key\":\"p1\",\"language\":\"java\",\"name\":\"Sonar Way\",\"rulesUpdatedAt\":\"2014-01-15T00:00:00+0000\"}"; | |||
static final String PHP_JSON = "{\"key\":\"p2\",\"language\":\"php\",\"name\":\"Sonar Way\",\"rulesUpdatedAt\":\"2014-02-20T00:00:00+0000\"}"; | |||
@Test | |||
public void from_and_to_json() { | |||
QProfile java = new QProfile().setKey("p1").setName("Sonar Way").setLanguage("java") | |||
.setRulesUpdatedAt(UtcDateUtils.parseDateTime("2014-01-15T00:00:00+0000")); | |||
QProfile php = new QProfile().setKey("p2").setName("Sonar Way").setLanguage("php") | |||
.setRulesUpdatedAt(UtcDateUtils.parseDateTime("2014-02-20T00:00:00+0000")); | |||
UsedQProfiles used = new UsedQProfiles().add(java).add(php); | |||
String json = "[" + JAVA_JSON + "," + PHP_JSON + "]"; | |||
assertThat(used.toJson()).isEqualTo(json); | |||
used = UsedQProfiles.fromJson(json); | |||
assertThat(used.profiles()).hasSize(2); | |||
assertThat(used.profiles().first().getKey()).isEqualTo("p1"); | |||
assertThat(used.profiles().last().getKey()).isEqualTo("p2"); | |||
} | |||
@Test | |||
public void do_not_duplicate_profiles() { | |||
QProfile java = new QProfile().setKey("p1").setName("Sonar Way").setLanguage("java"); | |||
QProfile php = new QProfile().setKey("p2").setName("Sonar Way").setLanguage("php"); | |||
UsedQProfiles used = new UsedQProfiles().addAll(Arrays.asList(java, java, php)); | |||
assertThat(used.profiles()).hasSize(2); | |||
} | |||
@Test | |||
public void group_profiles_by_key() { | |||
QProfile java = new QProfile().setKey("p1").setName("Sonar Way").setLanguage("java"); | |||
QProfile php = new QProfile().setKey("p2").setName("Sonar Way").setLanguage("php"); | |||
UsedQProfiles used = new UsedQProfiles().addAll(Arrays.asList(java, java, php)); | |||
Map<String, QProfile> map = used.profilesByKey(); | |||
assertThat(map).hasSize(2); | |||
assertThat(map.get("p1")).isSameAs(java); | |||
assertThat(map.get("p2")).isSameAs(php); | |||
} | |||
} |
@@ -41,6 +41,14 @@ message Metadata { | |||
string branch = 3; | |||
int32 root_component_ref = 4; | |||
bool cross_project_duplication_activated = 5; | |||
map<string, QProfile> qprofiles_per_language = 6; | |||
message QProfile { | |||
string key = 1; | |||
string name = 2; | |||
string language = 3; | |||
int64 rulesUpdatedAt = 4; | |||
} | |||
} | |||
message ActiveRule { | |||
@@ -90,6 +98,7 @@ message Component { | |||
DIRECTORY = 3; | |||
FILE = 4; | |||
} | |||
} | |||
message Measure { |