--- /dev/null
+/*
+ * 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.
+ */
+
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+
+public class AddScannerContextSensor implements Sensor {
+ @Override
+ public void execute(SensorContext context) {
+ context.addContextProperty("foo1", "bar1");
+ context.addContextProperty("foo2", "bar2");
+ }
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+
+import java.util.Map;
+import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+public class LogScannerContextPostTask implements PostProjectAnalysisTask {
+ private static final Logger LOG = Loggers.get(LogScannerContextPostTask.class);
+
+ @Override
+ public void finished(ProjectAnalysis analysis) {
+ for (Map.Entry<String, String> prop : analysis.getScannerContext().getProperties().entrySet()) {
+ LOG.info("POSTASKPLUGIN: ScannerProperty {}={}",
+ prop.getKey(),
+ prop.getValue());
+ }
+ }
+}
public class PostTaskPlugin extends SonarPlugin {
public List getExtensions() {
- return Arrays.asList(PostProjectAnalysisTaskImpl.class);
+ return Arrays.asList(PostProjectAnalysisTaskImpl.class,
+ LogScannerContextPostTask.class, AddScannerContextSensor.class);
}
}
import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
import org.sonar.api.ce.posttask.Project;
import org.sonar.api.ce.posttask.QualityGate;
+import org.sonar.api.ce.posttask.ScannerContext;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReader;
import org.sonar.server.computation.task.projectanalysis.qualitygate.Condition;
import org.sonar.server.computation.task.projectanalysis.qualitygate.ConditionStatus;
import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateHolder;
private final QualityGateHolder qualityGateHolder;
private final QualityGateStatusHolder qualityGateStatusHolder;
private final PostProjectAnalysisTask[] postProjectAnalysisTasks;
+ private final BatchReportReader reportReader;
/**
* Constructor used by Pico when there is no {@link PostProjectAnalysisTask} in the container.
*/
public PostProjectAnalysisTasksExecutor(org.sonar.ce.queue.CeTask ceTask,
AnalysisMetadataHolder analysisMetadataHolder,
- QualityGateHolder qualityGateHolder, QualityGateStatusHolder qualityGateStatusHolder) {
- this(ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, null);
+ QualityGateHolder qualityGateHolder, QualityGateStatusHolder qualityGateStatusHolder,
+ BatchReportReader reportReader) {
+ this(ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader, null);
}
public PostProjectAnalysisTasksExecutor(org.sonar.ce.queue.CeTask ceTask,
AnalysisMetadataHolder analysisMetadataHolder,
- QualityGateHolder qualityGateHolder, QualityGateStatusHolder qualityGateStatusHolder,
+ QualityGateHolder qualityGateHolder, QualityGateStatusHolder qualityGateStatusHolder, BatchReportReader reportReader,
@Nullable PostProjectAnalysisTask[] postProjectAnalysisTasks) {
this.analysisMetadataHolder = analysisMetadataHolder;
this.qualityGateHolder = qualityGateHolder;
this.qualityGateStatusHolder = qualityGateStatusHolder;
this.ceTask = ceTask;
+ this.reportReader = reportReader;
this.postProjectAnalysisTasks = postProjectAnalysisTasks == null ? NO_POST_PROJECT_ANALYSIS_TASKS : postProjectAnalysisTasks;
}
new CeTaskImpl(this.ceTask.getUuid(), status),
createProject(this.ceTask),
getAnalysisDate(),
+ ScannerContextImpl.from(reportReader.readContextProperties()),
status == SUCCESS ? createQualityGate(this.qualityGateHolder) : null);
}
private final CeTask ceTask;
private final Project project;
private final Date date;
+ private final ScannerContext scannerContext;
@CheckForNull
private final QualityGate qualityGate;
- private ProjectAnalysis(CeTask ceTask, Project project, Date date, @Nullable QualityGate qualityGate) {
+ private ProjectAnalysis(CeTask ceTask, Project project, Date date,
+ ScannerContext scannerContext, @Nullable QualityGate qualityGate) {
this.ceTask = requireNonNull(ceTask, "ceTask can not be null");
this.project = requireNonNull(project, "project can not be null");
this.date = requireNonNull(date, "date can not be null");
+ this.scannerContext = requireNonNull(scannerContext, "scannerContext can not be null");
this.qualityGate = qualityGate;
}
return date;
}
+ @Override
+ public ScannerContext getScannerContext() {
+ return scannerContext;
+ }
+
@Override
public String toString() {
return "ProjectAnalysis{" +
--- /dev/null
+/*
+ * 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.server.computation.task.projectanalysis.api.posttask;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ce.posttask.ScannerContext;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.scanner.protocol.output.ScannerReport;
+
+@Immutable
+class ScannerContextImpl implements ScannerContext {
+
+ private final Map<String, String> props;
+
+ private ScannerContextImpl(Map<String, String> props) {
+ this.props = props;
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return props;
+ }
+
+ static ScannerContextImpl from(CloseableIterator<ScannerReport.ContextProperty> it) {
+ try {
+ ImmutableMap.Builder<String, String> mapBuilder = ImmutableMap.builder();
+ while (it.hasNext()) {
+ ScannerReport.ContextProperty prop = it.next();
+ mapBuilder.put(prop.getKey(), prop.getValue());
+ }
+ return new ScannerContextImpl(mapBuilder.build());
+ } finally {
+ it.close();
+ }
+ }
+}
CloseableIterator<ScannerReport.Test> readTests(int testFileRef);
CloseableIterator<ScannerReport.CoverageDetail> readCoverageDetails(int testFileRef);
+
+ CloseableIterator<ScannerReport.ContextProperty> readContextProperties();
}
import org.sonar.scanner.protocol.output.ScannerReport;
public class BatchReportReaderImpl implements BatchReportReader {
- private final org.sonar.scanner.protocol.output.ScannerReportReader delegate;
+
+ private final BatchReportDirectoryHolder batchReportDirectoryHolder;
+ private org.sonar.scanner.protocol.output.ScannerReportReader delegate;
// caching of metadata which are read often
private ScannerReport.Metadata metadata;
public BatchReportReaderImpl(BatchReportDirectoryHolder batchReportDirectoryHolder) {
- this.delegate = new org.sonar.scanner.protocol.output.ScannerReportReader(batchReportDirectoryHolder.getDirectory());
+ this.batchReportDirectoryHolder = batchReportDirectoryHolder;
+ }
+
+ private void ensureInitialized() {
+ if (this.delegate == null) {
+ this.delegate = new org.sonar.scanner.protocol.output.ScannerReportReader(batchReportDirectoryHolder.getDirectory());
+ }
}
@Override
public ScannerReport.Metadata readMetadata() {
+ ensureInitialized();
if (this.metadata == null) {
this.metadata = delegate.readMetadata();
}
@Override
public CloseableIterator<String> readScannerLogs() {
+ ensureInitialized();
File file = delegate.getFileStructure().analysisLog();
if (!file.exists()) {
return CloseableIterator.emptyCloseableIterator();
@Override
public CloseableIterator<ScannerReport.ActiveRule> readActiveRules() {
+ ensureInitialized();
return delegate.readActiveRules();
}
@Override
public CloseableIterator<ScannerReport.Measure> readComponentMeasures(int componentRef) {
+ ensureInitialized();
return delegate.readComponentMeasures(componentRef);
}
@Override
@CheckForNull
public ScannerReport.Changesets readChangesets(int componentRef) {
+ ensureInitialized();
return delegate.readChangesets(componentRef);
}
@Override
public ScannerReport.Component readComponent(int componentRef) {
+ ensureInitialized();
return delegate.readComponent(componentRef);
}
@Override
public CloseableIterator<ScannerReport.Issue> readComponentIssues(int componentRef) {
+ ensureInitialized();
return delegate.readComponentIssues(componentRef);
}
@Override
public CloseableIterator<ScannerReport.Duplication> readComponentDuplications(int componentRef) {
+ ensureInitialized();
return delegate.readComponentDuplications(componentRef);
}
@Override
public CloseableIterator<ScannerReport.CpdTextBlock> readCpdTextBlocks(int componentRef) {
+ ensureInitialized();
return delegate.readCpdTextBlocks(componentRef);
}
@Override
public CloseableIterator<ScannerReport.Symbol> readComponentSymbols(int componentRef) {
+ ensureInitialized();
return delegate.readComponentSymbols(componentRef);
}
@Override
public CloseableIterator<ScannerReport.SyntaxHighlightingRule> readComponentSyntaxHighlighting(int fileRef) {
+ ensureInitialized();
return delegate.readComponentSyntaxHighlighting(fileRef);
}
@Override
public CloseableIterator<ScannerReport.LineCoverage> readComponentCoverage(int fileRef) {
+ ensureInitialized();
return delegate.readComponentCoverage(fileRef);
}
@Override
public Optional<CloseableIterator<String>> readFileSource(int fileRef) {
+ ensureInitialized();
File file = delegate.readFileSource(fileRef);
if (file == null) {
return Optional.absent();
}
try {
- return Optional.<CloseableIterator<String>>of(new CloseableLineIterator(IOUtils.lineIterator(FileUtils.openInputStream(file), StandardCharsets.UTF_8)));
+ return Optional.of(new CloseableLineIterator(IOUtils.lineIterator(FileUtils.openInputStream(file), StandardCharsets.UTF_8)));
} catch (IOException e) {
throw new IllegalStateException("Fail to traverse file: " + file, e);
}
@Override
public CloseableIterator<ScannerReport.Test> readTests(int testFileRef) {
+ ensureInitialized();
File file = delegate.readTests(testFileRef);
if (file == null) {
return CloseableIterator.emptyCloseableIterator();
@Override
public CloseableIterator<ScannerReport.CoverageDetail> readCoverageDetails(int testFileRef) {
+ ensureInitialized();
File file = delegate.readCoverageDetails(testFileRef);
if (file == null) {
return CloseableIterator.emptyCloseableIterator();
}
}
+ @Override
+ public CloseableIterator<ScannerReport.ContextProperty> readContextProperties() {
+ ensureInitialized();
+ return delegate.readContextProperties();
+ }
+
private static class ParserCloseableIterator<T> extends CloseableIterator<T> {
private final Parser<T> parser;
private final FileInputStream fileInputStream;
import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
import org.sonar.api.ce.posttask.Project;
import org.sonar.ce.queue.CeTask;
+import org.sonar.scanner.protocol.output.ScannerReport;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.server.computation.task.projectanalysis.batch.BatchReportReaderRule;
import org.sonar.server.computation.task.projectanalysis.metric.Metric;
import org.sonar.server.computation.task.projectanalysis.qualitygate.Condition;
import org.sonar.server.computation.task.projectanalysis.qualitygate.ConditionStatus;
import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateStatus;
import static com.google.common.collect.ImmutableList.of;
+import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@Rule
public MutableQualityGateStatusHolderRule qualityGateStatusHolder = new MutableQualityGateStatusHolderRule();
+ @Rule
+ public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+
private ArgumentCaptor<PostProjectAnalysisTask.ProjectAnalysis> projectAnalysisArgumentCaptor = ArgumentCaptor.forClass(PostProjectAnalysisTask.ProjectAnalysis.class);
private CeTask ceTask = new CeTask.Builder()
.setType("type")
private PostProjectAnalysisTask postProjectAnalysisTask = mock(PostProjectAnalysisTask.class);
private PostProjectAnalysisTasksExecutor underTest = new PostProjectAnalysisTasksExecutor(
ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder,
- new PostProjectAnalysisTask[] {postProjectAnalysisTask});
+ reportReader, new PostProjectAnalysisTask[] {postProjectAnalysisTask});
@Before
public void setUp() throws Exception {
@Test
@UseDataProvider("booleanValues")
public void does_not_fail_when_there_is_no_PostProjectAnalysisTasksExecutor(boolean allStepsExecuted) {
- new PostProjectAnalysisTasksExecutor(ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder)
+ new PostProjectAnalysisTasksExecutor(ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader)
.finished(allStepsExecuted);
}
InOrder inOrder = inOrder(postProjectAnalysisTask1, postProjectAnalysisTask2);
new PostProjectAnalysisTasksExecutor(
- ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder,
+ ceTask, analysisMetadataHolder, qualityGateHolder, qualityGateStatusHolder, reportReader,
new PostProjectAnalysisTask[] {postProjectAnalysisTask1, postProjectAnalysisTask2})
.finished(allStepsExecuted);
assertThat(qualityGate.getConditions()).hasSize(2);
}
+ @Test
+ public void scannerContext_loads_properties_from_scanner_report() {
+ reportReader.putContextProperties(asList(ScannerReport.ContextProperty.newBuilder().setKey("foo").setValue("bar").build()));
+ underTest.finished(true);
+
+ verify(postProjectAnalysisTask).finished(projectAnalysisArgumentCaptor.capture());
+
+ org.sonar.api.ce.posttask.ScannerContext scannerContext = projectAnalysisArgumentCaptor.getValue().getScannerContext();
+ assertThat(scannerContext.getProperties()).containsExactly(entry("foo", "bar"));
+ }
+
@DataProvider
public static Object[][] booleanValues() {
return new Object[][] {
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.junit.rules.TestRule;
private ScannerReport.Metadata metadata;
private List<String> scannerLogs;
private List<ScannerReport.ActiveRule> activeRules = new ArrayList<>();
+ private List<ScannerReport.ContextProperty> contextProperties = new ArrayList<>();
private Map<Integer, List<ScannerReport.Measure>> measures = new HashMap<>();
private Map<Integer, ScannerReport.Changesets> changesets = new HashMap<>();
private Map<Integer, ScannerReport.Component> components = new HashMap<>();
this.coverageDetails.clear();
}
+ @Override
+ public CloseableIterator<ScannerReport.ContextProperty> readContextProperties() {
+ return CloseableIterator.from(contextProperties.iterator());
+ }
+
+ public BatchReportReaderRule putContextProperties(List<ScannerReport.ContextProperty> contextProperties) {
+ this.contextProperties = Objects.requireNonNull(contextProperties);
+ return this;
+ }
+
@Override
public ScannerReport.Metadata readMetadata() {
if (metadata == null) {
*/
NewAnalysisError newAnalysisError();
+ /**
+ * Add a property to the scanner context. This context is available
+ * in Compute Engine when processing the report.
+ *
+ * @see org.sonar.api.ce.posttask.PostProjectAnalysisTask.ProjectAnalysis#getScannerContext()
+ * @since 6.1
+ */
+ void addContextProperty(String key, String value);
+
}
import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
import org.sonar.api.utils.SonarException;
+import static com.google.common.base.Preconditions.checkArgument;
+
class InMemorySensorStorage implements SensorStorage {
Table<String, String, Measure> measuresByComponentAndMetric = HashBasedTable.create();
Map<String, DefaultCpdTokens> cpdTokensByComponent = new HashMap<>();
Table<String, CoverageType, DefaultCoverage> coverageByComponentAndType = HashBasedTable.create();
Map<String, DefaultSymbolTable> symbolsPerComponent = new HashMap<>();
+ Map<String, String> contextProperties = new HashMap<>();
@Override
public void store(Measure measure) {
allAnalysisErrors.add(analysisError);
}
+ @Override
+ public void storeProperty(String key, String value) {
+ checkArgument(key != null, "Key of context property must not be null");
+ checkArgument(value != null, "Value of context property must not be null");
+ contextProperties.put(key, value);
+ }
}
*/
package org.sonar.api.batch.sensor.internal;
+import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.io.Serializable;
import java.nio.file.Path;
return null;
}
+ @Override
+ public void addContextProperty(String key, String value) {
+ sensorStorage.storeProperty(key, value);
+ }
+
+ /**
+ * @return an immutable map of the context properties defined with {@link SensorContext#addContextProperty(String, String)}.
+ * @since 6.1
+ */
+ public Map<String, String> getContextProperties() {
+ return ImmutableMap.copyOf(sensorStorage.contextProperties);
+ }
}
* @since 5.6
*/
void store(DefaultSymbolTable symbolTable);
-
+
/**
* @since 6.0
*/
void store(AnalysisError analysisError);
+
+ /**
+ * Value is overridden if the key was already stored.
+ * @throws IllegalArgumentException if key is null
+ * @throws IllegalArgumentException if value is null
+ * @since 6.1
+ */
+ void storeProperty(String key, String value);
}
*
*/
Date getDate();
+
+ /**
+ * Context as defined by scanner through {@link org.sonar.api.batch.sensor.SensorContext#addContextProperty(String, String)}.
+ * It does not contain the settings used by scanner.
+ *
+ * @since 6.1
+ */
+ ScannerContext getScannerContext();
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
private static final String PROJECT_CAN_NOT_BE_NULL = "project cannot be null";
private static final String CE_TASK_CAN_NOT_BE_NULL = "ceTask cannot be null";
private static final String STATUS_CAN_NOT_BE_NULL = "status cannot be null";
+ private static final String SCANNER_CONTEXT_CAN_NOT_BE_NULL = "scannerContext cannot be null";
private final PostProjectAnalysisTask underTest;
@CheckForNull
private Date date;
@CheckForNull
private QualityGate qualityGate;
+ private ScannerContext scannerContext;
private PostProjectAnalysisTaskTester(PostProjectAnalysisTask underTest) {
this.underTest = requireNonNull(underTest, "PostProjectAnalysisTask instance cannot be null");
return new ConditionBuilder();
}
+ public static ScannerContextBuilder newScannerContextBuilder() {
+ return new ScannerContextBuilder();
+ }
+
public PostProjectAnalysisTaskTester withCeTask(CeTask ceTask) {
this.ceTask = requireNonNull(ceTask, CE_TASK_CAN_NOT_BE_NULL);
return this;
return this;
}
+ /**
+ * @since 6.1
+ */
+ public PostProjectAnalysisTaskTester withScannerContext(ScannerContext scannerContext) {
+ this.scannerContext = requireNonNull(scannerContext, SCANNER_CONTEXT_CAN_NOT_BE_NULL);
+ return this;
+ }
+
public PostProjectAnalysisTaskTester at(Date date) {
this.date = requireNonNull(date, DATE_CAN_NOT_BE_NULL);
return this;
this.underTest.finished(
new PostProjectAnalysisTask.ProjectAnalysis() {
+ @Override
+ public ScannerContext getScannerContext() {
+ return scannerContext;
+ }
+
@Override
public CeTask getCeTask() {
return ceTask;
checkState(errorThreshold != null || warningThreshold != null, "At least one of errorThreshold and warningThreshold must be non null");
}
}
+
+ public static final class ScannerContextBuilder {
+ private final Map<String, String> properties = new HashMap<>();
+
+ private ScannerContextBuilder() {
+ // prevents instantiation outside PostProjectAnalysisTaskTester
+ }
+
+ public ScannerContext build() {
+ return new ScannerContext() {
+ @Override
+ public Map<String, String> getProperties() {
+ return properties;
+ }
+ };
+ }
+ }
}
--- /dev/null
+/*
+ * 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.api.ce.posttask;
+
+import java.util.Map;
+
+/**
+ * @since 6.1
+ */
+public interface ScannerContext {
+
+ /**
+ * @return immutable map of properties sent by scanner
+ */
+ Map<String, String> getProperties();
+}
--- /dev/null
+/*
+ * 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.api.batch.sensor.internal;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+
+public class InMemorySensorStorageTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ InMemorySensorStorage underTest = new InMemorySensorStorage();
+
+ @Test
+ public void test_storeProperty() {
+ assertThat(underTest.contextProperties).isEmpty();
+
+ underTest.storeProperty("foo", "bar");
+ assertThat(underTest.contextProperties).containsOnly(entry("foo", "bar"));
+ }
+
+ @Test
+ public void storeProperty_throws_IAE_if_key_is_null() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Key of context property must not be null");
+
+ underTest.storeProperty(null, "bar");
+ }
+
+ @Test
+ public void storeProperty_throws_IAE_if_value_is_null() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Value of context property must not be null");
+
+ underTest.storeProperty("foo", null);
+ }
+}
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
+import static org.assertj.core.data.MapEntry.entry;
public class SensorContextTesterTest {
}
@Test
- public void testCacnellation() {
+ public void testCancellation() {
assertThat(tester.isCancelled()).isFalse();
tester.setCancelled(true);
assertThat(tester.isCancelled()).isTrue();
}
+
+ @Test
+ public void testContextProperties() {
+ assertThat(tester.getContextProperties()).isEmpty();
+
+ tester.addContextProperty("foo", "bar");
+ assertThat(tester.getContextProperties()).containsOnly(entry("foo", "bar"));
+ }
}
--- /dev/null
+/*
+ * 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.scanner.report;
+
+import com.google.common.base.Function;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReportWriter;
+import org.sonar.scanner.repository.ContextPropertiesCache;
+
+import static com.google.common.collect.FluentIterable.from;
+
+public class ContextPropertiesPublisher implements ReportPublisherStep {
+ private final ContextPropertiesCache cache;
+
+ public ContextPropertiesPublisher(ContextPropertiesCache cache) {
+ this.cache = cache;
+ }
+
+ @Override
+ public void publish(ScannerReportWriter writer) {
+ Iterable<ScannerReport.ContextProperty> it = from(cache.getAll().entrySet()).transform(new MapEntryToContextPropertyFunction());
+ writer.writeContextProperties(it);
+ }
+
+ private static final class MapEntryToContextPropertyFunction implements Function<Map.Entry<String, String>, ScannerReport.ContextProperty> {
+ private final ScannerReport.ContextProperty.Builder builder = ScannerReport.ContextProperty.newBuilder();
+
+ public ScannerReport.ContextProperty apply(@Nonnull Map.Entry<String, String> input) {
+ return builder.clear().setKey(input.getKey()).setValue(input.getValue()).build();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.scanner.repository;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.sonar.api.batch.ScannerSide;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+@ScannerSide
+public class ContextPropertiesCache {
+
+ private final Map<String, String> props = new HashMap<>();
+
+ /**
+ * Value is overridden if the key was already stored.
+ * @throws IllegalArgumentException if key is null
+ * @throws IllegalArgumentException if value is null
+ * @since 6.1
+ */
+ public ContextPropertiesCache put(String key, String value) {
+ checkArgument(key != null, "Key of context property must not be null");
+ checkArgument(value != null, "Value of context property must not be null");
+ props.put(key, value);
+ return this;
+ }
+
+ public Map<String, String> getAll() {
+ return props;
+ }
+}
import org.sonar.scanner.report.ActiveRulesPublisher;
import org.sonar.scanner.report.AnalysisContextReportPublisher;
import org.sonar.scanner.report.ComponentsPublisher;
+import org.sonar.scanner.report.ContextPropertiesPublisher;
import org.sonar.scanner.report.CoveragePublisher;
import org.sonar.scanner.report.MeasuresPublisher;
import org.sonar.scanner.report.MetadataPublisher;
import org.sonar.scanner.report.ReportPublisher;
import org.sonar.scanner.report.SourcePublisher;
import org.sonar.scanner.report.TestExecutionAndCoveragePublisher;
+import org.sonar.scanner.repository.ContextPropertiesCache;
import org.sonar.scanner.repository.DefaultProjectRepositoriesLoader;
import org.sonar.scanner.repository.DefaultQualityProfileLoader;
import org.sonar.scanner.repository.DefaultServerIssuesLoader;
// Measures
MeasureCache.class,
+ // context
+ ContextPropertiesCache.class,
+ ContextPropertiesPublisher.class,
+
ProjectSettings.class,
// Report
return false;
}
+ @Override
+ public void addContextProperty(String key, String value) {
+ sensorStorage.storeProperty(key, value);
+ }
}
import org.sonar.scanner.protocol.output.ScannerReportWriter;
import org.sonar.scanner.report.ReportPublisher;
import org.sonar.scanner.report.ScannerReportUtils;
+import org.sonar.scanner.repository.ContextPropertiesCache;
import org.sonar.scanner.scan.measure.MeasureCache;
import org.sonar.scanner.sensor.coverage.CoverageExclusions;
private final ReportPublisher reportPublisher;
private final MeasureCache measureCache;
private final SonarCpdBlockIndex index;
+ private final ContextPropertiesCache contextPropertiesCache;
private final Settings settings;
public DefaultSensorStorage(MetricFinder metricFinder, ModuleIssues moduleIssues,
Settings settings,
- CoverageExclusions coverageExclusions, BatchComponentCache componentCache, ReportPublisher reportPublisher, MeasureCache measureCache, SonarCpdBlockIndex index) {
+ CoverageExclusions coverageExclusions, BatchComponentCache componentCache, ReportPublisher reportPublisher,
+ MeasureCache measureCache, SonarCpdBlockIndex index,
+ ContextPropertiesCache contextPropertiesCache) {
this.metricFinder = metricFinder;
this.moduleIssues = moduleIssues;
this.settings = settings;
this.reportPublisher = reportPublisher;
this.measureCache = measureCache;
this.index = index;
+ this.contextPropertiesCache = contextPropertiesCache;
}
private Metric findMetricOrFail(String metricKey) {
// no op
}
+ @Override
+ public void storeProperty(String key, String value) {
+ contextPropertiesCache.put(key, value);
+ }
}
--- /dev/null
+/*
+ * 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.scanner.report;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.scanner.repository.ContextPropertiesCache;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReportWriter;
+
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class ContextPropertiesPublisherTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ ContextPropertiesCache cache = new ContextPropertiesCache();
+ ContextPropertiesPublisher underTest = new ContextPropertiesPublisher(cache);
+
+ @Test
+ public void publish_writes_properties_to_report() {
+ cache.put("foo1", "bar1");
+ cache.put("foo2", "bar2");
+
+ ScannerReportWriter writer = mock(ScannerReportWriter.class);
+ underTest.publish(writer);
+
+ verify(writer).writeContextProperties(argThat(new TypeSafeMatcher<Iterable<ScannerReport.ContextProperty>>() {
+ @Override
+ protected boolean matchesSafely(Iterable<ScannerReport.ContextProperty> props) {
+ Map<String, ScannerReport.ContextProperty> map = Maps.uniqueIndex(props, ContextPropertyToKey.INSTANCE);
+ return map.size() == 2 &&
+ map.get("foo1").getValue().equals("bar1") &&
+ map.get("foo2").getValue().equals("bar2");
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ }
+ }));
+ }
+
+ @Test
+ public void publish_writes_no_properties_to_report() {
+ ScannerReportWriter writer = mock(ScannerReportWriter.class);
+ underTest.publish(writer);
+
+ verify(writer).writeContextProperties(argThat(new TypeSafeMatcher<Iterable<ScannerReport.ContextProperty>>() {
+ @Override
+ protected boolean matchesSafely(Iterable<ScannerReport.ContextProperty> props) {
+ return Iterables.isEmpty(props);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ }
+ }));
+ }
+
+ private enum ContextPropertyToKey implements Function<ScannerReport.ContextProperty, String> {
+ INSTANCE;
+ @Override
+ public String apply(@Nonnull ScannerReport.ContextProperty input) {
+ return input.getKey();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.scanner.repository;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+
+public class ContextPropertiesCacheTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ ContextPropertiesCache underTest = new ContextPropertiesCache();
+
+ @Test
+ public void put_property() {
+ assertThat(underTest.getAll()).isEmpty();
+
+ underTest.put("foo", "bar");
+ assertThat(underTest.getAll()).containsOnly(entry("foo", "bar"));
+ }
+
+ @Test
+ public void put_overrides_existing_value() {
+ underTest.put("foo", "bar");
+ underTest.put("foo", "baz");
+ assertThat(underTest.getAll()).containsOnly(entry("foo", "baz"));
+ }
+
+ @Test
+ public void put_throws_IAE_if_key_is_null() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Key of context property must not be null");
+
+ underTest.put(null, "bar");
+ }
+
+ @Test
+ public void put_throws_IAE_if_value_is_null() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Value of context property must not be null");
+
+ underTest.put("foo", null);
+ }
+}
import org.sonar.scanner.issue.ModuleIssues;
import org.sonar.scanner.protocol.output.ScannerReportWriter;
import org.sonar.scanner.report.ReportPublisher;
+import org.sonar.scanner.repository.ContextPropertiesCache;
import org.sonar.scanner.scan.measure.MeasureCache;
import org.sonar.scanner.sensor.coverage.CoverageExclusions;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
private ModuleIssues moduleIssues;
private Project project;
private MeasureCache measureCache;
-
+ private ContextPropertiesCache contextPropertiesCache = new ContextPropertiesCache();
private BatchComponentCache resourceCache;
@Before
ReportPublisher reportPublisher = mock(ReportPublisher.class);
when(reportPublisher.getWriter()).thenReturn(new ScannerReportWriter(temp.newFolder()));
underTest = new DefaultSensorStorage(metricFinder,
- moduleIssues, settings, coverageExclusions, resourceCache, reportPublisher, measureCache, mock(SonarCpdBlockIndex.class));
+ moduleIssues, settings, coverageExclusions, resourceCache, reportPublisher, measureCache,
+ mock(SonarCpdBlockIndex.class), contextPropertiesCache);
}
@Test
underTest.store(st);
}
+ @Test
+ public void shouldStoreContextProperty() {
+ underTest.storeProperty("foo", "bar");
+
+ assertThat(contextPropertiesCache.getAll()).containsOnly(entry("foo", "bar"));
+
+ }
+
}
return new File(dir, domain.filePrefix + componentRef + domain.fileSuffix);
}
+ public File contextProperties() {
+ return new File(dir, "context-props.pb");
+ }
}
return null;
}
+ public CloseableIterator<ScannerReport.ContextProperty> readContextProperties() {
+ File file = fileStructure.contextProperties();
+ if (!fileExists(file)) {
+ return emptyCloseableIterator();
+ }
+ return Protobuf.readStream(file, ScannerReport.ContextProperty.parser());
+ }
+
private static boolean fileExists(File file) {
return file.exists() && file.isFile();
}
return file;
}
+ public File writeContextProperties(Iterable<ScannerReport.ContextProperty> properties) {
+ File file = fileStructure.contextProperties();
+ Protobuf.writeStream(properties, file, false);
+ return file;
+ }
+
public File getSourceFile(int componentRef) {
return fileStructure.fileFor(FileStructure.Domain.SOURCE, componentRef);
}
}
}
+message ContextProperty {
+ string key = 1;
+ string value = 2;
+}
+
message ActiveRule {
string rule_repository = 1;
string rule_key = 2;
*/
package org.sonar.scanner.protocol.output;
+import java.io.File;
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import org.sonar.scanner.protocol.output.FileStructure;
-import java.io.File;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
assertThat(structure.fileFor(FileStructure.Domain.ISSUES, 3)).exists().isFile();
assertThat(structure.fileFor(FileStructure.Domain.ISSUES, 42)).doesNotExist();
}
+
+ @Test
+ public void contextProperties_file() throws Exception {
+ File dir = temp.newFolder();
+ File file = new File(dir, "context-props.pb");
+ FileUtils.write(file, "content");
+
+ FileStructure structure = new FileStructure(dir);
+ assertThat(structure.contextProperties()).exists().isFile().isEqualTo(file);
+ }
}