From d91ea92fe22cf1b6b52a8efba122daf8e7e58282 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Tue, 23 Nov 2021 13:06:28 -0600 Subject: SONAR-15686 Files provided are restricted when PR for selected sensors --- .../bootstrap/AbstractExtensionDictionary.java | 206 ++++++++++ .../bootstrap/AbstractExtensionDictionnary.java | 206 ---------- .../bootstrap/PostJobExtensionDictionary.java | 48 +++ .../bootstrap/PostJobExtensionDictionnary.java | 48 --- .../sonar/scanner/postjob/PostJobsExecutor.java | 6 +- .../sonar/scanner/scan/ModuleScanContainer.java | 4 +- .../sonar/scanner/scan/ProjectScanContainer.java | 8 +- .../scan/filesystem/DefaultModuleFileSystem.java | 3 +- .../scan/filesystem/DefaultProjectFileSystem.java | 3 +- .../scanner/scan/filesystem/MutableFileSystem.java | 59 +++ .../scanner/sensor/AbstractSensorWrapper.java | 18 +- .../sensor/ModuleSensorExtensionDictionary.java | 53 +++ .../sensor/ModuleSensorExtensionDictionnary.java | 46 --- .../sonar/scanner/sensor/ModuleSensorWrapper.java | 6 +- .../scanner/sensor/ModuleSensorsExecutor.java | 4 +- .../sensor/ProjectSensorExtensionDictionary.java | 54 +++ .../sensor/ProjectSensorExtensionDictionnary.java | 47 --- .../sonar/scanner/sensor/ProjectSensorWrapper.java | 7 +- .../scanner/sensor/ProjectSensorsExecutor.java | 4 +- .../ModuleSensorExtensionDictionaryTest.java | 435 +++++++++++++++++++++ .../ModuleSensorExtensionDictionnaryTest.java | 430 -------------------- .../bootstrap/PostJobExtensionDictionaryTest.java | 110 ++++++ .../bootstrap/PostJobExtensionDictionnaryTest.java | 110 ------ .../scanner/phases/ModuleSensorsExecutorTest.java | 127 +++++- .../sonar/scanner/phases/PostJobsExecutorTest.java | 4 +- .../scan/filesystem/MutableFileSystemTest.java | 85 ++++ 26 files changed, 1212 insertions(+), 919 deletions(-) create mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionary.java delete mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionnary.java create mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionary.java delete mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionnary.java create mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/MutableFileSystem.java create mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionary.java delete mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionnary.java create mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionary.java delete mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionnary.java create mode 100644 sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionaryTest.java delete mode 100644 sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionnaryTest.java create mode 100644 sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionaryTest.java delete mode 100644 sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionnaryTest.java create mode 100644 sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/MutableFileSystemTest.java (limited to 'sonar-scanner-engine/src') diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionary.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionary.java new file mode 100644 index 00000000000..a165751f2ce --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionary.java @@ -0,0 +1,206 @@ +/* + * 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.bootstrap; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.apache.commons.lang.ClassUtils; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.batch.Phase; +import org.sonar.api.utils.AnnotationUtils; +import org.sonar.api.utils.dag.DirectAcyclicGraph; +import org.sonar.core.platform.ComponentContainer; + +public abstract class AbstractExtensionDictionary { + + private final ComponentContainer componentContainer; + + public AbstractExtensionDictionary(ComponentContainer componentContainer) { + this.componentContainer = componentContainer; + } + + public Collection select(Class type, boolean sort, @Nullable ExtensionMatcher matcher) { + List result = getFilteredExtensions(type, matcher); + if (sort) { + return sort(result); + } + return result; + } + + private static Phase.Name evaluatePhase(Object extension) { + Phase phaseAnnotation = AnnotationUtils.getAnnotation(extension, Phase.class); + if (phaseAnnotation != null) { + return phaseAnnotation.name(); + } + return Phase.Name.DEFAULT; + } + + protected List getFilteredExtensions(Class type, @Nullable ExtensionMatcher matcher) { + List result = new ArrayList<>(); + + for (T extension : getExtensions(type)) { + if (shouldKeep(type, extension, matcher)) { + result.add(extension); + } + } + return result; + } + + private List getExtensions(Class type) { + List extensions = new ArrayList<>(); + completeScannerExtensions(componentContainer, extensions, type); + return extensions; + } + + private static void completeScannerExtensions(ComponentContainer container, List extensions, Class type) { + extensions.addAll(container.getComponentsByType(type)); + ComponentContainer parentContainer = container.getParent(); + if (parentContainer != null) { + completeScannerExtensions(parentContainer, extensions, type); + } + } + + protected Collection sort(Collection extensions) { + DirectAcyclicGraph dag = new DirectAcyclicGraph(); + + for (T extension : extensions) { + dag.add(extension); + for (Object dependency : getDependencies(extension)) { + dag.add(extension, dependency); + } + for (Object generates : getDependents(extension)) { + dag.add(generates, extension); + } + completePhaseDependencies(dag, extension); + } + List sortedList = dag.sort(); + + return (Collection) sortedList.stream() + .filter(extensions::contains) + .collect(Collectors.toList()); + } + + /** + * Extension dependencies + */ + private List getDependencies(T extension) { + return new ArrayList<>(evaluateAnnotatedClasses(extension, DependsUpon.class)); + } + + /** + * Objects that depend upon this extension. + */ + private List getDependents(T extension) { + return new ArrayList<>(evaluateAnnotatedClasses(extension, DependedUpon.class)); + } + + private static void completePhaseDependencies(DirectAcyclicGraph dag, Object extension) { + Phase.Name phase = evaluatePhase(extension); + dag.add(extension, phase); + for (Phase.Name name : Phase.Name.values()) { + if (phase.compareTo(name) < 0) { + dag.add(name, extension); + } else if (phase.compareTo(name) > 0) { + dag.add(extension, name); + } + } + } + + public List evaluateAnnotatedClasses(Object extension, Class annotation) { + List results = new ArrayList<>(); + Class aClass = extension.getClass(); + while (aClass != null) { + evaluateClass(aClass, annotation, results); + + for (Method method : aClass.getDeclaredMethods()) { + if (method.getAnnotation(annotation) != null) { + checkAnnotatedMethod(method); + evaluateMethod(extension, method, results); + } + } + aClass = aClass.getSuperclass(); + } + + return results; + } + + private static void evaluateClass(Class extensionClass, Class annotationClass, List results) { + Annotation annotation = extensionClass.getAnnotation(annotationClass); + if (annotation != null) { + if (annotation.annotationType().isAssignableFrom(DependsUpon.class)) { + results.addAll(Arrays.asList(((DependsUpon) annotation).value())); + + } else if (annotation.annotationType().isAssignableFrom(DependedUpon.class)) { + results.addAll(Arrays.asList(((DependedUpon) annotation).value())); + } + } + + Class[] interfaces = extensionClass.getInterfaces(); + for (Class anInterface : interfaces) { + evaluateClass(anInterface, annotationClass, results); + } + } + + private void evaluateMethod(Object extension, Method method, List results) { + try { + Object result = method.invoke(extension); + if (result != null) { + if (result instanceof Class) { + results.addAll(componentContainer.getComponentsByType((Class) result)); + + } else if (result instanceof Collection) { + results.addAll((Collection) result); + + } else if (result.getClass().isArray()) { + for (int i = 0; i < Array.getLength(result); i++) { + results.add(Array.get(result, i)); + } + + } else { + results.add(result); + } + } + } catch (Exception e) { + throw new IllegalStateException("Can not invoke method " + method, e); + } + } + + private static void checkAnnotatedMethod(Method method) { + if (!Modifier.isPublic(method.getModifiers())) { + throw new IllegalStateException("Annotated method must be public:" + method); + } + if (method.getParameterTypes().length > 0) { + throw new IllegalStateException("Annotated method must not have parameters:" + method); + } + } + + private static boolean shouldKeep(Class type, Object extension, @Nullable ExtensionMatcher matcher) { + return ClassUtils.isAssignable(extension.getClass(), type) && (matcher == null || matcher.accept(extension)); + } +} 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/AbstractExtensionDictionnary.java deleted file mode 100644 index bd56e76433b..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/AbstractExtensionDictionnary.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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.bootstrap; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Array; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; -import javax.annotation.Nullable; -import org.apache.commons.lang.ClassUtils; -import org.sonar.api.batch.DependedUpon; -import org.sonar.api.batch.DependsUpon; -import org.sonar.api.batch.Phase; -import org.sonar.api.utils.AnnotationUtils; -import org.sonar.api.utils.dag.DirectAcyclicGraph; -import org.sonar.core.platform.ComponentContainer; - -public abstract class AbstractExtensionDictionnary { - - private final ComponentContainer componentContainer; - - public AbstractExtensionDictionnary(ComponentContainer componentContainer) { - this.componentContainer = componentContainer; - } - - public Collection select(Class type, boolean sort, @Nullable ExtensionMatcher matcher) { - List result = getFilteredExtensions(type, matcher); - if (sort) { - return sort(result); - } - return result; - } - - private static Phase.Name evaluatePhase(Object extension) { - Phase phaseAnnotation = AnnotationUtils.getAnnotation(extension, Phase.class); - if (phaseAnnotation != null) { - return phaseAnnotation.name(); - } - return Phase.Name.DEFAULT; - } - - protected List getFilteredExtensions(Class type, @Nullable ExtensionMatcher matcher) { - List result = new ArrayList<>(); - - for (T extension : getExtensions(type)) { - if (shouldKeep(type, extension, matcher)) { - result.add(extension); - } - } - return result; - } - - private List getExtensions(Class type) { - List extensions = new ArrayList<>(); - completeScannerExtensions(componentContainer, extensions, type); - return extensions; - } - - private static void completeScannerExtensions(ComponentContainer container, List extensions, Class type) { - extensions.addAll(container.getComponentsByType(type)); - ComponentContainer parentContainer = container.getParent(); - if (parentContainer != null) { - completeScannerExtensions(parentContainer, extensions, type); - } - } - - protected Collection sort(Collection extensions) { - DirectAcyclicGraph dag = new DirectAcyclicGraph(); - - for (T extension : extensions) { - dag.add(extension); - for (Object dependency : getDependencies(extension)) { - dag.add(extension, dependency); - } - for (Object generates : getDependents(extension)) { - dag.add(generates, extension); - } - completePhaseDependencies(dag, extension); - } - List sortedList = dag.sort(); - - return (Collection) sortedList.stream() - .filter(extensions::contains) - .collect(Collectors.toList()); - } - - /** - * Extension dependencies - */ - private List getDependencies(T extension) { - return new ArrayList<>(evaluateAnnotatedClasses(extension, DependsUpon.class)); - } - - /** - * Objects that depend upon this extension. - */ - private List getDependents(T extension) { - return new ArrayList<>(evaluateAnnotatedClasses(extension, DependedUpon.class)); - } - - private static void completePhaseDependencies(DirectAcyclicGraph dag, Object extension) { - Phase.Name phase = evaluatePhase(extension); - dag.add(extension, phase); - for (Phase.Name name : Phase.Name.values()) { - if (phase.compareTo(name) < 0) { - dag.add(name, extension); - } else if (phase.compareTo(name) > 0) { - dag.add(extension, name); - } - } - } - - public List evaluateAnnotatedClasses(Object extension, Class annotation) { - List results = new ArrayList<>(); - Class aClass = extension.getClass(); - while (aClass != null) { - evaluateClass(aClass, annotation, results); - - for (Method method : aClass.getDeclaredMethods()) { - if (method.getAnnotation(annotation) != null) { - checkAnnotatedMethod(method); - evaluateMethod(extension, method, results); - } - } - aClass = aClass.getSuperclass(); - } - - return results; - } - - private static void evaluateClass(Class extensionClass, Class annotationClass, List results) { - Annotation annotation = extensionClass.getAnnotation(annotationClass); - if (annotation != null) { - if (annotation.annotationType().isAssignableFrom(DependsUpon.class)) { - results.addAll(Arrays.asList(((DependsUpon) annotation).value())); - - } else if (annotation.annotationType().isAssignableFrom(DependedUpon.class)) { - results.addAll(Arrays.asList(((DependedUpon) annotation).value())); - } - } - - Class[] interfaces = extensionClass.getInterfaces(); - for (Class anInterface : interfaces) { - evaluateClass(anInterface, annotationClass, results); - } - } - - private void evaluateMethod(Object extension, Method method, List results) { - try { - Object result = method.invoke(extension); - if (result != null) { - if (result instanceof Class) { - results.addAll(componentContainer.getComponentsByType((Class) result)); - - } else if (result instanceof Collection) { - results.addAll((Collection) result); - - } else if (result.getClass().isArray()) { - for (int i = 0; i < Array.getLength(result); i++) { - results.add(Array.get(result, i)); - } - - } else { - results.add(result); - } - } - } catch (Exception e) { - throw new IllegalStateException("Can not invoke method " + method, e); - } - } - - private static void checkAnnotatedMethod(Method method) { - if (!Modifier.isPublic(method.getModifiers())) { - throw new IllegalStateException("Annotated method must be public:" + method); - } - if (method.getParameterTypes().length > 0) { - throw new IllegalStateException("Annotated method must not have parameters:" + method); - } - } - - private static boolean shouldKeep(Class type, Object extension, @Nullable ExtensionMatcher matcher) { - return ClassUtils.isAssignable(extension.getClass(), type) && (matcher == null || matcher.accept(extension)); - } -} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionary.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionary.java new file mode 100644 index 00000000000..562792646fe --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionary.java @@ -0,0 +1,48 @@ +/* + * 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.bootstrap; + +import java.util.Collection; +import java.util.stream.Collectors; +import org.sonar.api.batch.postjob.PostJob; +import org.sonar.api.batch.postjob.PostJobContext; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.scanner.postjob.PostJobOptimizer; +import org.sonar.scanner.postjob.PostJobWrapper; + +public class PostJobExtensionDictionary extends AbstractExtensionDictionary { + + private final PostJobContext postJobContext; + private final PostJobOptimizer postJobOptimizer; + + public PostJobExtensionDictionary(ComponentContainer componentContainer, PostJobOptimizer postJobOptimizer, PostJobContext postJobContext) { + super(componentContainer); + this.postJobOptimizer = postJobOptimizer; + this.postJobContext = postJobContext; + } + + public Collection selectPostJobs() { + Collection result = sort(getFilteredExtensions(PostJob.class, null)); + return result.stream() + .map(j -> new PostJobWrapper(j, postJobContext, postJobOptimizer)) + .filter(PostJobWrapper::shouldExecute) + .collect(Collectors.toList()); + } +} 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/PostJobExtensionDictionnary.java deleted file mode 100644 index b647355a7a1..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionnary.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.bootstrap; - -import java.util.Collection; -import java.util.stream.Collectors; -import org.sonar.api.batch.postjob.PostJob; -import org.sonar.api.batch.postjob.PostJobContext; -import org.sonar.core.platform.ComponentContainer; -import org.sonar.scanner.postjob.PostJobOptimizer; -import org.sonar.scanner.postjob.PostJobWrapper; - -public class PostJobExtensionDictionnary extends AbstractExtensionDictionnary { - - private final PostJobContext postJobContext; - private final PostJobOptimizer postJobOptimizer; - - public PostJobExtensionDictionnary(ComponentContainer componentContainer, PostJobOptimizer postJobOptimizer, PostJobContext postJobContext) { - super(componentContainer); - this.postJobOptimizer = postJobOptimizer; - this.postJobContext = postJobContext; - } - - public Collection selectPostJobs() { - Collection result = sort(getFilteredExtensions(PostJob.class, null)); - return result.stream() - .map(j -> new PostJobWrapper(j, postJobContext, postJobOptimizer)) - .filter(PostJobWrapper::shouldExecute) - .collect(Collectors.toList()); - } -} 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/ModuleSensorExtensionDictionary.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionary.java new file mode 100644 index 00000000000..3df2f067fbc --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionary.java @@ -0,0 +1,53 @@ +/* + * 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.sensor; + +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.AbstractExtensionDictionary; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.filesystem.MutableFileSystem; + +public class ModuleSensorExtensionDictionary extends AbstractExtensionDictionary { + + private final ModuleSensorContext sensorContext; + private final ModuleSensorOptimizer sensorOptimizer; + private final MutableFileSystem fileSystem; + private final BranchConfiguration branchConfiguration; + + 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, 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/ModuleSensorExtensionDictionnary.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionnary.java deleted file mode 100644 index 734ebb9192a..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorExtensionDictionnary.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.sensor; - -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; - -public class ModuleSensorExtensionDictionnary extends AbstractExtensionDictionnary { - - private final ModuleSensorContext sensorContext; - private final ModuleSensorOptimizer sensorOptimizer; - - public ModuleSensorExtensionDictionnary(ComponentContainer componentContainer, ModuleSensorContext sensorContext, ModuleSensorOptimizer sensorOptimizer) { - super(componentContainer); - this.sensorContext = sensorContext; - this.sensorOptimizer = sensorOptimizer; - } - - public Collection selectSensors(boolean global) { - Collection result = sort(getFilteredExtensions(Sensor.class, null)); - return result.stream() - .map(s -> new ModuleSensorWrapper(s, sensorContext, sensorOptimizer)) - .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/ProjectSensorExtensionDictionary.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionary.java new file mode 100644 index 00000000000..44180231a10 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionary.java @@ -0,0 +1,54 @@ +/* + * 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.sensor; + +import java.util.Collection; +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.AbstractExtensionDictionary; +import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.filesystem.MutableFileSystem; + +public class ProjectSensorExtensionDictionary extends AbstractExtensionDictionary { + + private final ProjectSensorContext sensorContext; + private final ProjectSensorOptimizer sensorOptimizer; + private final MutableFileSystem fileSystem; + private final BranchConfiguration branchConfiguration; + + 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, fileSystem, branchConfiguration)) + .filter(ProjectSensorWrapper::shouldExecute) + .collect(Collectors.toList()); + } +} 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/ProjectSensorExtensionDictionnary.java deleted file mode 100644 index 00c47e17bde..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorExtensionDictionnary.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.sensor; - -import java.util.Collection; -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; - -public class ProjectSensorExtensionDictionnary extends AbstractExtensionDictionnary { - - private final ProjectSensorContext sensorContext; - private final ProjectSensorOptimizer sensorOptimizer; - - public ProjectSensorExtensionDictionnary(ComponentContainer componentContainer, ProjectSensorContext sensorContext, ProjectSensorOptimizer sensorOptimizer) { - super(componentContainer); - this.sensorContext = sensorContext; - this.sensorOptimizer = sensorOptimizer; - } - - public List selectSensors() { - Collection result = sort(getFilteredExtensions(ProjectSensor.class, null)); - return result.stream() - .map(s -> new ProjectSensorWrapper(s, sensorContext, sensorOptimizer)) - .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/ModuleSensorExtensionDictionaryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionaryTest.java new file mode 100644 index 00000000000..a539b0b51d3 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionaryTest.java @@ -0,0 +1,435 @@ +/* + * 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.bootstrap; + +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.picocontainer.behaviors.FieldDecorated; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.batch.Phase; +import org.sonar.api.batch.ScannerSide; +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.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.ModuleSensorExtensionDictionary; +import org.sonar.scanner.sensor.ModuleSensorOptimizer; +import org.sonar.scanner.sensor.ModuleSensorWrapper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +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 ModuleSensorExtensionDictionary newSelector(Object... extensions) { + ComponentContainer iocContainer = new ComponentContainer(); + for (Object extension : extensions) { + iocContainer.addSingleton(extension); + } + return new ModuleSensorExtensionDictionary(iocContainer, mock(ModuleSensorContext.class), sensorOptimizer, fileSystem, branchConfiguration); + } + + @Test + public void testGetFilteredExtensionWithExtensionMatcher() { + final Sensor sensor1 = new FakeSensor(); + final Sensor sensor2 = new FakeSensor(); + + ModuleSensorExtensionDictionary selector = newSelector(sensor1, sensor2); + Collection sensors = selector.select(Sensor.class, true, extension -> extension.equals(sensor1)); + assertThat(sensors).contains(sensor1); + assertEquals(1, sensors.size()); + } + + @Test + public void testGetFilteredExtensions() { + Sensor sensor1 = new FakeSensor(); + Sensor sensor2 = new FakeSensor(); + FieldDecorated.Decorator decorator = mock(FieldDecorated.Decorator.class); + + ModuleSensorExtensionDictionary selector = newSelector(sensor1, sensor2, decorator); + Collection sensors = selector.select(Sensor.class, false, null); + + assertThat(sensors).containsOnly(sensor1, sensor2); + } + + @Test + public void shouldSearchInParentContainers() { + Sensor a = new FakeSensor(); + Sensor b = new FakeSensor(); + Sensor c = new FakeSensor(); + + ComponentContainer grandParent = new ComponentContainer(); + grandParent.addSingleton(a); + + ComponentContainer parent = grandParent.createChild(); + parent.addSingleton(b); + + ComponentContainer child = parent.createChild(); + child.addSingleton(c); + + ModuleSensorExtensionDictionary dictionnary = new ModuleSensorExtensionDictionary(child, mock(ModuleSensorContext.class), mock(ModuleSensorOptimizer.class), + fileSystem, branchConfiguration); + assertThat(dictionnary.select(Sensor.class, true, null)).containsOnly(a, b, c); + } + + @Test + public void sortExtensionsByDependency() { + Object a = new MethodDependentOf(null); + Object b = new MethodDependentOf(a); + Object c = new MethodDependentOf(b); + + ModuleSensorExtensionDictionary selector = newSelector(b, c, a); + List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(3); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + assertThat(extensions.get(2)).isEqualTo(c); + } + + @Test + public void useMethodAnnotationsToSortExtensions() { + Object a = new GeneratesSomething("foo"); + Object b = new MethodDependentOf("foo"); + + ModuleSensorExtensionDictionary selector = newSelector(a, b); + List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions.size()).isEqualTo(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + + // different initial order + selector = newSelector(b, a); + extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test + public void methodDependsUponCollection() { + Object a = new GeneratesSomething("foo"); + Object b = new MethodDependentOf(Arrays.asList("foo")); + + ModuleSensorExtensionDictionary selector = newSelector(a, b); + List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + + // different initial order + selector = newSelector(b, a); + extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test + public void methodDependsUponArray() { + Object a = new GeneratesSomething("foo"); + Object b = new MethodDependentOf(new String[] {"foo"}); + + ModuleSensorExtensionDictionary selector = newSelector(a, b); + List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + + // different initial order + selector = newSelector(b, a); + extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test + public void useClassAnnotationsToSortExtensions() { + Object a = new ClassDependedUpon(); + Object b = new ClassDependsUpon(); + + ModuleSensorExtensionDictionary selector = newSelector(a, b); + List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + + // different initial order + selector = newSelector(b, a); + extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test + public void useClassAnnotationsOnInterfaces() { + Object a = new InterfaceDependedUpon() { + }; + Object b = new InterfaceDependsUpon() { + }; + + ModuleSensorExtensionDictionary selector = newSelector(a, b); + List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + + // different initial order + selector = newSelector(b, a); + extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test + public void inheritAnnotations() { + Object a = new SubClass("foo"); + Object b = new MethodDependentOf("foo"); + + ModuleSensorExtensionDictionary selector = newSelector(b, a); + List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + + // change initial order + selector = newSelector(a, b); + extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); + + assertThat(extensions).hasSize(2); + assertThat(extensions.get(0)).isEqualTo(a); + assertThat(extensions.get(1)).isEqualTo(b); + } + + @Test(expected = IllegalStateException.class) + public void annotatedMethodsCanNotBePrivate() { + ModuleSensorExtensionDictionary selector = newSelector(); + Object wrong = new Object() { + @DependsUpon + private Object foo() { + return "foo"; + } + }; + selector.evaluateAnnotatedClasses(wrong, DependsUpon.class); + } + + @Test + public void dependsUponPhaseForSensors() { + PreSensor pre = new PreSensor(); + NormalSensor normal = new NormalSensor(); + PostSensor post = new PostSensor(); + + ModuleSensorExtensionDictionary selector = newSelector(normal, post, pre); + assertThat(selector.selectSensors(false)).extracting("wrappedSensor").containsExactly(pre, normal, post); + } + + @Test + public void dependsUponInheritedPhase() { + PreSensorSubclass pre = new PreSensorSubclass(); + NormalSensor normal = new NormalSensor(); + PostSensorSubclass post = new PostSensorSubclass(); + + ModuleSensorExtensionDictionary selector = newSelector(normal, post, pre); + List extensions = Lists.newArrayList(selector.select(Sensor.class, true, null)); + + assertThat(extensions).containsExactly(pre, normal, post); + } + + @Test + public void selectSensors() { + FakeSensor nonGlobalSensor = new FakeSensor(); + FakeGlobalSensor globalSensor = new FakeGlobalSensor(); + ModuleSensorExtensionDictionary selector = newSelector(nonGlobalSensor, globalSensor); + + // verify non-global sensor + Collection extensions = selector.selectSensors(false); + assertThat(extensions).hasSize(1); + assertThat(extensions).extracting("wrappedSensor").containsExactly(nonGlobalSensor); + + // verify global sensor + extensions = selector.selectSensors(true); + assertThat(extensions).extracting("wrappedSensor").containsExactly(globalSensor); + } + + interface Marker { + + } + + static class FakeSensor implements Sensor { + + @Override + public void describe(SensorDescriptor descriptor) { + + } + + @Override + public void execute(SensorContext context) { + + } + } + + static class FakeGlobalSensor implements Sensor { + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor.global(); + } + + @Override + public void execute(SensorContext context) { + } + + } + + @ScannerSide static + class MethodDependentOf implements Marker { + private Object dep; + + MethodDependentOf(Object o) { + this.dep = o; + } + + @DependsUpon + public Object dependsUponObject() { + return dep; + } + } + + @ScannerSide + @DependsUpon("flag") static + class ClassDependsUpon implements Marker { + } + + @ScannerSide + @DependedUpon("flag") static + class ClassDependedUpon implements Marker { + } + + @ScannerSide + @DependsUpon("flag") + interface InterfaceDependsUpon extends Marker { + } + + @ScannerSide + @DependedUpon("flag") + interface InterfaceDependedUpon extends Marker { + } + + @ScannerSide static + class GeneratesSomething implements Marker { + private Object gen; + + GeneratesSomething(Object o) { + this.gen = o; + } + + @DependedUpon + public Object generates() { + return gen; + } + } + + class SubClass extends GeneratesSomething implements Marker { + SubClass(Object o) { + super(o); + } + } + + static class NormalSensor implements Sensor { + + @Override + public void describe(SensorDescriptor descriptor) { + } + + @Override + public void execute(SensorContext context) { + } + + } + + @Phase(name = Phase.Name.PRE) static + class PreSensor implements Sensor { + + @Override + public void describe(SensorDescriptor descriptor) { + } + + @Override + public void execute(SensorContext context) { + } + + } + + class PreSensorSubclass extends PreSensor { + + } + + @Phase(name = Phase.Name.POST) static + class PostSensor implements Sensor { + + @Override + public void describe(SensorDescriptor descriptor) { + } + + @Override + public void execute(SensorContext context) { + } + + } + + class PostSensorSubclass extends PostSensor { + + } + +} 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/ModuleSensorExtensionDictionnaryTest.java deleted file mode 100644 index 61481a22cdc..00000000000 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ModuleSensorExtensionDictionnaryTest.java +++ /dev/null @@ -1,430 +0,0 @@ -/* - * 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.bootstrap; - -import com.google.common.collect.Lists; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import org.junit.Before; -import org.junit.Test; -import org.picocontainer.behaviors.FieldDecorated; -import org.sonar.api.batch.DependedUpon; -import org.sonar.api.batch.DependsUpon; -import org.sonar.api.batch.Phase; -import org.sonar.api.batch.ScannerSide; -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.core.platform.ComponentContainer; -import org.sonar.scanner.sensor.ModuleSensorContext; -import org.sonar.scanner.sensor.ModuleSensorExtensionDictionnary; -import org.sonar.scanner.sensor.ModuleSensorOptimizer; -import org.sonar.scanner.sensor.ModuleSensorWrapper; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertEquals; -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); - - @Before - public void setUp() { - when(sensorOptimizer.shouldExecute(any(DefaultSensorDescriptor.class))).thenReturn(true); - } - - private ModuleSensorExtensionDictionnary newSelector(Object... extensions) { - ComponentContainer iocContainer = new ComponentContainer(); - for (Object extension : extensions) { - iocContainer.addSingleton(extension); - } - return new ModuleSensorExtensionDictionnary(iocContainer, mock(ModuleSensorContext.class), sensorOptimizer); - } - - @Test - public void testGetFilteredExtensionWithExtensionMatcher() { - final Sensor sensor1 = new FakeSensor(); - final Sensor sensor2 = new FakeSensor(); - - ModuleSensorExtensionDictionnary selector = newSelector(sensor1, sensor2); - Collection sensors = selector.select(Sensor.class, true, extension -> extension.equals(sensor1)); - assertThat(sensors).contains(sensor1); - assertEquals(1, sensors.size()); - } - - @Test - public void testGetFilteredExtensions() { - Sensor sensor1 = new FakeSensor(); - Sensor sensor2 = new FakeSensor(); - FieldDecorated.Decorator decorator = mock(FieldDecorated.Decorator.class); - - ModuleSensorExtensionDictionnary selector = newSelector(sensor1, sensor2, decorator); - Collection sensors = selector.select(Sensor.class, false, null); - - assertThat(sensors).containsOnly(sensor1, sensor2); - } - - @Test - public void shouldSearchInParentContainers() { - Sensor a = new FakeSensor(); - Sensor b = new FakeSensor(); - Sensor c = new FakeSensor(); - - ComponentContainer grandParent = new ComponentContainer(); - grandParent.addSingleton(a); - - ComponentContainer parent = grandParent.createChild(); - parent.addSingleton(b); - - ComponentContainer child = parent.createChild(); - child.addSingleton(c); - - ModuleSensorExtensionDictionnary dictionnary = new ModuleSensorExtensionDictionnary(child, mock(ModuleSensorContext.class), mock(ModuleSensorOptimizer.class)); - assertThat(dictionnary.select(Sensor.class, true, null)).containsOnly(a, b, c); - } - - @Test - public void sortExtensionsByDependency() { - Object a = new MethodDependentOf(null); - Object b = new MethodDependentOf(a); - Object c = new MethodDependentOf(b); - - ModuleSensorExtensionDictionnary selector = newSelector(b, c, a); - List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(3); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - assertThat(extensions.get(2)).isEqualTo(c); - } - - @Test - public void useMethodAnnotationsToSortExtensions() { - Object a = new GeneratesSomething("foo"); - Object b = new MethodDependentOf("foo"); - - ModuleSensorExtensionDictionnary selector = newSelector(a, b); - List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions.size()).isEqualTo(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - - // different initial order - selector = newSelector(b, a); - extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - } - - @Test - public void methodDependsUponCollection() { - Object a = new GeneratesSomething("foo"); - Object b = new MethodDependentOf(Arrays.asList("foo")); - - ModuleSensorExtensionDictionnary selector = newSelector(a, b); - List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - - // different initial order - selector = newSelector(b, a); - extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - } - - @Test - public void methodDependsUponArray() { - Object a = new GeneratesSomething("foo"); - Object b = new MethodDependentOf(new String[] {"foo"}); - - ModuleSensorExtensionDictionnary selector = newSelector(a, b); - List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - - // different initial order - selector = newSelector(b, a); - extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - } - - @Test - public void useClassAnnotationsToSortExtensions() { - Object a = new ClassDependedUpon(); - Object b = new ClassDependsUpon(); - - ModuleSensorExtensionDictionnary selector = newSelector(a, b); - List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - - // different initial order - selector = newSelector(b, a); - extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - } - - @Test - public void useClassAnnotationsOnInterfaces() { - Object a = new InterfaceDependedUpon() { - }; - Object b = new InterfaceDependsUpon() { - }; - - ModuleSensorExtensionDictionnary selector = newSelector(a, b); - List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - - // different initial order - selector = newSelector(b, a); - extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - } - - @Test - public void inheritAnnotations() { - Object a = new SubClass("foo"); - Object b = new MethodDependentOf("foo"); - - ModuleSensorExtensionDictionnary selector = newSelector(b, a); - List extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - - // change initial order - selector = newSelector(a, b); - extensions = Lists.newArrayList(selector.select(Marker.class, true, null)); - - assertThat(extensions).hasSize(2); - assertThat(extensions.get(0)).isEqualTo(a); - assertThat(extensions.get(1)).isEqualTo(b); - } - - @Test(expected = IllegalStateException.class) - public void annotatedMethodsCanNotBePrivate() { - ModuleSensorExtensionDictionnary selector = newSelector(); - Object wrong = new Object() { - @DependsUpon - private Object foo() { - return "foo"; - } - }; - selector.evaluateAnnotatedClasses(wrong, DependsUpon.class); - } - - @Test - public void dependsUponPhaseForSensors() { - PreSensor pre = new PreSensor(); - NormalSensor normal = new NormalSensor(); - PostSensor post = new PostSensor(); - - ModuleSensorExtensionDictionnary selector = newSelector(normal, post, pre); - assertThat(selector.selectSensors(false)).extracting("wrappedSensor").containsExactly(pre, normal, post); - } - - @Test - public void dependsUponInheritedPhase() { - PreSensorSubclass pre = new PreSensorSubclass(); - NormalSensor normal = new NormalSensor(); - PostSensorSubclass post = new PostSensorSubclass(); - - ModuleSensorExtensionDictionnary selector = newSelector(normal, post, pre); - List extensions = Lists.newArrayList(selector.select(Sensor.class, true, null)); - - assertThat(extensions).containsExactly(pre, normal, post); - } - - @Test - public void selectSensors() { - FakeSensor nonGlobalSensor = new FakeSensor(); - FakeGlobalSensor globalSensor = new FakeGlobalSensor(); - ModuleSensorExtensionDictionnary selector = newSelector(nonGlobalSensor, globalSensor); - - // verify non-global sensor - Collection extensions = selector.selectSensors(false); - assertThat(extensions).hasSize(1); - assertThat(extensions).extracting("wrappedSensor").containsExactly(nonGlobalSensor); - - // verify global sensor - extensions = selector.selectSensors(true); - assertThat(extensions).extracting("wrappedSensor").containsExactly(globalSensor); - } - - interface Marker { - - } - - static class FakeSensor implements Sensor { - - @Override - public void describe(SensorDescriptor descriptor) { - - } - - @Override - public void execute(SensorContext context) { - - } - } - - static class FakeGlobalSensor implements Sensor { - - @Override - public void describe(SensorDescriptor descriptor) { - descriptor.global(); - } - - @Override - public void execute(SensorContext context) { - } - - } - - @ScannerSide static - class MethodDependentOf implements Marker { - private Object dep; - - MethodDependentOf(Object o) { - this.dep = o; - } - - @DependsUpon - public Object dependsUponObject() { - return dep; - } - } - - @ScannerSide - @DependsUpon("flag") static - class ClassDependsUpon implements Marker { - } - - @ScannerSide - @DependedUpon("flag") static - class ClassDependedUpon implements Marker { - } - - @ScannerSide - @DependsUpon("flag") - interface InterfaceDependsUpon extends Marker { - } - - @ScannerSide - @DependedUpon("flag") - interface InterfaceDependedUpon extends Marker { - } - - @ScannerSide static - class GeneratesSomething implements Marker { - private Object gen; - - GeneratesSomething(Object o) { - this.gen = o; - } - - @DependedUpon - public Object generates() { - return gen; - } - } - - class SubClass extends GeneratesSomething implements Marker { - SubClass(Object o) { - super(o); - } - } - - static class NormalSensor implements Sensor { - - @Override - public void describe(SensorDescriptor descriptor) { - } - - @Override - public void execute(SensorContext context) { - } - - } - - @Phase(name = Phase.Name.PRE) static - class PreSensor implements Sensor { - - @Override - public void describe(SensorDescriptor descriptor) { - } - - @Override - public void execute(SensorContext context) { - } - - } - - class PreSensorSubclass extends PreSensor { - - } - - @Phase(name = Phase.Name.POST) static - class PostSensor implements Sensor { - - @Override - public void describe(SensorDescriptor descriptor) { - } - - @Override - public void execute(SensorContext context) { - } - - } - - class PostSensorSubclass extends PostSensor { - - } - -} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionaryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionaryTest.java new file mode 100644 index 00000000000..3a016aa2acc --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionaryTest.java @@ -0,0 +1,110 @@ +/* + * 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.bootstrap; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.batch.Phase; +import org.sonar.api.batch.postjob.PostJob; +import org.sonar.api.batch.postjob.PostJobContext; +import org.sonar.api.batch.postjob.PostJobDescriptor; +import org.sonar.api.batch.postjob.internal.DefaultPostJobDescriptor; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.scanner.postjob.PostJobOptimizer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PostJobExtensionDictionaryTest { + private PostJobOptimizer postJobOptimizer = mock(PostJobOptimizer.class); + + @Before + public void setUp() { + when(postJobOptimizer.shouldExecute(any(DefaultPostJobDescriptor.class))).thenReturn(true); + } + + private PostJobExtensionDictionary newSelector(Object... extensions) { + ComponentContainer iocContainer = new ComponentContainer(); + for (Object extension : extensions) { + iocContainer.addSingleton(extension); + } + return new PostJobExtensionDictionary(iocContainer, postJobOptimizer, mock(PostJobContext.class)); + } + + @Test + public void dependsUponPhaseForPostJob() { + PrePostJob pre = new PrePostJob(); + NormalPostJob normal = new NormalPostJob(); + + PostJobExtensionDictionary selector = newSelector(normal, pre); + assertThat(selector.selectPostJobs()).extracting("wrappedPostJob").containsExactly(pre, normal); + } + + interface Marker { + + } + + @Phase(name = Phase.Name.POST) static + class PostSensor implements Sensor { + + @Override + public void describe(SensorDescriptor descriptor) { + } + + @Override + public void execute(SensorContext context) { + } + + } + + class PostSensorSubclass extends PostSensor { + + } + + static class NormalPostJob implements PostJob { + + @Override + public void describe(PostJobDescriptor descriptor) { + } + + @Override + public void execute(PostJobContext context) { + } + + } + + @Phase(name = Phase.Name.PRE) static + class PrePostJob implements PostJob { + + @Override + public void describe(PostJobDescriptor descriptor) { + } + + @Override + public void execute(PostJobContext context) { + } + + } +} 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/PostJobExtensionDictionnaryTest.java deleted file mode 100644 index bfc29675ffa..00000000000 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PostJobExtensionDictionnaryTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.bootstrap; - -import org.junit.Before; -import org.junit.Test; -import org.sonar.api.batch.Phase; -import org.sonar.api.batch.postjob.PostJob; -import org.sonar.api.batch.postjob.PostJobContext; -import org.sonar.api.batch.postjob.PostJobDescriptor; -import org.sonar.api.batch.postjob.internal.DefaultPostJobDescriptor; -import org.sonar.api.batch.sensor.Sensor; -import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.batch.sensor.SensorDescriptor; -import org.sonar.core.platform.ComponentContainer; -import org.sonar.scanner.postjob.PostJobOptimizer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class PostJobExtensionDictionnaryTest { - private PostJobOptimizer postJobOptimizer = mock(PostJobOptimizer.class); - - @Before - public void setUp() { - when(postJobOptimizer.shouldExecute(any(DefaultPostJobDescriptor.class))).thenReturn(true); - } - - private PostJobExtensionDictionnary newSelector(Object... extensions) { - ComponentContainer iocContainer = new ComponentContainer(); - for (Object extension : extensions) { - iocContainer.addSingleton(extension); - } - return new PostJobExtensionDictionnary(iocContainer, postJobOptimizer, mock(PostJobContext.class)); - } - - @Test - public void dependsUponPhaseForPostJob() { - PrePostJob pre = new PrePostJob(); - NormalPostJob normal = new NormalPostJob(); - - PostJobExtensionDictionnary selector = newSelector(normal, pre); - assertThat(selector.selectPostJobs()).extracting("wrappedPostJob").containsExactly(pre, normal); - } - - interface Marker { - - } - - @Phase(name = Phase.Name.POST) static - class PostSensor implements Sensor { - - @Override - public void describe(SensorDescriptor descriptor) { - } - - @Override - public void execute(SensorContext context) { - } - - } - - class PostSensorSubclass extends PostSensor { - - } - - static class NormalPostJob implements PostJob { - - @Override - public void describe(PostJobDescriptor descriptor) { - } - - @Override - public void execute(PostJobContext context) { - } - - } - - @Phase(name = Phase.Name.PRE) static - class PrePostJob implements PostJob { - - @Override - public void describe(PostJobDescriptor descriptor) { - } - - @Override - public void execute(PostJobContext context) { - } - - } -} 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); + } + +} -- cgit v1.2.3