descriptor | descriptor | ||||
.name("One Issue Per Line") | .name("One Issue Per Line") | ||||
.onlyOnLanguages(Xoo.KEY, Xoo2.KEY) | .onlyOnLanguages(Xoo.KEY, Xoo2.KEY) | ||||
.createIssuesForRuleRepositories(XooRulesDefinition.XOO_REPOSITORY, XooRulesDefinition.XOO2_REPOSITORY); | |||||
.createIssuesForRuleRepositories(XooRulesDefinition.XOO_REPOSITORY, XooRulesDefinition.XOO2_REPOSITORY) | |||||
.processesFilesIndependently(); | |||||
} | } | ||||
@Override | @Override |
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import java.util.stream.Stream; | import java.util.stream.Stream; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.sonar.api.batch.fs.InputFile; | import org.sonar.api.batch.fs.InputFile; | ||||
import org.sonar.api.batch.sensor.SensorDescriptor; | import org.sonar.api.batch.sensor.SensorDescriptor; | ||||
import org.sonar.api.config.Configuration; | import org.sonar.api.config.Configuration; | ||||
public class DefaultSensorDescriptor implements SensorDescriptor { | public class DefaultSensorDescriptor implements SensorDescriptor { | ||||
public static final Set<String> SENSORS_ONLY_CHANGED_IN_PR = Collections.unmodifiableSet(Stream.of( | |||||
"CSS Metrics", | |||||
"CSS Rules", | |||||
"HTML", | |||||
"XML Sensor" | |||||
public static final Set<String> HARDCODED_INDEPENDENT_FILE_SENSORS = Collections.unmodifiableSet(Stream.of( | |||||
"CSS Metrics", | |||||
"CSS Rules", | |||||
"HTML", | |||||
"XML Sensor" | |||||
).collect(Collectors.toSet())); | ).collect(Collectors.toSet())); | ||||
private String name; | private String name; | ||||
private String[] ruleRepositories = new String[0]; | private String[] ruleRepositories = new String[0]; | ||||
private boolean global = false; | private boolean global = false; | ||||
private Predicate<Configuration> configurationPredicate; | private Predicate<Configuration> configurationPredicate; | ||||
private boolean onlyChangedFilesInPullRequests = false; | |||||
private boolean processesFilesIndependently = false; | |||||
public String name() { | public String name() { | ||||
return name; | return name; | ||||
return global; | return global; | ||||
} | } | ||||
public boolean onlyChangedFilesInPullRequest() { | |||||
return onlyChangedFilesInPullRequests; | |||||
public boolean isProcessesFilesIndependently() { | |||||
return processesFilesIndependently; | |||||
} | } | ||||
@Override | @Override | ||||
public DefaultSensorDescriptor name(String name) { | public DefaultSensorDescriptor name(String name) { | ||||
// TODO: Add onlyChangedFilesInPullRequest into API and implement it at sensors | |||||
if (SENSORS_ONLY_CHANGED_IN_PR.contains(name)) { | |||||
onlyChangedFilesInPullRequests = true; | |||||
// TODO: Remove this hardcoded list once all plugins will implement the new API "processFilesIndependently" | |||||
if (HARDCODED_INDEPENDENT_FILE_SENSORS.contains(name)) { | |||||
processesFilesIndependently = true; | |||||
} | } | ||||
this.name = name; | this.name = name; | ||||
return this; | return this; | ||||
return this; | return this; | ||||
} | } | ||||
@Override | |||||
public SensorDescriptor processesFilesIndependently() { | |||||
this.processesFilesIndependently = true; | |||||
return this; | |||||
} | |||||
} | } |
@RunWith(DataProviderRunner.class) | @RunWith(DataProviderRunner.class) | ||||
public class DefaultSensorDescriptorTest { | public class DefaultSensorDescriptorTest { | ||||
@Test | |||||
public void describe_defaults() { | |||||
DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); | |||||
descriptor | |||||
.name("Foo"); | |||||
assertThat(descriptor.name()).isEqualTo("Foo"); | |||||
assertThat(descriptor.languages()).isEmpty(); | |||||
assertThat(descriptor.type()).isNull(); | |||||
assertThat(descriptor.ruleRepositories()).isEmpty(); | |||||
assertThat(descriptor.isProcessesFilesIndependently()).isFalse(); | |||||
} | |||||
@Test | @Test | ||||
public void describe() { | public void describe() { | ||||
DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); | ||||
.onlyOnLanguage("java") | .onlyOnLanguage("java") | ||||
.onlyOnFileType(InputFile.Type.MAIN) | .onlyOnFileType(InputFile.Type.MAIN) | ||||
.onlyWhenConfiguration(c -> c.hasKey("sonar.foo.reportPath2") && c.hasKey("sonar.foo.reportPath")) | .onlyWhenConfiguration(c -> c.hasKey("sonar.foo.reportPath2") && c.hasKey("sonar.foo.reportPath")) | ||||
.createIssuesForRuleRepository("squid-java"); | |||||
.createIssuesForRuleRepository("squid-java") | |||||
.processesFilesIndependently(); | |||||
assertThat(descriptor.name()).isEqualTo("Foo"); | assertThat(descriptor.name()).isEqualTo("Foo"); | ||||
assertThat(descriptor.languages()).containsOnly("java"); | assertThat(descriptor.languages()).containsOnly("java"); | ||||
settings.setProperty("sonar.foo.reportPath2", "foo"); | settings.setProperty("sonar.foo.reportPath2", "foo"); | ||||
assertThat(descriptor.configurationPredicate().test(settings.asConfig())).isTrue(); | assertThat(descriptor.configurationPredicate().test(settings.asConfig())).isTrue(); | ||||
assertThat(descriptor.ruleRepositories()).containsOnly("squid-java"); | assertThat(descriptor.ruleRepositories()).containsOnly("squid-java"); | ||||
assertThat(descriptor.isProcessesFilesIndependently()).isTrue(); | |||||
} | } | ||||
@Test | @Test | ||||
@UseDataProvider("sensorsOnlyChangedInPR") | |||||
@UseDataProvider("independentFilesSensors") | |||||
public void describe_with_restricted_sensor(String sensorName) { | public void describe_with_restricted_sensor(String sensorName) { | ||||
DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); | ||||
descriptor | descriptor | ||||
.name(sensorName); | .name(sensorName); | ||||
assertThat(descriptor.onlyChangedFilesInPullRequest()).isTrue(); | |||||
assertThat(descriptor.isProcessesFilesIndependently()).isTrue(); | |||||
} | } | ||||
@Test | @Test | ||||
@UseDataProvider("sensorsOnlyChangedInPR") | |||||
@UseDataProvider("independentFilesSensors") | |||||
public void describe_with_non_restricted_sensor(String sensorName) { | public void describe_with_non_restricted_sensor(String sensorName) { | ||||
DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); | DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); | ||||
descriptor | descriptor | ||||
.name(sensorName + "other"); | .name(sensorName + "other"); | ||||
assertThat(descriptor.onlyChangedFilesInPullRequest()).isFalse(); | |||||
assertThat(descriptor.isProcessesFilesIndependently()).isFalse(); | |||||
} | } | ||||
@DataProvider | @DataProvider | ||||
public static Object[][] sensorsOnlyChangedInPR() { | |||||
return new Object[][] {DefaultSensorDescriptor.SENSORS_ONLY_CHANGED_IN_PR.toArray()}; | |||||
public static Object[][] independentFilesSensors() { | |||||
return new Object[][] {DefaultSensorDescriptor.HARDCODED_INDEPENDENT_FILE_SENSORS.toArray()}; | |||||
} | } | ||||
} | } |
* @since 6.5 | * @since 6.5 | ||||
*/ | */ | ||||
SensorDescriptor onlyWhenConfiguration(Predicate<Configuration> predicate); | SensorDescriptor onlyWhenConfiguration(Predicate<Configuration> predicate); | ||||
/** | |||||
* Advertise that this sensor process each file independently. It means executing the sensor twice with FileSystem=[FileA] and FileSystem=[FileB] will produce the same result | |||||
* than executing the sensor only once with FileSystem=[FileA,FileB]. | |||||
* This will allow the platform to optimize sensor execution. | |||||
* @since 9.3 | |||||
*/ | |||||
SensorDescriptor processesFilesIndependently(); | |||||
} | } |
} | } | ||||
public void analyse() { | public void analyse() { | ||||
boolean sensorIsRestricted = descriptor.onlyChangedFilesInPullRequest() && isPullRequest; | |||||
boolean sensorIsRestricted = descriptor.isProcessesFilesIndependently() && isPullRequest; | |||||
if (sensorIsRestricted) { | if (sensorIsRestricted) { | ||||
LOGGER.info("Sensor {} is restricted to changed files only", descriptor.name()); | LOGGER.info("Sensor {} is restricted to changed files only", descriptor.name()); | ||||
} | } |
package org.sonar.scanner.mediumtest.branch; | package org.sonar.scanner.mediumtest.branch; | ||||
import com.google.common.collect.ImmutableMap; | import com.google.common.collect.ImmutableMap; | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||
import java.nio.file.Files; | import java.nio.file.Files; | ||||
import java.nio.file.Path; | import java.nio.file.Path; | ||||
import java.util.List; | |||||
import org.apache.commons.io.FileUtils; | import org.apache.commons.io.FileUtils; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | import org.sonar.api.batch.fs.internal.DefaultInputFile; | ||||
import org.sonar.api.batch.fs.internal.FileMetadata; | import org.sonar.api.batch.fs.internal.FileMetadata; | ||||
import org.sonar.api.notifications.AnalysisWarnings; | import org.sonar.api.notifications.AnalysisWarnings; | ||||
import org.sonar.api.utils.log.LogTester; | |||||
import org.sonar.scanner.mediumtest.AnalysisResult; | import org.sonar.scanner.mediumtest.AnalysisResult; | ||||
import org.sonar.scanner.mediumtest.ScannerMediumTester; | import org.sonar.scanner.mediumtest.ScannerMediumTester; | ||||
import org.sonar.scanner.protocol.output.ScannerReport; | import org.sonar.scanner.protocol.output.ScannerReport; | ||||
private static final String PROJECT_KEY = "sample"; | private static final String PROJECT_KEY = "sample"; | ||||
private static final String FILE_PATH = "HelloJava.xoo"; | private static final String FILE_PATH = "HelloJava.xoo"; | ||||
private static final String FILE_CONTENT = "xoooo"; | private static final String FILE_CONTENT = "xoooo"; | ||||
public static final String ONE_ISSUE_PER_LINE_IS_RESTRICTED_TO_CHANGED_FILES_ONLY = "Sensor One Issue Per Line is restricted to changed files only"; | |||||
private File baseDir; | private File baseDir; | ||||
@Rule | @Rule | ||||
public TemporaryFolder temp = new TemporaryFolder(); | public TemporaryFolder temp = new TemporaryFolder(); | ||||
@Rule | |||||
public LogTester logTester = new LogTester(); | |||||
@Rule | @Rule | ||||
public ScannerMediumTester tester = new ScannerMediumTester() | public ScannerMediumTester tester = new ScannerMediumTester() | ||||
.registerPlugin("xoo", new XooPlugin()) | .registerPlugin("xoo", new XooPlugin()) | ||||
assertThat(result2.getReportReader().readFileSource(fileId)).isNull(); | assertThat(result2.getReportReader().readFileSource(fileId)).isNull(); | ||||
} | } | ||||
@Test | |||||
public void shouldSkipSensorForUnchangedFilesOnPr() throws Exception { | |||||
AnalysisResult result = getResult(tester | |||||
.setBranchName("myBranch") | |||||
.setBranchTarget("master") | |||||
.setBranchType(BranchType.PULL_REQUEST)); | |||||
final DefaultInputFile file = (DefaultInputFile) result.inputFile(FILE_PATH); | |||||
List<ScannerReport.Issue> issues = result.issuesFor(file); | |||||
assertThat(issues).isEmpty(); | |||||
assertThat(logTester.logs()).contains(ONE_ISSUE_PER_LINE_IS_RESTRICTED_TO_CHANGED_FILES_ONLY); | |||||
} | |||||
@Test | |||||
public void shouldNotSkipSensorForUnchangedFilesOnBranch() throws Exception { | |||||
AnalysisResult result = getResult(tester | |||||
.setBranchName("myBranch") | |||||
.setBranchTarget("master") | |||||
.setBranchType(BranchType.BRANCH)); | |||||
final DefaultInputFile file = (DefaultInputFile) result.inputFile(FILE_PATH); | |||||
List<ScannerReport.Issue> issues = result.issuesFor(file); | |||||
assertThat(issues).isNotEmpty(); | |||||
assertThat(logTester.logs()).doesNotContain(ONE_ISSUE_PER_LINE_IS_RESTRICTED_TO_CHANGED_FILES_ONLY); | |||||
} | |||||
@Test | @Test | ||||
public void verify_metadata() { | public void verify_metadata() { | ||||
String branchName = "feature"; | String branchName = "feature"; |
@DataProvider | @DataProvider | ||||
public static Object[][] sensorsOnlyChangedInPR() { | public static Object[][] sensorsOnlyChangedInPR() { | ||||
return new Object[][] {DefaultSensorDescriptor.SENSORS_ONLY_CHANGED_IN_PR.toArray()}; | |||||
return new Object[][] {DefaultSensorDescriptor.HARDCODED_INDEPENDENT_FILE_SENSORS.toArray()}; | |||||
} | } | ||||
private ModuleSensorsExecutor createModuleExecutor(String sensorName) throws IOException { | private ModuleSensorsExecutor createModuleExecutor(String sensorName) throws IOException { |