From d91ea92fe22cf1b6b52a8efba122daf8e7e58282 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Tue, 23 Nov 2021 13:06:28 -0600 Subject: [PATCH] SONAR-15686 Files provided are restricted when PR for selected sensors --- .../predicates/ChangedFilePredicate.java | 38 ++++++ .../internal/DefaultSensorDescriptor.java | 19 +++ .../predicates/ChangedFilePredicateTest.java | 94 +++++++++++++ .../internal/DefaultSensorDescriptorTest.java | 30 +++++ ....java => AbstractExtensionDictionary.java} | 4 +- ...y.java => PostJobExtensionDictionary.java} | 4 +- .../scanner/postjob/PostJobsExecutor.java | 6 +- .../scanner/scan/ModuleScanContainer.java | 4 +- .../scanner/scan/ProjectScanContainer.java | 8 +- .../filesystem/DefaultModuleFileSystem.java | 3 +- .../filesystem/DefaultProjectFileSystem.java | 3 +- .../scan/filesystem/MutableFileSystem.java | 59 ++++++++ .../scanner/sensor/AbstractSensorWrapper.java | 18 ++- ...a => ModuleSensorExtensionDictionary.java} | 15 ++- .../scanner/sensor/ModuleSensorWrapper.java | 6 +- .../scanner/sensor/ModuleSensorsExecutor.java | 4 +- ... => ProjectSensorExtensionDictionary.java} | 15 ++- .../scanner/sensor/ProjectSensorWrapper.java | 7 +- .../sensor/ProjectSensorsExecutor.java | 4 +- ... ModuleSensorExtensionDictionaryTest.java} | 43 +++--- ...va => PostJobExtensionDictionaryTest.java} | 8 +- .../phases/ModuleSensorsExecutorTest.java | 127 ++++++++++++++++-- .../scanner/phases/PostJobsExecutorTest.java | 4 +- .../filesystem/MutableFileSystemTest.java | 85 ++++++++++++ 24 files changed, 541 insertions(+), 67 deletions(-) create mode 100644 sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/fs/internal/predicates/ChangedFilePredicate.java create mode 100644 sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/fs/internal/predicates/ChangedFilePredicateTest.java rename sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/{AbstractExtensionDictionnary.java => AbstractExtensionDictionary.java} (98%) rename sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/{PostJobExtensionDictionnary.java => PostJobExtensionDictionary.java} (88%) create mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/MutableFileSystem.java rename sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/{ModuleSensorExtensionDictionnary.java => ModuleSensorExtensionDictionary.java} (68%) rename sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/{ProjectSensorExtensionDictionnary.java => ProjectSensorExtensionDictionary.java} (68%) rename sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/{ModuleSensorExtensionDictionnaryTest.java => ModuleSensorExtensionDictionaryTest.java} (86%) rename sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/{PostJobExtensionDictionnaryTest.java => PostJobExtensionDictionaryTest.java} (91%) create mode 100644 sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/MutableFileSystemTest.java diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/fs/internal/predicates/ChangedFilePredicate.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/fs/internal/predicates/ChangedFilePredicate.java new file mode 100644 index 00000000000..08736a7e61d --- /dev/null +++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/fs/internal/predicates/ChangedFilePredicate.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.batch.fs.internal.predicates; + +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.InputFile; + +public class ChangedFilePredicate implements FilePredicate { + + private final FilePredicate originalPredicate; + + public ChangedFilePredicate(FilePredicate originalPredicate) { + this.originalPredicate = originalPredicate; + } + + @Override + public boolean apply(InputFile inputFile) { + return originalPredicate.apply(inputFile) && InputFile.Status.SAME != inputFile.status(); + } + +} diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/DefaultSensorDescriptor.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/DefaultSensorDescriptor.java index ee5ced6886b..e0819050190 100644 --- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/DefaultSensorDescriptor.java +++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/DefaultSensorDescriptor.java @@ -21,13 +21,23 @@ package org.sonar.api.batch.sensor.internal; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Nullable; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.config.Configuration; public class DefaultSensorDescriptor implements SensorDescriptor { + public static final Set SENSORS_ONLY_CHANGED_IN_PR = Collections.unmodifiableSet(Stream.of( + "CSS Metrics", + "CSS Rules", + "HTML", + "XML Sensor" + ).collect(Collectors.toSet())); private String name; private String[] languages = new String[0]; @@ -35,6 +45,7 @@ public class DefaultSensorDescriptor implements SensorDescriptor { private String[] ruleRepositories = new String[0]; private boolean global = false; private Predicate configurationPredicate; + private boolean onlyChangedFilesInPullRequests = false; public String name() { return name; @@ -61,8 +72,16 @@ public class DefaultSensorDescriptor implements SensorDescriptor { return global; } + public boolean onlyChangedFilesInPullRequest() { + return onlyChangedFilesInPullRequests; + } + @Override 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; + } this.name = name; return this; } diff --git a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/fs/internal/predicates/ChangedFilePredicateTest.java b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/fs/internal/predicates/ChangedFilePredicateTest.java new file mode 100644 index 00000000000..f3fcd9309a2 --- /dev/null +++ b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/fs/internal/predicates/ChangedFilePredicateTest.java @@ -0,0 +1,94 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.batch.fs.internal.predicates; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.InputFile; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ChangedFilePredicateTest { + + private final FilePredicate predicate = mock(FilePredicate.class); + private final InputFile inputFile = mock(InputFile.class); + + private final ChangedFilePredicate underTest = new ChangedFilePredicate(predicate); + + @Test + public void apply_when_file_is_changed_and_predicate_is_true() { + when(inputFile.status()).thenReturn(InputFile.Status.CHANGED); + when(predicate.apply(inputFile)).thenReturn(true); + + Assertions.assertThat(underTest.apply(inputFile)).isTrue(); + + verify(predicate, times(1)).apply(any()); + verify(inputFile, times(1)).status(); + } + + @Test + public void apply_when_file_is_added_and_predicate_is_true() { + when(inputFile.status()).thenReturn(InputFile.Status.ADDED); + when(predicate.apply(inputFile)).thenReturn(true); + + Assertions.assertThat(underTest.apply(inputFile)).isTrue(); + + verify(predicate, times(1)).apply(any()); + verify(inputFile, times(1)).status(); + } + + @Test + public void do_not_apply_when_file_is_same_and_predicate_is_true() { + when(inputFile.status()).thenReturn(InputFile.Status.SAME); + when(predicate.apply(inputFile)).thenReturn(true); + + Assertions.assertThat(underTest.apply(inputFile)).isFalse(); + + verify(predicate, times(1)).apply(any()); + verify(inputFile, times(1)).status(); + } + + @Test + public void predicate_is_evaluated_before_file_status() { + when(predicate.apply(inputFile)).thenReturn(false); + + Assertions.assertThat(underTest.apply(inputFile)).isFalse(); + + verify(inputFile, never()).status(); + } + + @Test + public void do_not_apply_when_file_is_same_and_predicate_is_false() { + when(inputFile.status()).thenReturn(InputFile.Status.SAME); + when(predicate.apply(inputFile)).thenReturn(true); + + Assertions.assertThat(underTest.apply(inputFile)).isFalse(); + + verify(predicate, times(1)).apply(any()); + verify(inputFile, times(1)).status(); + } + +} diff --git a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/DefaultSensorDescriptorTest.java b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/DefaultSensorDescriptorTest.java index ac6c72c24dd..e34c5385cad 100644 --- a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/DefaultSensorDescriptorTest.java +++ b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/DefaultSensorDescriptorTest.java @@ -19,12 +19,17 @@ */ package org.sonar.api.batch.sensor.internal; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import org.junit.Test; +import org.junit.runner.RunWith; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.config.internal.MapSettings; import static org.assertj.core.api.Assertions.assertThat; +@RunWith(DataProviderRunner.class) public class DefaultSensorDescriptorTest { @Test @@ -48,4 +53,29 @@ public class DefaultSensorDescriptorTest { assertThat(descriptor.ruleRepositories()).containsOnly("squid-java"); } + @Test + @UseDataProvider("sensorsOnlyChangedInPR") + public void describe_with_restricted_sensor(String sensorName) { + DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); + descriptor + .name(sensorName); + + assertThat(descriptor.onlyChangedFilesInPullRequest()).isTrue(); + } + + @Test + @UseDataProvider("sensorsOnlyChangedInPR") + public void describe_with_non_restricted_sensor(String sensorName) { + DefaultSensorDescriptor descriptor = new DefaultSensorDescriptor(); + descriptor + .name(sensorName + "other"); + + assertThat(descriptor.onlyChangedFilesInPullRequest()).isFalse(); + } + + @DataProvider + public static Object[][] sensorsOnlyChangedInPR() { + return new Object[][] {DefaultSensorDescriptor.SENSORS_ONLY_CHANGED_IN_PR.toArray()}; + } + } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionnary.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionary.java similarity index 98% rename from sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionnary.java rename to sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionary.java index bd56e76433b..a165751f2ce 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionnary.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionary.java @@ -37,11 +37,11 @@ import org.sonar.api.utils.AnnotationUtils; import org.sonar.api.utils.dag.DirectAcyclicGraph; import org.sonar.core.platform.ComponentContainer; -public abstract class AbstractExtensionDictionnary { +public abstract class AbstractExtensionDictionary { private final ComponentContainer componentContainer; - public AbstractExtensionDictionnary(ComponentContainer componentContainer) { + public AbstractExtensionDictionary(ComponentContainer componentContainer) { this.componentContainer = componentContainer; } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionnary.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionary.java similarity index 88% rename from sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionnary.java rename to sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionary.java index b647355a7a1..562792646fe 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionnary.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionary.java @@ -27,12 +27,12 @@ import org.sonar.core.platform.ComponentContainer; import org.sonar.scanner.postjob.PostJobOptimizer; import org.sonar.scanner.postjob.PostJobWrapper; -public class PostJobExtensionDictionnary extends AbstractExtensionDictionnary { +public class PostJobExtensionDictionary extends AbstractExtensionDictionary { private final PostJobContext postJobContext; private final PostJobOptimizer postJobOptimizer; - public PostJobExtensionDictionnary(ComponentContainer componentContainer, PostJobOptimizer postJobOptimizer, PostJobContext postJobContext) { + public PostJobExtensionDictionary(ComponentContainer componentContainer, PostJobOptimizer postJobOptimizer, PostJobContext postJobContext) { super(componentContainer); this.postJobOptimizer = postJobOptimizer; this.postJobContext = postJobContext; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/postjob/PostJobsExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/postjob/PostJobsExecutor.java index 098fdea74ff..21486a39536 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/postjob/PostJobsExecutor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/postjob/PostJobsExecutor.java @@ -23,14 +23,14 @@ import java.util.Collection; import java.util.stream.Collectors; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; -import org.sonar.scanner.bootstrap.PostJobExtensionDictionnary; +import org.sonar.scanner.bootstrap.PostJobExtensionDictionary; public class PostJobsExecutor { private static final Logger LOG = Loggers.get(PostJobsExecutor.class); - private final PostJobExtensionDictionnary selector; + private final PostJobExtensionDictionary selector; - public PostJobsExecutor(PostJobExtensionDictionnary selector) { + public PostJobsExecutor(PostJobExtensionDictionary selector) { this.selector = selector; } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java index e67d65da9f1..3ab08728bbd 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java @@ -26,7 +26,7 @@ import org.sonar.scanner.bootstrap.ExtensionInstaller; import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem; import org.sonar.scanner.scan.filesystem.ModuleInputComponentStore; import org.sonar.scanner.sensor.ModuleSensorContext; -import org.sonar.scanner.sensor.ModuleSensorExtensionDictionnary; +import org.sonar.scanner.sensor.ModuleSensorExtensionDictionary; import org.sonar.scanner.sensor.ModuleSensorOptimizer; import org.sonar.scanner.sensor.ModuleSensorsExecutor; @@ -65,7 +65,7 @@ public class ModuleScanContainer extends ComponentContainer { ModuleSensorOptimizer.class, ModuleSensorContext.class, - ModuleSensorExtensionDictionnary.class + ModuleSensorExtensionDictionary.class ); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java index d77df0452ee..94c44b23a73 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java @@ -41,7 +41,7 @@ import org.sonar.scanner.analysis.AnalysisTempFolderProvider; import org.sonar.scanner.bootstrap.ExtensionInstaller; import org.sonar.scanner.bootstrap.ExtensionMatcher; import org.sonar.scanner.bootstrap.GlobalAnalysisMode; -import org.sonar.scanner.bootstrap.PostJobExtensionDictionnary; +import org.sonar.scanner.bootstrap.PostJobExtensionDictionary; import org.sonar.scanner.ci.CiConfigurationProvider; import org.sonar.scanner.ci.vendors.AppVeyor; import org.sonar.scanner.ci.vendors.AwsCodeBuild; @@ -126,7 +126,7 @@ import org.sonar.scanner.scm.ScmPublisher; import org.sonar.scanner.scm.ScmRevisionImpl; import org.sonar.scanner.sensor.DefaultSensorStorage; import org.sonar.scanner.sensor.ProjectSensorContext; -import org.sonar.scanner.sensor.ProjectSensorExtensionDictionnary; +import org.sonar.scanner.sensor.ProjectSensorExtensionDictionary; import org.sonar.scanner.sensor.ProjectSensorOptimizer; import org.sonar.scanner.sensor.ProjectSensorsExecutor; import org.sonar.scm.git.GitScmSupport; @@ -256,7 +256,7 @@ public class ProjectScanContainer extends ComponentContainer { PostJobsExecutor.class, PostJobOptimizer.class, DefaultPostJobContext.class, - PostJobExtensionDictionnary.class, + PostJobExtensionDictionary.class, // SCM ScmConfiguration.class, @@ -269,7 +269,7 @@ public class ProjectScanContainer extends ComponentContainer { ProjectSensorContext.class, ProjectSensorOptimizer.class, ProjectSensorsExecutor.class, - ProjectSensorExtensionDictionnary.class, + ProjectSensorExtensionDictionary.class, // Filesystem DefaultProjectFileSystem.class, diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DefaultModuleFileSystem.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DefaultModuleFileSystem.java index d7b4a93d3ff..fb5263d8d03 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DefaultModuleFileSystem.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DefaultModuleFileSystem.java @@ -19,11 +19,10 @@ */ package org.sonar.scanner.scan.filesystem; -import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.batch.fs.internal.predicates.DefaultFilePredicates; -public class DefaultModuleFileSystem extends DefaultFileSystem { +public class DefaultModuleFileSystem extends MutableFileSystem { public DefaultModuleFileSystem(ModuleInputComponentStore moduleInputFileCache, DefaultInputModule module) { super(module.getBaseDir(), moduleInputFileCache, new DefaultFilePredicates(module.getBaseDir())); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DefaultProjectFileSystem.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DefaultProjectFileSystem.java index fb5082bdefa..1f3a5c1c128 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DefaultProjectFileSystem.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DefaultProjectFileSystem.java @@ -19,11 +19,10 @@ */ package org.sonar.scanner.scan.filesystem; -import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.batch.fs.internal.DefaultInputProject; import org.sonar.api.batch.fs.internal.predicates.DefaultFilePredicates; -public class DefaultProjectFileSystem extends DefaultFileSystem { +public class DefaultProjectFileSystem extends MutableFileSystem { public DefaultProjectFileSystem(InputComponentStore inputComponentStore, DefaultInputProject project) { super(project.getBaseDir(), inputComponentStore, new DefaultFilePredicates(project.getBaseDir())); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/MutableFileSystem.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/MutableFileSystem.java new file mode 100644 index 00000000000..f17b15eb394 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/MutableFileSystem.java @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.scan.filesystem; + +import java.nio.file.Path; +import org.sonar.api.batch.fs.FilePredicate; +import org.sonar.api.batch.fs.FilePredicates; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.predicates.ChangedFilePredicate; + +public class MutableFileSystem extends DefaultFileSystem { + private boolean restrictToChangedFiles = false; + + public MutableFileSystem(Path baseDir, Cache cache, FilePredicates filePredicates) { + super(baseDir, cache, filePredicates); + } + + public MutableFileSystem(Path baseDir) { + super(baseDir); + } + + @Override + public Iterable inputFiles(FilePredicate requestPredicate) { + if (restrictToChangedFiles) { + return super.inputFiles(new ChangedFilePredicate(requestPredicate)); + } + return super.inputFiles(requestPredicate); + } + + @Override + public InputFile inputFile(FilePredicate requestPredicate) { + if (restrictToChangedFiles) { + return super.inputFile(new ChangedFilePredicate(requestPredicate)); + } + return super.inputFile(requestPredicate); + } + + public void setRestrictToChangedFiles(boolean restrictToChangedFiles) { + this.restrictToChangedFiles = restrictToChangedFiles; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/AbstractSensorWrapper.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/AbstractSensorWrapper.java index 404bf73dfa2..21e5308d66e 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/AbstractSensorWrapper.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/AbstractSensorWrapper.java @@ -22,18 +22,29 @@ package org.sonar.scanner.sensor; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; import org.sonar.api.scanner.sensor.ProjectSensor; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.branch.BranchType; +import org.sonar.scanner.scan.filesystem.MutableFileSystem; public abstract class AbstractSensorWrapper { + private static final Logger LOGGER = Loggers.get(AbstractSensorWrapper.class); + private final G wrappedSensor; private final SensorContext context; + private final MutableFileSystem fileSystem; private final DefaultSensorDescriptor descriptor; private final AbstractSensorOptimizer optimizer; + private final boolean isPullRequest; - public AbstractSensorWrapper(G sensor, SensorContext context, AbstractSensorOptimizer optimizer) { + public AbstractSensorWrapper(G sensor, SensorContext context, AbstractSensorOptimizer optimizer, MutableFileSystem fileSystem, BranchConfiguration branchConfiguration) { this.wrappedSensor = sensor; this.optimizer = optimizer; this.context = context; this.descriptor = new DefaultSensorDescriptor(); + this.fileSystem = fileSystem; + this.isPullRequest = branchConfiguration.branchType() == BranchType.PULL_REQUEST; sensor.describe(this.descriptor); if (descriptor.name() == null) { descriptor.name(sensor.getClass().getName()); @@ -45,6 +56,11 @@ public abstract class AbstractSensorWrapper { } public void analyse() { + boolean sensorIsRestricted = descriptor.onlyChangedFilesInPullRequest() && isPullRequest; + if (sensorIsRestricted) { + LOGGER.info("Sensor {} is restricted to changed files only", descriptor.name()); + } + fileSystem.setRestrictToChangedFiles(sensorIsRestricted); wrappedSensor.execute(context); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionnary.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionary.java similarity index 68% rename from sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionnary.java rename to sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionary.java index 734ebb9192a..3df2f067fbc 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionnary.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionary.java @@ -23,23 +23,30 @@ import java.util.Collection; import java.util.stream.Collectors; import org.sonar.api.batch.sensor.Sensor; import org.sonar.core.platform.ComponentContainer; -import org.sonar.scanner.bootstrap.AbstractExtensionDictionnary; +import org.sonar.scanner.bootstrap.AbstractExtensionDictionary; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.filesystem.MutableFileSystem; -public class ModuleSensorExtensionDictionnary extends AbstractExtensionDictionnary { +public class ModuleSensorExtensionDictionary extends AbstractExtensionDictionary { private final ModuleSensorContext sensorContext; private final ModuleSensorOptimizer sensorOptimizer; + private final MutableFileSystem fileSystem; + private final BranchConfiguration branchConfiguration; - public ModuleSensorExtensionDictionnary(ComponentContainer componentContainer, ModuleSensorContext sensorContext, ModuleSensorOptimizer sensorOptimizer) { + public ModuleSensorExtensionDictionary(ComponentContainer componentContainer, ModuleSensorContext sensorContext, ModuleSensorOptimizer sensorOptimizer, + MutableFileSystem fileSystem, BranchConfiguration branchConfiguration) { super(componentContainer); this.sensorContext = sensorContext; this.sensorOptimizer = sensorOptimizer; + this.fileSystem = fileSystem; + this.branchConfiguration = branchConfiguration; } public Collection selectSensors(boolean global) { Collection result = sort(getFilteredExtensions(Sensor.class, null)); return result.stream() - .map(s -> new ModuleSensorWrapper(s, sensorContext, sensorOptimizer)) + .map(s -> new ModuleSensorWrapper(s, sensorContext, sensorOptimizer, fileSystem, branchConfiguration)) .filter(s -> global == s.isGlobal() && s.shouldExecute()) .collect(Collectors.toList()); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorWrapper.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorWrapper.java index 4fbd1eae629..31c28b78a07 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorWrapper.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorWrapper.java @@ -20,10 +20,12 @@ package org.sonar.scanner.sensor; import org.sonar.api.batch.sensor.Sensor; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.filesystem.MutableFileSystem; public class ModuleSensorWrapper extends AbstractSensorWrapper { - public ModuleSensorWrapper(Sensor sensor, ModuleSensorContext context, ModuleSensorOptimizer optimizer) { - super(sensor, context, optimizer); + public ModuleSensorWrapper(Sensor sensor, ModuleSensorContext context, ModuleSensorOptimizer optimizer, MutableFileSystem fileSystem, BranchConfiguration branchConfiguration) { + super(sensor, context, optimizer, fileSystem, branchConfiguration); } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorsExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorsExecutor.java index 6b5d59b57cb..4cfa093133b 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorsExecutor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorsExecutor.java @@ -34,12 +34,12 @@ import org.sonar.scanner.fs.InputModuleHierarchy; public class ModuleSensorsExecutor { private static final Logger LOG = Loggers.get(ModuleSensorsExecutor.class); private static final Profiler profiler = Profiler.create(LOG); - private final ModuleSensorExtensionDictionnary selector; + private final ModuleSensorExtensionDictionary selector; private final SensorStrategy strategy; private final ScannerPluginRepository pluginRepo; private final boolean isRoot; - public ModuleSensorsExecutor(ModuleSensorExtensionDictionnary selector, DefaultInputModule module, InputModuleHierarchy hierarchy, + public ModuleSensorsExecutor(ModuleSensorExtensionDictionary selector, DefaultInputModule module, InputModuleHierarchy hierarchy, SensorStrategy strategy, ScannerPluginRepository pluginRepo) { this.selector = selector; this.strategy = strategy; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionnary.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionary.java similarity index 68% rename from sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionnary.java rename to sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionary.java index 00c47e17bde..44180231a10 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionnary.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionary.java @@ -24,23 +24,30 @@ import java.util.List; import java.util.stream.Collectors; import org.sonar.api.scanner.sensor.ProjectSensor; import org.sonar.core.platform.ComponentContainer; -import org.sonar.scanner.bootstrap.AbstractExtensionDictionnary; +import org.sonar.scanner.bootstrap.AbstractExtensionDictionary; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.filesystem.MutableFileSystem; -public class ProjectSensorExtensionDictionnary extends AbstractExtensionDictionnary { +public class ProjectSensorExtensionDictionary extends AbstractExtensionDictionary { private final ProjectSensorContext sensorContext; private final ProjectSensorOptimizer sensorOptimizer; + private final MutableFileSystem fileSystem; + private final BranchConfiguration branchConfiguration; - public ProjectSensorExtensionDictionnary(ComponentContainer componentContainer, ProjectSensorContext sensorContext, ProjectSensorOptimizer sensorOptimizer) { + public ProjectSensorExtensionDictionary(ComponentContainer componentContainer, ProjectSensorContext sensorContext, ProjectSensorOptimizer sensorOptimizer, + MutableFileSystem fileSystem, BranchConfiguration branchConfiguration) { super(componentContainer); this.sensorContext = sensorContext; this.sensorOptimizer = sensorOptimizer; + this.fileSystem = fileSystem; + this.branchConfiguration = branchConfiguration; } public List selectSensors() { Collection result = sort(getFilteredExtensions(ProjectSensor.class, null)); return result.stream() - .map(s -> new ProjectSensorWrapper(s, sensorContext, sensorOptimizer)) + .map(s -> new ProjectSensorWrapper(s, sensorContext, sensorOptimizer, fileSystem, branchConfiguration)) .filter(ProjectSensorWrapper::shouldExecute) .collect(Collectors.toList()); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorWrapper.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorWrapper.java index d56c733c60c..24e5dc483e9 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorWrapper.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorWrapper.java @@ -20,11 +20,14 @@ package org.sonar.scanner.sensor; import org.sonar.api.scanner.sensor.ProjectSensor; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.filesystem.MutableFileSystem; public class ProjectSensorWrapper extends AbstractSensorWrapper { - public ProjectSensorWrapper(ProjectSensor sensor, ProjectSensorContext context, ProjectSensorOptimizer optimizer) { - super(sensor, context, optimizer); + public ProjectSensorWrapper(ProjectSensor sensor, ProjectSensorContext context, ProjectSensorOptimizer optimizer, + MutableFileSystem fileSystem, BranchConfiguration branchConfiguration) { + super(sensor, context, optimizer, fileSystem, branchConfiguration); } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorsExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorsExecutor.java index 81ba19409b6..6af61c7c74b 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorsExecutor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorsExecutor.java @@ -29,10 +29,10 @@ import org.sonar.scanner.bootstrap.ScannerPluginRepository; public class ProjectSensorsExecutor { private static final Logger LOG = Loggers.get(ProjectSensorsExecutor.class); private static final Profiler profiler = Profiler.create(LOG); - private final ProjectSensorExtensionDictionnary selector; + private final ProjectSensorExtensionDictionary selector; private final ScannerPluginRepository pluginRepo; - public ProjectSensorsExecutor(ProjectSensorExtensionDictionnary selector, ScannerPluginRepository pluginRepo) { + public ProjectSensorsExecutor(ProjectSensorExtensionDictionary selector, ScannerPluginRepository pluginRepo) { this.selector = selector; this.pluginRepo = pluginRepo; } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionnaryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionaryTest.java similarity index 86% rename from sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionnaryTest.java rename to sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionaryTest.java index 61481a22cdc..a539b0b51d3 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionnaryTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionaryTest.java @@ -35,8 +35,10 @@ import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.SensorDescriptor; import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; import org.sonar.core.platform.ComponentContainer; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.filesystem.MutableFileSystem; import org.sonar.scanner.sensor.ModuleSensorContext; -import org.sonar.scanner.sensor.ModuleSensorExtensionDictionnary; +import org.sonar.scanner.sensor.ModuleSensorExtensionDictionary; import org.sonar.scanner.sensor.ModuleSensorOptimizer; import org.sonar.scanner.sensor.ModuleSensorWrapper; @@ -46,20 +48,22 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class ModuleSensorExtensionDictionnaryTest { - private ModuleSensorOptimizer sensorOptimizer = mock(ModuleSensorOptimizer.class); +public class ModuleSensorExtensionDictionaryTest { + private final ModuleSensorOptimizer sensorOptimizer = mock(ModuleSensorOptimizer.class); + private final MutableFileSystem fileSystem = mock(MutableFileSystem.class); + private final BranchConfiguration branchConfiguration = mock(BranchConfiguration.class); @Before public void setUp() { when(sensorOptimizer.shouldExecute(any(DefaultSensorDescriptor.class))).thenReturn(true); } - private ModuleSensorExtensionDictionnary newSelector(Object... extensions) { + private ModuleSensorExtensionDictionary newSelector(Object... extensions) { ComponentContainer iocContainer = new ComponentContainer(); for (Object extension : extensions) { iocContainer.addSingleton(extension); } - return new ModuleSensorExtensionDictionnary(iocContainer, mock(ModuleSensorContext.class), sensorOptimizer); + return new ModuleSensorExtensionDictionary(iocContainer, mock(ModuleSensorContext.class), sensorOptimizer, fileSystem, branchConfiguration); } @Test @@ -67,7 +71,7 @@ public class ModuleSensorExtensionDictionnaryTest { final Sensor sensor1 = new FakeSensor(); final Sensor sensor2 = new FakeSensor(); - ModuleSensorExtensionDictionnary selector = newSelector(sensor1, sensor2); + ModuleSensorExtensionDictionary selector = newSelector(sensor1, sensor2); Collection sensors = selector.select(Sensor.class, true, extension -> extension.equals(sensor1)); assertThat(sensors).contains(sensor1); assertEquals(1, sensors.size()); @@ -79,7 +83,7 @@ public class ModuleSensorExtensionDictionnaryTest { Sensor sensor2 = new FakeSensor(); FieldDecorated.Decorator decorator = mock(FieldDecorated.Decorator.class); - ModuleSensorExtensionDictionnary selector = newSelector(sensor1, sensor2, decorator); + ModuleSensorExtensionDictionary selector = newSelector(sensor1, sensor2, decorator); Collection sensors = selector.select(Sensor.class, false, null); assertThat(sensors).containsOnly(sensor1, sensor2); @@ -100,7 +104,8 @@ public class ModuleSensorExtensionDictionnaryTest { ComponentContainer child = parent.createChild(); child.addSingleton(c); - ModuleSensorExtensionDictionnary dictionnary = new ModuleSensorExtensionDictionnary(child, mock(ModuleSensorContext.class), mock(ModuleSensorOptimizer.class)); + ModuleSensorExtensionDictionary dictionnary = new ModuleSensorExtensionDictionary(child, mock(ModuleSensorContext.class), mock(ModuleSensorOptimizer.class), + fileSystem, branchConfiguration); assertThat(dictionnary.select(Sensor.class, true, null)).containsOnly(a, b, c); } @@ -110,7 +115,7 @@ public class ModuleSensorExtensionDictionnaryTest { Object b = new MethodDependentOf(a); Object c = new MethodDependentOf(b); - ModuleSensorExtensionDictionnary selector = newSelector(b, c, a); + ModuleSensorExtensionDictionary selector = newSelector(b, c, a); List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); assertThat(extensions).hasSize(3); @@ -124,7 +129,7 @@ public class ModuleSensorExtensionDictionnaryTest { Object a = new GeneratesSomething("foo"); Object b = new MethodDependentOf("foo"); - ModuleSensorExtensionDictionnary selector = newSelector(a, b); + ModuleSensorExtensionDictionary selector = newSelector(a, b); List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); assertThat(extensions.size()).isEqualTo(2); @@ -145,7 +150,7 @@ public class ModuleSensorExtensionDictionnaryTest { Object a = new GeneratesSomething("foo"); Object b = new MethodDependentOf(Arrays.asList("foo")); - ModuleSensorExtensionDictionnary selector = newSelector(a, b); + ModuleSensorExtensionDictionary selector = newSelector(a, b); List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); assertThat(extensions).hasSize(2); @@ -166,7 +171,7 @@ public class ModuleSensorExtensionDictionnaryTest { Object a = new GeneratesSomething("foo"); Object b = new MethodDependentOf(new String[] {"foo"}); - ModuleSensorExtensionDictionnary selector = newSelector(a, b); + ModuleSensorExtensionDictionary selector = newSelector(a, b); List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); assertThat(extensions).hasSize(2); @@ -187,7 +192,7 @@ public class ModuleSensorExtensionDictionnaryTest { Object a = new ClassDependedUpon(); Object b = new ClassDependsUpon(); - ModuleSensorExtensionDictionnary selector = newSelector(a, b); + ModuleSensorExtensionDictionary selector = newSelector(a, b); List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); assertThat(extensions).hasSize(2); @@ -210,7 +215,7 @@ public class ModuleSensorExtensionDictionnaryTest { Object b = new InterfaceDependsUpon() { }; - ModuleSensorExtensionDictionnary selector = newSelector(a, b); + ModuleSensorExtensionDictionary selector = newSelector(a, b); List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); assertThat(extensions).hasSize(2); @@ -231,7 +236,7 @@ public class ModuleSensorExtensionDictionnaryTest { Object a = new SubClass("foo"); Object b = new MethodDependentOf("foo"); - ModuleSensorExtensionDictionnary selector = newSelector(b, a); + ModuleSensorExtensionDictionary selector = newSelector(b, a); List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); assertThat(extensions).hasSize(2); @@ -249,7 +254,7 @@ public class ModuleSensorExtensionDictionnaryTest { @Test(expected = IllegalStateException.class) public void annotatedMethodsCanNotBePrivate() { - ModuleSensorExtensionDictionnary selector = newSelector(); + ModuleSensorExtensionDictionary selector = newSelector(); Object wrong = new Object() { @DependsUpon private Object foo() { @@ -265,7 +270,7 @@ public class ModuleSensorExtensionDictionnaryTest { NormalSensor normal = new NormalSensor(); PostSensor post = new PostSensor(); - ModuleSensorExtensionDictionnary selector = newSelector(normal, post, pre); + ModuleSensorExtensionDictionary selector = newSelector(normal, post, pre); assertThat(selector.selectSensors(false)).extracting("wrappedSensor").containsExactly(pre, normal, post); } @@ -275,7 +280,7 @@ public class ModuleSensorExtensionDictionnaryTest { NormalSensor normal = new NormalSensor(); PostSensorSubclass post = new PostSensorSubclass(); - ModuleSensorExtensionDictionnary selector = newSelector(normal, post, pre); + ModuleSensorExtensionDictionary selector = newSelector(normal, post, pre); List extensions = Lists.newArrayList(selector.select(Sensor.class, true, null)); assertThat(extensions).containsExactly(pre, normal, post); @@ -285,7 +290,7 @@ public class ModuleSensorExtensionDictionnaryTest { public void selectSensors() { FakeSensor nonGlobalSensor = new FakeSensor(); FakeGlobalSensor globalSensor = new FakeGlobalSensor(); - ModuleSensorExtensionDictionnary selector = newSelector(nonGlobalSensor, globalSensor); + ModuleSensorExtensionDictionary selector = newSelector(nonGlobalSensor, globalSensor); // verify non-global sensor Collection extensions = selector.selectSensors(false); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionnaryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionaryTest.java similarity index 91% rename from sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionnaryTest.java rename to sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionaryTest.java index bfc29675ffa..3a016aa2acc 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionnaryTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionaryTest.java @@ -37,7 +37,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class PostJobExtensionDictionnaryTest { +public class PostJobExtensionDictionaryTest { private PostJobOptimizer postJobOptimizer = mock(PostJobOptimizer.class); @Before @@ -45,12 +45,12 @@ public class PostJobExtensionDictionnaryTest { when(postJobOptimizer.shouldExecute(any(DefaultPostJobDescriptor.class))).thenReturn(true); } - private PostJobExtensionDictionnary newSelector(Object... extensions) { + private PostJobExtensionDictionary newSelector(Object... extensions) { ComponentContainer iocContainer = new ComponentContainer(); for (Object extension : extensions) { iocContainer.addSingleton(extension); } - return new PostJobExtensionDictionnary(iocContainer, postJobOptimizer, mock(PostJobContext.class)); + return new PostJobExtensionDictionary(iocContainer, postJobOptimizer, mock(PostJobContext.class)); } @Test @@ -58,7 +58,7 @@ public class PostJobExtensionDictionnaryTest { PrePostJob pre = new PrePostJob(); NormalPostJob normal = new NormalPostJob(); - PostJobExtensionDictionnary selector = newSelector(normal, pre); + PostJobExtensionDictionary selector = newSelector(normal, pre); assertThat(selector.selectPostJobs()).extracting("wrappedPostJob").containsExactly(pre, normal); } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleSensorsExecutorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleSensorsExecutorTest.java index 2b0095d23bf..820a528c040 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleSensorsExecutorTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/ModuleSensorsExecutorTest.java @@ -19,41 +19,65 @@ */ package org.sonar.scanner.phases; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.io.IOException; import java.util.Collections; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.batch.fs.internal.SensorStrategy; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; +import org.sonar.api.utils.log.LogTester; import org.sonar.scanner.bootstrap.ScannerPluginRepository; -import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.scanner.fs.InputModuleHierarchy; -import org.sonar.api.batch.fs.internal.TestInputFileBuilder; -import org.sonar.scanner.sensor.ModuleSensorExtensionDictionnary; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.branch.BranchType; +import org.sonar.scanner.scan.filesystem.MutableFileSystem; +import org.sonar.scanner.sensor.ModuleSensorContext; +import org.sonar.scanner.sensor.ModuleSensorExtensionDictionary; +import org.sonar.scanner.sensor.ModuleSensorOptimizer; import org.sonar.scanner.sensor.ModuleSensorWrapper; import org.sonar.scanner.sensor.ModuleSensorsExecutor; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +@RunWith(DataProviderRunner.class) public class ModuleSensorsExecutorTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public LogTester logTester = new LogTester(); + private ModuleSensorsExecutor rootModuleExecutor; private ModuleSensorsExecutor subModuleExecutor; - private SensorStrategy strategy = new SensorStrategy(); + private final SensorStrategy strategy = new SensorStrategy(); - private ModuleSensorWrapper perModuleSensor = mock(ModuleSensorWrapper.class); - private ModuleSensorWrapper globalSensor = mock(ModuleSensorWrapper.class); - private ScannerPluginRepository pluginRepository = mock(ScannerPluginRepository.class); + private final ModuleSensorWrapper perModuleSensor = mock(ModuleSensorWrapper.class); + private final ModuleSensorWrapper globalSensor = mock(ModuleSensorWrapper.class); + private final ScannerPluginRepository pluginRepository = mock(ScannerPluginRepository.class); + private final ModuleSensorContext context = mock(ModuleSensorContext.class); + private final ModuleSensorOptimizer optimizer = mock(ModuleSensorOptimizer.class); + private final ScannerPluginRepository pluginRepo = mock(ScannerPluginRepository.class); + private final MutableFileSystem fileSystem = mock(MutableFileSystem.class); + private final BranchConfiguration branchConfiguration = mock(BranchConfiguration.class); @Before public void setUp() throws IOException { @@ -65,7 +89,7 @@ public class ModuleSensorsExecutorTest { when(globalSensor.shouldExecute()).thenReturn(true); when(globalSensor.wrappedSensor()).thenReturn(mock(Sensor.class)); - ModuleSensorExtensionDictionnary selector = mock(ModuleSensorExtensionDictionnary.class); + ModuleSensorExtensionDictionary selector = mock(ModuleSensorExtensionDictionary.class); when(selector.selectSensors(false)).thenReturn(Collections.singleton(perModuleSensor)); when(selector.selectSensors(true)).thenReturn(Collections.singleton(globalSensor)); @@ -105,4 +129,91 @@ public class ModuleSensorsExecutorTest { verifyNoMoreInteractions(perModuleSensor, globalSensor); } + + @Test + @UseDataProvider("sensorsOnlyChangedInPR") + public void should_restrict_filesystem_when_pull_request_and_expected_sensor(String sensorName) throws IOException { + when(branchConfiguration.branchType()).thenReturn(BranchType.PULL_REQUEST); + ModuleSensorsExecutor executor = createModuleExecutor(sensorName); + + executor.execute(); + + verify(fileSystem, times(2)).setRestrictToChangedFiles(true); + + assertThat(logTester.logs().stream().anyMatch( + p -> p.contains(String.format("Sensor %s is restricted to changed files only", sensorName))) + ).isTrue(); + } + + @Test + @UseDataProvider("sensorsOnlyChangedInPR") + public void should_not_restrict_filesystem_when_branch(String sensorName) throws IOException { + when(branchConfiguration.branchType()).thenReturn(BranchType.BRANCH); + ModuleSensorsExecutor executor = createModuleExecutor(sensorName); + + executor.execute(); + + verify(fileSystem, times(2)).setRestrictToChangedFiles(false); + + assertThat(logTester.logs().stream().anyMatch( + p -> p.contains(String.format("Sensor %s is restricted to changed files only", sensorName))) + ).isFalse(); + } + + @Test + public void should_not_restrict_filesystem_when_pull_request_and_non_expected_sensor() throws IOException { + String sensorName = "NonRestrictedSensor"; + when(branchConfiguration.branchType()).thenReturn(BranchType.PULL_REQUEST); + ModuleSensorsExecutor executor = createModuleExecutor(sensorName); + + executor.execute(); + + verify(fileSystem, times(2)).setRestrictToChangedFiles(false); + + assertThat(logTester.logs().stream().anyMatch( + p -> p.contains(String.format("Sensor %s is restricted to changed files only", sensorName))) + ).isFalse(); + } + + @DataProvider + public static Object[][] sensorsOnlyChangedInPR() { + return new Object[][] {DefaultSensorDescriptor.SENSORS_ONLY_CHANGED_IN_PR.toArray()}; + } + + private ModuleSensorsExecutor createModuleExecutor(String sensorName) throws IOException { + Sensor sensor = new TestSensor(sensorName); + ModuleSensorWrapper sensorWrapper = new ModuleSensorWrapper(sensor, context, optimizer, fileSystem, branchConfiguration); + + ModuleSensorExtensionDictionary selector = mock(ModuleSensorExtensionDictionary.class); + when(selector.selectSensors(false)).thenReturn(Collections.singleton(sensorWrapper)); + when(selector.selectSensors(true)).thenReturn(Collections.singleton(sensorWrapper)); + + ProjectDefinition rootDef = ProjectDefinition.create().setKey("root").setBaseDir(temp.newFolder()).setWorkDir(temp.newFolder()); + + DefaultInputModule rootModule = TestInputFileBuilder.newDefaultInputModule(rootDef); + + InputModuleHierarchy hierarchy = mock(InputModuleHierarchy.class); + when(hierarchy.isRoot(rootModule)).thenReturn(true); + + return new ModuleSensorsExecutor(selector, rootModule, hierarchy, strategy, pluginRepo); + } + + private static class TestSensor implements Sensor { + + private final String name; + + public TestSensor(String name) { + this.name = name; + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor.name(name); + } + + @Override + public void execute(SensorContext context) { + + } + } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/PostJobsExecutorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/PostJobsExecutorTest.java index a4888b77686..318b42f6b15 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/PostJobsExecutorTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/phases/PostJobsExecutorTest.java @@ -22,7 +22,7 @@ package org.sonar.scanner.phases; import java.util.Arrays; import org.junit.Before; import org.junit.Test; -import org.sonar.scanner.bootstrap.PostJobExtensionDictionnary; +import org.sonar.scanner.bootstrap.PostJobExtensionDictionary; import org.sonar.scanner.postjob.PostJobWrapper; import org.sonar.scanner.postjob.PostJobsExecutor; @@ -34,7 +34,7 @@ import static org.mockito.Mockito.when; public class PostJobsExecutorTest { private PostJobsExecutor executor; - private PostJobExtensionDictionnary selector = mock(PostJobExtensionDictionnary.class); + private PostJobExtensionDictionary selector = mock(PostJobExtensionDictionary.class); private PostJobWrapper job1 = mock(PostJobWrapper.class); private PostJobWrapper job2 = mock(PostJobWrapper.class); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/MutableFileSystemTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/MutableFileSystemTest.java new file mode 100644 index 00000000000..f55a46f65ae --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/MutableFileSystemTest.java @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.scan.filesystem; + +import java.util.Locale; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MutableFileSystemTest { + + private static final String LANGUAGE = "php"; + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + private MutableFileSystem underTest; + + @Before + public void prepare() throws Exception { + underTest = new MutableFileSystem(temp.newFolder().toPath()); + } + + @Test + public void return_all_files_when_not_restricted() { + assertThat(underTest.inputFiles(underTest.predicates().all())).isEmpty(); + addFileWithAllStatus(); + underTest.setRestrictToChangedFiles(false); + + assertThat(underTest.inputFiles(underTest.predicates().all())).hasSize(3); + assertThat(underTest.inputFile(underTest.predicates().hasFilename(generateFilename(InputFile.Status.ADDED)))).isNotNull(); + assertThat(underTest.inputFile(underTest.predicates().hasFilename(generateFilename(InputFile.Status.SAME)))).isNotNull(); + assertThat(underTest.inputFile(underTest.predicates().hasFilename(generateFilename(InputFile.Status.CHANGED)))).isNotNull(); + } + + @Test + public void return_only_changed_files_when_restricted() { + assertThat(underTest.inputFiles(underTest.predicates().all())).isEmpty(); + addFileWithAllStatus(); + underTest.setRestrictToChangedFiles(true); + + assertThat(underTest.inputFiles(underTest.predicates().all())).hasSize(2); + assertThat(underTest.inputFile(underTest.predicates().hasFilename(generateFilename(InputFile.Status.ADDED)))).isNotNull(); + assertThat(underTest.inputFile(underTest.predicates().hasFilename(generateFilename(InputFile.Status.SAME)))).isNull(); + assertThat(underTest.inputFile(underTest.predicates().hasFilename(generateFilename(InputFile.Status.CHANGED)))).isNotNull(); + } + + private void addFileWithAllStatus() { + addFile(InputFile.Status.ADDED); + addFile(InputFile.Status.CHANGED); + addFile(InputFile.Status.SAME); + } + + private void addFile(InputFile.Status status) { + underTest.add(new TestInputFileBuilder("foo", String.format("src/%s", generateFilename(status))) + .setLanguage(LANGUAGE).setStatus(status).build()); + } + + private String generateFilename(InputFile.Status status) { + return String.format("%s.%s", status.name().toLowerCase(Locale.ROOT), LANGUAGE); + } + +} -- 2.39.5