Browse Source

SONAR-7368 Deprecate quality_profiles measure and add data in scanner report

tags/5.5-M11
Julien HENRY 8 years ago
parent
commit
0fd2f2204c
23 changed files with 207 additions and 476 deletions
  1. 4
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/analysis/AnalysisMetadataHolder.java
  2. 18
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/analysis/AnalysisMetadataHolderImpl.java
  3. 7
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/analysis/MutableAnalysisMetadataHolder.java
  4. 34
    17
      server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeQProfileMeasureStep.java
  5. 15
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadReportAnalysisMetadataHolderStep.java
  6. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/step/ReportComputationSteps.java
  7. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/util/InitializedProperty.java
  8. 15
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/analysis/AnalysisMetadataHolderRule.java
  9. 13
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/analysis/MutableAnalysisMetadataHolderRule.java
  10. 48
    53
      server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeQProfileMeasureStepTest.java
  11. 1
    4
      sonar-core/src/main/java/org/sonar/core/metric/BatchMetrics.java
  12. 1
    1
      sonar-core/src/test/java/org/sonar/core/metric/BatchMetricsTest.java
  13. 4
    0
      sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
  14. 12
    1
      sonar-scanner-engine/src/main/java/org/sonar/batch/report/MetadataPublisher.java
  15. 0
    72
      sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfileSensor.java
  16. 0
    112
      sonar-scanner-engine/src/main/java/org/sonar/batch/rule/UsedQProfiles.java
  17. 0
    2
      sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
  18. 1
    1
      sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java
  19. 1
    2
      sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java
  20. 21
    1
      sonar-scanner-engine/src/test/java/org/sonar/batch/report/MetadataPublisherTest.java
  21. 0
    135
      sonar-scanner-engine/src/test/java/org/sonar/batch/rule/QProfileSensorTest.java
  22. 0
    72
      sonar-scanner-engine/src/test/java/org/sonar/batch/rule/UsedQProfilesTest.java
  23. 9
    0
      sonar-scanner-protocol/src/main/protobuf/scanner_report.proto

+ 4
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/analysis/AnalysisMetadataHolder.java View File

@@ -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();

}

+ 18
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/analysis/AnalysisMetadataHolderImpl.java View File

@@ -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();
}

}

+ 7
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/analysis/MutableAnalysisMetadataHolder.java View File

@@ -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);

}

+ 34
- 17
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeQProfileMeasureStep.java View File

@@ -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())));
}

+ 15
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/step/LoadReportAnalysisMetadataHolderStep.java View File

@@ -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) {

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/step/ReportComputationSteps.java View File

@@ -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;


+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/util/InitializedProperty.java View File

@@ -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;

+ 15
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/analysis/AnalysisMetadataHolderRule.java View File

@@ -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();
}
}

+ 13
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/analysis/MutableAnalysisMetadataHolderRule.java View File

@@ -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();
}
}

+ 48
- 53
server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeQProfileMeasureStepTest.java View File

@@ -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));
}

}

+ 1
- 4
sonar-core/src/main/java/org/sonar/core/metric/BatchMetrics.java View File

@@ -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;


+ 1
- 1
sonar-core/src/test/java/org/sonar/core/metric/BatchMetricsTest.java View File

@@ -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

+ 4
- 0
sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java View File

@@ -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)

+ 12
- 1
sonar-scanner-engine/src/main/java/org/sonar/batch/report/MetadataPublisher.java View File

@@ -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());
}
}

+ 0
- 72
sonar-scanner-engine/src/main/java/org/sonar/batch/rule/QProfileSensor.java View File

@@ -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();
}
}

+ 0
- 112
sonar-scanner-engine/src/main/java/org/sonar/batch/rule/UsedQProfiles.java View File

@@ -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;
}
}

+ 0
- 2
sonar-scanner-engine/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java View File

@@ -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

+ 1
- 1
sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java View File

@@ -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));

+ 1
- 2
sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java View File

@@ -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));

+ 21
- 1
sonar-scanner-engine/src/test/java/org/sonar/batch/report/MetadataPublisherTest.java View File

@@ -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

+ 0
- 135
sonar-scanner-engine/src/test/java/org/sonar/batch/rule/QProfileSensorTest.java View File

@@ -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\"}]")));
}
}

+ 0
- 72
sonar-scanner-engine/src/test/java/org/sonar/batch/rule/UsedQProfilesTest.java View File

@@ -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);
}
}

+ 9
- 0
sonar-scanner-protocol/src/main/protobuf/scanner_report.proto View File

@@ -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 {

Loading…
Cancel
Save